From bd5d71d3f96400bc6534575618006a0eeb05b618 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Thu, 22 Oct 2015 00:14:22 +0000 Subject: [PATCH 001/933] Task #8651: create task branch -- GitLab From ba176ad8cb89e5e5e933998422ffd5ccf29af3cd Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Thu, 22 Oct 2015 00:27:11 +0000 Subject: [PATCH 002/933] Task #8651: commit ObservationStartListener and its __init__.py. Also commit supplemental scripts and systemd service config file --- .gitattributes | 3 + .../etc/LofarObservationStartListener.service | 43 +++ .../src/ObservationStartListener.py | 323 ++++++++++++++++++ .../ObservationStartListener/src/__init__.py | 23 ++ .../src/lofarlogger.sh | 15 + .../src/slurm-submit-cobalt-outputproc.sh | 20 ++ 6 files changed, 427 insertions(+) create mode 100644 LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service create mode 100755 LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py create mode 100644 LCS/MessageDaemons/ObservationStartListener/src/__init__.py create mode 100755 LCS/MessageDaemons/ObservationStartListener/src/lofarlogger.sh create mode 100755 LCS/MessageDaemons/ObservationStartListener/src/slurm-submit-cobalt-outputproc.sh diff --git a/.gitattributes b/.gitattributes index 1463185443f..992895d7ae8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2567,6 +2567,9 @@ LCS/MessageBus/test/tPyMsgBus.run eol=lf LCS/MessageBus/test/tPyMsgBus.sh eol=lf LCS/MessageBus/test/tPyProtocols.run eol=lf LCS/MessageBus/test/tPyProtocols.sh eol=lf +LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service -text +LCS/MessageDaemons/ObservationStartListener/src/lofarlogger.sh eol=lf +LCS/MessageDaemons/ObservationStartListener/src/slurm-submit-cobalt-outputproc.sh eol=lf LCS/MessageDaemons/src/MessageRouter -text LCS/MessageDaemons/src/MessageRouter.conf.ccu001 -text LCS/MessageDaemons/src/MessageRouter.conf.ccu099 -text diff --git a/LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service b/LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service new file mode 100644 index 00000000000..d55e0f49c2e --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service @@ -0,0 +1,43 @@ +# LofarObservationStartListener.service systemd service description for DRAGNET +# +# Not part of LOFAR roll-out, since cluster specific and to be installed into: +# /usr/lib/systemd/system/ (CentOS) +# /lib/systemd/system/ (Ubuntu) +# Then run: sudo systemctl daemon-reload && sudo systemctl restart LofarObservationStartListener +# +# $Id$ + +[Unit] +Description=LOFAR daemon that listens to observation start messages. DRAGNET cluster config. +Requires=network.target +After=network.target + +[Service] + +# Note: appears to require a local (or at least non-NIS) account for some reason +User=lofarsys +Group=dragnet + +# Type, ExecStart: no daemonization (-d) needed when managed by systemd as Type=simple +Type=simple +Environment='PYTHONPATH=/opt/lofar/lib64/python2.7/site-packages' +ExecStart=/usr/bin/python /opt/lofar/bin/ObservationStartListener.py \ + --broker ccu001.control.lofar \ + --address dump.lofar.task.specification.system \ + --match-prefix 'drg,drag' \ + --msg-save-dir /opt/lofar/var/run \ + --exec /opt/lofar/bin/slurm-submit-cobalt-outputproc.sh \ + --logfile /opt/lofar/var/log/ObservationStartListener.log \ + --quiet + +# To test: +# Service: /usr/bin/python /home/amesfoort/obslistener/ObservationStartListener.py --broker localhost --address alexander.test.task.specification.system --match-prefix 'node,yike' --msg-save-dir /home/amesfoort/obslistener/parsetsxml --exec /home/amesfoort/obslistener/slurm-submit-cobalt-outputproc.sh --logfile /home/amesfoort/obslistener/ObservationStartListener.log +# Client: qpid-send -a alexander.test.task.specification.system --content-string "`/bin/cat testout3.parset`" + +Restart=on-failure + +# Raise prio somewhat. This service must be responsive. +Nice=-15 + +[Install] +WantedBy=multi-user.target diff --git a/LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py b/LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py new file mode 100755 index 00000000000..61002bd9872 --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python +# ObservationStartListener.py: Receive observation messages to dispatch tasks +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id$ + +""" +LOFAR daemon that listens to incoming messages. If a message concerns our +cluster, run a program and pass it the message and the affected (matched) hosts. +This can e.g. be used to start observation processes via a cluster job manager. + +Pass -h or --help to print the usage description. +""" + +from sys import argv +import os +import signal +import logging +from socket import gethostname +from time import sleep +from daemon import DaemonContext +from subprocess import Popen +from optparse import OptionParser + +import lofar.parameterset as lofParset +import lofar.messagebus.messagebus as lofMess + +__version__ = "1.0" + +#TODO: set it up in CMake env, install as pkg under lofar module +#TODO: add test case; also test parset key expand + +def runProcess(execPath, parsetFilename, hosts): + """ + Run command execPath with arguments parsetFilename and the sequence hosts. + The command's stderr is hooked up to our file logger. + Raises OSError on error. + """ + cmd = [execPath, parsetFilename] + cmd.extend(hosts) + logFileStream = logger.handlers[0].stream # logfile was set up as handler 0 + logger.info('Executing command: %s', cmd) + proc = Popen(cmd, stderr=logFileStream, close_fds=True) # SIGCHLD = SIG_IGN + +def getOutputHosts(paramset, matchPrefix): + """ + Returns list of output hostnames. May contain duplicates. + paramset must be a lofar.parameterset with the LOFAR observation key/value + pairs populated. + matchPrefix must be a str, unicode, or a tuple of these (no list!) that + contains the host prefix. Hostnames starting with matchPrefix are returned. + """ + hosts = [] + + # Pass suitable default values to all paramset.getXXX() functions, + # so we don't have to try/except RuntimeError. + uvOutputEnabled = paramset.getBool('ObsSW.Observation.DataProducts.Output_Correlated.enabled', False) + if uvOutputEnabled: + uvOutputLocations = paramset.getStringVector('ObsSW.Observation.DataProducts.Output_Correlated.locations', []) + + # E.g. 'node' in ['node1:/a/b', 'nope:/x/y'] -> ['node1'] + hosts.extend([loc.split(':')[0] for loc in uvOutputLocations if loc.startswith(matchPrefix)]) + + cohStokesOutputEnabled = paramset.getBool('ObsSW.Observation.DataProducts.Output_CoherentStokes.enabled', False) + if cohStokesOutputEnabled: + cohStokesOutputLocations = paramset.getStringVector('ObsSW.Observation.DataProducts.Output_CoherentStokes.locations', []) + hosts.extend([loc.split(':')[0] for loc in cohStokesOutputLocations if loc.startswith(matchPrefix)]) + + incohStokesOutputEnabled = paramset.getBool('ObsSW.Observation.DataProducts.Output_IncoherentStokes.enabled', False) + if incohStokesOutputEnabled: + incohStokesOutputLocations = paramset.getStringVector('ObsSW.Observation.DataProducts.Output_IncoherentStokes.locations', []) + hosts.extend([loc.split(':')[0] for loc in incohStokesOutputLocations if loc.startswith(matchPrefix)]) + + return hosts + +def saveData(filename, data): + with file(filename, 'wb') as f: # binary: write as-is; no conv! + f.write(data) + +def uniq(seq): + """ Returns non-duplicate elements of sequence seq. Not order preserving. """ + return type(seq)(set(seq)) + +def processMessages(receiver, matchPrefix, execPath, msgSaveDir): + while True: + msg = None + try: + msg = receiver.get() # blocking + if msg is None: + continue + + content = msg.content() + # payload type can be unicode, but parameterset only converts str to std::string + message = str(content.payload) + ps = lofParset.parameterset() + ps.adoptBuffer(message) + hosts = getOutputHosts(ps, matchPrefix) + if hosts: + logger.info('Received message is applicable to us, so act on it') + + obsId = content.sasid + messageFilename = msgSaveDir + 'L' + obsId + '.parset.xml' + + try: + saveData(messageFilename, message) + + hosts = uniq(hosts) + hosts.sort() + + runProcess(execPath, messageFilename, hosts) + except IOError as exc: # saveData() + logger.error('Skipped running executable: failed to save message to %s: %s', + exc.filename, exc.strerror) + except OSError as exc: # runProcess() + logger.error('Failed to run executable: %s', exc.strerror) + + logger.info('Done with message') + + except lofMess.message.MessageException as exc: # XMLDoc(), _get_data() + logger.error('Failed to parse or retrieve node from XML message: %s', exc.message) + + finally: + if msg is not None: + receiver.ack(msg) # optional for topics, needed for queues + +def run(broker, address, matchPrefix, execPath, msgSaveDir): + # Receiver test: qpid-receive -b broker_hostname -a queue_or_exchange_name -f -m 1 + # Sender test: qpid-send -a queue_or_exchange_name --content-string "`/bin/cat L12345-task-specif.xml`" + timeout = 0.1 + while True: + receiver = None + try: + logger.info('Listening to broker %s at address %s', broker, address) + receiver = lofMess.FromBus(address, broker = broker) + timeout = 0.2 # sec + + processMessages(receiver, matchPrefix, execPath, msgSaveDir) + + except lofMess.BusException as exc: + # FromBus() failed: fatal iff it has never worked, else retry. + # FromBus' Session also retries, but wrt conn, not wrt addr/queue. + if timeout == 0.1: + logger.exception(exc) # e.g. no such queue: q.x.y.z + break + logger.error('%s; sleeping %.1f seconds', exc.message, timeout) + sleep(timeout) + if timeout < 8.0: # capped binary backoff + timeout *= 2.0 + + finally: + if receiver is not None: + logger.info('closing connection to broker') + receiver.close() # optional; FromBus (actually Session) does a delayed close + + return 1 + +def sigterm_handler(signr, stack_frame): + logger.info('Received SIGTERM') + + # Easiest way to unblock blocking reads and execute __del__() and 'finally:' + global sigterm_seen + sigterm_seen = True + raise KeyboardInterrupt('received SIGTERM') + +def getModuleName(): + if __package__ is not None: + name = __package__ + else: + try: + name = os.path.splitext(os.path.basename(__file__))[0] + except NameError as exc: + name = 'ObservationStartListener' # poor, but __file__ is N/A interactively + return name + +def initLogger(logfilename, quiet): + # RootLogger may have already been set up by an imported LOFAR pkg (broken). + # If so, reuse its log format. Else, set up something reasonable. + global logger + logger = logging.getLogger() + if logger.handlers: + sh = logger.handlers[0] + fmt = sh.formatter + else: + logger = logging.getLogger(getModuleName()) + logger.setLevel(logging.INFO) + sh = logging.StreamHandler() # stderr by default + fmt = logging.Formatter('%(asctime)s %(levelname)s %(message)s') + sh.setFormatter(fmt) + + # Always make fh handler 0 to easily hook up created processes to it. + fh = logging.FileHandler(logfilename) # appends or creates, may raise + fh.setLevel(logging.INFO) + fh.setFormatter(fmt) + logger.handlers[0] = fh + if not quiet: + logger.addHandler(sh) + return fh + +def registerCmdOptions(parser): + # already supported options: -h, --help, --version, -- + parser.add_option('-b', '--broker', dest='broker', + help='qpid broker hostname') + parser.add_option('-a', '--address', dest='address', + help='qpid address (i.e. queue or topic name) on BROKER to subscribe to') + parser.add_option('-p', '--match-prefix', dest='matchPrefix', + default=gethostname(), metavar='MATCH_PREFIX', + help='One or more comma-separated prefix(es) in a single argument value to match message values to. Default: node\'s hostname. (E.g. pass "cbt" for COBALT, "drg,drag" for DRAGNET.)') + parser.add_option('-m', '--msg-save-dir', dest='msgSaveDir', metavar='MSG_SAVE_DIR', + help='directory where matched messages are saved. To pass filename to EXEC and for debugging.') + parser.add_option('-x', '--exec', dest='execPath', metavar='EXEC', + help='executable to run for each matched message received. The message filename and a list of matched hostnames are passed to the executable.') + parser.add_option('-l', '--logfile', dest='logfilename', + default='/dev/null', metavar='LOG_FILE', + help='(also) log to LOGFILE') + parser.add_option('-q', '--quiet', action='store_true', dest='quiet', + default=False, + help='suppress logging stream to stderr. Useful with -l and when run from systemd to keep system log clean.') + parser.add_option('-d', '--daemon', action='store_true', dest='daemonize', + default=False, + help='run this program as a daemon') + +def checkArgs(parser, options, leftOverArgs): + # Mandatory option is contradictory, but these as positional args is unclear. + if options.broker is None: + parser.error('--broker (-b) is required (or pass -h for usage)') + if options.address is None: + parser.error('--address (-a) is required (or pass -h for usage)') + if options.msgSaveDir is None: + parser.error('--msg-save-dir (-m) is required (or pass -h for usage)') + if options.execPath is None: + parser.error('--exec (-x) is required (or pass -h for usage)') + + options.matchPrefix = tuple(options.matchPrefix.split(',')) # for str.startswith() + + options.execPath = os.path.abspath(options.execPath) # for -d or systemd + if not os.path.isfile(options.execPath) or not os.access(options.execPath, os.X_OK): + parser.error('--exec (-x): No such executable file at %s', options.execPath) + + options.msgSaveDir = os.path.abspath(options.msgSaveDir) # for -d or systemd + if options.msgSaveDir and options.msgSaveDir[-1] != os.path.sep: + options.msgSaveDir += os.path.sep + try: + os.makedirs(options.msgSaveDir) # exist_ok=True only since Python 3.2 + except OSError as exc: + if exc.errno != os.errno.EEXIST or not os.path.isdir(options.msgSaveDir): + parser.error('--msg-save-dir (-m): Failed to create %s: %s', + exc.filename, exc.strerror) + + if leftOverArgs: + parser.error('unused command line arguments: %s', leftOverArgs) + +def main(args): + """ + Start the program, possibly as a daemon depending on args. + The parameter args is like argv[1:]. + Returns an exit status which is nearly always 1, since the program is a + service in an infinite loop except for fatal errors. + SIGINT (KeyboardInterrupt) leads to return with exit status 1. + SIGTERM ends the program. It is properly re-raised after internal cleanup. + """ + usageStr = 'Usage: %prog -b BROKER -a ADDR [-p MATCH_PREFIX] -m MSG_SAVE_DIR -x EXEC [-l LOGFILE] [-q] [-d]' + versionStr = getModuleName() + ' ' + __version__ + parser = OptionParser(usage=usageStr, version=versionStr) + registerCmdOptions(parser) + (options, leftOverArgs) = parser.parse_args(args) + checkArgs(parser, options, leftOverArgs) + logFileHandler = initLogger(options.logfilename, options.quiet) # may raise + + status = 1 + try: + # systemd stops us via SIGTERM (& SIGKILL). Deal via KeyboardInterrupt. + global sigterm_seen + sigterm_seen = False + signal.signal(signal.SIGTERM, sigterm_handler) + signal.signal(signal.SIGPIPE, signal.SIG_IGN) # maintain service + signal.signal(signal.SIGHUP, signal.SIG_IGN) # maintain service + signal.signal(signal.SIGCHLD, signal.SIG_IGN) # auto-reap child procs + + if options.daemonize: + with DaemonContext(files_preserve = [logFileHandler.stream]): + logger.info('%s v%s pid %d on %s. Args: %s', argv[0], __version__, + os.getpid(), gethostname(), args) + status = run(options.broker, options.address, options.matchPrefix, + options.execPath, options.msgSaveDir) + else: + logger.info('%s %s pid %d on %s. Args: %s', argv[0], __version__, + os.getpid(), gethostname(), args) + status = run(options.broker, options.address, options.matchPrefix, + options.execPath, options.msgSaveDir) + except KeyboardInterrupt: + if sigterm_seen: + # after cleanup re-raise SIGTERM for parent; exit(val) is inadequate + signal.signal(signal.SIGTERM, signal.SIG_DFL) + logger.info('Exiting due to SIGTERM (status was %d)', status) + os.kill(os.getpid(), signal.SIGTERM) + else: + logger.warn('KeyboardInterrupt') + status = 1 # maybe need os.kill() too, but Python exits 1 on kbint + + logger.info('Exiting with status %d', status) + return status + +if __name__ == '__main__': + from sys import exit + exit(main(argv[1:])) + diff --git a/LCS/MessageDaemons/ObservationStartListener/src/__init__.py b/LCS/MessageDaemons/ObservationStartListener/src/__init__.py new file mode 100644 index 00000000000..bdee86d7700 --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/src/__init__.py @@ -0,0 +1,23 @@ +# __init__.py: Initialization of package ObservationStartListener +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the APERTIF software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id$ + +from ObservationStartListener import main, __version__ diff --git a/LCS/MessageDaemons/ObservationStartListener/src/lofarlogger.sh b/LCS/MessageDaemons/ObservationStartListener/src/lofarlogger.sh new file mode 100755 index 00000000000..ae3c6140b8b --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/src/lofarlogger.sh @@ -0,0 +1,15 @@ +#!/bin/echo Usage: source lofarlogger.sh +# +# Usage: source lofarlogger.sh +# then e.g.: log INFO "foo $bar" +# logs e.g.: 2015-10-16 16:00:46,186 INFO foo bar +# +# $Id$ + +log() { + loglevel=$1 # one of: DEBUG INFO WARNING ERROR CRITICAL + message=$2 + ts=`date --utc '+%F %T,%3N'` # e.g. 2015-10-16 16:00:46,186 + echo "$ts $loglevel $message" >&2 +} + diff --git a/LCS/MessageDaemons/ObservationStartListener/src/slurm-submit-cobalt-outputproc.sh b/LCS/MessageDaemons/ObservationStartListener/src/slurm-submit-cobalt-outputproc.sh new file mode 100755 index 00000000000..ff0a3e2b000 --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/src/slurm-submit-cobalt-outputproc.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Submit COBALT outputProc as SLURM job from args +# +# Usage: slurm-submit-cobalt-outputproc.sh obs_start_settings.qpid.msg storage_host01 ... +# +# $Id$ + +SRUN=/usr/local/bin/srun +PROGRAM=/opt/lofar/bin/outputProc + +progname=`basename -- $0` +progdir=`dirname -- $0` +settings_file=$1 +shift +hostnames=$* + +. "$progdir/lofarlogger.sh" + +log INFO "[$progname] Submitting slurm job to run COBALT outputProc with settings $settings_file on hosts: $hostnames" +#exec "$SRUN" --nodelist=$hostnames "$PROGRAM" $settings_file -- GitLab From beea8ce81af484585c8ccca639f1b57622a132c6 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Thu, 22 Oct 2015 00:29:35 +0000 Subject: [PATCH 003/933] Task #8651: patch some flaws wrt broken incoming messages in LCS MessageBus python i/f. This code is in the process of being replaced, but some important utils use it. Since I have (manually) tested improvements that fix flags, commit them. --- LCS/MessageBus/src/message.py | 41 ++++++++++++++++++++-------- LCS/MessageBus/src/messagebus.py | 18 ++++++------ LCS/MessageBus/src/noqpidfallback.py | 2 +- LCS/MessageBus/test/tPyMsgBus.py | 2 +- LCS/MessageBus/test/tPyProtocols.py | 2 +- 5 files changed, 40 insertions(+), 25 deletions(-) diff --git a/LCS/MessageBus/src/message.py b/LCS/MessageBus/src/message.py index fb587717860..25303e51260 100644 --- a/LCS/MessageBus/src/message.py +++ b/LCS/MessageBus/src/message.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # Copyright (C) 2012-2015 ASTRON (Netherlands Institute for Radio Astronomy) # P.O. Box 2, 7990 AA Dwingeloo, The Netherlands # @@ -25,6 +25,7 @@ except ImportError: import xml.dom.minidom as xml import xml.parsers.expat as expat +from xml.sax.saxutils import escape import datetime # @@ -83,12 +84,22 @@ class XMLDoc(object): return self.document.toxml() def getXMLdata(self, name): - """ Return the value of an XML key, given by its XPath. """ - return self._get_data(self._getXMLnode(name)) + """ Return the value of an XML key, given by its XPath. + Raise MessageException if name is N/A. """ + node = self._getXMLnode(name) + if node is None: + raise MessageException('node ' + name + ' not in XML document') + + return self._get_data(node) def setXMLdata(self, name, data): - """ Set the value of an XML key, given by its XPath. """ - return self._set_data(self._getXMLnode(name), data) + """ Set the value of an XML key, given by its XPath. + Raise MessageException if name is N/A. """ + node = self._getXMLnode(name) + if node is None: + raise MessageException('node ' + name + ' not in XML document') + + return self._set_data(node, data) def insertXML(self, parent, xmlStr): """ Insert XML into the current message. """ @@ -174,13 +185,19 @@ class MessageContent(object): self.timestamp = _timestamp() self.momid = momid self.sasid = sasid - else: - # Set properties by provided qpidMsg - - # Replace literal << in the content, which is occasionally inserted by the C++ - # code as part of the Parset ("Observation.Clock=<<Clock200"), - # if libxml++ is not used. - self.document = XMLDoc(qpidMsg.content.replace("<<","<<")) + else: # Set properties by provided qpidMsg + # Encode '<', '&', '>' in the content payload. Content header should not + # have these and going through all header fields is tedious; tough luck. + # Some senders fail to use libxml++ properly/at all. Fix it! Hack ahead! + if qpidMsg.content is None: + qpidMsg.content = '' # avoid find() or replace() via escape() on None + plIdx = qpidMsg.content.find('<payload>') + if plIdx != -1: + plIdx += len('<payload>') + plEndIdx = qpidMsg.content.rfind('</payload>', plIdx) + if plEndIdx != -1: + qpidMsg.content = qpidMsg.content[ : plIdx] + escape(qpidMsg.content[plIdx : plEndIdx]) + qpidMsg.content[plEndIdx : ] + self.document = XMLDoc(qpidMsg.content) # may raise MessageException def _add_property(self, name, element): def getter(self): diff --git a/LCS/MessageBus/src/messagebus.py b/LCS/MessageBus/src/messagebus.py index 77b07749c1d..fc20fe2b852 100644 --- a/LCS/MessageBus/src/messagebus.py +++ b/LCS/MessageBus/src/messagebus.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # Copyright (C) 2012-2013 ASTRON (Netherlands Institute for Radio Astronomy) # P.O. Box 2, 7990 AA Dwingeloo, The Netherlands # @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License along # with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. # -# id.. TDB +# $Id$ try: import qpid.messaging as messaging @@ -26,7 +26,6 @@ except ImportError: MESSAGING_ENABLED = False import os -import signal import logging import lofar.messagebus.message as message import atexit @@ -46,19 +45,18 @@ class BusException(Exception): class Session: def __init__(self, broker): self.closed = False - - logger.info("[Bus] Connecting to broker %s", broker) self.connection = messaging.Connection(broker) self.connection.reconnect = True - logger.info("[Bus] Connected to broker %s", broker) + logger.info("[Bus] Connecting to broker %s", broker) try: self.connection.open() + logger.info("[Bus] Connected to broker %s", broker) self.session = self.connection.session() except messaging.MessagingError, m: raise BusException(m) - # NOTE: We cannuot use: + # NOTE: We cannot use: # __del__: its broken (does not always get called, destruction order is unpredictable) # with: not supported in python 2.4, does not work well on arrays of objects # weakref: dpes not guarantee to be called (depends on gc) @@ -138,12 +136,12 @@ class FromBus(Session): def add_queue(self, queue, options=options): try: receiver = self.session.receiver(self.address(queue, options)) - - # Need capacity >=1 for 'self.session.next_receiver' to function across multiple queues - receiver.capacity = 1 #32 except messaging.MessagingError, m: raise BusException(m) + # Need capacity >=1 for 'self.session.next_receiver' to function across multiple queues + receiver.capacity = 1 + def get(self, timeout=None): msg = None diff --git a/LCS/MessageBus/src/noqpidfallback.py b/LCS/MessageBus/src/noqpidfallback.py index 26c6257a4c8..36e56c3427e 100644 --- a/LCS/MessageBus/src/noqpidfallback.py +++ b/LCS/MessageBus/src/noqpidfallback.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python import sys print >>sys.stderr, "QPID support NOT enabled! Will NOT connect to any broker, and messages will be lost!" diff --git a/LCS/MessageBus/test/tPyMsgBus.py b/LCS/MessageBus/test/tPyMsgBus.py index ffaea0d9eb7..dff6a8df62e 100644 --- a/LCS/MessageBus/test/tPyMsgBus.py +++ b/LCS/MessageBus/test/tPyMsgBus.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # # Test the basic functionality of FromBus and ToBus, both # to send and to forward messages. diff --git a/LCS/MessageBus/test/tPyProtocols.py b/LCS/MessageBus/test/tPyProtocols.py index 8c5feab3e87..6da6f430c89 100644 --- a/LCS/MessageBus/test/tPyProtocols.py +++ b/LCS/MessageBus/test/tPyProtocols.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python from lofar.parameterset import parameterset # Test task.feedback.dataproducts -- GitLab From 1e2eb18702eef4c3117cb2917bcb3c2e9c955281 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Thu, 22 Oct 2015 00:30:06 +0000 Subject: [PATCH 004/933] Task #8651: dragnet variants file comment update --- CMake/variants/variants.dragnet | 1 + 1 file changed, 1 insertion(+) diff --git a/CMake/variants/variants.dragnet b/CMake/variants/variants.dragnet index f7bcc2395d6..c771252f765 100644 --- a/CMake/variants/variants.dragnet +++ b/CMake/variants/variants.dragnet @@ -6,6 +6,7 @@ option(USE_MPI "Use MPI" ON) option(USE_OPENMP "Use OpenMP" ON) option(USE_CUDA "Use CUDA" ON) +# Specify versions, such that ABI incompat updates of these don't break already installed LOFAR binaries. Matters when we have to roll-back. set(LOG4CPLUS_ROOT_DIR /opt/log4cplus-1.1.2) set(BLITZ_ROOT_DIR /opt/blitz-0.10) set(CUDA_TOOLKIT_ROOT_DIR /opt/cuda-7.0) -- GitLab From 5c274e9747648de9f39a436abaed944d7e0c98df Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Thu, 22 Oct 2015 00:33:08 +0000 Subject: [PATCH 005/933] Task #8651: add svn props to .service file --- .gitattributes | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 992895d7ae8..e95ef0b4e49 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2567,7 +2567,6 @@ LCS/MessageBus/test/tPyMsgBus.run eol=lf LCS/MessageBus/test/tPyMsgBus.sh eol=lf LCS/MessageBus/test/tPyProtocols.run eol=lf LCS/MessageBus/test/tPyProtocols.sh eol=lf -LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service -text LCS/MessageDaemons/ObservationStartListener/src/lofarlogger.sh eol=lf LCS/MessageDaemons/ObservationStartListener/src/slurm-submit-cobalt-outputproc.sh eol=lf LCS/MessageDaemons/src/MessageRouter -text -- GitLab From 9588a9ac3b2155bf9d5f3ba48641535542c87efa Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Thu, 22 Oct 2015 12:25:53 +0000 Subject: [PATCH 006/933] Task #8651: add ObservationStartListener into CMake system as a new LOFAR package. Verified make install installs the right files. --- CMake/LofarPackageList.cmake | 3 ++- LCS/MessageDaemons/CMakeLists.txt | 2 ++ .../ObservationStartListener/CMakeLists.txt | 11 +++++++++++ .../ObservationStartListener/etc/CMakeLists.txt | 5 +++++ .../etc/LofarObservationStartListener.service | 4 +++- .../ObservationStartListener/src/CMakeLists.txt | 14 ++++++++++++++ 6 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 LCS/MessageDaemons/ObservationStartListener/CMakeLists.txt create mode 100644 LCS/MessageDaemons/ObservationStartListener/etc/CMakeLists.txt create mode 100644 LCS/MessageDaemons/ObservationStartListener/src/CMakeLists.txt diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index 169e826ad19..c7d2ae93e48 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -1,7 +1,7 @@ # - Create for each LOFAR package a variable containing the absolute path to # its source directory. # -# Generated by gen_LofarPackageList_cmake.sh at Tue Jun 23 21:08:57 UTC 2015 +# Generated by gen_LofarPackageList_cmake.sh at Thu Oct 22 14:16:00 CEST 2015 # # ---- DO NOT EDIT ---- # @@ -73,6 +73,7 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(Transport_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCS/Transport) set(MSLofar_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCS/MSLofar) set(LofarStMan_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCS/LofarStMan) + set(ObservationStartListener_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCS/MessageDaemons/ObservationStartListener) set(Firmware_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCU/Firmware) set(StationTest_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCU/StationTest) set(checkhardware_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCU/checkhardware) diff --git a/LCS/MessageDaemons/CMakeLists.txt b/LCS/MessageDaemons/CMakeLists.txt index d6d6a3731b5..8b3fb56c358 100644 --- a/LCS/MessageDaemons/CMakeLists.txt +++ b/LCS/MessageDaemons/CMakeLists.txt @@ -8,3 +8,5 @@ lofar_find_package(QPID REQUIRED) # MessageBus may be able to fake it without QP add_subdirectory(src) add_subdirectory(test) add_subdirectory(webmonitor) + +lofar_add_package(ObservationStartListener) diff --git a/LCS/MessageDaemons/ObservationStartListener/CMakeLists.txt b/LCS/MessageDaemons/ObservationStartListener/CMakeLists.txt new file mode 100644 index 00000000000..e89c7347bd5 --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/CMakeLists.txt @@ -0,0 +1,11 @@ +# $Id$ + +lofar_package(ObservationStartListener 1.0 DEPENDS MessageBus pyparameterset) + +include(LofarFindPackage) +lofar_find_package(QPID) +lofar_find_package(LibXMLxx) + +add_subdirectory(etc) +add_subdirectory(src) +#add_subdirectory(test) diff --git a/LCS/MessageDaemons/ObservationStartListener/etc/CMakeLists.txt b/LCS/MessageDaemons/ObservationStartListener/etc/CMakeLists.txt new file mode 100644 index 00000000000..48711a9666e --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/etc/CMakeLists.txt @@ -0,0 +1,5 @@ +# $Id$ + +# Do not install this systemd service file, as it must go into a system path. +#LofarObservationStartListener.service + diff --git a/LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service b/LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service index d55e0f49c2e..f850c23efb3 100644 --- a/LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service +++ b/LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service @@ -8,7 +8,7 @@ # $Id$ [Unit] -Description=LOFAR daemon that listens to observation start messages. DRAGNET cluster config. +Description=LOFAR daemon that listens to qpid messages that match to a prefix and then runs a program passing the message and the matched values. Requires=network.target After=network.target @@ -31,8 +31,10 @@ ExecStart=/usr/bin/python /opt/lofar/bin/ObservationStartListener.py \ --quiet # To test: +# Create test exchange: qpid-config add exchange fanout alexander.test.task.specification.system # Service: /usr/bin/python /home/amesfoort/obslistener/ObservationStartListener.py --broker localhost --address alexander.test.task.specification.system --match-prefix 'node,yike' --msg-save-dir /home/amesfoort/obslistener/parsetsxml --exec /home/amesfoort/obslistener/slurm-submit-cobalt-outputproc.sh --logfile /home/amesfoort/obslistener/ObservationStartListener.log # Client: qpid-send -a alexander.test.task.specification.system --content-string "`/bin/cat testout3.parset`" +# Delete test exchange: qpid-config del exchange alexander.test.task.specification.system Restart=on-failure diff --git a/LCS/MessageDaemons/ObservationStartListener/src/CMakeLists.txt b/LCS/MessageDaemons/ObservationStartListener/src/CMakeLists.txt new file mode 100644 index 00000000000..3849d138e21 --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# $Id$ + +include(PythonInstall) + +python_install( + __init__.py + ObservationStartListener.py + DESTINATION lofar/ObservationStartListener +) + +lofar_add_bin_scripts( + lofarlogger.sh + slurm-submit-cobalt-outputproc.sh +) -- GitLab From ffe312c4af79b970c24ebedeebcf86ca5f371468 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Fri, 23 Oct 2015 18:35:29 +0000 Subject: [PATCH 007/933] Task #8651: ObservationStartListener: add test + small bug fixes. Fix parset list expansion bug. Rename and fix systemd config. --- .gitattributes | 3 ++ .../ObservationStartListener/CMakeLists.txt | 2 +- ...rObservationStartListener-dragnet.service} | 14 ++---- .../src/ObservationStartListener.py | 11 ++--- .../test/CMakeLists.txt | 7 +++ .../tObservationStartListener.in.execmock.sh | 8 ++++ ...StartListener.parset.correct.match.msg.xml | 47 +++++++++++++++++++ ...artListener.parset.correct.nomatch.msg.xml | 47 +++++++++++++++++++ ...tionStartListener.parset.incorrect.msg.xml | 45 ++++++++++++++++++ .../test/tObservationStartListener.py | 10 ++++ .../test/tObservationStartListener.run | 42 +++++++++++++++++ .../test/tObservationStartListener.sh | 2 + 12 files changed, 221 insertions(+), 17 deletions(-) rename LCS/MessageDaemons/ObservationStartListener/etc/{LofarObservationStartListener.service => LofarObservationStartListener-dragnet.service} (56%) create mode 100644 LCS/MessageDaemons/ObservationStartListener/test/CMakeLists.txt create mode 100755 LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.in.execmock.sh create mode 100644 LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.correct.match.msg.xml create mode 100644 LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.correct.nomatch.msg.xml create mode 100644 LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.incorrect.msg.xml create mode 100755 LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.py create mode 100755 LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.run create mode 100755 LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.sh diff --git a/.gitattributes b/.gitattributes index e95ef0b4e49..20b272ee0b6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2569,6 +2569,9 @@ LCS/MessageBus/test/tPyProtocols.run eol=lf LCS/MessageBus/test/tPyProtocols.sh eol=lf LCS/MessageDaemons/ObservationStartListener/src/lofarlogger.sh eol=lf LCS/MessageDaemons/ObservationStartListener/src/slurm-submit-cobalt-outputproc.sh eol=lf +LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.in.execmock.sh eol=lf +LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.run eol=lf +LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.sh eol=lf LCS/MessageDaemons/src/MessageRouter -text LCS/MessageDaemons/src/MessageRouter.conf.ccu001 -text LCS/MessageDaemons/src/MessageRouter.conf.ccu099 -text diff --git a/LCS/MessageDaemons/ObservationStartListener/CMakeLists.txt b/LCS/MessageDaemons/ObservationStartListener/CMakeLists.txt index e89c7347bd5..c0774c7b52b 100644 --- a/LCS/MessageDaemons/ObservationStartListener/CMakeLists.txt +++ b/LCS/MessageDaemons/ObservationStartListener/CMakeLists.txt @@ -8,4 +8,4 @@ lofar_find_package(LibXMLxx) add_subdirectory(etc) add_subdirectory(src) -#add_subdirectory(test) +add_subdirectory(test) diff --git a/LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service b/LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener-dragnet.service similarity index 56% rename from LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service rename to LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener-dragnet.service index f850c23efb3..a56d8d6b716 100644 --- a/LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener.service +++ b/LCS/MessageDaemons/ObservationStartListener/etc/LofarObservationStartListener-dragnet.service @@ -1,9 +1,9 @@ -# LofarObservationStartListener.service systemd service description for DRAGNET +# LofarObservationStartListener-dragnet.service systemd service description for DRAGNET # # Not part of LOFAR roll-out, since cluster specific and to be installed into: # /usr/lib/systemd/system/ (CentOS) # /lib/systemd/system/ (Ubuntu) -# Then run: sudo systemctl daemon-reload && sudo systemctl restart LofarObservationStartListener +# Then run: sudo systemctl daemon-reload && sudo systemctl restart LofarObservationStartListener-dragnet # # $Id$ @@ -20,8 +20,10 @@ Group=dragnet # Type, ExecStart: no daemonization (-d) needed when managed by systemd as Type=simple Type=simple + +# Note: you must use {} when using env vars Environment='PYTHONPATH=/opt/lofar/lib64/python2.7/site-packages' -ExecStart=/usr/bin/python /opt/lofar/bin/ObservationStartListener.py \ +ExecStart=/usr/bin/python "${PYTHONPATH}/lofar/ObservationStartListener/ObservationStartListener.py" \ --broker ccu001.control.lofar \ --address dump.lofar.task.specification.system \ --match-prefix 'drg,drag' \ @@ -30,12 +32,6 @@ ExecStart=/usr/bin/python /opt/lofar/bin/ObservationStartListener.py \ --logfile /opt/lofar/var/log/ObservationStartListener.log \ --quiet -# To test: -# Create test exchange: qpid-config add exchange fanout alexander.test.task.specification.system -# Service: /usr/bin/python /home/amesfoort/obslistener/ObservationStartListener.py --broker localhost --address alexander.test.task.specification.system --match-prefix 'node,yike' --msg-save-dir /home/amesfoort/obslistener/parsetsxml --exec /home/amesfoort/obslistener/slurm-submit-cobalt-outputproc.sh --logfile /home/amesfoort/obslistener/ObservationStartListener.log -# Client: qpid-send -a alexander.test.task.specification.system --content-string "`/bin/cat testout3.parset`" -# Delete test exchange: qpid-config del exchange alexander.test.task.specification.system - Restart=on-failure # Raise prio somewhat. This service must be responsive. diff --git a/LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py b/LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py index 61002bd9872..ef67e529fa9 100755 --- a/LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py +++ b/LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py @@ -44,9 +44,6 @@ import lofar.messagebus.messagebus as lofMess __version__ = "1.0" -#TODO: set it up in CMake env, install as pkg under lofar module -#TODO: add test case; also test parset key expand - def runProcess(execPath, parsetFilename, hosts): """ Run command execPath with arguments parsetFilename and the sequence hosts. @@ -73,19 +70,19 @@ def getOutputHosts(paramset, matchPrefix): # so we don't have to try/except RuntimeError. uvOutputEnabled = paramset.getBool('ObsSW.Observation.DataProducts.Output_Correlated.enabled', False) if uvOutputEnabled: - uvOutputLocations = paramset.getStringVector('ObsSW.Observation.DataProducts.Output_Correlated.locations', []) + uvOutputLocations = paramset.getStringVector('ObsSW.Observation.DataProducts.Output_Correlated.locations', [], True) # E.g. 'node' in ['node1:/a/b', 'nope:/x/y'] -> ['node1'] hosts.extend([loc.split(':')[0] for loc in uvOutputLocations if loc.startswith(matchPrefix)]) cohStokesOutputEnabled = paramset.getBool('ObsSW.Observation.DataProducts.Output_CoherentStokes.enabled', False) if cohStokesOutputEnabled: - cohStokesOutputLocations = paramset.getStringVector('ObsSW.Observation.DataProducts.Output_CoherentStokes.locations', []) + cohStokesOutputLocations = paramset.getStringVector('ObsSW.Observation.DataProducts.Output_CoherentStokes.locations', [], True) hosts.extend([loc.split(':')[0] for loc in cohStokesOutputLocations if loc.startswith(matchPrefix)]) incohStokesOutputEnabled = paramset.getBool('ObsSW.Observation.DataProducts.Output_IncoherentStokes.enabled', False) if incohStokesOutputEnabled: - incohStokesOutputLocations = paramset.getStringVector('ObsSW.Observation.DataProducts.Output_IncoherentStokes.locations', []) + incohStokesOutputLocations = paramset.getStringVector('ObsSW.Observation.DataProducts.Output_IncoherentStokes.locations', [], True) hosts.extend([loc.split(':')[0] for loc in incohStokesOutputLocations if loc.startswith(matchPrefix)]) return hosts @@ -234,7 +231,7 @@ def registerCmdOptions(parser): help='suppress logging stream to stderr. Useful with -l and when run from systemd to keep system log clean.') parser.add_option('-d', '--daemon', action='store_true', dest='daemonize', default=False, - help='run this program as a daemon') + help='run this program as a daemon. Use absolute paths in other options.') def checkArgs(parser, options, leftOverArgs): # Mandatory option is contradictory, but these as positional args is unclear. diff --git a/LCS/MessageDaemons/ObservationStartListener/test/CMakeLists.txt b/LCS/MessageDaemons/ObservationStartListener/test/CMakeLists.txt new file mode 100644 index 00000000000..7230a79692d --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/test/CMakeLists.txt @@ -0,0 +1,7 @@ +# $Id$ + +include(LofarCTest) + +if(HAVE_QPID) + lofar_add_test(tObservationStartListener) +endif(HAVE_QPID) diff --git a/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.in.execmock.sh b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.in.execmock.sh new file mode 100755 index 00000000000..d562497228a --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.in.execmock.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# Mock program as a value for --exec parameter in tests. +# +# $Id$ + +# stderr is hooked to the program's log file, which is used for output verif +# looking for (part of) this echo. +echo "[`basename -- $0`] Args: $*" >&2 diff --git a/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.correct.match.msg.xml b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.correct.match.msg.xml new file mode 100644 index 00000000000..4f9fb076eb8 --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.correct.match.msg.xml @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<message> + <header> + <system>LOFAR</system> + <version>1.0.0</version> + <protocol> + <name>task.specification.system</name> + <version>1.0.0</version> + </protocol> + <source> + <name>LOFAR.MACScheduler</name> + <user></user> + <uuid>9ecef8b9-5d36-4a08-9810-8f72b53d8a94</uuid> + <timestamp>2015-01-01T03:01:00</timestamp> + <summary></summary> + </source> + <ids> + <momid>100000</momid> + <sasid>301010</sasid> + </ids> + </header> + <payload>key1=unusedvalue1 +key2=unusedvalue2 + +ObsSW.Observation.Campaign.CO_I="Dicey, Rose" +ObsSW.Observation.Campaign.PI="Random, Joe" +ObsSW.Observation.Campaign.contact="Random, Joe" +ObsSW.Observation.Campaign.name="TEST-001" +ObsSW.Observation.Campaign.title="SOFTWARE TEST PROJECT" + +ObsSW.Observation.DataProducts.Output_CoherentStokes.enabled=true +ObsSW.Observation.DataProducts.Output_CoherentStokes.filenames=[L301010_B000_cs.h5] +ed, since enabled=false +ObsSW.Observation.DataProducts.Output_CoherentStokes.locations=[node000:/data/L301010/] + +ObsSW.Observation.DataProducts.Output_Correlated.enabled=true +ObsSW.Observation.DataProducts.Output_Correlated.filenames=[L301010_SB001_uv.MS,L301010_SB002_uv.MS] +ObsSW.Observation.DataProducts.Output_Correlated.locations=[node001:/data/L301010/,yyy001:/data/L301010/] + +ObsSW.Observation.DataProducts.Output_IncoherentStokes.enabled=true +ObsSW.Observation.DataProducts.Output_IncoherentStokes.filenames=[L301010_B000_is.h5,L301010_B001_is.h5] +ObsSW.Observation.DataProducts.Output_IncoherentStokes.locations=[yikes002:/data/L301010/,1*yikes003.xxx.yyy.lofar:/data/L301010/] + +ObsSW.Observation.startTime=2015-01-01 03:03:00 +ObsSW.Observation.stopTime=2015-01-01 03:13:00 +</payload> +</message> diff --git a/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.correct.nomatch.msg.xml b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.correct.nomatch.msg.xml new file mode 100644 index 00000000000..97366479690 --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.correct.nomatch.msg.xml @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<message> + <header> + <system>LOFAR</system> + <version>1.0.0</version> + <protocol> + <name>task.specification.system</name> + <version>1.0.0</version> + </protocol> + <source> + <name>LOFAR.MACScheduler</name> + <user></user> + <uuid>7ecef8b9-5d36-4a08-9810-8f72b53d8a94</uuid> + <timestamp>2015-01-01T01:01:00</timestamp> + <summary></summary> + </source> + <ids> + <momid>100000</momid> + <sasid>101010</sasid> + </ids> + </header> + <payload>key1=unusedvalue1 +key2=unusedvalue2 + +ObsSW.Observation.Campaign.CO_I="Dicey, Rose" +ObsSW.Observation.Campaign.PI="Random, Joe" +ObsSW.Observation.Campaign.contact="Random, Joe" +ObsSW.Observation.Campaign.name="TEST-001" +ObsSW.Observation.Campaign.title="SOFTWARE TEST PROJECT" + +ObsSW.Observation.DataProducts.Output_CoherentStokes.enabled=false +ObsSW.Observation.DataProducts.Output_CoherentStokes.filenames=[L101010_B000_cs.h5] # should not be us +ed, since enabled=false +ObsSW.Observation.DataProducts.Output_CoherentStokes.locations=[node000:/data/L101010/] # idem + +ObsSW.Observation.DataProducts.Output_Correlated.enabled=true +ObsSW.Observation.DataProducts.Output_Correlated.filenames=[L101010_SB001_uv.MS,L101010_SB002_uv.MS] +ObsSW.Observation.DataProducts.Output_Correlated.locations=[nodXYZ:/data/L101010/,yyy001:/data/L101010/] + +ObsSW.Observation.DataProducts.Output_IncoherentStokes.enabled=false +ObsSW.Observation.DataProducts.Output_IncoherentStokes.filenames=[] +ObsSW.Observation.DataProducts.Output_IncoherentStokes.locations=[] + +ObsSW.Observation.startTime=2015-01-01 01:03:00 +ObsSW.Observation.stopTime=2015-01-01 01:13:00 +</payload> +</message> diff --git a/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.incorrect.msg.xml b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.incorrect.msg.xml new file mode 100644 index 00000000000..10833c6ab49 --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.parset.incorrect.msg.xml @@ -0,0 +1,45 @@ +<?xml version="1.0"?> +<message> + <header> + <system>LOFAR</system> + <version>1.0.0</version> + <protocol> + <name>task.specification.system</name> + <version>1.0.0</version> + </protocol> + <source> + <name>LOFAR.MACScheduler</name> + <user></user> + <uuid>8ecef8b9-5d36-4a08-9810-8f72b53d8a94</uuid> + <timestamp>2015-01-02T01:01:00</timestamp> + <summary></summary> + </source> + <ids> + <momid>200000</momid> + <sasid>201010</sasid> + </ids> + </header> + <payload>key1=unusedvalue1 +key2=unusedvalue2 + +ObsSW.Observation.Campaign.CO_I="Dicey, Rose" +ObsSW.Observation.Campaign.PI="Random, Joe" +ObsSW.Observation.Campaign.contact="Random, Joe" +ObsSW.Observation.Campaign.name="TEST-001" +ObsSW.Observation.Campaign.title="SOFTWARE TEST PROJECT" + +ObsSW.Observation.DataProducts.Output_CoherentStokes.enabled=true +ObsSW.Observation.DataProducts.Output_CoherentStokes.filenames=[L201010_B000_cs.h5] +ed, since enabled=false +ObsSW.Observation.DataProducts.Output_CoherentStokes.locations=[node000:/data/L201010/] + +ObsSW.Observation.DataProducts.Output_Correlated.enabled=true +ObsSW.Observation.DataProducts.Output_Correlated.filenames=[L201010_SB001_uv.MS,L201010_SB002_uv.MS] +ObsSW.Observation.DataProducts.Output_Correlated.locations=[node001:/data/L201010/,yikezzz:/data/L201010/] + +ObsSW.Observation.DataProducts.Output_IncoherentStokes.enabled=true +ObsSW.Observation.DataProducts.Output_IncoherentStokes.filenames=[L201010_SB010_is.MS] +ObsSW.Observation.DataProducts.Output_IncoherentStokes.locations=[node002:/data/L201010/,node003:/data/L201010/] + +ObsSW.Observation.startTime=2015-01-01 02:03:00 +ObsSW.Observation.stopTime=2015-01-01 02:13:00 # NOTE: hereafter, on purpose missing XML close tags to test with a broken message diff --git a/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.py b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.py new file mode 100755 index 00000000000..e190bc1b198 --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +import lofar.ObservationStartListener as osl + +print('program version: ' + osl.__version__) # test get program version; --version ends calling sys.exit(0) +print('') + +from sys import argv +osl.main(argv[1:]) +# Don't add anything here, since the above invocation will be terminated using SIGTERM diff --git a/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.run b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.run new file mode 100755 index 00000000000..940f383d918 --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.run @@ -0,0 +1,42 @@ +#!/bin/bash -e + +trap 'kill -9 $pid 2>/dev/null|| :' EXIT # '|| :' avoids this kill from influencing exit status (i.e. test Passed/Failed) + +source MessageFuncs.sh # generate and echo a unique QUEUE_PREFIX, create_queue (auto-del, but keeps our trap), send_msg + +QUEUE=tObservationStartListener # NOTE: actual qpid queue name will be ${QUEUE_PREFIX}tObservationStartListener +MSGDIR=tObservationStartListener-msgsavedir-$QUEUE_PREFIX # avoid relative path if --daemon (-d) would be used +EXEC=tObservationStartListener.in.execmock.sh # idem on path +LOGFILE=tObservationStartListener-${QUEUE_PREFIX}log # idem on path + +create_queue $QUEUE # NOTE: actual qpid queue name will be ${QUEUE_PREFIX}tObservationStartListener + +python tObservationStartListener.py --broker localhost --address "$QUEUE" --match-prefix 'node,yike' --msg-save-dir "$MSGDIR" --exec "$EXEC" --logfile "$LOGFILE" & +pid=$! + +# Msg 1: Send incorrect message: ignore and continue +send_msg $QUEUE "`cat tObservationStartListener.parset.incorrect.msg.xml`" + +# Msg 2: Send correct message that has no match: ignore and continue +send_msg $QUEUE "`cat tObservationStartListener.parset.correct.nomatch.msg.xml`" + +# Msg 3: Send correct message that has some match: --exec script must be run (and continue) +send_msg $QUEUE "`cat tObservationStartListener.parset.correct.match.msg.xml`" + +# Msg 4: Redo Msg 3 +send_msg $QUEUE "`cat tObservationStartListener.parset.correct.match.msg.xml`" + +sleep 0.5 # wait for executed program triggered by Msg 3 & 4 to finish +kill -s SIGTERM $pid 2>/dev/null +wait $pid || : # mask exit status for bash -e +echo + +echo 'Verifying output...' +EXPECTED_SUBSTRING='L301010.parset.xml node000 node001 yikes002 yikes003.xxx.yyy.lofar' +NR_CORRECT_MATCHES=`grep '[tObservationStartListener.in.execmock.sh]' "$LOGFILE" | grep "$EXPECTED_SUBSTRING" | wc -l` +if [ $NR_CORRECT_MATCHES -eq 2 ]; then + echo "Found 2 entries from --exec passed script in $LOGFILE" +else + echo "ERROR: Found $NR_CORRECT_MATCHES entries from --exec passed script, but expected 2 in $LOGFILE" + exit 1 +fi diff --git a/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.sh b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.sh new file mode 100755 index 00000000000..37c09f8cb48 --- /dev/null +++ b/LCS/MessageDaemons/ObservationStartListener/test/tObservationStartListener.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh tObservationStartListener -- GitLab From 09202b7de153fbe81837e9b0b6c98de44bff2452 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Fri, 23 Oct 2015 19:06:41 +0000 Subject: [PATCH 008/933] Task #8651: ObservationStartListener: make python usage of daemon module optional, since many LOFAR systems do not have it --- .../src/ObservationStartListener.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py b/LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py index ef67e529fa9..fd8700f02c7 100755 --- a/LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py +++ b/LCS/MessageDaemons/ObservationStartListener/src/ObservationStartListener.py @@ -35,9 +35,13 @@ import signal import logging from socket import gethostname from time import sleep -from daemon import DaemonContext from subprocess import Popen from optparse import OptionParser +daemon_exc = None +try: + from daemon import DaemonContext +except ImportError as exc: + daemon_exc = exc import lofar.parameterset as lofParset import lofar.messagebus.messagebus as lofMess @@ -229,9 +233,11 @@ def registerCmdOptions(parser): parser.add_option('-q', '--quiet', action='store_true', dest='quiet', default=False, help='suppress logging stream to stderr. Useful with -l and when run from systemd to keep system log clean.') + daemon_help = 'run this program as a daemon. Use absolute paths in other options.' + if daemon_exc is not None: + daemon_help += ' (N/A: ImportError: ' + daemon_exc.message + ')' parser.add_option('-d', '--daemon', action='store_true', dest='daemonize', - default=False, - help='run this program as a daemon. Use absolute paths in other options.') + default=False, help=daemon_help) def checkArgs(parser, options, leftOverArgs): # Mandatory option is contradictory, but these as positional args is unclear. @@ -243,6 +249,8 @@ def checkArgs(parser, options, leftOverArgs): parser.error('--msg-save-dir (-m) is required (or pass -h for usage)') if options.execPath is None: parser.error('--exec (-x) is required (or pass -h for usage)') + if options.daemonize and daemon_exc is not None: + parser.error('--daemon (-d) is N/A: ImportError: ' + daemon_exc.message) options.matchPrefix = tuple(options.matchPrefix.split(',')) # for str.startswith() -- GitLab From d93e8a96bc81dc8749019ed4f5c974f6796c53a0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 20 Jan 2016 14:49:25 +0000 Subject: [PATCH 009/933] Task #8725: minor fixes from production testing --- LTA/LTAIngest/ltacp.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index 6f6b0554257..31608e41deb 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -178,10 +178,10 @@ class LtaCp: self.started_procs[p_md5_local] = cmd_md5_local # start computing adler checksum of incoming data stream - cmd_a32_local = ['./md5adler/a32', self.local_adler32_fifo] - #cmd_a32_local = ['md5sum', self.local_adler32_fifo] + currdir = os.path.dirname(os.path.realpath(__file__)) + cmd_a32_local = [currdir + '/md5adler/a32', self.local_adler32_fifo] logger.info('ltacp %s: computing local adler32 checksum. executing: %s' % (self.logId, ' '.join(cmd_a32_local))) - p_a32_local = Popen(cmd_a32_local, stdout=PIPE, stderr=PIPE) + p_a32_local = Popen(cmd_a32_local, stdin=PIPE, stdout=PIPE, stderr=PIPE) self.started_procs[p_a32_local] = cmd_a32_local # start copy fifo stream to SRM @@ -277,7 +277,7 @@ class LtaCp: return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / float(10**6) # waiting for output, comparing checksums, etc. - logger.info('ltacp %s: transfering...' % self.logId) + logger.info('ltacp %s: transfering... waiting for progress...' % self.logId) transfer_start_time = datetime.utcnow() prev_progress_time = datetime.utcnow() prev_bytes_transfered = 0 @@ -367,7 +367,7 @@ class LtaCp: if p_a32_local.returncode != 0: raise LtacpException('ltacp %s: local adler32 checksum computation failed: %s' (self.logId, str(output_a32_local))) logger.debug('ltacp %s: finished computation of local adler32 checksum' % self.logId) - a32_checksum_local = output_a32_local[0].split()[1] + a32_checksum_local = output_a32_local[0].split()[1].zfill(8) logger.info('ltacp %s: fetching adler32 checksum from LTA...' % self.logId) srm_ok, srm_file_size, srm_a32_checksum = get_srm_size_and_a32_checksum(self.dst_surl) @@ -382,7 +382,7 @@ class LtaCp: logger.info('ltacp %s: adler32 checksums are equal: %s' % (self.logId, a32_checksum_local)) - if(srm_file_size != byte_count): + if(int(srm_file_size) != int(byte_count)): raise LtacpException('ltacp %s: file size reported by srm (%s) does not match datastream byte count (%s)' % (self.logId, srm_file_size, byte_count)) -- GitLab From 6bcca0a940f7f0ccbbca65b4e050a4f451e82580 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 1 Feb 2016 11:02:00 +0000 Subject: [PATCH 010/933] Task #8725: use OptionParser for cmdline args. Do not tar for single file transfers. --- LTA/LTAIngest/ltacp.py | 65 +++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index 31608e41deb..6271d75ca4a 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -8,6 +8,7 @@ # adler32 is used between localhost and the SRM. import logging +from optparse import OptionParser from subprocess import Popen, PIPE import socket import os, sys, getpass @@ -79,7 +80,7 @@ def createNetCatCmd(user, host): # nc has no version option or other ways to check it's version # so, just try the variants and pick the first one that does not fail - nc_variants = ['nc --send-only', 'nc -q 0'] + nc_variants = ['nc --send-only', 'nc -q 0', 'nc'] for nc_variant in nc_variants: cmd = ['ssh', '-n', '-x', '%s@%s' % (user, host), nc_variant] @@ -233,6 +234,25 @@ class LtaCp: if p_remote_mkfifo.returncode != 0: raise LtacpException('ltacp %s: remote fifo creation failed: \nstdout: %s\nstderr: %s' % (self.logId, output_remote_mkfifo[0],output_remote_mkfifo[1])) + + # get input filetype + cmd_remote_filetype = self.ssh_cmd + ['ls -l %s' % (self.src_path_data,)] + logger.info('ltacp %s: remote getting file info. executing: %s' % (self.logId, ' '.join(cmd_remote_filetype))) + p_remote_filetype = Popen(cmd_remote_filetype, stdout=PIPE, stderr=PIPE) + self.started_procs[p_remote_filetype] = cmd_remote_filetype + + # block until ls is finished + output_remote_filetype = p_remote_filetype.communicate() + del self.started_procs[p_remote_filetype] + if p_remote_filetype.returncode != 0: + raise LtacpException('ltacp %s: remote file listing failed: \nstdout: %s\nstderr: %s' % (self.logId, + output_remote_filetype[0], + output_remote_filetype[1])) + + # determine if input is file + input_is_file = (output_remote_filetype[0][0] == '-') + + # get input datasize cmd_remote_du = self.ssh_cmd + ['du -b --max-depth=0 %s' % (self.src_path_data,)] logger.info('ltacp %s: remote getting datasize. executing: %s' % (self.logId, ' '.join(cmd_remote_du))) @@ -243,9 +263,9 @@ class LtaCp: output_remote_du = p_remote_du.communicate() del self.started_procs[p_remote_du] if p_remote_du.returncode != 0: - raise LtacpException('ltacp %s: remote fifo creation failed: \nstdout: %s\nstderr: %s' % (self.logId, - output_remote_du[0], - output_remote_du[1])) + raise LtacpException('ltacp %s: remote du failed: \nstdout: %s\nstderr: %s' % (self.logId, + output_remote_du[0], + output_remote_du[1])) # compute various parameters for progress logging input_datasize = int(output_remote_du[0].split()[0]) logger.info('ltacp %s: input datasize: %d bytes, %s' % (self.logId, input_datasize, humanreadablesize(input_datasize))) @@ -255,13 +275,20 @@ class LtaCp: with open(os.devnull, 'r') as devnull: # start sending remote data, tee to fifo - src_path_parent, src_path_child = os.path.split(self.src_path_data) - cmd_remote_data = self.ssh_cmd + ['cd %s && tar c --blocking-factor=20 --checkpoint=1000 --checkpoint-action="ttyout=checkpoint %%u\\n" -O %s | tee %s | %s %s %s' % (src_path_parent, - src_path_child, - self.remote_data_fifo, - self.remoteNetCatCmd, - self.localIPAddress, - port_data)] + if input_is_file: + cmd_remote_data = self.ssh_cmd + ['cat %s | tee %s | %s %s %s' % (self.src_path_data, + self.remote_data_fifo, + self.remoteNetCatCmd, + self.localIPAddress, + port_data)] + else: + src_path_parent, src_path_child = os.path.split(self.src_path_data) + cmd_remote_data = self.ssh_cmd + ['cd %s && tar c --blocking-factor=20 --checkpoint=1000 --checkpoint-action="ttyout=checkpoint %%u\\n" -O %s | tee %s | %s %s %s' % (src_path_parent, + src_path_child, + self.remote_data_fifo, + self.remoteNetCatCmd, + self.localIPAddress, + port_data)] logger.info('ltacp %s: remote starting transfer. executing: %s' % (self.logId, ' '.join(cmd_remote_data))) p_remote_data = Popen(cmd_remote_data, stdin=devnull, stdout=PIPE, stderr=PIPE) self.started_procs[p_remote_data] = cmd_remote_data @@ -555,12 +582,18 @@ def create_missing_directories(surl): # limited standalone mode for testing: # usage: ltacp.py <remote-host> <remote-path> <surl> if __name__ == '__main__': + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO) + + # Check the invocation arguments + parser = OptionParser("%prog [options] <source_host> <source_path> <lta-detination-srm-url>", + description='copy a file/directory from <source_host>:<source_path> to the LTA <lta-detination-srm-url>') + parser.add_option("-u", "--user", dest="user", type="string", default=getpass.getuser(), help="username for to login on <host>, default: %s" % getpass.getuser()) + (options, args) = parser.parse_args() - if len(sys.argv) < 4: - print 'example: ./ltacp.py 10.196.232.11 /home/users/ingest/1M.txt srm://lofar-srm.fz-juelich.de:8443/pnfs/fz-juelich.de/data/lofar/ops/test/eor/1M.txt' - sys.exit() + if len(args) != 3: + parser.print_help() + sys.exit(1) - # transfer test: - cp = LtaCp(sys.argv[1], sys.argv[2], sys.argv[3], 'ingest') + cp = LtaCp(args[0], args[1], args[2], options.user) cp.transfer() -- GitLab From 4e8dcee0b8f7a2bb069bcf6e4635627a95da0f63 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 18 Feb 2016 08:20:13 +0000 Subject: [PATCH 011/933] Task #8725: skip SIP transfer to catalogue if not specified in eor job file --- LTA/LTAIngest/ingestpipeline.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index 8dede787e44..71ea23c3b7a 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -381,10 +381,8 @@ class IngestPipeline(): self.logger.debug('SIP received for %s from MoM with size %d (%s): %s' % (self.JobId, len(self.SIP), humanreadablesize(len(self.SIP)), self.SIP[0:256])) elif self.Type.lower() == "eor": try: - sip_host = job['SIPLocation'].split(':')[0] - for i in range(1, 43): - sip_host = sip_host.replace('node%d.intra.dawn.rug.nl' % (i+100,), '10.196.232.%d' % (i+10,)) - sip_path = job['SIPLocation'].split(':')[1] + sip_host = self.job['SIPLocation'].split(':')[0] + sip_path = self.job['SIPLocation'].split(':')[1] cmd = ['ssh', '-tt', '-n', '-x', '-q', '%s@%s' % (getpass.getuser(), sip_host), 'cat %s' % sip_path] self.logger.debug("GetSIP for %s with mom2DPId %s - StorageTicket %s - FileName %s - Uri %s - cmd %s" % (self.JobId, self.ArchiveId, self.ticket, self.FileName, self.PrimaryUri, ' ' .join(cmd))) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -441,6 +439,7 @@ class IngestPipeline(): self.SIP = sip_dom.toxml("utf-8") self.SIP = self.SIP.replace('<stationType>Europe</stationType>','<stationType>International</stationType>') + self.SIP = self.SIP.replace('<source>EOR</source>','<source>EoR</source>') #EoR sips sometime contain EOR instead of EoR as source except: self.logger.exception('Getting SIP from EoR failed') @@ -530,16 +529,18 @@ class IngestPipeline(): start = time.time() self.RetryRun(self.GetStorageTicket, self.ltaRetry, 'Getting storage ticket') - self.RetryRun(self.TransferFileNew, self.srmRetry , 'Transfering file') - #if self.Type.lower() == "eor": - #self.RetryRun(self.TransferFileNew, self.srmRetry , 'Transfering file') - #else: - #self.RetryRun(self.TransferFile, self.srmRetry , 'Transfering file') + if self.Type.lower() == "eor": + self.RetryRun(self.TransferFileNew, self.srmRetry , 'Transfering file') + else: + self.RetryRun(self.TransferFile, self.srmRetry , 'Transfering file') self.RetryRun(self.SendChecksums, self.ltaRetry, 'Sending Checksums') # self.RenameFile() - self.RetryRun(self.GetSIP, self.momRetry, 'Get SIP from MoM') - self.RetryRun(self.SendSIP, self.ltaRetry, 'Sending SIP') + if self.Type.lower() == "eor" and not 'SIPLocation' in self.job: + self.logger.info("No SIPLocation specified for eor job %s. Skipping SIP transfer to catalogue." % (self.JobId)) + else: + self.RetryRun(self.GetSIP, self.momRetry, 'Get SIP from MoM') + self.RetryRun(self.SendSIP, self.ltaRetry, 'Sending SIP') self.RetryRun(self.SendStatus, self.ltaRetry, 'Setting LTA status', IngestSuccessful) elapsed = time.time() - start try: -- GitLab From 4448380c5460e1f96c20e61ae2ea1b91b8b67fd1 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 18 Feb 2016 15:44:11 +0000 Subject: [PATCH 012/933] Task #8725: removed wasp servers from sara. minor fix in md5 parsing. minor tweak in progress logging --- LTA/LTAIngest/ltacp.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index 6271d75ca4a..e89b911b046 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -58,7 +58,6 @@ def getLocalIPAddress(): def convert_surl_to_turl(surl): #list of sara doors is based on recent actual transfers using srmcp, which translates the surl to a 'random' turl sara_nodes = ['fly%d' % i for i in range(1, 11)] + \ - ['wasp%d' % i for i in range(1, 10)] + \ ['by27-%d' % i for i in range(1, 10)] + \ ['bw27-%d' % i for i in range(1, 10)] + \ ['by32-%d' % i for i in range(1, 10)] + \ @@ -330,8 +329,7 @@ class LtaCp: prev_bytes_transfered = total_bytes_transfered percentage_to_go = 100.0 - percentage_done time_to_go = elapsed_secs_since_start * percentage_to_go / percentage_done - logger.info('ltacp %s: transfered %d bytes, %s, %.1f%% at avgSpeed=%s curSpeed=%s to_go=%s' % (self.logId, - total_bytes_transfered, + logger.info('ltacp %s: transfered %s %.1f%% at avgSpeed=%s curSpeed=%s to_go=%s to %s' % (self.logId, humanreadablesize(total_bytes_transfered), percentage_done, humanreadablesize(avg_speed, 'Bps'), @@ -375,13 +373,14 @@ class LtaCp: try: md5_checksum_remote = output_md5_receive[0].split(' ')[0] md5_checksum_local = output_md5_local[0].split(' ')[0] - if(md5_checksum_remote != md5_checksum_local): - raise LtacpException('md5 checksum reported by client (%s) does not match local checksum of incoming data stream (%s)' % (self.logId, md5_checksum_remote, md5_checksum_local)) - logger.info('ltacp %s: remote and local md5 checksums are equal: %s' % (self.logId, md5_checksum_local,)) except Exception as e: logger.error('ltacp %s: error while parsing md5 checksum outputs: local=%s received=%s' % (self.logId, output_md5_local[0], output_md5_receive[0])) raise + if(md5_checksum_remote != md5_checksum_local): + raise LtacpException('md5 checksum reported by client (%s) does not match local checksum of incoming data stream (%s)' % (self.logId, md5_checksum_remote, md5_checksum_local)) + logger.info('ltacp %s: remote and local md5 checksums are equal: %s' % (self.logId, md5_checksum_local,)) + logger.debug('ltacp %s: waiting for local byte count on datastream...' % self.logId) output_byte_count = p_byte_count.communicate() if p_byte_count.returncode != 0: -- GitLab From a02184a2071e28a45e6db464aefbf2b8519e7426 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 18 Feb 2016 15:45:20 +0000 Subject: [PATCH 013/933] Task #8725: added functionality to process jobs in a dir in parallel --- LTA/LTAIngest/ingestpipeline.py | 144 +++++++++++++++++++++++++++----- 1 file changed, 122 insertions(+), 22 deletions(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index 71ea23c3b7a..8743ce19909 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -69,7 +69,7 @@ class IngestPipeline(): if 'summary' in self.DataProduct: self.FileType = pulp_type self.JobId = job['JobId'] - self.ArchiveId = int(job['ArchiveId']) + self.ArchiveId = int(job['ArchiveId']) self.ObsId = int(job['ObservationId']) self.HostLocation = job['Location'].split(':')[0] self.Location = job['Location'].split(':')[1] @@ -101,6 +101,7 @@ class IngestPipeline(): self.ltaRetry = ltaRetry self.srmRetry = srmRetry self.status = IngestStarted + self.finishedSuccessfully = True ## Set logger self.logger =logging.getLogger('Slave') @@ -536,11 +537,8 @@ class IngestPipeline(): self.RetryRun(self.SendChecksums, self.ltaRetry, 'Sending Checksums') # self.RenameFile() - if self.Type.lower() == "eor" and not 'SIPLocation' in self.job: - self.logger.info("No SIPLocation specified for eor job %s. Skipping SIP transfer to catalogue." % (self.JobId)) - else: - self.RetryRun(self.GetSIP, self.momRetry, 'Get SIP from MoM') - self.RetryRun(self.SendSIP, self.ltaRetry, 'Sending SIP') + self.RetryRun(self.GetSIP, self.momRetry, 'Get SIP from MoM') + self.RetryRun(self.SendSIP, self.ltaRetry, 'Sending SIP') self.RetryRun(self.SendStatus, self.ltaRetry, 'Setting LTA status', IngestSuccessful) elapsed = time.time() - start try: @@ -549,7 +547,8 @@ class IngestPipeline(): self.logger.debug("Ingest Pipeline finished for %s in %d sec with average speed of %s for %s including all overhead" % (self.JobId, elapsed, humanreadablesize(avgSpeed, 'Bps'), humanreadablesize(float(self.FileSize), 'B'))) except Exception: self.logger.debug("Ingest Pipeline finished for %s in %d sec" % (self.JobId, elapsed)) - + self.finishedSuccessfully = True + except PipelineError as pe: self.logger.debug('Encountered PipelineError for %s : %s' % (self.JobId, str(pe))) ## roll back transfer if necessary @@ -606,30 +605,131 @@ class IngestPipeline(): #----------------------------------------------------------------- selfstarter - if __name__ == '__main__': import sys + import threading + import os + import os.path + import job_parser + if len(sys.argv) != 2: print 'usage: ingestpipeline.py <path_to_jobfile.xml>' + print 'or' + print 'usage: ingestpipeline.py <path_to_dir_containing_set_of_jobfiles>' logging.basicConfig(format="%(asctime)-15s %(levelname)s %(message)s", level=logging.DEBUG) logger = logging.getLogger('Slave') - jobfile = sys.argv[1] - import job_parser - parser = job_parser.parser(logger) - job = parser.parse(jobfile) - job['filename'] = jobfile - - logger.info(str(job)) - if getpass.getuser() == 'ingest': import ingest_config as config else: import ingest_config_test as config - #create misc mock args - ltacphost = 'mock-ltacphost' - ltacpport = -1 - mailCommand = '' - srmInit = '' + maxParallelJobs = 4 + path = sys.argv[1] + + if os.path.isfile(path): + parser = job_parser.parser(logger) + job = parser.parse(path) + job['filename'] = path + logger.info("Parsed jobfile %s: %s" % (path, str(job))) + jobPipeline = IngestPipeline(None, jobToRun, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit) + jobPipeline.run() + exit(0) + + if os.path.isdir(path): + dirEntries = [os.path.join(path, x) for x in os.listdir(path)] + jobfilepaths = [p for p in dirEntries if os.path.isfile(p)] - standalone = IngestPipeline(None, job, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit) - standalone.run() + try: + if not os.path.exists(os.path.join(path, 'done')): + os.mkdir(os.path.join(path, 'done')) + except Exception as e: + logger.error(str(e)) + exit(-1) + + # scan all jobfilepaths + # put each job in hostJobQueues dict, in a list per host + # this gives us a job queue per host, so we can parallelize. + hostJobQueues = {} + for jobfilepath in jobfilepaths: + try: + parser = job_parser.parser(logger) + job = parser.parse(jobfilepath) + job['filename'] = jobfilepath + logger.info("Parsed jobfile %s: %s" % (jobfilepath, str(job))) + + if 'host' in job: + host = job['host'] + if not host in hostJobQueues: + hostJobQueues[host] = [] + hostJobQueues[host].append(job) + except Exception as e: + logger.error("Could not parse %s: %s" % (jobfilepath, str(e))) + + runningPipelinesPerHost = {} + + while hostJobQueues: + busyHosts = set(runningPipelinesPerHost.keys()) + freeHosts = set(hostJobQueues.keys()) - busyHosts + + startedNewJobs = False + + # start new pipeline for one job per free host + for host in freeHosts: + jobQueue = hostJobQueues[host] + + if not jobQueue: + logging.info('no more jobs for host: %s' % host) + del hostJobQueues[host] + elif len(runningPipelinesPerHost) < maxParallelJobs: + jobToRun = jobQueue.pop(0) + + logging.info('starting job %s on host %s' % (jobToRun['JobId'], host)) + jobPipeline = IngestPipeline(None, jobToRun, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit) + + def runPipeline(pl): + try: + pl.run() + except Exception as e: + logger.error(str(e)) + + pipelineThread = threading.Thread(target=runPipeline, kwargs={'pl':jobPipeline}) + pipelineThread.daemon = True + pipelineThread.start() + + runningPipelinesPerHost[host] = (jobPipeline, pipelineThread) + logging.info('started job %s on host %s' % (jobToRun['JobId'], host)) + startedNewJobs = True + + if startedNewJobs: + for h, q in hostJobQueues.items(): + logger.info("%s: %s jobs in queue" % (h, len(q))) + + # check which jobs are done + busyHosts = set(runningPipelinesPerHost.keys()) + + for host in busyHosts: + try: + jobPipeline, pipelineThread = runningPipelinesPerHost[host] + + if pipelineThread.isAlive(): + logging.info('job %s on host %s is running' % (jobPipeline.job['JobId'], host)) + else: + logging.info('job %s on host %s is done' % (jobPipeline.job['JobId'], host)) + pipelineThread.join(10) + del runningPipelinesPerHost[host] + + if jobPipeline.finishedSuccessfully: + try: + if os.path.isdir(path): + path, filename = os.split(jobPipeline.job['filename']) + os.rename(jobPipeline.job['filename'], os.path.join(path, 'done', filename)) + except Exception as e: + logger.error(str(e)) + else: + #retry, put back in jobqueue + logging.info('job %s on host %s was put back in the queue' % (jobPipeline.job['JobId'], host)) + hostJobQueues[host].append(jobPipeline.job) + except Exception as e: + logger.error(str(e)) + + time.sleep(30) -- GitLab From fb57fd86332b01ca01567bae29fe16ef48657029 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Feb 2016 09:44:30 +0000 Subject: [PATCH 014/933] Task #8725: use one tee to split datastream. --- LTA/LTAIngest/ltacp.py | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index e89b911b046..ddf698c50b2 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -34,15 +34,6 @@ logger = logging.getLogger('Slave') _ingest_init_script = '/globalhome/ingest/service/bin/init.sh' -if __name__ == '__main__': - log_handler = logging.StreamHandler() - formatter = logging.Formatter('%(asctime)-15s %(levelname)s %(message)s') - formatter.converter = time.gmtime - log_handler.setFormatter(formatter) - logger.addHandler(log_handler) - logger.setLevel(logging.INFO) - - class LtacpException(Exception): def __init__(self, value): self.value = value @@ -153,17 +144,11 @@ class LtaCp: self.local_byte_count_fifo = createLocalFifo('local_byte_count') self.local_adler32_fifo = createLocalFifo('local_adler32') - # tee incoming data stream to fifo (and pipe stream in tee_proc.stdout) - def teeDataStreams(pipe_in, fifo_out): - cmd_tee = ['tee', fifo_out] - logger.info('ltacp %s: splitting datastream. executing: %s' % (self.logId, ' '.join(cmd_tee),)) - tee_proc = Popen(cmd_tee, stdin=pipe_in, stdout=PIPE, stderr=PIPE) - self.started_procs[tee_proc] = cmd_tee - return tee_proc - - p_tee_data = teeDataStreams(p_data_in.stdout, self.local_data_fifo) - p_tee_byte_count = teeDataStreams(p_tee_data.stdout, self.local_byte_count_fifo) - p_tee_checksums = teeDataStreams(p_tee_byte_count.stdout, self.local_adler32_fifo) + # tee incoming data stream to fifos (and pipe stream in tee_proc.stdout) + cmd_tee = ['tee', self.local_data_fifo, self.local_byte_count_fifo, self.local_adler32_fifo] + logger.info('ltacp %s: splitting datastream. executing: %s' % (self.logId, ' '.join(cmd_tee),)) + p_tee_data = Popen(cmd_tee, stdin=p_data_in.stdout, stdout=PIPE, stderr=PIPE) + self.started_procs[p_tee_data] = cmd_tee # start counting number of bytes in incoming data stream cmd_byte_count = ['wc', '-c', self.local_byte_count_fifo] @@ -174,7 +159,7 @@ class LtaCp: # start computing md5 checksum of incoming data stream cmd_md5_local = ['md5sum'] logger.info('ltacp %s: computing local md5 checksum. executing on data pipe: %s' % (self.logId, ' '.join(cmd_md5_local))) - p_md5_local = Popen(cmd_md5_local, stdin=p_tee_checksums.stdout, stdout=PIPE, stderr=PIPE) + p_md5_local = Popen(cmd_md5_local, stdin=p_tee_data.stdout, stdout=PIPE, stderr=PIPE) self.started_procs[p_md5_local] = cmd_md5_local # start computing adler checksum of incoming data stream @@ -250,7 +235,7 @@ class LtaCp: # determine if input is file input_is_file = (output_remote_filetype[0][0] == '-') - + logger.info('ltacp %s: remote path is a %s' % (self.logId, 'file' if input_is_file else 'directory')) # get input datasize cmd_remote_du = self.ssh_cmd + ['du -b --max-depth=0 %s' % (self.src_path_data,)] @@ -313,7 +298,7 @@ class LtaCp: try: # read and process tar stdout lines to create progress messages nextline = p_remote_data.stdout.readline().strip() - if len(nextline) > 0: + if len(nextline) > 0 and not input_is_file: record_nr = int(nextline.split()[-1].strip()) total_bytes_transfered = record_nr * tar_record_size percentage_done = (100.0*float(total_bytes_transfered))/float(estimated_tar_size) @@ -329,15 +314,20 @@ class LtaCp: prev_bytes_transfered = total_bytes_transfered percentage_to_go = 100.0 - percentage_done time_to_go = elapsed_secs_since_start * percentage_to_go / percentage_done - logger.info('ltacp %s: transfered %s %.1f%% at avgSpeed=%s curSpeed=%s to_go=%s to %s' % (self.logId, + logger.info('ltacp %s: transfered %s %.1f%% at avgSpeed=%s (%s) curSpeed=%s (%s) to_go=%s to %s' % (self.logId, humanreadablesize(total_bytes_transfered), percentage_done, humanreadablesize(avg_speed, 'Bps'), + humanreadablesize(avg_speed*8, 'bps'), humanreadablesize(current_speed, 'Bps'), - timedelta(seconds=int(round(time_to_go))))) + humanreadablesize(current_speed*8, 'bps'), + timedelta(seconds=int(round(time_to_go))), + dst_turl)) time.sleep(0.05) except KeyboardInterrupt: self.cleanup() + except Exception as e: + logger.error('ltacp %s: %s' % (self.logId, str(e))) logger.info('ltacp %s: waiting for transfer via globus-url-copy to LTA to finish...' % self.logId) output_data_out = p_data_out.communicate() @@ -455,7 +445,7 @@ class LtaCp: if hasattr(self, 'remote_data_fifo') and self.remote_data_fifo: '''remove a file (or fifo) on a remote host. Test if file exists before deleting.''' - cmd_remote_rm = self.ssh_cmd + ['if [ -e "%s" ] ; then rm %s ; fi ;' % (self.remote_data_fifo, self.remote_data_fifo)] + cmd_remote_rm = self.ssh_cmd + ['rm %s' % (self.remote_data_fifo,)] logger.info('ltacp %s: removing remote fifo. executing: %s' % (self.logId, ' '.join(cmd_remote_rm))) p_remote_rm = Popen(cmd_remote_rm, stdout=PIPE, stderr=PIPE) p_remote_rm.communicate() -- GitLab From 9275943994a2370ab1b0fea33f10567ddbbd7b49 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Feb 2016 10:09:14 +0000 Subject: [PATCH 015/933] Task #8725: first wait for all other processes than globus-url-copy to finish so they are done, then wait for guc, because the remote server can take a long time to compute the remote checksums. --- LTA/LTAIngest/ltacp.py | 93 +++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index ddf698c50b2..65a4fd65cbb 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -329,12 +329,6 @@ class LtaCp: except Exception as e: logger.error('ltacp %s: %s' % (self.logId, str(e))) - logger.info('ltacp %s: waiting for transfer via globus-url-copy to LTA to finish...' % self.logId) - output_data_out = p_data_out.communicate() - if p_data_out.returncode != 0: - raise LtacpException('ltacp %s: transfer via globus-url-copy to LTA failed: %s' % (self.logId, output_data_out[1])) - logger.info('ltacp %s: data transfer via globus-url-copy to LTA complete.' % self.logId) - logger.info('ltacp %s: waiting for remote data transfer to finish...' % self.logId) output_remote_data = p_remote_data.communicate() if p_remote_data.returncode != 0: @@ -347,43 +341,49 @@ class LtaCp: raise LtacpException('ltacp %s: Error in remote md5 checksum computation: %s' % (self.logId, output_remote_checksum[1])) logger.debug('ltacp %s: remote md5 checksum computation finished.' % self.logId) - logger.debug('ltacp %s: waiting to receive remote md5 checksum...' % self.logId) - output_md5_receive = p_md5_receive.communicate() - if p_md5_receive.returncode != 0: - raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_md5_receive[1])) - logger.debug('ltacp %s: received md5 checksum.' % self.logId) - - logger.debug('ltacp %s: waiting for local computation of md5 checksum...' % self.logId) - output_md5_local = p_md5_local.communicate() - if p_md5_local.returncode != 0: - raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_md5_local[1])) - logger.debug('ltacp %s: computed local md5 checksum.' % self.logId) - - # compare remote and local md5 checksums - try: - md5_checksum_remote = output_md5_receive[0].split(' ')[0] - md5_checksum_local = output_md5_local[0].split(' ')[0] - except Exception as e: - logger.error('ltacp %s: error while parsing md5 checksum outputs: local=%s received=%s' % (self.logId, output_md5_local[0], output_md5_receive[0])) - raise - - if(md5_checksum_remote != md5_checksum_local): - raise LtacpException('md5 checksum reported by client (%s) does not match local checksum of incoming data stream (%s)' % (self.logId, md5_checksum_remote, md5_checksum_local)) - logger.info('ltacp %s: remote and local md5 checksums are equal: %s' % (self.logId, md5_checksum_local,)) - - logger.debug('ltacp %s: waiting for local byte count on datastream...' % self.logId) - output_byte_count = p_byte_count.communicate() - if p_byte_count.returncode != 0: - raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_byte_count[1])) - byte_count = int(output_byte_count[0].split()[0].strip()) - logger.info('ltacp %s: byte count of datastream is %d %s' % (self.logId, byte_count, humanreadablesize(byte_count))) - - logger.debug('ltacp %s: waiting for local adler32 checksum to complete...' % self.logId) - output_a32_local = p_a32_local.communicate() - if p_a32_local.returncode != 0: - raise LtacpException('ltacp %s: local adler32 checksum computation failed: %s' (self.logId, str(output_a32_local))) - logger.debug('ltacp %s: finished computation of local adler32 checksum' % self.logId) - a32_checksum_local = output_a32_local[0].split()[1].zfill(8) + logger.debug('ltacp %s: waiting to receive remote md5 checksum...' % self.logId) + output_md5_receive = p_md5_receive.communicate() + if p_md5_receive.returncode != 0: + raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_md5_receive[1])) + logger.debug('ltacp %s: received md5 checksum.' % self.logId) + + logger.debug('ltacp %s: waiting for local computation of md5 checksum...' % self.logId) + output_md5_local = p_md5_local.communicate() + if p_md5_local.returncode != 0: + raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_md5_local[1])) + logger.debug('ltacp %s: computed local md5 checksum.' % self.logId) + + # compare remote and local md5 checksums + try: + md5_checksum_remote = output_md5_receive[0].split(' ')[0] + md5_checksum_local = output_md5_local[0].split(' ')[0] + except Exception as e: + logger.error('ltacp %s: error while parsing md5 checksum outputs: local=%s received=%s' % (self.logId, output_md5_local[0], output_md5_receive[0])) + raise + + if(md5_checksum_remote != md5_checksum_local): + raise LtacpException('md5 checksum reported by client (%s) does not match local checksum of incoming data stream (%s)' % (self.logId, md5_checksum_remote, md5_checksum_local)) + logger.info('ltacp %s: remote and local md5 checksums are equal: %s' % (self.logId, md5_checksum_local,)) + + logger.debug('ltacp %s: waiting for local byte count on datastream...' % self.logId) + output_byte_count = p_byte_count.communicate() + if p_byte_count.returncode != 0: + raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_byte_count[1])) + byte_count = int(output_byte_count[0].split()[0].strip()) + logger.info('ltacp %s: byte count of datastream is %d %s' % (self.logId, byte_count, humanreadablesize(byte_count))) + + logger.debug('ltacp %s: waiting for local adler32 checksum to complete...' % self.logId) + output_a32_local = p_a32_local.communicate() + if p_a32_local.returncode != 0: + raise LtacpException('ltacp %s: local adler32 checksum computation failed: %s' (self.logId, str(output_a32_local))) + logger.debug('ltacp %s: finished computation of local adler32 checksum' % self.logId) + a32_checksum_local = output_a32_local[0].split()[1].zfill(8) + + logger.info('ltacp %s: waiting for transfer via globus-url-copy to LTA to finish...' % self.logId) + output_data_out = p_data_out.communicate() + if p_data_out.returncode != 0: + raise LtacpException('ltacp %s: transfer via globus-url-copy to LTA failed: %s' % (self.logId, output_data_out[1])) + logger.info('ltacp %s: data transfer via globus-url-copy to LTA complete.' % self.logId) logger.info('ltacp %s: fetching adler32 checksum from LTA...' % self.logId) srm_ok, srm_file_size, srm_a32_checksum = get_srm_size_and_a32_checksum(self.dst_surl) @@ -391,16 +391,17 @@ class LtaCp: if not srm_ok: raise LtacpException('ltacp %s: Could not get srm adler32 checksum for: %s' % (self.logId, self.dst_surl)) - if(srm_a32_checksum != a32_checksum_local): + if srm_a32_checksum != a32_checksum_local: raise LtacpException('ltacp %s: adler32 checksum reported by srm (%s) does not match original data checksum (%s)' % (self.logId, srm_a32_checksum, a32_checksum_local)) logger.info('ltacp %s: adler32 checksums are equal: %s' % (self.logId, a32_checksum_local)) - if(int(srm_file_size) != int(byte_count)): + if int(srm_file_size) != int(byte_count): raise LtacpException('ltacp %s: file size reported by srm (%s) does not match datastream byte count (%s)' % (self.logId, - srm_file_size, byte_count)) + srm_file_size, + byte_count)) logger.info('ltacp %s: srm file size and datastream byte count are equal: %s bytes (%s)' % (self.logId, srm_file_size, -- GitLab From ccf66e39c369535e1b13466e87d2387688a2c934 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Feb 2016 10:13:21 +0000 Subject: [PATCH 016/933] Task #8725: use OptionParser for cmdline args --user and --parallel --- LTA/LTAIngest/ingestpipeline.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index 8743ce19909..f6691d59ef8 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -40,7 +40,7 @@ class PipelineError(Exception): #---------------------- IngestPipeline ------------------------------------------ class IngestPipeline(): - def __init__(self, logdir, job, momClient, ltaClient, ltacphost, ltacpport, mailCommand, momRetry, ltaRetry, srmRetry, srmInit): + def __init__(self, logdir, job, momClient, ltaClient, ltacphost, ltacpport, mailCommand, momRetry, ltaRetry, srmRetry, srmInit, user=getpass.getuser()): self.logdir = logdir self.job = job self.momClient = momClient @@ -48,6 +48,7 @@ class IngestPipeline(): self.ltacphost = ltacphost self.ltacpport = ltacpport self.mailCommand = mailCommand + self.user = user self.Project = job['Project'] self.DataProduct = job['DataProduct'] @@ -228,7 +229,8 @@ class IngestPipeline(): cp = ltacp.LtaCp(host, os.path.join(self.LocationDir, self.Source), - self.PrimaryUri) + self.PrimaryUri, + self.user) self.MD5Checksum, self.Adler32Checksum, self.FileSize = cp.transfer() @@ -384,7 +386,7 @@ class IngestPipeline(): try: sip_host = self.job['SIPLocation'].split(':')[0] sip_path = self.job['SIPLocation'].split(':')[1] - cmd = ['ssh', '-tt', '-n', '-x', '-q', '%s@%s' % (getpass.getuser(), sip_host), 'cat %s' % sip_path] + cmd = ['ssh', '-tt', '-n', '-x', '-q', '%s@%s' % (self.user, sip_host), 'cat %s' % sip_path] self.logger.debug("GetSIP for %s with mom2DPId %s - StorageTicket %s - FileName %s - Uri %s - cmd %s" % (self.JobId, self.ArchiveId, self.ticket, self.FileName, self.PrimaryUri, ' ' .join(cmd))) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() @@ -609,8 +611,19 @@ if __name__ == '__main__': import os import os.path import job_parser + from optparse import OptionParser + + # Check the invocation arguments + parser = OptionParser("%prog [options] <jobfile/dir_with_job_files>", + description='Run the ingestpipeline on a single jobfile, or a directory containing many jobfiles.') + parser.add_option("-u", "--user", dest="user", type="string", default=getpass.getuser(), help="username for to login on <host>, [default: %default]") + parser.add_option("-p", "--parallel", dest="maxParallelJobs", type="int", default=4, help="number of parellel pipelines to run when processing a directory of jobfiles [default: %default]") + (options, args) = parser.parse_args() + + if len(args) != 1: + parser.print_help() + sys.exit(1) - if len(sys.argv) != 2: print 'usage: ingestpipeline.py <path_to_jobfile.xml>' print 'or' print 'usage: ingestpipeline.py <path_to_dir_containing_set_of_jobfiles>' @@ -618,12 +631,11 @@ if __name__ == '__main__': logging.basicConfig(format="%(asctime)-15s %(levelname)s %(message)s", level=logging.DEBUG) logger = logging.getLogger('Slave') - if getpass.getuser() == 'ingest': + if options.user == 'ingest': import ingest_config as config else: import ingest_config_test as config - maxParallelJobs = 4 path = sys.argv[1] if os.path.isfile(path): @@ -680,11 +692,11 @@ if __name__ == '__main__': if not jobQueue: logging.info('no more jobs for host: %s' % host) del hostJobQueues[host] - elif len(runningPipelinesPerHost) < maxParallelJobs: + elif len(runningPipelinesPerHost) < options.maxParallelJobs: jobToRun = jobQueue.pop(0) logging.info('starting job %s on host %s' % (jobToRun['JobId'], host)) - jobPipeline = IngestPipeline(None, jobToRun, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit) + jobPipeline = IngestPipeline(None, jobToRun, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit, options.user) def runPipeline(pl): try: -- GitLab From b929d160a8cdfee8853b71441273bb4d3b504ca8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Feb 2016 10:34:41 +0000 Subject: [PATCH 017/933] Task #8725: use parser args from cmdline --- LTA/LTAIngest/ingestpipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index f6691d59ef8..612b6032ce0 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -636,7 +636,7 @@ if __name__ == '__main__': else: import ingest_config_test as config - path = sys.argv[1] + path = args[0] if os.path.isfile(path): parser = job_parser.parser(logger) -- GitLab From f387eaf5b0e644e0aca49c42ecb0d7a558aeabf9 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Feb 2016 13:41:20 +0000 Subject: [PATCH 018/933] Task #8725: made md5a32bc process which takes stdin and output same data on stdout. md5 adler32 and bytecount are printed on stderr --- .gitattributes | 2 + LTA/LTAIngest/md5adler/makefile | 8 +- LTA/LTAIngest/md5adler/md5a32bc | Bin 0 -> 17528 bytes LTA/LTAIngest/md5adler/md5a32bc.c | 341 ++++++++++++++++++++++++++++++ 4 files changed, 349 insertions(+), 2 deletions(-) create mode 100755 LTA/LTAIngest/md5adler/md5a32bc create mode 100644 LTA/LTAIngest/md5adler/md5a32bc.c diff --git a/.gitattributes b/.gitattributes index 5d89f6c5e41..8e4944cdded 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2876,6 +2876,8 @@ LTA/LTAIngest/ltaingest_build.sh eol=lf LTA/LTAIngest/md5adler/a32 -text LTA/LTAIngest/md5adler/foo -text LTA/LTAIngest/md5adler/md5a32 -text +LTA/LTAIngest/md5adler/md5a32bc -text +LTA/LTAIngest/md5adler/md5a32bc.c -text LTA/LTAIngest/mechanize-0.2.5/PKG-INFO -text LTA/LTAIngest/mechanize-0.2.5/examples/forms/data.dat -text LTA/LTAIngest/mechanize-0.2.5/examples/forms/echo.cgi -text diff --git a/LTA/LTAIngest/md5adler/makefile b/LTA/LTAIngest/md5adler/makefile index 1881dac9029..6768b346366 100644 --- a/LTA/LTAIngest/md5adler/makefile +++ b/LTA/LTAIngest/md5adler/makefile @@ -2,17 +2,21 @@ CFLAGS=-O3 -all: md5a32 a32 +all: md5a32 a32 md5a32bc md5a32: md5a32.o adler32.o md5a32.o: md5.h +md5a32bc: md5a32bc.o adler32.o + +md5a32bc.o: md5.h + adler32: adler32.c adler32.h gcc -c adler32.c -o adler32.o a32: a32.o adler32.o clean: - rm -f adler32.o md5a32.o a32.o md5a32 a32 + rm -f adler32.o md5a32.o md5a32bc.o a32.o md5a32 md5a32bc a32 diff --git a/LTA/LTAIngest/md5adler/md5a32bc b/LTA/LTAIngest/md5adler/md5a32bc new file mode 100755 index 0000000000000000000000000000000000000000..e4ef3ca29f14f7c2fd647d3528d167435527070c GIT binary patch literal 17528 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 OtbjJSz0f4l?FRr?ygsG? literal 0 HcmV?d00001 diff --git a/LTA/LTAIngest/md5adler/md5a32bc.c b/LTA/LTAIngest/md5adler/md5a32bc.c new file mode 100644 index 00000000000..55f8aeef310 --- /dev/null +++ b/LTA/LTAIngest/md5adler/md5a32bc.c @@ -0,0 +1,341 @@ +/* + ********************************************************************** + ** md5a32bc.c ** + ** Compute md5, adler32 and byte count while transfering data from ** + ** stdin to stdout. Results are printed on stderr. ** + ** ** + ** md5 methods are copied from ** + ** RSA Data Security, Inc. MD5 Message Digest Algorithm ** + ********************************************************************** + */ + +/* + ********************************************************************** + ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. ** + ** ** + ** License to copy and use this software is granted provided that ** + ** it is identified as the "RSA Data Security, Inc. MD5 Message ** + ** Digest Algorithm" in all material mentioning or referencing this ** + ** software or this function. ** + ** ** + ** License is also granted to make and use derivative works ** + ** provided that such works are identified as "derived from the RSA ** + ** Data Security, Inc. MD5 Message Digest Algorithm" in all ** + ** material mentioning or referencing the derived work. ** + ** ** + ** RSA Data Security, Inc. makes no representations concerning ** + ** either the merchantability of this software or the suitability ** + ** of this software for any particular purpose. It is provided "as ** + ** is" without express or implied warranty of any kind. ** + ** ** + ** These notices must be retained in any copies of any part of this ** + ** documentation and/or software. ** + ********************************************************************** + */ + +#include <stdio.h> +#include "md5.h" + +/* forward declaration */ +static void Transform (); + +static unsigned char PADDING[64] = { + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* F, G and H are basic MD5 functions: selection, majority, parity */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s, ac) \ + {(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) \ + {(a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) \ + {(a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) \ + {(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +void MD5Init (mdContext) +MD5_CTX *mdContext; +{ + mdContext->i[0] = mdContext->i[1] = (UINT4)0; + + /* Load magic initialization constants. + */ + mdContext->buf[0] = (UINT4)0x67452301; + mdContext->buf[1] = (UINT4)0xefcdab89; + mdContext->buf[2] = (UINT4)0x98badcfe; + mdContext->buf[3] = (UINT4)0x10325476; + + /* Set adler32 init value to one + */ + mdContext->adler32 = 1; + + mdContext->byte_count = 0; +} + +void MD5Update (mdContext, inBuf, inLen) +MD5_CTX *mdContext; +unsigned char *inBuf; +unsigned int inLen; +{ + UINT4 in[16]; + int mdi; + unsigned int i, ii; + + + /* compute number of bytes mod 64 */ + mdi = (int)((mdContext->i[0] >> 3) & 0x3F); + + /* update number of bits */ + if ((mdContext->i[0] + ((UINT4)inLen << 3)) < mdContext->i[0]) + mdContext->i[1]++; + mdContext->i[0] += ((UINT4)inLen << 3); + mdContext->i[1] += ((UINT4)inLen >> 29); + + while (inLen--) { + /* add new character to buffer, increment mdi */ + mdContext->in[mdi++] = *inBuf++; + + /* transform if necessary */ + if (mdi == 0x40) { + for (i = 0, ii = 0; i < 16; i++, ii += 4) + in[i] = (((UINT4)mdContext->in[ii+3]) << 24) | + (((UINT4)mdContext->in[ii+2]) << 16) | + (((UINT4)mdContext->in[ii+1]) << 8) | + ((UINT4)mdContext->in[ii]); + Transform (mdContext->buf, in); + mdi = 0; + } + } + +} + +void MD5Final (mdContext) +MD5_CTX *mdContext; +{ + UINT4 in[16]; + int mdi; + unsigned int i, ii; + unsigned int padLen; + + /* save number of bits */ + in[14] = mdContext->i[0]; + in[15] = mdContext->i[1]; + + /* compute number of bytes mod 64 */ + mdi = (int)((mdContext->i[0] >> 3) & 0x3F); + + /* pad out to 56 mod 64 */ + padLen = (mdi < 56) ? (56 - mdi) : (120 - mdi); + MD5Update (mdContext, PADDING, padLen); + + /* append length in bits and transform */ + for (i = 0, ii = 0; i < 14; i++, ii += 4) + in[i] = (((UINT4)mdContext->in[ii+3]) << 24) | + (((UINT4)mdContext->in[ii+2]) << 16) | + (((UINT4)mdContext->in[ii+1]) << 8) | + ((UINT4)mdContext->in[ii]); + Transform (mdContext->buf, in); + + /* store buffer in digest */ + for (i = 0, ii = 0; i < 4; i++, ii += 4) { + mdContext->digest[ii] = (unsigned char)(mdContext->buf[i] & 0xFF); + mdContext->digest[ii+1] = + (unsigned char)((mdContext->buf[i] >> 8) & 0xFF); + mdContext->digest[ii+2] = + (unsigned char)((mdContext->buf[i] >> 16) & 0xFF); + mdContext->digest[ii+3] = + (unsigned char)((mdContext->buf[i] >> 24) & 0xFF); + } +} + +/* Basic MD5 step. Transform buf based on in. + */ +static void Transform (buf, in) +UINT4 *buf; +UINT4 *in; +{ + UINT4 a = buf[0], b = buf[1], c = buf[2], d = buf[3]; + + /* Round 1 */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 + FF ( a, b, c, d, in[ 0], S11, 3614090360); /* 1 */ + FF ( d, a, b, c, in[ 1], S12, 3905402710); /* 2 */ + FF ( c, d, a, b, in[ 2], S13, 606105819); /* 3 */ + FF ( b, c, d, a, in[ 3], S14, 3250441966); /* 4 */ + FF ( a, b, c, d, in[ 4], S11, 4118548399); /* 5 */ + FF ( d, a, b, c, in[ 5], S12, 1200080426); /* 6 */ + FF ( c, d, a, b, in[ 6], S13, 2821735955); /* 7 */ + FF ( b, c, d, a, in[ 7], S14, 4249261313); /* 8 */ + FF ( a, b, c, d, in[ 8], S11, 1770035416); /* 9 */ + FF ( d, a, b, c, in[ 9], S12, 2336552879); /* 10 */ + FF ( c, d, a, b, in[10], S13, 4294925233); /* 11 */ + FF ( b, c, d, a, in[11], S14, 2304563134); /* 12 */ + FF ( a, b, c, d, in[12], S11, 1804603682); /* 13 */ + FF ( d, a, b, c, in[13], S12, 4254626195); /* 14 */ + FF ( c, d, a, b, in[14], S13, 2792965006); /* 15 */ + FF ( b, c, d, a, in[15], S14, 1236535329); /* 16 */ + + /* Round 2 */ +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 + GG ( a, b, c, d, in[ 1], S21, 4129170786); /* 17 */ + GG ( d, a, b, c, in[ 6], S22, 3225465664); /* 18 */ + GG ( c, d, a, b, in[11], S23, 643717713); /* 19 */ + GG ( b, c, d, a, in[ 0], S24, 3921069994); /* 20 */ + GG ( a, b, c, d, in[ 5], S21, 3593408605); /* 21 */ + GG ( d, a, b, c, in[10], S22, 38016083); /* 22 */ + GG ( c, d, a, b, in[15], S23, 3634488961); /* 23 */ + GG ( b, c, d, a, in[ 4], S24, 3889429448); /* 24 */ + GG ( a, b, c, d, in[ 9], S21, 568446438); /* 25 */ + GG ( d, a, b, c, in[14], S22, 3275163606); /* 26 */ + GG ( c, d, a, b, in[ 3], S23, 4107603335); /* 27 */ + GG ( b, c, d, a, in[ 8], S24, 1163531501); /* 28 */ + GG ( a, b, c, d, in[13], S21, 2850285829); /* 29 */ + GG ( d, a, b, c, in[ 2], S22, 4243563512); /* 30 */ + GG ( c, d, a, b, in[ 7], S23, 1735328473); /* 31 */ + GG ( b, c, d, a, in[12], S24, 2368359562); /* 32 */ + + /* Round 3 */ +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 + HH ( a, b, c, d, in[ 5], S31, 4294588738); /* 33 */ + HH ( d, a, b, c, in[ 8], S32, 2272392833); /* 34 */ + HH ( c, d, a, b, in[11], S33, 1839030562); /* 35 */ + HH ( b, c, d, a, in[14], S34, 4259657740); /* 36 */ + HH ( a, b, c, d, in[ 1], S31, 2763975236); /* 37 */ + HH ( d, a, b, c, in[ 4], S32, 1272893353); /* 38 */ + HH ( c, d, a, b, in[ 7], S33, 4139469664); /* 39 */ + HH ( b, c, d, a, in[10], S34, 3200236656); /* 40 */ + HH ( a, b, c, d, in[13], S31, 681279174); /* 41 */ + HH ( d, a, b, c, in[ 0], S32, 3936430074); /* 42 */ + HH ( c, d, a, b, in[ 3], S33, 3572445317); /* 43 */ + HH ( b, c, d, a, in[ 6], S34, 76029189); /* 44 */ + HH ( a, b, c, d, in[ 9], S31, 3654602809); /* 45 */ + HH ( d, a, b, c, in[12], S32, 3873151461); /* 46 */ + HH ( c, d, a, b, in[15], S33, 530742520); /* 47 */ + HH ( b, c, d, a, in[ 2], S34, 3299628645); /* 48 */ + + /* Round 4 */ +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + II ( a, b, c, d, in[ 0], S41, 4096336452); /* 49 */ + II ( d, a, b, c, in[ 7], S42, 1126891415); /* 50 */ + II ( c, d, a, b, in[14], S43, 2878612391); /* 51 */ + II ( b, c, d, a, in[ 5], S44, 4237533241); /* 52 */ + II ( a, b, c, d, in[12], S41, 1700485571); /* 53 */ + II ( d, a, b, c, in[ 3], S42, 2399980690); /* 54 */ + II ( c, d, a, b, in[10], S43, 4293915773); /* 55 */ + II ( b, c, d, a, in[ 1], S44, 2240044497); /* 56 */ + II ( a, b, c, d, in[ 8], S41, 1873313359); /* 57 */ + II ( d, a, b, c, in[15], S42, 4264355552); /* 58 */ + II ( c, d, a, b, in[ 6], S43, 2734768916); /* 59 */ + II ( b, c, d, a, in[13], S44, 1309151649); /* 60 */ + II ( a, b, c, d, in[ 4], S41, 4149444226); /* 61 */ + II ( d, a, b, c, in[11], S42, 3174756917); /* 62 */ + II ( c, d, a, b, in[ 2], S43, 718787259); /* 63 */ + II ( b, c, d, a, in[ 9], S44, 3951481745); /* 64 */ + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#include <stdio.h> + +int main (int argc, char *argv[]) +{ + MD5_CTX mdContext; + int num_bytes_read, num_bytes_written; + const int BUFFSIZE = 4096; + unsigned char data[BUFFSIZE]; + int num_progress_bytes = -1; + int progress_step_cntr = 0; + int progress_step = 0; + + if (argc == 3 && strcmp (argv[1], "-p") == 0) + { + int num = atoi(argv[2]); + if(num > 0) + num_progress_bytes = num; + } + + MD5Init (&mdContext); + + while (1) + { + num_bytes_read = fread (data, 1, BUFFSIZE, stdin); + + if(num_bytes_read == 0) + break; + + mdContext.adler32=adler32(mdContext.adler32, data, num_bytes_read); + MD5Update (&mdContext, data, num_bytes_read); + mdContext.byte_count += num_bytes_read; + + num_bytes_written = fwrite (data, 1, num_bytes_read, stdout); + + if(num_bytes_written != num_bytes_read) + return -1; + + progress_step = mdContext.byte_count / num_progress_bytes; + + if(progress_step > progress_step_cntr) + { + fprintf (stderr, "%lu bytes processed\n", mdContext.byte_count); + fflush(stderr); + progress_step_cntr = progress_step; + } + } + + fflush(stdout); + fflush(stderr); + + MD5Final (&mdContext); + + for (int i = 0; i < 16; i++) + fprintf (stderr, "%02x", mdContext.digest[i]); + + fprintf (stderr, " %x %lu\n", mdContext.adler32, mdContext.byte_count); + fflush(stderr); + + return 0; +} -- GitLab From 3264a5711992be2689f13ffec1b563f7ecb387f1 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Feb 2016 13:41:30 +0000 Subject: [PATCH 019/933] Task #8725: made md5a32bc process which takes stdin and output same data on stdout. md5 adler32 and bytecount are printed on stderr --- LTA/LTAIngest/md5adler/md5.h | 1 + 1 file changed, 1 insertion(+) diff --git a/LTA/LTAIngest/md5adler/md5.h b/LTA/LTAIngest/md5adler/md5.h index a45ba7ae955..17ffae6be52 100644 --- a/LTA/LTAIngest/md5adler/md5.h +++ b/LTA/LTAIngest/md5adler/md5.h @@ -45,6 +45,7 @@ typedef unsigned int UINT4; /* Data structure for MD5 (Message Digest) computation */ typedef struct { unsigned int adler32; /* adler 32 crc */ + unsigned long byte_count; /* total number of bytes processed */ UINT4 i[2]; /* number of _bits_ handled mod 2^64 */ UINT4 buf[4]; /* scratch buffer */ unsigned char in[64]; /* input buffer */ -- GitLab From 765ba1dd9bb87f28abd2a092949f75263aea8940 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 22 Feb 2016 08:10:26 +0000 Subject: [PATCH 020/933] Task #8725: print usage. pick resultstream for writing results --- LTA/LTAIngest/md5adler/md5a32bc | Bin 17528 -> 17808 bytes LTA/LTAIngest/md5adler/md5a32bc.c | 53 +++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/LTA/LTAIngest/md5adler/md5a32bc b/LTA/LTAIngest/md5adler/md5a32bc index e4ef3ca29f14f7c2fd647d3528d167435527070c..64d6aa9eb8597fa9042461796da9082f6835d73d 100755 GIT binary patch delta 4564 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 SQZ8m(304s9D(1;iwsHWJ79;Ec delta 3220 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 BWPJbt diff --git a/LTA/LTAIngest/md5adler/md5a32bc.c b/LTA/LTAIngest/md5adler/md5a32bc.c index 55f8aeef310..fc4573012c3 100644 --- a/LTA/LTAIngest/md5adler/md5a32bc.c +++ b/LTA/LTAIngest/md5adler/md5a32bc.c @@ -283,6 +283,23 @@ UINT4 *in; int main (int argc, char *argv[]) { + if (argc == 2 && strcmp (argv[1], "-h") == 0) + { + printf("md5a32bc is a tool which computes the md5 and adler32 checksum and counts the number of bytes on the stdin datastream.\n"); + printf("this input datastream is copied and written to stdout by default, or to the output file given as last argument.\n"); + printf("progress messages can be written every <n> bytes with flag -p <n>.\n"); + printf("\n"); + printf("Usage:\n"); + printf("<some_prog> | md5a32bc\n"); + printf("or:\n"); + printf("<some_prog> | md5a32bc <my_output_file>\n"); + printf("or:\n"); + printf("<some_prog> | md5a32bc -p <n> <my_output_file>\n"); + printf("or:\n"); + printf("<some_prog> | md5a32bc -p <n>\n"); + exit(0); + } + MD5_CTX mdContext; int num_bytes_read, num_bytes_written; const int BUFFSIZE = 4096; @@ -291,13 +308,23 @@ int main (int argc, char *argv[]) int progress_step_cntr = 0; int progress_step = 0; - if (argc == 3 && strcmp (argv[1], "-p") == 0) + if (argc >= 3 && strcmp (argv[1], "-p") == 0) { int num = atoi(argv[2]); if(num > 0) num_progress_bytes = num; } + FILE *outfile = stdout; + FILE *logstream = stderr; + FILE *resultstream = stderr; + + if (argc == 2 || argc == 4) + { + outfile = fopen(argv[argc-1], "wb"); + logstream = stdout; + } + MD5Init (&mdContext); while (1) @@ -311,31 +338,39 @@ int main (int argc, char *argv[]) MD5Update (&mdContext, data, num_bytes_read); mdContext.byte_count += num_bytes_read; - num_bytes_written = fwrite (data, 1, num_bytes_read, stdout); + num_bytes_written = fwrite (data, 1, num_bytes_read, outfile); if(num_bytes_written != num_bytes_read) + { + fprintf (logstream, "error while writing\n"); + if(outfile != stdout) + fclose(outfile); return -1; + } progress_step = mdContext.byte_count / num_progress_bytes; if(progress_step > progress_step_cntr) { - fprintf (stderr, "%lu bytes processed\n", mdContext.byte_count); - fflush(stderr); + fprintf (logstream, "%lu bytes processed\n", mdContext.byte_count); + fflush(logstream); progress_step_cntr = progress_step; } } - fflush(stdout); - fflush(stderr); + fflush(outfile); + fflush(logstream); + + if(outfile != stdout) + fclose(outfile); MD5Final (&mdContext); for (int i = 0; i < 16; i++) - fprintf (stderr, "%02x", mdContext.digest[i]); + fprintf (resultstream, "%02x", mdContext.digest[i]); - fprintf (stderr, " %x %lu\n", mdContext.adler32, mdContext.byte_count); - fflush(stderr); + fprintf (resultstream, " %x %lu\n", mdContext.adler32, mdContext.byte_count); + fflush(resultstream); return 0; } -- GitLab From d9d68dfd12c593de914d9e9a601f9ec340acb036 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 22 Feb 2016 08:10:48 +0000 Subject: [PATCH 021/933] Task #8725: comments --- LTA/LTAIngest/md5adler/md5.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LTA/LTAIngest/md5adler/md5.h b/LTA/LTAIngest/md5adler/md5.h index 17ffae6be52..d8f4dab91cc 100644 --- a/LTA/LTAIngest/md5adler/md5.h +++ b/LTA/LTAIngest/md5adler/md5.h @@ -42,7 +42,9 @@ /* typedef unsigned long int UINT4; */ typedef unsigned int UINT4; -/* Data structure for MD5 (Message Digest) computation */ +/* Data structure for MD5 (Message Digest) computation + * plus additional adler32 and byte_count variables + */ typedef struct { unsigned int adler32; /* adler 32 crc */ unsigned long byte_count; /* total number of bytes processed */ -- GitLab From f601042cb37be4a243f0d9706d3ebe85c2d03bec Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 22 Feb 2016 08:14:36 +0000 Subject: [PATCH 022/933] Task #8725: use new md5a32bc tool to process data and compute checksums and bytecount. This saves many tee and other commands, and this saves many copy steps. So this speeds up transfer a lot, and reduces cpu load. --- LTA/LTAIngest/ltacp.py | 157 +++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 91 deletions(-) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index 65a4fd65cbb..366cca867fe 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -115,6 +115,26 @@ class LtaCp: dst_turl = convert_surl_to_turl(self.dst_surl) logger.info('ltacp %s: initiating transfer of %s:%s to %s' % (self.logId, self.src_host, self.src_path_data, self.dst_surl)) + # get input datasize + cmd_remote_du = self.ssh_cmd + ['du -b --max-depth=0 %s' % (self.src_path_data,)] + logger.info('ltacp %s: remote getting datasize. executing: %s' % (self.logId, ' '.join(cmd_remote_du))) + p_remote_du = Popen(cmd_remote_du, stdout=PIPE, stderr=PIPE) + self.started_procs[p_remote_du] = cmd_remote_du + + # block until du is finished + output_remote_du = p_remote_du.communicate() + del self.started_procs[p_remote_du] + if p_remote_du.returncode != 0: + raise LtacpException('ltacp %s: remote du failed: \nstdout: %s\nstderr: %s' % (self.logId, + output_remote_du[0], + output_remote_du[1])) + # compute various parameters for progress logging + input_datasize = int(output_remote_du[0].split()[0]) + logger.info('ltacp %s: input datasize: %d bytes, %s' % (self.logId, input_datasize, humanreadablesize(input_datasize))) + estimated_tar_size = 512*(input_datasize / 512) + 3*512 #512byte header, 2*512byte ending, 512byte modulo data + logger.info('ltacp %s: estimated_tar_size: %d bytes, %s' % (self.logId, estimated_tar_size, humanreadablesize(estimated_tar_size))) + + #--- # Server part #--- @@ -126,50 +146,27 @@ class LtaCp: p_data_in, port_data = self._ncListen('data') p_md5_receive, port_md5 = self._ncListen('md5 checksums') - # create fifo paths - self.local_fifo_basename = '/tmp/ltacp_datapipe_%s_%s' % (self.src_host, self.logId) - - def createLocalFifo(fifo_postfix): - fifo_path = '%s_%s' % (self.local_fifo_basename, fifo_postfix) - logger.info('ltacp %s: creating data fifo: %s' % (self.logId, fifo_path)) - if os.path.exists(fifo_path): - os.remove(fifo_path) - os.mkfifo(fifo_path) - if not os.path.exists(fifo_path): - raise LtacpException("ltacp %s: Could not create fifo: %s" % (self.logId, fifo_path)) - self.fifos.append(fifo_path) - return fifo_path - - self.local_data_fifo = createLocalFifo('globus_url_copy') - self.local_byte_count_fifo = createLocalFifo('local_byte_count') - self.local_adler32_fifo = createLocalFifo('local_adler32') - - # tee incoming data stream to fifos (and pipe stream in tee_proc.stdout) - cmd_tee = ['tee', self.local_data_fifo, self.local_byte_count_fifo, self.local_adler32_fifo] - logger.info('ltacp %s: splitting datastream. executing: %s' % (self.logId, ' '.join(cmd_tee),)) - p_tee_data = Popen(cmd_tee, stdin=p_data_in.stdout, stdout=PIPE, stderr=PIPE) - self.started_procs[p_tee_data] = cmd_tee - - # start counting number of bytes in incoming data stream - cmd_byte_count = ['wc', '-c', self.local_byte_count_fifo] - logger.info('ltacp %s: computing byte count. executing: %s' % (self.logId, ' '.join(cmd_byte_count))) - p_byte_count = Popen(cmd_byte_count, stdout=PIPE, stderr=PIPE, env=dict(os.environ, LC_ALL="C")) - self.started_procs[p_byte_count] = cmd_byte_count - - # start computing md5 checksum of incoming data stream - cmd_md5_local = ['md5sum'] - logger.info('ltacp %s: computing local md5 checksum. executing on data pipe: %s' % (self.logId, ' '.join(cmd_md5_local))) - p_md5_local = Popen(cmd_md5_local, stdin=p_tee_data.stdout, stdout=PIPE, stderr=PIPE) - self.started_procs[p_md5_local] = cmd_md5_local - - # start computing adler checksum of incoming data stream + + self.local_data_fifo = '/tmp/ltacp_datapipe_%s_%s' % (self.src_host, self.logId) + + logger.info('ltacp %s: creating data fifo: %s' % (self.logId, self.local_data_fifo)) + if os.path.exists(self.local_data_fifo): + os.remove(self.local_data_fifo) + os.mkfifo(self.local_data_fifo) + if not os.path.exists(self.local_data_fifo): + raise LtacpException("ltacp %s: Could not create fifo: %s" % (self.logId, self.local_data_fifo)) + + # transfer incomming data stream via md5a32bc to compute md5, adler32 and byte_count + # data is written to fifo, which is then later fed into globus-url-copy + # on stdout we can monitor progress + # set progress message step 0f 0.5% of estimated_tar_size currdir = os.path.dirname(os.path.realpath(__file__)) - cmd_a32_local = [currdir + '/md5adler/a32', self.local_adler32_fifo] - logger.info('ltacp %s: computing local adler32 checksum. executing: %s' % (self.logId, ' '.join(cmd_a32_local))) - p_a32_local = Popen(cmd_a32_local, stdin=PIPE, stdout=PIPE, stderr=PIPE) - self.started_procs[p_a32_local] = cmd_a32_local + cmd_md5a32bc = [currdir + '/md5adler/md5a32bc', '-p', str(estimated_tar_size/200), self.local_data_fifo] + logger.info('ltacp %s: processing data stream for md5, adler32 and byte_count. executing: %s' % (self.logId, ' '.join(cmd_md5a32bc),)) + p_md5a32bc = Popen(cmd_md5a32bc, stdin=p_data_in.stdout, stdout=PIPE, stderr=PIPE) + self.started_procs[p_md5a32bc] = cmd_md5a32bc - # start copy fifo stream to SRM + # start copy fifo stream to globus-url-copy guc_options = ['-cd', #create remote directories if missing '-p 4', #number of parallel ftp connections '-bs 131072', #buffer size @@ -237,26 +234,6 @@ class LtaCp: input_is_file = (output_remote_filetype[0][0] == '-') logger.info('ltacp %s: remote path is a %s' % (self.logId, 'file' if input_is_file else 'directory')) - # get input datasize - cmd_remote_du = self.ssh_cmd + ['du -b --max-depth=0 %s' % (self.src_path_data,)] - logger.info('ltacp %s: remote getting datasize. executing: %s' % (self.logId, ' '.join(cmd_remote_du))) - p_remote_du = Popen(cmd_remote_du, stdout=PIPE, stderr=PIPE) - self.started_procs[p_remote_du] = cmd_remote_du - - # block until du is finished - output_remote_du = p_remote_du.communicate() - del self.started_procs[p_remote_du] - if p_remote_du.returncode != 0: - raise LtacpException('ltacp %s: remote du failed: \nstdout: %s\nstderr: %s' % (self.logId, - output_remote_du[0], - output_remote_du[1])) - # compute various parameters for progress logging - input_datasize = int(output_remote_du[0].split()[0]) - logger.info('ltacp %s: input datasize: %d bytes, %s' % (self.logId, input_datasize, humanreadablesize(input_datasize))) - estimated_tar_size = 512*(input_datasize / 512) + 3*512 #512byte header, 2*512byte ending, 512byte modulo data - logger.info('ltacp %s: estimated_tar_size: %d bytes, %s' % (self.logId, estimated_tar_size, humanreadablesize(estimated_tar_size))) - tar_record_size = 10240 # 20 * 512 byte blocks - with open(os.devnull, 'r') as devnull: # start sending remote data, tee to fifo if input_is_file: @@ -267,7 +244,7 @@ class LtaCp: port_data)] else: src_path_parent, src_path_child = os.path.split(self.src_path_data) - cmd_remote_data = self.ssh_cmd + ['cd %s && tar c --blocking-factor=20 --checkpoint=1000 --checkpoint-action="ttyout=checkpoint %%u\\n" -O %s | tee %s | %s %s %s' % (src_path_parent, + cmd_remote_data = self.ssh_cmd + ['cd %s && tar c -O %s | tee %s | %s %s %s' % (src_path_parent, src_path_child, self.remote_data_fifo, self.remoteNetCatCmd, @@ -296,11 +273,11 @@ class LtaCp: # wait and poll for progress while all processes are runnning while len([p for p in self.started_procs.keys() if p.poll() is not None]) == 0: try: - # read and process tar stdout lines to create progress messages - nextline = p_remote_data.stdout.readline().strip() - if len(nextline) > 0 and not input_is_file: - record_nr = int(nextline.split()[-1].strip()) - total_bytes_transfered = record_nr * tar_record_size + # read and process md5a32bc stdout lines to create progress messages + nextline = p_md5a32bc.stdout.readline().strip() + + if len(nextline) > 0: + total_bytes_transfered = int(nextline.split()[0].strip()) percentage_done = (100.0*float(total_bytes_transfered))/float(estimated_tar_size) current_progress_time = datetime.utcnow() elapsed_secs_since_start = timedelta_total_seconds(current_progress_time - transfer_start_time) @@ -347,37 +324,35 @@ class LtaCp: raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_md5_receive[1])) logger.debug('ltacp %s: received md5 checksum.' % self.logId) - logger.debug('ltacp %s: waiting for local computation of md5 checksum...' % self.logId) - output_md5_local = p_md5_local.communicate() - if p_md5_local.returncode != 0: - raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_md5_local[1])) - logger.debug('ltacp %s: computed local md5 checksum.' % self.logId) + logger.info('ltacp %s: waiting for local computation of md5 adler32 and byte_count...' % self.logId) + output_md5a32bc_local = p_md5a32bc.communicate() + if p_md5a32bc.returncode != 0: + raise LtacpException('ltacp %s: Error while computing md5 adler32 and byte_count: %s' % (self.logId, output_md5a32bc_local[1])) + logger.debug('ltacp %s: computed local md5 adler32 and byte_count.' % self.logId) - # compare remote and local md5 checksums + # process remote md5 checksums try: md5_checksum_remote = output_md5_receive[0].split(' ')[0] - md5_checksum_local = output_md5_local[0].split(' ')[0] except Exception as e: - logger.error('ltacp %s: error while parsing md5 checksum outputs: local=%s received=%s' % (self.logId, output_md5_local[0], output_md5_receive[0])) + logger.error('ltacp %s: error while parsing remote md5: %s' % (self.logId, output_md5_receive[0])) raise - if(md5_checksum_remote != md5_checksum_local): - raise LtacpException('md5 checksum reported by client (%s) does not match local checksum of incoming data stream (%s)' % (self.logId, md5_checksum_remote, md5_checksum_local)) - logger.info('ltacp %s: remote and local md5 checksums are equal: %s' % (self.logId, md5_checksum_local,)) + # process local md5 adler32 and byte_count + try: + items = output_md5a32bc_local[1].splitlines()[-1].split(' ') + md5_checksum_local = items[0].strip() + a32_checksum_local = items[1].strip().zfill(8) + byte_count = int(items[2].strip()) + except Exception as e: + logger.error('ltacp %s: error while parsing md5 adler32 and byte_count outputs: %s' % (self.logId, output_md5a32bc_local[0])) + raise - logger.debug('ltacp %s: waiting for local byte count on datastream...' % self.logId) - output_byte_count = p_byte_count.communicate() - if p_byte_count.returncode != 0: - raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_byte_count[1])) - byte_count = int(output_byte_count[0].split()[0].strip()) logger.info('ltacp %s: byte count of datastream is %d %s' % (self.logId, byte_count, humanreadablesize(byte_count))) - logger.debug('ltacp %s: waiting for local adler32 checksum to complete...' % self.logId) - output_a32_local = p_a32_local.communicate() - if p_a32_local.returncode != 0: - raise LtacpException('ltacp %s: local adler32 checksum computation failed: %s' (self.logId, str(output_a32_local))) - logger.debug('ltacp %s: finished computation of local adler32 checksum' % self.logId) - a32_checksum_local = output_a32_local[0].split()[1].zfill(8) + # compare local and remote md5 + if(md5_checksum_remote != md5_checksum_local): + raise LtacpException('md5 checksum reported by client (%s) does not match local checksum of incoming data stream (%s)' % (self.logId, md5_checksum_remote, md5_checksum_local)) + logger.info('ltacp %s: remote and local md5 checksums are equal: %s' % (self.logId, md5_checksum_local,)) logger.info('ltacp %s: waiting for transfer via globus-url-copy to LTA to finish...' % self.logId) output_data_out = p_data_out.communicate() @@ -396,7 +371,7 @@ class LtaCp: srm_a32_checksum, a32_checksum_local)) - logger.info('ltacp %s: adler32 checksums are equal: %s' % (self.logId, a32_checksum_local)) + logger.info('ltacp %s: adler32 checksums are equal: %s' % (self.logId, a32_checksum_local,)) if int(srm_file_size) != int(byte_count): raise LtacpException('ltacp %s: file size reported by srm (%s) does not match datastream byte count (%s)' % (self.logId, -- GitLab From 44570fbb26f6f6c02e2193e6879198c2609f53ec Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 22 Feb 2016 10:52:23 +0000 Subject: [PATCH 023/933] Task #8437: Created task branch for SLURM+Docker support in the pipeline framework -- GitLab From 7e876df2e9fd9cd632b1b91c708f697f62be60bc Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 22 Feb 2016 11:50:20 +0000 Subject: [PATCH 024/933] Task #8725: no need to transfer output of remote md5 via netcat. Just parse the remote output from ssh. This saves another connection --- LTA/LTAIngest/ltacp.py | 43 ++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index 366cca867fe..831ad4908a8 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -144,7 +144,6 @@ class LtaCp: random.seed(hash(self.src_path_data) ^ hash(time.time())) p_data_in, port_data = self._ncListen('data') - p_md5_receive, port_md5 = self._ncListen('md5 checksums') self.local_data_fifo = '/tmp/ltacp_datapipe_%s_%s' % (self.src_host, self.logId) @@ -203,7 +202,9 @@ class LtaCp: # 3) simultaneously to 2), calculate checksum of fifo stream # 4) break fifo - self.remote_data_fifo = '/tmp/ltacp_md5_pipe_%s_%s' % (self.logId, port_md5) + self.remote_data_fifo = '/tmp/ltacp_md5_pipe_%s' % (self.logId, ) + #make sure there is no old remote fifo + self._removeRemoteFifo() cmd_remote_mkfifo = self.ssh_cmd + ['mkfifo %s' % (self.remote_data_fifo,)] logger.info('ltacp %s: remote creating fifo. executing: %s' % (self.logId, ' '.join(cmd_remote_mkfifo))) p_remote_mkfifo = Popen(cmd_remote_mkfifo, stdout=PIPE, stderr=PIPE) @@ -255,7 +256,7 @@ class LtaCp: self.started_procs[p_remote_data] = cmd_remote_data # start computation of checksum on remote fifo stream - cmd_remote_checksum = self.ssh_cmd + ['md5sum %s | %s %s %s' % (self.remote_data_fifo, self.remoteNetCatCmd, self.localIPAddress, port_md5)] + cmd_remote_checksum = self.ssh_cmd + ['md5sum %s' % (self.remote_data_fifo,)] logger.info('ltacp %s: remote starting computation of md5 checksum. executing: %s' % (self.logId, ' '.join(cmd_remote_checksum))) p_remote_checksum = Popen(cmd_remote_checksum, stdin=devnull, stdout=PIPE, stderr=PIPE) self.started_procs[p_remote_checksum] = cmd_remote_checksum @@ -318,12 +319,6 @@ class LtaCp: raise LtacpException('ltacp %s: Error in remote md5 checksum computation: %s' % (self.logId, output_remote_checksum[1])) logger.debug('ltacp %s: remote md5 checksum computation finished.' % self.logId) - logger.debug('ltacp %s: waiting to receive remote md5 checksum...' % self.logId) - output_md5_receive = p_md5_receive.communicate() - if p_md5_receive.returncode != 0: - raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_md5_receive[1])) - logger.debug('ltacp %s: received md5 checksum.' % self.logId) - logger.info('ltacp %s: waiting for local computation of md5 adler32 and byte_count...' % self.logId) output_md5a32bc_local = p_md5a32bc.communicate() if p_md5a32bc.returncode != 0: @@ -332,9 +327,9 @@ class LtaCp: # process remote md5 checksums try: - md5_checksum_remote = output_md5_receive[0].split(' ')[0] + md5_checksum_remote = output_remote_checksum[0].split(' ')[0] except Exception as e: - logger.error('ltacp %s: error while parsing remote md5: %s' % (self.logId, output_md5_receive[0])) + logger.error('ltacp %s: error while parsing remote md5: %s\n%s' % (self.logId, output_remote_checksum[0], output_remote_checksum[1])) raise # process local md5 adler32 and byte_count @@ -416,18 +411,26 @@ class LtaCp: return (p_listen, port) + def _removeRemoteFifo(self): + if hasattr(self, 'remote_data_fifo') and self.remote_data_fifo: + '''remove a file (or fifo) on a remote host. Test if file exists before deleting.''' + cmd_remote_ls = self.ssh_cmd + ['ls %s' % (self.remote_data_fifo,)] + p_remote_ls = Popen(cmd_remote_ls, stdout=PIPE, stderr=PIPE) + p_remote_ls.communicate() + + if p_remote_ls.returncode == 0: + cmd_remote_rm = self.ssh_cmd + ['rm %s' % (self.remote_data_fifo,)] + logger.info('ltacp %s: removing remote fifo. executing: %s' % (self.logId, ' '.join(cmd_remote_rm))) + p_remote_rm = Popen(cmd_remote_rm, stdout=PIPE, stderr=PIPE) + p_remote_rm.communicate() + if p_remote_rm.returncode != 0: + logger.error("Could not remove remote fifo %s@%s:%s\n%s" % (self.src_user, self.src_host, self.remote_data_fifo, p_remote_rm.stderr)) + self.remote_data_fifo = None + def cleanup(self): logger.debug('ltacp %s: cleaning up' % (self.logId)) - if hasattr(self, 'remote_data_fifo') and self.remote_data_fifo: - '''remove a file (or fifo) on a remote host. Test if file exists before deleting.''' - cmd_remote_rm = self.ssh_cmd + ['rm %s' % (self.remote_data_fifo,)] - logger.info('ltacp %s: removing remote fifo. executing: %s' % (self.logId, ' '.join(cmd_remote_rm))) - p_remote_rm = Popen(cmd_remote_rm, stdout=PIPE, stderr=PIPE) - p_remote_rm.communicate() - if p_remote_rm.returncode != 0: - logger.error("Could not remove remote fifo %s@%s:%s\n%s" % (self.src_user, self.src_host, self.remote_data_fifo, p_remote_rm.stderr)) - self.remote_data_fifo = None + self._removeRemoteFifo() # remove local fifos for fifo in self.fifos: -- GitLab From 834567ac503ba6e8715ba09366c2d3aa2cfb65f9 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 24 Feb 2016 14:36:24 +0000 Subject: [PATCH 025/933] Task #8437: Created MAC_Services package --- CMake/LofarPackageList.cmake | 1 + MAC/CMakeLists.txt | 1 + MAC/Services/CMakeLists.txt | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 MAC/Services/CMakeLists.txt diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index 1e8ed2516be..ffc7a948850 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -85,6 +85,7 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(PPSTune_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCU/PPSTune) set(Firmware-Tools_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCU/Firmware/tools) set(MACTools_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCU/StationTest/MACTools) + set(MAC_Services_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Services) set(LTAIngest_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LTA/LTAIngest) set(ltastorageoverview_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LTA/ltastorageoverview) set(APLCommon_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/APL/APLCommon) diff --git a/MAC/CMakeLists.txt b/MAC/CMakeLists.txt index 6c1ba3b75eb..f76cd264cd6 100644 --- a/MAC/CMakeLists.txt +++ b/MAC/CMakeLists.txt @@ -6,6 +6,7 @@ lofar_add_package(APL) # Application subdirectory lofar_add_package(Deployment) # Deployment related functions lofar_add_package(Navigator2) lofar_add_package(MACTools Tools) +lofar_add_package(MAC_Services Services) lofar_add_package(PVSS_Datapoints Deployment/data/PVSS) lofar_add_package(OTDB_Comps Deployment/data/OTDB) lofar_add_package(StaticMetaData Deployment/data/StaticMetaData) diff --git a/MAC/Services/CMakeLists.txt b/MAC/Services/CMakeLists.txt new file mode 100644 index 00000000000..0f5f355a0a8 --- /dev/null +++ b/MAC/Services/CMakeLists.txt @@ -0,0 +1,3 @@ +# $Id$ + +lofar_package(MAC_Services 1.0 DEPENDS PyMessaging) -- GitLab From 2e3a0702896660790192da457a8b8ea761fff4ec Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 1 Mar 2016 13:17:55 +0000 Subject: [PATCH 026/933] Task #8437: Create specific exception for timeouts on RPC --- LCS/Messaging/python/messaging/RPC.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/LCS/Messaging/python/messaging/RPC.py b/LCS/Messaging/python/messaging/RPC.py index e6aaa05bcc1..f61bf4f8d52 100644 --- a/LCS/Messaging/python/messaging/RPC.py +++ b/LCS/Messaging/python/messaging/RPC.py @@ -59,6 +59,10 @@ class RPCException(Exception): "Exception occured in the RPC code itself, like time-out, invalid message received, etc." pass +class RPCTimeoutException(RPCException): + "Exception occured when the RPC call times out." + pass + class RPC(): """ This class provides an easy way to invoke a Remote Rrocedure Call to a @@ -156,7 +160,7 @@ class RPC(): status["state"] = "TIMEOUT" status["errmsg"] = "RPC Timed out" status["backtrace"] = "" - raise RPCException(status) + raise RPCTimeoutException(status) # Check for illegal message type if isinstance(answer, ReplyMessage) is False: -- GitLab From f01f4d315ab629f9b0d96e7e5ea76447d146585a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 1 Mar 2016 13:44:40 +0000 Subject: [PATCH 027/933] Task #8437: Added generic pipeline starting script runPipeline.sh --- .gitattributes | 1 + CEP/Pipeline/recipes/sip/CMakeLists.txt | 1 + CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 67 +++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100755 CEP/Pipeline/recipes/sip/bin/runPipeline.sh diff --git a/.gitattributes b/.gitattributes index b11844625cb..318c14409b8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1521,6 +1521,7 @@ CEP/Pipeline/recipes/sip/bin/msss_calibrator_pipeline.py eol=lf CEP/Pipeline/recipes/sip/bin/msss_imager_pipeline.py eol=lf CEP/Pipeline/recipes/sip/bin/msss_target_pipeline.py eol=lf CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py -text +CEP/Pipeline/recipes/sip/bin/runPipeline.sh eol=lf CEP/Pipeline/recipes/sip/bin/selfcal_imager_pipeline.py eol=lf CEP/Pipeline/recipes/sip/bin/startPython.sh eol=lf CEP/Pipeline/recipes/sip/bin/startPythonVersion.sh -text diff --git a/CEP/Pipeline/recipes/sip/CMakeLists.txt b/CEP/Pipeline/recipes/sip/CMakeLists.txt index 997cadbab45..90f0ad44888 100644 --- a/CEP/Pipeline/recipes/sip/CMakeLists.txt +++ b/CEP/Pipeline/recipes/sip/CMakeLists.txt @@ -74,6 +74,7 @@ lofar_add_bin_scripts( bin/pulsar_pipeline.py bin/long_baseline_pipeline.py bin/selfcal_imager_pipeline.py + bin/runPipeline.sh bin/startPython.sh bin/startPythonVersion.sh bin/stopPython.sh diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh new file mode 100755 index 00000000000..d09bdcb68ec --- /dev/null +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -0,0 +1,67 @@ +#!/bin/bash -e + +# Entry script from MAC on non-CEP2 clusters (CEP2 uses startPython.sh). +# +# The following chain is executed: +# +# setStatus(STARTED) +# getParset() +# (execute pipeline as specified in parset) +# setStatus(COMPLETING) +# setStatus(FINISHED/ABORTED) +# +# Syntax: +# +# runPipeline.sh <obsid> + +OBSID=$1 + +if [ -z "$OBSID" ]; then + echo "Usage: $0 <obsid>" + exit 1 +fi + +# Queue on which to post status changes +SETSTATUS_BUS=lofar.otdb.setStatus + +# Mark as started +setStatus.py -o $OBSID -s active -b $SETSTATUS_BUS || true +trap "setStatus.py -o $OBSID -s aborted -b $SETSTATUS_BUS || true" SIGTERM SIGINT SIGQUIT SIGHUP + +# Fetch parset +PARSET=${LOFARROOT}/var/run/Observation${OBSID}.parset +getParset.py -o $OBSID >$PARSET + +# Fetch parameters from parset +PROGRAM_NAME=$(getparsetvalue $PARSET "ObsSW.Observation.ObservationControl.PythonControl.programName") + +# Run pipeline +OPTIONS=" \ + -d \ + -c ${LOFARROOT}/share/pipeline/pipeline.cfg \ + -t ${LOFARROOT}/share/pipeline/tasks.cfg" + +# Set up the environment (information to propagate to the node scripts for monitoring and logging) +export LOFAR_OBSID="$OBSID" + +# Start the Python program +echo "**** $(date) ****" +echo "Executing: ${PROGRAM_NAME} ${OPTIONS} ${PARSET}" +${PROGRAM_NAME} ${OPTIONS} ${PARSET} +RESULT=$? + +# Process the result +setStatus.py -o $OBSID -s completing -b $SETSTATUS_BUS || true +if [ $RESULT -eq 0 ]; then + # Wait for feedback to propagate + sleep 60 + + # Mark as succesful + setStatus.py -o $OBSID -s finished -b $SETSTATUS_BUS || true +else + # Mark as failed + setStatus.py -o $OBSID -s aborted -b $SETSTATUS_BUS || true +fi + +# Propagate result to caller +exit $RESULT -- GitLab From 33edcc72c2687fe5c9646faf068d12382b2589cf Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 1 Mar 2016 13:45:21 +0000 Subject: [PATCH 028/933] Task #8437: Added setStatus and getParset scripts --- SAS/OTDB_Services/CMakeLists.txt | 2 ++ SAS/OTDB_Services/getParset.py | 59 ++++++++++++++++++++++++++++++++ SAS/OTDB_Services/setStatus.py | 55 +++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100755 SAS/OTDB_Services/getParset.py create mode 100755 SAS/OTDB_Services/setStatus.py diff --git a/SAS/OTDB_Services/CMakeLists.txt b/SAS/OTDB_Services/CMakeLists.txt index e945bfbc0e8..4cca75836e0 100644 --- a/SAS/OTDB_Services/CMakeLists.txt +++ b/SAS/OTDB_Services/CMakeLists.txt @@ -6,6 +6,8 @@ lofar_find_package(Python 2.6 REQUIRED) include(PythonInstall) lofar_add_bin_scripts( + getParset.py + setStatus.py TreeService.py TreeStatusEvents.py ) diff --git a/SAS/OTDB_Services/getParset.py b/SAS/OTDB_Services/getParset.py new file mode 100755 index 00000000000..3ab751dae80 --- /dev/null +++ b/SAS/OTDB_Services/getParset.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +#coding: iso-8859-15 +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id$ +""" + +""" + +from lofar.messaging.RPC import RPC + +def getParset(obsid, status, otdb_busname="lofar.otdb.command"): + with RPC("TaskSpecification", busname=otdb_busname, timeout=10) as parset_rpc: + result, _ = parset_rpc(OtdbID=obsid) + + return result + +if __name__ == "__main__": + from optparse import OptionParser + import logging + import sys + + logging.basicConfig(stream=sys.stdout, level=logging.INFO) + logger = logging.getLogger(__name__) + + # Check the invocation arguments + parser = OptionParser("%prog -o obsid [options]") + parser.add_option("-B", "--busname", dest="busname", type="string", default="lofar.otdb.command", + help="Busname on which OTDB commands are sent") + parser.add_option("-o", "--obsid", dest="obsid", type="int", default="", + help="Observation/tree ID to get parset of") + (options, args) = parser.parse_args() + + if not options.busname or not options.obsid: + parser.print_help() + sys.exit(1) + + parset = getParset(options.obsid, otdb_busname=options.busname) + + for k,v in parset.iteritems(): + print "%s = %s" % (k,v) + diff --git a/SAS/OTDB_Services/setStatus.py b/SAS/OTDB_Services/setStatus.py new file mode 100755 index 00000000000..778730568e5 --- /dev/null +++ b/SAS/OTDB_Services/setStatus.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +#coding: iso-8859-15 +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id$ +""" + +""" + +from lofar.messaging.RPC import RPC + +def setStatus(obsid, status, otdb_busname="lofar.otdb.command"): + with RPC("StatusUpdateCmd", busname=otdb_busname, timeout=10) as status_rpc: + result, _ = status_rpc(OtdbID=obsid, NewStatus=status) + +if __name__ == "__main__": + from optparse import OptionParser + import logging + import sys + + logging.basicConfig(stream=sys.stdout, level=logging.INFO) + logger = logging.getLogger(__name__) + + # Check the invocation arguments + parser = OptionParser("%prog -o obsid -s status [options]") + parser.add_option("-B", "--busname", dest="busname", type="string", default="lofar.otdb.command", + help="Busname on which OTDB commands are sent") + parser.add_option("-o", "--obsid", dest="obsid", type="int", default="", + help="Observation/tree ID to set status for") + parser.add_option("-s", "--status", dest="status", type="int", default="", + help="New status") + (options, args) = parser.parse_args() + + if not options.busname or not options.obsid or not options.status: + parser.print_help() + sys.exit(1) + + setStatus(options.obsid, options.status, otdb_busname=options.busname) -- GitLab From 03c5844b7b4589476551e0e0dffb82652c7ade4d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 1 Mar 2016 13:46:08 +0000 Subject: [PATCH 029/933] Task #8437: Initial implementation of a Pipeline Starter service for CEP4 --- .gitattributes | 2 + MAC/Services/src/CMakeLists.txt | 15 ++ MAC/Services/src/PipelineStarter.py | 275 +++++++++++++++++++++++++++ MAC/Services/src/pipelinestarter | 48 +++++ MAC/Services/src/pipelinestarter.ini | 7 + 5 files changed, 347 insertions(+) create mode 100644 MAC/Services/src/CMakeLists.txt create mode 100755 MAC/Services/src/PipelineStarter.py create mode 100644 MAC/Services/src/pipelinestarter create mode 100644 MAC/Services/src/pipelinestarter.ini diff --git a/.gitattributes b/.gitattributes index 318c14409b8..ce87a934000 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4126,6 +4126,8 @@ MAC/Navigator2/scripts/monitorStationAlarms.ctl -text MAC/Navigator2/scripts/readStationConfigs.ctl -text MAC/Navigator2/scripts/readStationConnections.ctl -text MAC/Navigator2/scripts/transferMPs.ctl -text +MAC/Services/src/pipelinestarter -text +MAC/Services/src/pipelinestarter.ini -text MAC/Test/APL/PVSSproject/colorDB/Lofar[!!-~]colors -text svneol=native#application/octet-stream MAC/Test/APL/PVSSproject/colorDB/colorDB_de -text svneol=native#application/octet-stream MAC/Test/APL/PVSSproject/config/config -text svneol=native#application/octet-stream diff --git a/MAC/Services/src/CMakeLists.txt b/MAC/Services/src/CMakeLists.txt new file mode 100644 index 00000000000..cf4cde0d7b6 --- /dev/null +++ b/MAC/Services/src/CMakeLists.txt @@ -0,0 +1,15 @@ +# $Id$ + +lofar_add_bin_scripts( + pipelinestarter +) + +python_install( + PipelineStarter.py + DESTINATION lofar/mac +) + +# supervisord config files +install(FILES + pipelinestarter.ini + DESTINATION etc/supervisord.d) diff --git a/MAC/Services/src/PipelineStarter.py b/MAC/Services/src/PipelineStarter.py new file mode 100755 index 00000000000..0817c443b1f --- /dev/null +++ b/MAC/Services/src/PipelineStarter.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python +#coding: iso-8859-15 +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id$ +""" +Daemon that listens to OTDB status changes to PRESCHEDULED and SCHEDULED, requests +the parset of such jobs (+ their predecessors), and posts them on the bus. +""" + +from lofar.messaging import FromBus, ToBus, RPC, EventMessage +from lofar.parameterset import PyParameterValue +from lofar.sas.otdb.OTDBBusListener import OTDBBusListener +from lofar.sas.otdb.setStatus import setStatus +from lofar.common.util import waitForInterrupt + +import subprocess +import datetime +import os + +import logging +logger = logging.getLogger(__name__) + +def runCommand(self, cmdline, input=None): + logger.info("Running '%s'", cmdline) + + stdout, _ = subprocess.Popen( + cmdline, + stdin=file("/dev/null"), + stdout=subprocess.PIPE, + ).communicate(input) + + logger.debug(stdout) + + return stdout.strip() + +def runSlurmCommand(self, args): + cmdline = "ssh head01.cep4 %s" % (args,) + runCommand(cmdline) + +""" Prefix that is common to all parset keys, depending on the exact source. """ +PARSET_PREFIX="ObsSW." + +class Parset(dict): + def predecessors(self): + """ Extract the list of predecessor obs IDs from the given parset. """ + + key = PARSET_PREFIX + "Observation.Scheduler.predecessors" + strlist = PyParameterValue(str(parset[key]), True).getStringVector() + + # Key contains "Lxxxxx" values, we want to have "xxxxx" only + result = [int(filter(str.isdigit,x)) for x in strlist] + + return result + + def dockerTag(self): + # For now, return OUR tag + return runCommand("docker-template", "${LOFAR_TAG}") + + def slurmJobName(self): + return self[PARSET_PREFIX + "Observation.ObsID"] + +def getSlurmJobInfo(self): + stdout = runSlurmCommand("scontrol show job --oneliner") + + jobs = {} + for l in stdout.split(): + # One line is one job + job_properties = {} + + # Information is in k=v pairs + for i in l.split(): + k,v = i.split("=", 2) + job_properties[k] = v + + name = job_properties["JobName"] + if name in jobs: + logger.warning("Duplicate job name: %s" % (name,)) + jobs[name] = job_properties + + return jobs + +class PipelineStarter(OTDBBusListener): + def __init__(self, otdb_busname=None, setStatus_busname=None **kwargs): + super(PipelineStarter, self).__init__(busname=otdb_busname, **kwargs) + + self.parset_rpc = RPC(service="TaskSpecification", busname=otdb_busname) + self.setStatus_busname = setStatus_busname if setStatus_busname else otdb_busname + + def start_listening(self, **kwargs): + self.parset_rpc.open() + self.send_bus.open() + + super(PipelineStarter, self).start_listening(**kwargs) + + def stop_listening(self, **kwargs): + super(PipelineStarter, self).stop_listening(**kwargs) + + self.send_bus.close() + self.parset_rpc.close() + + + def _shouldHandle(self, parset): + if parset[PARSET_PREFIX + "Observation.processType"] != "Pipeline": + logger.info("Not processing tree: is not a pipeline") + return False + + if parset[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] in ["", "CEP2"]: + logger.info("Not processing tree: is a CEP2 pipeline") + return False + + return True + + def _slurmJobNames(self, parsets, allowed): + names = [] + + for p in parsets: + processType = p[PARSET_PREFIX + "Observation.processType"] + if p not in allowed: + raise KeyError("No SLURM job for predecessor. Expected job %s." % (obsid,jobName)) + + names.append(p.slurmJobName()) + + return names + + def _getPredecessorParsets(self, parset): + obsIDs = parset.predecessors() + + logger.info("Obtaining predecessor parsets %s", predecessor_obsIDs) + + preparsets = {} + for obsid in obsIDs: + preparsets[p] = Parset(self.parset_rpc( OtdbID=obsid, timeout=10 )[0]) + + return preparsets + + def onObservationAborted(self, treeId, modificationTime): + logger.info("***** STOP Tree ID %s *****", treeId) + + # Request the parset + parset = Parset(self.parset_rpc( OtdbID=treeId, timeout=10 )[0]) + + if not self.shouldHandle(parset): + return + + # Cancel corresponding SLURM job, causnig any successors + # to be cancelled as well. + stdout = self.runSlurmCommand("scancel --jobname %s" % (self._slurmJobName(parset),)) + + """ + More statusses we want to abort on. + """ + onObservationConflict = onObservationAborted + onObservationHold = onObservationAborted + + def _minStartTime(self, preparsets): + result = None + + for preparset in preparsets: + processType = preparset[PARSET_PREFIX + "Observation.processType"] + + if processType == "Observation": + # If we depend on an observation, start 1 minute after it + obs_endtime = datetime.datetime.strptime(preparset[PARSET_PREFIX + "Observation.stopTime"], "%Y-%m-%d %H:%M:%S") + min_starttime = obs_endtime + datetime.timedelta(0, 60, 0) + + result = max(result, min_starttime) if result else min_starttime + + return result + + def onObservationScheduled(self, treeId, modificationTime): + logger.info("***** QUEUE Tree ID %s *****", treeId) + + # Request the parset + parset = Parset(self.parset_rpc( OtdbID=treeId, timeout=10 )[0]) + + if not self.shouldHandle(parset): + return + + """ + Collect predecessor information. + """ + + # Collect the parsets of predecessors + preparsets = self._getPredecessorParsets(parset) + + # Collect SLURM job information + logger.info("Obtaining SLURM job list") + slurm_jobs = getSlurmJobInfo() + + """ + Schedule "docker-runPipeline.sh", which will fetch the parset and run the pipeline within + a SLURM job. + """ + + logger.info("Scheduling SLURM job") + + # Determine SLURM parameters + sbatch_params = ["--job-name=%s" % (parset.slurmJobName(),), + + # Only run job if all nodes are ready + "--wait-all-nodes=1", + + # Enforce the dependencies, instead of creating lingering jobs + "--kill-on-invalid-dep=yes", + + # Restart job if a node fails + "--requeue", + + # Maximum run time for job (31 days) + "--time=31-0", + + # TODO: Compute nr nodes + "--nodes=50", + + # Define better places to write the output + os.path.expandvars("--error=$LOFARROOT/var/log/docker-startPython-%s.stderr" % (treeId,)), + os.path.expandvars("--output=$LOFARROOT/var/log/docker-startPython-%s.log" % (treeId,)), + + ] + + min_starttime = self._minStartTime(preparsets) + if min_starttime: + sbatch_params.append("--begin=%s" % (min_starttime.strftime("%FT%T"),)) + + predecessor_jobs = self._slurmJobNames(preparsets, slurm_jobs.keys()) + if predecessor_jobs: + sbatch_params.append("--dependency=%s" % (",".join(("afterok:%s" % x for x in predecessor_jobs)),)) + + # Schedule job + slurm_job_id = runSlurmCommand( + "sbatch %s bash -c '%s'" % (" ".join(sbatch_params), + + # Supply script to run on-the-fly to reduce dependencies on + # compute nodes. + "docker run --rm lofar-pipeline:{tag}" + " --net=host" + " -v /data:/data" + " -e LUSER=$UID" + " -v $HOME/.ssh:/home/lofar/.ssh:ro" + " -e SLURM_JOB_ID=$SLURM_JOB_ID" + " runPipeline.sh {obsid}".format( + obsid = treeId, + tag = parset.dockerTag(), + ) + ) + logger.info("Scheduled SLURM job %s" % (slurm_job_id,)) + + # Set OTDB status to QUEUED + logger.info("Setting status to QUEUED") + try: + setStatus(treeId, "queued", otdb_busname=self.setStatus_busname) + except RPCTimeoutException, e: + # We use a queue, so delivery is guaranteed. We don't care about the answer. + pass + + logger.info("Pipeline processed.") + diff --git a/MAC/Services/src/pipelinestarter b/MAC/Services/src/pipelinestarter new file mode 100644 index 00000000000..8962d1b4f9e --- /dev/null +++ b/MAC/Services/src/pipelinestarter @@ -0,0 +1,48 @@ +#!/usr/bin/env python +#coding: iso-8859-15 +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: JobsToSchedule.py 33364 2016-01-21 21:21:12Z mol $ +""" +Daemon that listens to OTDB status changes to PRESCHEDULED, requests +the parset of such jobs (+ their predecessors), and posts them on the bus. +""" + +from lofar.mac.PipelineStarter import PipelineStarter + +if __name__ == "__main__": + import sys + from optparse import OptionParser + + # Check the invocation arguments + parser = OptionParser("%prog -O otdb_bus -S setStatus_bus [options]") + parser.add_option("-O", "--otdb_bus", dest="otdb_busname", type="string", default="", + help="Bus or queue OTDB operates on") + parser.add_option("-S", "--setStatus_bus", dest="setStatus_busname", type="string", default="", + help="Bus or queue we publish state changes on") + (options, args) = parser.parse_args() + + if not options.otdb_busname: + parser.print_help() + sys.exit(1) + + with PipelineStarter(otdb_busname=options.otdb_busname, setStatus_busname=options.setStatus_busname) as ps: + waitForInterrupt() + diff --git a/MAC/Services/src/pipelinestarter.ini b/MAC/Services/src/pipelinestarter.ini new file mode 100644 index 00000000000..0d68a1f961b --- /dev/null +++ b/MAC/Services/src/pipelinestarter.ini @@ -0,0 +1,7 @@ +[program:PipelineStarter] +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;pipelinestarter' +user=lofarsys +stopsignal=INT ; KeyboardInterrupt +stopasgroup=true +stdout_logfile=%(program_name)s.log +stderr_logfile=%(program_name)s.stderr -- GitLab From b88dd5848342606975a85139ddeb746979a270a5 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 2 Mar 2016 15:42:57 +0000 Subject: [PATCH 030/933] Task #8437: Added dependency on OTDB_Services, and fixed default values for obsid parameter --- MAC/Services/CMakeLists.txt | 2 +- SAS/OTDB_Services/getParset.py | 2 +- SAS/OTDB_Services/setStatus.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MAC/Services/CMakeLists.txt b/MAC/Services/CMakeLists.txt index 0f5f355a0a8..8c93633aab6 100644 --- a/MAC/Services/CMakeLists.txt +++ b/MAC/Services/CMakeLists.txt @@ -1,3 +1,3 @@ # $Id$ -lofar_package(MAC_Services 1.0 DEPENDS PyMessaging) +lofar_package(MAC_Services 1.0 DEPENDS PyMessaging OTDB_Services) diff --git a/SAS/OTDB_Services/getParset.py b/SAS/OTDB_Services/getParset.py index 3ab751dae80..d6abdae4ce7 100755 --- a/SAS/OTDB_Services/getParset.py +++ b/SAS/OTDB_Services/getParset.py @@ -44,7 +44,7 @@ if __name__ == "__main__": parser = OptionParser("%prog -o obsid [options]") parser.add_option("-B", "--busname", dest="busname", type="string", default="lofar.otdb.command", help="Busname on which OTDB commands are sent") - parser.add_option("-o", "--obsid", dest="obsid", type="int", default="", + parser.add_option("-o", "--obsid", dest="obsid", type="int", default=0, help="Observation/tree ID to get parset of") (options, args) = parser.parse_args() diff --git a/SAS/OTDB_Services/setStatus.py b/SAS/OTDB_Services/setStatus.py index 778730568e5..c3379282a3b 100755 --- a/SAS/OTDB_Services/setStatus.py +++ b/SAS/OTDB_Services/setStatus.py @@ -42,9 +42,9 @@ if __name__ == "__main__": parser = OptionParser("%prog -o obsid -s status [options]") parser.add_option("-B", "--busname", dest="busname", type="string", default="lofar.otdb.command", help="Busname on which OTDB commands are sent") - parser.add_option("-o", "--obsid", dest="obsid", type="int", default="", + parser.add_option("-o", "--obsid", dest="obsid", type="int", default=0, help="Observation/tree ID to set status for") - parser.add_option("-s", "--status", dest="status", type="int", default="", + parser.add_option("-s", "--status", dest="status", type="string", default="", help="New status") (options, args) = parser.parse_args() -- GitLab From cab089a55dfa6587037e81763690157f3f56cec0 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 4 Mar 2016 11:49:15 +0000 Subject: [PATCH 031/933] Task #8437: Set ABORTED through a separate job, fixed typos, added tests --- .gitattributes | 2 + CEP/Pipeline/recipes/sip/CMakeLists.txt | 1 + .../recipes/sip/bin/pipelineAborted.sh | 26 ++++ CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 9 +- MAC/Services/CMakeLists.txt | 5 +- MAC/Services/src/PipelineStarter.py | 118 ++++++++++++++---- MAC/Services/test/CMakeLists.txt | 8 ++ MAC/Services/test/tPipelineStarter.py | 97 ++++++++++++++ MAC/Services/test/tPipelineStarter.sh | 2 + 9 files changed, 235 insertions(+), 33 deletions(-) create mode 100755 CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh create mode 100644 MAC/Services/test/CMakeLists.txt create mode 100644 MAC/Services/test/tPipelineStarter.py create mode 100755 MAC/Services/test/tPipelineStarter.sh diff --git a/.gitattributes b/.gitattributes index ce87a934000..a3551495ed8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1520,6 +1520,7 @@ CEP/Pipeline/recipes/sip/bin/long_baseline_pipeline.py eol=lf CEP/Pipeline/recipes/sip/bin/msss_calibrator_pipeline.py eol=lf CEP/Pipeline/recipes/sip/bin/msss_imager_pipeline.py eol=lf CEP/Pipeline/recipes/sip/bin/msss_target_pipeline.py eol=lf +CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh eol=lf CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py -text CEP/Pipeline/recipes/sip/bin/runPipeline.sh eol=lf CEP/Pipeline/recipes/sip/bin/selfcal_imager_pipeline.py eol=lf @@ -4128,6 +4129,7 @@ MAC/Navigator2/scripts/readStationConnections.ctl -text MAC/Navigator2/scripts/transferMPs.ctl -text MAC/Services/src/pipelinestarter -text MAC/Services/src/pipelinestarter.ini -text +MAC/Services/test/tPipelineStarter.sh eol=lf MAC/Test/APL/PVSSproject/colorDB/Lofar[!!-~]colors -text svneol=native#application/octet-stream MAC/Test/APL/PVSSproject/colorDB/colorDB_de -text svneol=native#application/octet-stream MAC/Test/APL/PVSSproject/config/config -text svneol=native#application/octet-stream diff --git a/CEP/Pipeline/recipes/sip/CMakeLists.txt b/CEP/Pipeline/recipes/sip/CMakeLists.txt index 90f0ad44888..f3654d95a5b 100644 --- a/CEP/Pipeline/recipes/sip/CMakeLists.txt +++ b/CEP/Pipeline/recipes/sip/CMakeLists.txt @@ -74,6 +74,7 @@ lofar_add_bin_scripts( bin/pulsar_pipeline.py bin/long_baseline_pipeline.py bin/selfcal_imager_pipeline.py + bin/pipelineAborted.sh bin/runPipeline.sh bin/startPython.sh bin/startPythonVersion.sh diff --git a/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh b/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh new file mode 100755 index 00000000000..f4a2bc76f2d --- /dev/null +++ b/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh @@ -0,0 +1,26 @@ +#!/bin/bash -e + +# Signals a specific obs id as ABORTED +# +# The following chain is executed: +# +# setStatus(ABORTED) +# +# Syntax: +# +# runPipeline.sh <obsid> || pipelineAborted.sh <obsid> + +OBSID=$1 + +if [ -z "$OBSID" ]; then + echo "Usage: $0 <obsid>" + exit 1 +fi + +# Queue on which to post status changes +SETSTATUS_BUS=lofar.otdb.setStatus + +# Mark as aborted +setStatus.py -o $OBSID -s aborted -b $SETSTATUS_BUS || true + +exit 0 diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh index d09bdcb68ec..5c5de91a59e 100755 --- a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -8,11 +8,11 @@ # getParset() # (execute pipeline as specified in parset) # setStatus(COMPLETING) -# setStatus(FINISHED/ABORTED) +# setStatus(FINISHED) # # Syntax: # -# runPipeline.sh <obsid> +# runPipeline.sh <obsid> || pipelineAborted.sh <obsid> OBSID=$1 @@ -26,7 +26,6 @@ SETSTATUS_BUS=lofar.otdb.setStatus # Mark as started setStatus.py -o $OBSID -s active -b $SETSTATUS_BUS || true -trap "setStatus.py -o $OBSID -s aborted -b $SETSTATUS_BUS || true" SIGTERM SIGINT SIGQUIT SIGHUP # Fetch parset PARSET=${LOFARROOT}/var/run/Observation${OBSID}.parset @@ -52,15 +51,13 @@ RESULT=$? # Process the result setStatus.py -o $OBSID -s completing -b $SETSTATUS_BUS || true + if [ $RESULT -eq 0 ]; then # Wait for feedback to propagate sleep 60 # Mark as succesful setStatus.py -o $OBSID -s finished -b $SETSTATUS_BUS || true -else - # Mark as failed - setStatus.py -o $OBSID -s aborted -b $SETSTATUS_BUS || true fi # Propagate result to caller diff --git a/MAC/Services/CMakeLists.txt b/MAC/Services/CMakeLists.txt index 8c93633aab6..25f37c622c3 100644 --- a/MAC/Services/CMakeLists.txt +++ b/MAC/Services/CMakeLists.txt @@ -1,3 +1,6 @@ # $Id$ -lofar_package(MAC_Services 1.0 DEPENDS PyMessaging OTDB_Services) +lofar_package(MAC_Services 1.0 DEPENDS PyMessaging OTDB_Services pyparameterset) + +add_subdirectory(src) +add_subdirectory(test) diff --git a/MAC/Services/src/PipelineStarter.py b/MAC/Services/src/PipelineStarter.py index 0817c443b1f..f3320f57fed 100755 --- a/MAC/Services/src/PipelineStarter.py +++ b/MAC/Services/src/PipelineStarter.py @@ -21,15 +21,41 @@ # # $Id$ """ -Daemon that listens to OTDB status changes to PRESCHEDULED and SCHEDULED, requests -the parset of such jobs (+ their predecessors), and posts them on the bus. +Daemon that listens to OTDB status changes to SCHEDULED, requests +the parset of such jobs, and starts them using SLURM and Docker. + +The execution chain is as follows: + +[SCHEDULED] -> PipelineStarter schedules + + runPipeline.sh <obsid> || pipelineAborted.sh <obsid> + + using two SLURM jobs, guaranteeing that pipelineAborted.sh is + called in the following circumstances: + + - runPipeline.sh exits with failure + - runPipeline.sh is killed by SLURM + - runPipeline.sh job is cancelled in the queue + + State is set to [QUEUED]. + +(runPipeline.sh) -> Calls + - state <- [ACTIVE] + - getParset + - (run pipeline) + - state <- [COMPLETING] + - (wrap up) + - state <- [FINISHED] + +(pipelineAborted.sh) -> Calls + - state <- [ABORTED] """ from lofar.messaging import FromBus, ToBus, RPC, EventMessage from lofar.parameterset import PyParameterValue from lofar.sas.otdb.OTDBBusListener import OTDBBusListener -from lofar.sas.otdb.setStatus import setStatus from lofar.common.util import waitForInterrupt +from lofar.messaging.RPC import RPC import subprocess import datetime @@ -38,21 +64,31 @@ import os import logging logger = logging.getLogger(__name__) -def runCommand(self, cmdline, input=None): +def runCommand(cmdline, input=None): logger.info("Running '%s'", cmdline) - stdout, _ = subprocess.Popen( + # Start command + proc = subprocess.Popen( cmdline, - stdin=file("/dev/null"), + stdin=subprocess.PIPE if input else file("/dev/null"), stdout=subprocess.PIPE, - ).communicate(input) + shell=True, + universal_newlines=True + ) + # Feed input and wait for termination + stdout, _ = proc.communicate(input) logger.debug(stdout) + # Check exit status, bail on error + if proc.returncode != 0: + raise subprocess.CalledProcessError(proc.returncode, cmdline) + + # Return output return stdout.strip() -def runSlurmCommand(self, args): - cmdline = "ssh head01.cep4 %s" % (args,) +def runSlurmCommand(cmdline, headnode="head01.cep4"): + cmdline = "ssh %s %s" % (headnode,args) runCommand(cmdline) """ Prefix that is common to all parset keys, depending on the exact source. """ @@ -77,17 +113,20 @@ class Parset(dict): def slurmJobName(self): return self[PARSET_PREFIX + "Observation.ObsID"] -def getSlurmJobInfo(self): +def getSlurmJobInfo(): stdout = runSlurmCommand("scontrol show job --oneliner") + if stdout == "No jobs in the system": + return {} + jobs = {} - for l in stdout.split(): + for l in stdout.split("\n"): # One line is one job job_properties = {} # Information is in k=v pairs for i in l.split(): - k,v = i.split("=", 2) + k,v = i.split("=", 1) job_properties[k] = v name = job_properties["JobName"] @@ -98,12 +137,20 @@ def getSlurmJobInfo(self): return jobs class PipelineStarter(OTDBBusListener): - def __init__(self, otdb_busname=None, setStatus_busname=None **kwargs): + def __init__(self, otdb_busname=None, setStatus_busname=None, **kwargs): super(PipelineStarter, self).__init__(busname=otdb_busname, **kwargs) self.parset_rpc = RPC(service="TaskSpecification", busname=otdb_busname) self.setStatus_busname = setStatus_busname if setStatus_busname else otdb_busname + def _setStatus(self, obsid, status): + try: + with RPC("StatusUpdateCmd", busname=self.setStatus_busname, timeout=10) as status_rpc: + result, _ = status_rpc(OtdbID=obsid, NewStatus=status) + except RPCTimeoutException, e: + # We use a queue, so delivery is guaranteed. We don't care about the answer. + pass + def start_listening(self, **kwargs): self.parset_rpc.open() self.send_bus.open() @@ -116,7 +163,7 @@ class PipelineStarter(OTDBBusListener): self.send_bus.close() self.parset_rpc.close() - + @classmethod def _shouldHandle(self, parset): if parset[PARSET_PREFIX + "Observation.processType"] != "Pipeline": logger.info("Not processing tree: is not a pipeline") @@ -128,6 +175,7 @@ class PipelineStarter(OTDBBusListener): return True + @classmethod def _slurmJobNames(self, parsets, allowed): names = [] @@ -140,6 +188,7 @@ class PipelineStarter(OTDBBusListener): return names + @classmethod def _getPredecessorParsets(self, parset): obsIDs = parset.predecessors() @@ -160,7 +209,7 @@ class PipelineStarter(OTDBBusListener): if not self.shouldHandle(parset): return - # Cancel corresponding SLURM job, causnig any successors + # Cancel corresponding SLURM job, causing any successors # to be cancelled as well. stdout = self.runSlurmCommand("scancel --jobname %s" % (self._slurmJobName(parset),)) @@ -170,6 +219,7 @@ class PipelineStarter(OTDBBusListener): onObservationConflict = onObservationAborted onObservationHold = onObservationAborted + @classmethod def _minStartTime(self, preparsets): result = None @@ -210,8 +260,6 @@ class PipelineStarter(OTDBBusListener): a SLURM job. """ - logger.info("Scheduling SLURM job") - # Determine SLURM parameters sbatch_params = ["--job-name=%s" % (parset.slurmJobName(),), @@ -244,7 +292,12 @@ class PipelineStarter(OTDBBusListener): if predecessor_jobs: sbatch_params.append("--dependency=%s" % (",".join(("afterok:%s" % x for x in predecessor_jobs)),)) - # Schedule job + # Set OTDB status to QUEUED + logger.info("Setting status to QUEUED") + self.setStatus(treeId, "queued") + + # Schedule runPipeline.sh + logger.info("Scheduling SLURM job for runPipeline.sh") slurm_job_id = runSlurmCommand( "sbatch %s bash -c '%s'" % (" ".join(sbatch_params), @@ -260,16 +313,29 @@ class PipelineStarter(OTDBBusListener): obsid = treeId, tag = parset.dockerTag(), ) - ) + )) logger.info("Scheduled SLURM job %s" % (slurm_job_id,)) - # Set OTDB status to QUEUED - logger.info("Setting status to QUEUED") - try: - setStatus(treeId, "queued", otdb_busname=self.setStatus_busname) - except RPCTimeoutException, e: - # We use a queue, so delivery is guaranteed. We don't care about the answer. - pass + # Schedule pipelineAborted.sh + logger.info("Scheduling SLURM job for pipelineAborted.sh") + slurm_cancel_job_id = runSlurmCommand( + "sbatch" + " --job-name=%s-aborted" + " --cpus-per=task=1 --ntasks=1" + " --dependency=afternotok:%d" + " --kill-on-invalid-dep=yes" + " --requeue" + " bash -c '%s'" % (parset.slurmJobName(), slurm_job_id, + + "docker run --rm lofar-pipeline:{tag}" + " --net=host" + " -e LUSER=$UID" + " pipelineAborted.sh {obsid}".format( + obsid = treeId, + tag = parset.dockerTag(), + ) + )) + logger.info("Scheduled SLURM job %s" % (slurm_cancel_job_id,)) logger.info("Pipeline processed.") diff --git a/MAC/Services/test/CMakeLists.txt b/MAC/Services/test/CMakeLists.txt new file mode 100644 index 00000000000..86b9a1579b8 --- /dev/null +++ b/MAC/Services/test/CMakeLists.txt @@ -0,0 +1,8 @@ +# $Id$ + +include(LofarCTest) + +lofar_find_package(Python REQUIRED) + +lofar_add_test(tPipelineStarter) + diff --git a/MAC/Services/test/tPipelineStarter.py b/MAC/Services/test/tPipelineStarter.py new file mode 100644 index 00000000000..e45dc406fb5 --- /dev/null +++ b/MAC/Services/test/tPipelineStarter.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python + +# Be able to find service python file +import sys, os +sys.path.insert(0, "{srcdir}/../src".format(**os.environ)) + +import lofar.mac.PipelineStarter as module +import subprocess + +import unittest + +import logging +logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) + +def setUpModule(): + pass + +def tearDownModule(): + pass + +class TestRunCommand(unittest.TestCase): + def test_basic(self): + """ Test whether we can run a trivial command. """ + module.runCommand("true") + + def test_invalid_command(self): + """ Test whether an invalid command produces an error. """ + with self.assertRaises(subprocess.CalledProcessError): + output = module.runCommand(".") + + def test_shell(self): + """ Test whether the command is parsed by a shell. """ + module.runCommand("true --version") + + def test_output(self): + """ Test whether we catch the command output correctly. """ + output = module.runCommand("echo yes") + self.assertEqual(output, "yes") + + def test_input(self): + """ Test whether we can provide input. """ + output = module.runCommand("cat -", "yes") + self.assertEqual(output, "yes") + +class TestSlurmJobInfo(unittest.TestCase): + def test_no_jobs(self): + """ Test 'scontrol show job' output if there are no jobs. """ + module.runSlurmCommand = lambda _: """No jobs in the system""" + self.assertEqual(module.getSlurmJobInfo(), {}) + + def test_one_job(self): + """ Test 'scontrol show job' output for a single job. """ + module.runSlurmCommand = lambda _: """JobId=119 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901736 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:07 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:05:52 EligibleTime=2016-03-04T12:05:52 StartTime=2016-03-04T12:05:52 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7040 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" + + jobs = module.getSlurmJobInfo() + self.assertEqual(jobs["foo"]["JobName"], "foo") + self.assertEqual(jobs["foo"]["JobId"], "119") + + def test_two_jobs(self): + """ Test 'scontrol show job' output for multiple jobs. """ + module.runSlurmCommand = lambda _: """JobId=120 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901735 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:17 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:53 EligibleTime=2016-03-04T12:09:53 StartTime=2016-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0 + JobId=121 JobName=bar UserId=mol(7261) GroupId=mol(7261) Priority=4294901734 Nice=0 Account=(null) QOS=(null) JobState=PENDING Reason=Resources Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:00 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:59 EligibleTime=2016-03-04T12:09:59 StartTime=2017-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=(null) NumNodes=2-2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=0 MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" + + jobs = module.getSlurmJobInfo() + self.assertEqual(jobs["foo"]["JobName"], "foo") + self.assertEqual(jobs["foo"]["JobId"], "120") + + self.assertEqual(jobs["bar"]["JobName"], "bar") + self.assertEqual(jobs["bar"]["JobId"], "121") + +class TestPipelineStarter(unittest.TestCase): + def test_shouldHandle(self): + """ Test whether we filter the right OTDB trees. """ + + trials = [ { "type": "Observation", "cluster": "CEP2", "shouldHandle": False }, + { "type": "Observation", "cluster": "CEP4", "shouldHandle": False }, + { "type": "Observation", "cluster": "foo", "shouldHandle": False }, + { "type": "Observation", "cluster": "", "shouldHandle": False }, + { "type": "Pipeline", "cluster": "CEP2", "shouldHandle": False }, + { "type": "Pipeline", "cluster": "CEP4", "shouldHandle": True }, + { "type": "Pipeline", "cluster": "foo", "shouldHandle": True }, + { "type": "Pipeline", "cluster": "", "shouldHandle": False }, + ] + + for t in trials: + parset = { "ObsSW.Observation.processType": t["type"], + "ObsSW.Observation.Cluster.ProcessingCluster.clusterName": t["cluster"] } + self.assertEqual(module.PipelineStarter._shouldHandle(parset), t["shouldHandle"]) + +def main(argv): + unittest.main(verbosity=2) + +if __name__ == "__main__": + # run all tests + import sys + main(sys.argv[1:]) + diff --git a/MAC/Services/test/tPipelineStarter.sh b/MAC/Services/test/tPipelineStarter.sh new file mode 100755 index 00000000000..d3a7329b76f --- /dev/null +++ b/MAC/Services/test/tPipelineStarter.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh tPipelineStarter -- GitLab From 1b86a19683321df35e03f566b136729de84a9796 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 7 Mar 2016 15:17:17 +0000 Subject: [PATCH 032/933] Task #8437: Include server error message in client raise --- LCS/Messaging/python/messaging/RPC.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LCS/Messaging/python/messaging/RPC.py b/LCS/Messaging/python/messaging/RPC.py index f61bf4f8d52..3d8e4f8c366 100644 --- a/LCS/Messaging/python/messaging/RPC.py +++ b/LCS/Messaging/python/messaging/RPC.py @@ -190,7 +190,7 @@ class RPC(): excep_mod = __import__("exceptions") excep_class_ = getattr(excep_mod, answer.errmsg.split(':')[0], None) if (excep_class_ != None): - instance = excep_class_(answer.backtrace) + instance = excep_class_("%s%s" % (answer.errmsg.split(':',1)[1].strip(), answer.backtrace)) raise (instance) else: raise RPCException(answer.errmsg) -- GitLab From 4a6a2b71093908e5087876030e696110350f2a46 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 7 Mar 2016 15:18:28 +0000 Subject: [PATCH 033/933] Task #8437: Added test for PipelineStarter core functionality, and fixed revealed bugs. --- MAC/Services/src/PipelineStarter.py | 45 ++++---- MAC/Services/test/tPipelineStarter.py | 144 +++++++++++++++++++++++++- 2 files changed, 166 insertions(+), 23 deletions(-) diff --git a/MAC/Services/src/PipelineStarter.py b/MAC/Services/src/PipelineStarter.py index f3320f57fed..5aaec1f9774 100755 --- a/MAC/Services/src/PipelineStarter.py +++ b/MAC/Services/src/PipelineStarter.py @@ -55,7 +55,7 @@ from lofar.messaging import FromBus, ToBus, RPC, EventMessage from lofar.parameterset import PyParameterValue from lofar.sas.otdb.OTDBBusListener import OTDBBusListener from lofar.common.util import waitForInterrupt -from lofar.messaging.RPC import RPC +from lofar.messaging.RPC import RPC, RPCTimeoutException import subprocess import datetime @@ -88,7 +88,7 @@ def runCommand(cmdline, input=None): return stdout.strip() def runSlurmCommand(cmdline, headnode="head01.cep4"): - cmdline = "ssh %s %s" % (headnode,args) + cmdline = "ssh %s %s" % (headnode, cmdline) runCommand(cmdline) """ Prefix that is common to all parset keys, depending on the exact source. """ @@ -99,7 +99,7 @@ class Parset(dict): """ Extract the list of predecessor obs IDs from the given parset. """ key = PARSET_PREFIX + "Observation.Scheduler.predecessors" - strlist = PyParameterValue(str(parset[key]), True).getStringVector() + strlist = PyParameterValue(str(self[key]), True).getStringVector() # Key contains "Lxxxxx" values, we want to have "xxxxx" only result = [int(filter(str.isdigit,x)) for x in strlist] @@ -129,6 +129,10 @@ def getSlurmJobInfo(): k,v = i.split("=", 1) job_properties[k] = v + if "JobName" not in job_properties: + logger.warning("Could not find job name in line: %s" % (l,)) + continue + name = job_properties["JobName"] if name in jobs: logger.warning("Duplicate job name: %s" % (name,)) @@ -140,12 +144,12 @@ class PipelineStarter(OTDBBusListener): def __init__(self, otdb_busname=None, setStatus_busname=None, **kwargs): super(PipelineStarter, self).__init__(busname=otdb_busname, **kwargs) - self.parset_rpc = RPC(service="TaskSpecification", busname=otdb_busname) + self.parset_rpc = RPC(service="TaskSpecification", busname=otdb_busname, ForwardExceptions=True) self.setStatus_busname = setStatus_busname if setStatus_busname else otdb_busname def _setStatus(self, obsid, status): try: - with RPC("StatusUpdateCmd", busname=self.setStatus_busname, timeout=10) as status_rpc: + with RPC("StatusUpdateCmd", busname=self.setStatus_busname, timeout=10, ForwardExceptions=True) as status_rpc: result, _ = status_rpc(OtdbID=obsid, NewStatus=status) except RPCTimeoutException, e: # We use a queue, so delivery is guaranteed. We don't care about the answer. @@ -153,14 +157,12 @@ class PipelineStarter(OTDBBusListener): def start_listening(self, **kwargs): self.parset_rpc.open() - self.send_bus.open() super(PipelineStarter, self).start_listening(**kwargs) def stop_listening(self, **kwargs): super(PipelineStarter, self).stop_listening(**kwargs) - self.send_bus.close() self.parset_rpc.close() @classmethod @@ -179,24 +181,25 @@ class PipelineStarter(OTDBBusListener): def _slurmJobNames(self, parsets, allowed): names = [] - for p in parsets: + for obsid, p in parsets.iteritems(): processType = p[PARSET_PREFIX + "Observation.processType"] - if p not in allowed: - raise KeyError("No SLURM job for predecessor. Expected job %s." % (obsid,jobName)) + jobName = p.slurmJobName() + + if jobName not in allowed: + raise KeyError("No SLURM job for predecessor %d (JobName '%s')." % (obsid, jobName)) names.append(p.slurmJobName()) return names - @classmethod def _getPredecessorParsets(self, parset): obsIDs = parset.predecessors() - logger.info("Obtaining predecessor parsets %s", predecessor_obsIDs) + logger.info("Obtaining predecessor parsets %s", obsIDs) preparsets = {} for obsid in obsIDs: - preparsets[p] = Parset(self.parset_rpc( OtdbID=obsid, timeout=10 )[0]) + preparsets[obsid] = Parset(self.parset_rpc( OtdbID=obsid, timeout=10 )[0]) return preparsets @@ -206,7 +209,7 @@ class PipelineStarter(OTDBBusListener): # Request the parset parset = Parset(self.parset_rpc( OtdbID=treeId, timeout=10 )[0]) - if not self.shouldHandle(parset): + if not self._shouldHandle(parset): return # Cancel corresponding SLURM job, causing any successors @@ -223,7 +226,7 @@ class PipelineStarter(OTDBBusListener): def _minStartTime(self, preparsets): result = None - for preparset in preparsets: + for preparset in preparsets.values(): processType = preparset[PARSET_PREFIX + "Observation.processType"] if processType == "Observation": @@ -241,7 +244,7 @@ class PipelineStarter(OTDBBusListener): # Request the parset parset = Parset(self.parset_rpc( OtdbID=treeId, timeout=10 )[0]) - if not self.shouldHandle(parset): + if not self._shouldHandle(parset): return """ @@ -292,10 +295,6 @@ class PipelineStarter(OTDBBusListener): if predecessor_jobs: sbatch_params.append("--dependency=%s" % (",".join(("afterok:%s" % x for x in predecessor_jobs)),)) - # Set OTDB status to QUEUED - logger.info("Setting status to QUEUED") - self.setStatus(treeId, "queued") - # Schedule runPipeline.sh logger.info("Scheduling SLURM job for runPipeline.sh") slurm_job_id = runSlurmCommand( @@ -322,7 +321,7 @@ class PipelineStarter(OTDBBusListener): "sbatch" " --job-name=%s-aborted" " --cpus-per=task=1 --ntasks=1" - " --dependency=afternotok:%d" + " --dependency=afternotok:%s" " --kill-on-invalid-dep=yes" " --requeue" " bash -c '%s'" % (parset.slurmJobName(), slurm_job_id, @@ -337,5 +336,9 @@ class PipelineStarter(OTDBBusListener): )) logger.info("Scheduled SLURM job %s" % (slurm_cancel_job_id,)) + # Set OTDB status to QUEUED + logger.info("Setting status to QUEUED") + self._setStatus(treeId, "queued") + logger.info("Pipeline processed.") diff --git a/MAC/Services/test/tPipelineStarter.py b/MAC/Services/test/tPipelineStarter.py index e45dc406fb5..334b159c868 100644 --- a/MAC/Services/test/tPipelineStarter.py +++ b/MAC/Services/test/tPipelineStarter.py @@ -5,12 +5,17 @@ import sys, os sys.path.insert(0, "{srcdir}/../src".format(**os.environ)) import lofar.mac.PipelineStarter as module +from lofar.sas.otdb.OTDBBusListener import OTDBBusListener +from lofar.messaging import ToBus, Service, EventMessage import subprocess import unittest +import uuid +import datetime +from threading import Condition, Lock import logging -logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +logging.basicConfig(stream=sys.stdout, level=logging.INFO) def setUpModule(): pass @@ -68,7 +73,7 @@ class TestSlurmJobInfo(unittest.TestCase): self.assertEqual(jobs["bar"]["JobName"], "bar") self.assertEqual(jobs["bar"]["JobId"], "121") -class TestPipelineStarter(unittest.TestCase): +class TestPipelineStarterClassMethods(unittest.TestCase): def test_shouldHandle(self): """ Test whether we filter the right OTDB trees. """ @@ -87,6 +92,141 @@ class TestPipelineStarter(unittest.TestCase): "ObsSW.Observation.Cluster.ProcessingCluster.clusterName": t["cluster"] } self.assertEqual(module.PipelineStarter._shouldHandle(parset), t["shouldHandle"]) +class TestPipelineStarter(unittest.TestCase): + def setUp(self): + # Catch SLURM calls + def _runSlurmCommand(cmdline, **kwargs): + print "SLURM call: %s" % cmdline + + return "" + + module.runSlurmCommand = _runSlurmCommand + + # Catch functions to prevent system calls + module.Parset.dockerTag = lambda self: "trunk" + module.getSlurmJobInfo = lambda: { + "1": { "JobName": "1" }, + "2": { "JobName": "2" }, + "3": { "JobName": "3" }, + } + + # Create a random bus + self.busname = "%s-%s" % (sys.argv[0], str(uuid.uuid4())[:8]) + self.bus = ToBus(self.busname, { "create": "always", "delete": "always", "node": { "type": "topic" } }) + self.bus.open() + + # Define the services we use + self.status_service = "%s/otdb.treestatus" % (self.busname,) + + # ================================ + # Setup mock parset service + # ================================ + + def TaskSpecificationService( OtdbID ): + print "***** TaskSpecificationService(%s) *****" % (OtdbID,) + + if OtdbID == 1: + predecessors = "[2,3]" + elif OtdbID == 2: + predecessors = "[3]" + elif OtdbID == 3: + predecessors = "[]" + else: + raise Exception("Invalid OtdbID: %s" % OtdbID) + + return { + "Version.number": "1", + module.PARSET_PREFIX + "Observation.ObsID": str(OtdbID), + module.PARSET_PREFIX + "Observation.Scheduler.predecessors": predecessors, + module.PARSET_PREFIX + "Observation.processType": "Pipeline", + module.PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", + } + + def StatusUpdateCmd( OtdbID, NewStatus ): + print "***** StatusUpdateCmd(%s,%s) *****" % (OtdbID, NewStatus) + + # Broadcast the state change + content = { "treeID" : OtdbID, "state" : NewStatus, "time_of_change" : datetime.datetime.utcnow() } + msg = EventMessage(context="otdb.treestatus", content=content) + self.bus.send(msg) + + self.parset_service = Service("TaskSpecification", TaskSpecificationService, busname=self.busname) + self.parset_service.start_listening() + + self.setstate_service = Service("StatusUpdateCmd", StatusUpdateCmd, busname=self.busname) + self.setstate_service.start_listening() + + # ================================ + # Setup listener to catch result + # of our service + # ================================ + + class Listener(OTDBBusListener): + def __init__(self, **kwargs): + super(Listener, self).__init__(**kwargs) + + self.messageReceived = False + self.lock = Lock() + self.cond = Condition(self.lock) + + def onObservationQueued(self, sasId, modificationTime): + self.messageReceived = True + + self.sasID = sasId + + # Release waiting parent + with self.lock: + self.cond.notify() + + def waitForMessage(self): + with self.lock: + self.cond.wait(5.0) + return self.messageReceived + + self.listener = Listener(busname=self.busname) + self.listener.start_listening() + + def tearDown(self): + self.listener.stop_listening() + self.setstate_service.stop_listening() + self.parset_service.stop_listening() + self.bus.close() + + # Undo our overrides + reload(module) + + def testNoPredecessors(self): + """ + Request the resources for a simulated obsid 3, with the following predecessor tree: + + 3 requires nothing + """ + with module.PipelineStarter(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: + # Send fake status update + ps._setStatus(3, "scheduled") + + # Wait for message to arrive + self.assertTrue(self.listener.waitForMessage()) + + # Verify message + self.assertEqual(self.listener.sasID, 3) + + def testPredecessors(self): + """ + Request the resources for a simulated obsid 3, with the following predecessor tree: + + 1 requires 2, 3 + """ + with module.PipelineStarter(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: + # Send fake status update + ps._setStatus(1, "scheduled") + + # Wait for message to arrive + self.assertTrue(self.listener.waitForMessage()) + + # Verify message + self.assertEqual(self.listener.sasID, 1) + def main(argv): unittest.main(verbosity=2) -- GitLab From 33acb0a75e56cadc42e4c5d7200088028133aea9 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 7 Mar 2016 21:04:53 +0000 Subject: [PATCH 034/933] Task #8437: Use python mock classes, and moved triggering functionality for OTDBBusListener to separate class --- MAC/Services/test/methodtrigger.py | 66 +++++++++++++ MAC/Services/test/tPipelineStarter.py | 135 +++++++++++++------------- 2 files changed, 131 insertions(+), 70 deletions(-) create mode 100644 MAC/Services/test/methodtrigger.py diff --git a/MAC/Services/test/methodtrigger.py b/MAC/Services/test/methodtrigger.py new file mode 100644 index 00000000000..4a036e37ef3 --- /dev/null +++ b/MAC/Services/test/methodtrigger.py @@ -0,0 +1,66 @@ +from threading import Lock, Condition + +class MethodTrigger: + """ + Set a flag when a specific method is called, possibly asynchronously. Caller can wait on this flag. + + Example: + + class Foo(object): + def bar(self): + pass + + foo = Foo() + trigger = MethodTrigger(foo, "bar") + + if trigger.wait(): # Waits for 10 seconds for foo.bar() to get called + print "foo.bar() got called" + else + # This will happen, as foo.bar() wasn't called + print "foo.bar() did not get called" + + Calls that were made before the trigger has been installed will not get recorded. + """ + + def __init__(self, obj, method): + assert isinstance(obj, object), "Object %s does not derive from object." % (obj,) + + self.obj = obj + self.method = method + self.old_func = obj.__getattribute__(method) + + self.called = False + self.args = [] + self.kwargs = {} + + self.lock = Lock() + self.cond = Condition(self.lock) + + # Patch the target method + obj.__setattr__(method, self.trigger) + + def trigger(self, *args, **kwargs): + # Save the call parameters + self.args = args + self.kwargs = kwargs + + # Call the original method + self.old_func(*args, **kwargs) + + # Restore the original method + self.obj.__setattr__(self.method, self.old_func) + + # Release waiting thread + with self.lock: + self.called = True + self.cond.notify() + + def wait(self, timeout=10.0): + # Wait for method to get called + with self.lock: + if self.called: + return True + + self.cond.wait(timeout) + + return self.called diff --git a/MAC/Services/test/tPipelineStarter.py b/MAC/Services/test/tPipelineStarter.py index 334b159c868..943e02c978c 100644 --- a/MAC/Services/test/tPipelineStarter.py +++ b/MAC/Services/test/tPipelineStarter.py @@ -1,22 +1,30 @@ #!/usr/bin/env python # Be able to find service python file -import sys, os -sys.path.insert(0, "{srcdir}/../src".format(**os.environ)) +import sys + import lofar.mac.PipelineStarter as module from lofar.sas.otdb.OTDBBusListener import OTDBBusListener -from lofar.messaging import ToBus, Service, EventMessage -import subprocess +from lofar.messaging import ToBus, Service, EventMessagea +from methodtrigger import MethodTrigger + +import subprocess import unittest import uuid import datetime -from threading import Condition, Lock import logging logging.basicConfig(stream=sys.stdout, level=logging.INFO) +try: + from mock import patch +except ImportError: + print "Cannot run test without python MagicMock" + print "Call 'pip install mock' / 'apt-get install python-mock'" + exit(3) + def setUpModule(): pass @@ -50,28 +58,32 @@ class TestRunCommand(unittest.TestCase): class TestSlurmJobInfo(unittest.TestCase): def test_no_jobs(self): """ Test 'scontrol show job' output if there are no jobs. """ - module.runSlurmCommand = lambda _: """No jobs in the system""" - self.assertEqual(module.getSlurmJobInfo(), {}) + with patch('lofar.mac.PipelineStarter.runSlurmCommand') as MockRunSlurmCommand: + MockRunSlurmCommand.return_value = """No jobs in the system""" + + self.assertEqual(module.getSlurmJobInfo(), {}) def test_one_job(self): """ Test 'scontrol show job' output for a single job. """ - module.runSlurmCommand = lambda _: """JobId=119 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901736 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:07 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:05:52 EligibleTime=2016-03-04T12:05:52 StartTime=2016-03-04T12:05:52 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7040 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" + with patch('lofar.mac.PipelineStarter.runSlurmCommand') as MockRunSlurmCommand: + MockRunSlurmCommand.return_value = """JobId=119 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901736 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:07 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:05:52 EligibleTime=2016-03-04T12:05:52 StartTime=2016-03-04T12:05:52 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7040 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" - jobs = module.getSlurmJobInfo() - self.assertEqual(jobs["foo"]["JobName"], "foo") - self.assertEqual(jobs["foo"]["JobId"], "119") + jobs = module.getSlurmJobInfo() + self.assertEqual(jobs["foo"]["JobName"], "foo") + self.assertEqual(jobs["foo"]["JobId"], "119") def test_two_jobs(self): """ Test 'scontrol show job' output for multiple jobs. """ - module.runSlurmCommand = lambda _: """JobId=120 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901735 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:17 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:53 EligibleTime=2016-03-04T12:09:53 StartTime=2016-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0 + with patch('lofar.mac.PipelineStarter.runSlurmCommand') as MockRunSlurmCommand: + MockRunSlurmCommand.return_value = """JobId=120 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901735 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:17 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:53 EligibleTime=2016-03-04T12:09:53 StartTime=2016-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0 JobId=121 JobName=bar UserId=mol(7261) GroupId=mol(7261) Priority=4294901734 Nice=0 Account=(null) QOS=(null) JobState=PENDING Reason=Resources Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:00 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:59 EligibleTime=2016-03-04T12:09:59 StartTime=2017-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=(null) NumNodes=2-2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=0 MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" - jobs = module.getSlurmJobInfo() - self.assertEqual(jobs["foo"]["JobName"], "foo") - self.assertEqual(jobs["foo"]["JobId"], "120") + jobs = module.getSlurmJobInfo() + self.assertEqual(jobs["foo"]["JobName"], "foo") + self.assertEqual(jobs["foo"]["JobId"], "120") - self.assertEqual(jobs["bar"]["JobName"], "bar") - self.assertEqual(jobs["bar"]["JobId"], "121") + self.assertEqual(jobs["bar"]["JobName"], "bar") + self.assertEqual(jobs["bar"]["JobId"], "121") class TestPipelineStarterClassMethods(unittest.TestCase): def test_shouldHandle(self): @@ -94,29 +106,34 @@ class TestPipelineStarterClassMethods(unittest.TestCase): class TestPipelineStarter(unittest.TestCase): def setUp(self): - # Catch SLURM calls + # Create a random bus + self.busname = "%s-%s" % (sys.argv[0], str(uuid.uuid4())[:8]) + self.bus = ToBus(self.busname, { "create": "always", "delete": "always", "node": { "type": "topic" } }) + self.bus.open() + self.addCleanup(self.bus.close) + + # Patch SLURM def _runSlurmCommand(cmdline, **kwargs): print "SLURM call: %s" % cmdline return "" - module.runSlurmCommand = _runSlurmCommand + patcher = patch('lofar.mac.PipelineStarter.runSlurmCommand') + patcher.start().side_effect = _runSlurmCommand + self.addCleanup(patcher.stop) - # Catch functions to prevent system calls - module.Parset.dockerTag = lambda self: "trunk" - module.getSlurmJobInfo = lambda: { + patcher = patch('lofar.mac.PipelineStarter.getSlurmJobInfo') + patcher.start().return_value = { "1": { "JobName": "1" }, "2": { "JobName": "2" }, "3": { "JobName": "3" }, } - - # Create a random bus - self.busname = "%s-%s" % (sys.argv[0], str(uuid.uuid4())[:8]) - self.bus = ToBus(self.busname, { "create": "always", "delete": "always", "node": { "type": "topic" } }) - self.bus.open() + self.addCleanup(patcher.stop) - # Define the services we use - self.status_service = "%s/otdb.treestatus" % (self.busname,) + # Catch functions to prevent running executables + patcher = patch('lofar.mac.PipelineStarter.Parset.dockerTag') + patcher.start().return_value = "trunk" + self.addCleanup(patcher.stop) # ================================ # Setup mock parset service @@ -142,6 +159,14 @@ class TestPipelineStarter(unittest.TestCase): module.PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", } + service = Service("TaskSpecification", TaskSpecificationService, busname=self.busname) + service.start_listening() + self.addCleanup(service.stop_listening) + + # ================================ + # Setup mock status update service + # ================================ + def StatusUpdateCmd( OtdbID, NewStatus ): print "***** StatusUpdateCmd(%s,%s) *****" % (OtdbID, NewStatus) @@ -150,50 +175,20 @@ class TestPipelineStarter(unittest.TestCase): msg = EventMessage(context="otdb.treestatus", content=content) self.bus.send(msg) - self.parset_service = Service("TaskSpecification", TaskSpecificationService, busname=self.busname) - self.parset_service.start_listening() - - self.setstate_service = Service("StatusUpdateCmd", StatusUpdateCmd, busname=self.busname) - self.setstate_service.start_listening() + service = Service("StatusUpdateCmd", StatusUpdateCmd, busname=self.busname) + service.start_listening() + self.addCleanup(service.stop_listening) # ================================ # Setup listener to catch result # of our service # ================================ - class Listener(OTDBBusListener): - def __init__(self, **kwargs): - super(Listener, self).__init__(**kwargs) - - self.messageReceived = False - self.lock = Lock() - self.cond = Condition(self.lock) - - def onObservationQueued(self, sasId, modificationTime): - self.messageReceived = True - - self.sasID = sasId - - # Release waiting parent - with self.lock: - self.cond.notify() - - def waitForMessage(self): - with self.lock: - self.cond.wait(5.0) - return self.messageReceived - - self.listener = Listener(busname=self.busname) - self.listener.start_listening() - - def tearDown(self): - self.listener.stop_listening() - self.setstate_service.stop_listening() - self.parset_service.stop_listening() - self.bus.close() + listener = OTDBBusListener(busname=self.busname) + listener.start_listening() + self.addCleanup(listener.stop_listening) - # Undo our overrides - reload(module) + self.trigger = MethodTrigger(listener, "onObservationQueued") def testNoPredecessors(self): """ @@ -206,10 +201,10 @@ class TestPipelineStarter(unittest.TestCase): ps._setStatus(3, "scheduled") # Wait for message to arrive - self.assertTrue(self.listener.waitForMessage()) + self.assertTrue(self.trigger.wait()) # Verify message - self.assertEqual(self.listener.sasID, 3) + self.assertEqual(self.trigger.args[0], 3) # treeId def testPredecessors(self): """ @@ -222,10 +217,10 @@ class TestPipelineStarter(unittest.TestCase): ps._setStatus(1, "scheduled") # Wait for message to arrive - self.assertTrue(self.listener.waitForMessage()) + self.assertTrue(self.trigger.wait()) # Verify message - self.assertEqual(self.listener.sasID, 1) + self.assertEqual(self.trigger.args[0], 1) # treeId def main(argv): unittest.main(verbosity=2) -- GitLab From 72fb6861f67c7116059b9f7880f8915c12cb7308 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 7 Mar 2016 21:06:08 +0000 Subject: [PATCH 035/933] Task #8437: Minor cleanup, fixed typo --- MAC/Services/test/methodtrigger.py | 2 ++ MAC/Services/test/tPipelineStarter.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/MAC/Services/test/methodtrigger.py b/MAC/Services/test/methodtrigger.py index 4a036e37ef3..800d8d11ea1 100644 --- a/MAC/Services/test/methodtrigger.py +++ b/MAC/Services/test/methodtrigger.py @@ -1,5 +1,7 @@ from threading import Lock, Condition +__all__ = ["MethodTrigger"] + class MethodTrigger: """ Set a flag when a specific method is called, possibly asynchronously. Caller can wait on this flag. diff --git a/MAC/Services/test/tPipelineStarter.py b/MAC/Services/test/tPipelineStarter.py index 943e02c978c..ad4b7834ffe 100644 --- a/MAC/Services/test/tPipelineStarter.py +++ b/MAC/Services/test/tPipelineStarter.py @@ -6,7 +6,7 @@ import sys import lofar.mac.PipelineStarter as module from lofar.sas.otdb.OTDBBusListener import OTDBBusListener -from lofar.messaging import ToBus, Service, EventMessagea +from lofar.messaging import ToBus, Service, EventMessage from methodtrigger import MethodTrigger -- GitLab From 51ca073a40695a909ee5472ce4fa096104003280 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 7 Mar 2016 21:09:04 +0000 Subject: [PATCH 036/933] Task #8437: Removed import layer "module." --- MAC/Services/test/tPipelineStarter.py | 33 +++++++++++++-------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/MAC/Services/test/tPipelineStarter.py b/MAC/Services/test/tPipelineStarter.py index ad4b7834ffe..f3dc432ed29 100644 --- a/MAC/Services/test/tPipelineStarter.py +++ b/MAC/Services/test/tPipelineStarter.py @@ -3,8 +3,7 @@ # Be able to find service python file import sys - -import lofar.mac.PipelineStarter as module +from lofar.mac.PipelineStarter import * from lofar.sas.otdb.OTDBBusListener import OTDBBusListener from lofar.messaging import ToBus, Service, EventMessage @@ -34,25 +33,25 @@ def tearDownModule(): class TestRunCommand(unittest.TestCase): def test_basic(self): """ Test whether we can run a trivial command. """ - module.runCommand("true") + runCommand("true") def test_invalid_command(self): """ Test whether an invalid command produces an error. """ with self.assertRaises(subprocess.CalledProcessError): - output = module.runCommand(".") + output = runCommand(".") def test_shell(self): """ Test whether the command is parsed by a shell. """ - module.runCommand("true --version") + runCommand("true --version") def test_output(self): """ Test whether we catch the command output correctly. """ - output = module.runCommand("echo yes") + output = runCommand("echo yes") self.assertEqual(output, "yes") def test_input(self): """ Test whether we can provide input. """ - output = module.runCommand("cat -", "yes") + output = runCommand("cat -", "yes") self.assertEqual(output, "yes") class TestSlurmJobInfo(unittest.TestCase): @@ -61,14 +60,14 @@ class TestSlurmJobInfo(unittest.TestCase): with patch('lofar.mac.PipelineStarter.runSlurmCommand') as MockRunSlurmCommand: MockRunSlurmCommand.return_value = """No jobs in the system""" - self.assertEqual(module.getSlurmJobInfo(), {}) + self.assertEqual(getSlurmJobInfo(), {}) def test_one_job(self): """ Test 'scontrol show job' output for a single job. """ with patch('lofar.mac.PipelineStarter.runSlurmCommand') as MockRunSlurmCommand: MockRunSlurmCommand.return_value = """JobId=119 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901736 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:07 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:05:52 EligibleTime=2016-03-04T12:05:52 StartTime=2016-03-04T12:05:52 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7040 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" - jobs = module.getSlurmJobInfo() + jobs = getSlurmJobInfo() self.assertEqual(jobs["foo"]["JobName"], "foo") self.assertEqual(jobs["foo"]["JobId"], "119") @@ -78,7 +77,7 @@ class TestSlurmJobInfo(unittest.TestCase): MockRunSlurmCommand.return_value = """JobId=120 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901735 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:17 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:53 EligibleTime=2016-03-04T12:09:53 StartTime=2016-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0 JobId=121 JobName=bar UserId=mol(7261) GroupId=mol(7261) Priority=4294901734 Nice=0 Account=(null) QOS=(null) JobState=PENDING Reason=Resources Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:00 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:59 EligibleTime=2016-03-04T12:09:59 StartTime=2017-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=(null) NumNodes=2-2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=0 MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" - jobs = module.getSlurmJobInfo() + jobs = getSlurmJobInfo() self.assertEqual(jobs["foo"]["JobName"], "foo") self.assertEqual(jobs["foo"]["JobId"], "120") @@ -102,7 +101,7 @@ class TestPipelineStarterClassMethods(unittest.TestCase): for t in trials: parset = { "ObsSW.Observation.processType": t["type"], "ObsSW.Observation.Cluster.ProcessingCluster.clusterName": t["cluster"] } - self.assertEqual(module.PipelineStarter._shouldHandle(parset), t["shouldHandle"]) + self.assertEqual(PipelineStarter._shouldHandle(parset), t["shouldHandle"]) class TestPipelineStarter(unittest.TestCase): def setUp(self): @@ -153,10 +152,10 @@ class TestPipelineStarter(unittest.TestCase): return { "Version.number": "1", - module.PARSET_PREFIX + "Observation.ObsID": str(OtdbID), - module.PARSET_PREFIX + "Observation.Scheduler.predecessors": predecessors, - module.PARSET_PREFIX + "Observation.processType": "Pipeline", - module.PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", + PARSET_PREFIX + "Observation.ObsID": str(OtdbID), + PARSET_PREFIX + "Observation.Scheduler.predecessors": predecessors, + PARSET_PREFIX + "Observation.processType": "Pipeline", + PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", } service = Service("TaskSpecification", TaskSpecificationService, busname=self.busname) @@ -196,7 +195,7 @@ class TestPipelineStarter(unittest.TestCase): 3 requires nothing """ - with module.PipelineStarter(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: + with PipelineStarter(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: # Send fake status update ps._setStatus(3, "scheduled") @@ -212,7 +211,7 @@ class TestPipelineStarter(unittest.TestCase): 1 requires 2, 3 """ - with module.PipelineStarter(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: + with PipelineStarter(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: # Send fake status update ps._setStatus(1, "scheduled") -- GitLab From a259425064c5e8a138191eeecdb13263fed058ef Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 7 Mar 2016 21:12:06 +0000 Subject: [PATCH 037/933] Task #8437: Added task for PipelineStarter._setStatus --- MAC/Services/test/tPipelineStarter.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAC/Services/test/tPipelineStarter.py b/MAC/Services/test/tPipelineStarter.py index f3dc432ed29..1abc758c91c 100644 --- a/MAC/Services/test/tPipelineStarter.py +++ b/MAC/Services/test/tPipelineStarter.py @@ -189,6 +189,14 @@ class TestPipelineStarter(unittest.TestCase): self.trigger = MethodTrigger(listener, "onObservationQueued") + def test_setStatus(self): + with PipelineStarter(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: + ps._setStatus(12345, "queued") + + # Wait for the staatus to propagate + self.assertTrue(self.trigger.wait()) + self.assertEqual(self.trigger.args[0], 12345) + def testNoPredecessors(self): """ Request the resources for a simulated obsid 3, with the following predecessor tree: -- GitLab From 5c89829630bf7afcf020b9a0f05b951fe12054e8 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 7 Mar 2016 22:16:44 +0000 Subject: [PATCH 038/933] Task #8437: Wrap and mock Slurm better --- MAC/Services/src/PipelineStarter.py | 166 ++++++++++++++------------ MAC/Services/test/tPipelineStarter.py | 81 +++++++++---- 2 files changed, 148 insertions(+), 99 deletions(-) diff --git a/MAC/Services/src/PipelineStarter.py b/MAC/Services/src/PipelineStarter.py index 5aaec1f9774..f780c3088a9 100755 --- a/MAC/Services/src/PipelineStarter.py +++ b/MAC/Services/src/PipelineStarter.py @@ -87,10 +87,6 @@ def runCommand(cmdline, input=None): # Return output return stdout.strip() -def runSlurmCommand(cmdline, headnode="head01.cep4"): - cmdline = "ssh %s %s" % (headnode, cmdline) - runCommand(cmdline) - """ Prefix that is common to all parset keys, depending on the exact source. """ PARSET_PREFIX="ObsSW." @@ -111,34 +107,56 @@ class Parset(dict): return runCommand("docker-template", "${LOFAR_TAG}") def slurmJobName(self): - return self[PARSET_PREFIX + "Observation.ObsID"] + return str(self.treeId()) + + def treeId(self): + return int(self[PARSET_PREFIX + "Observation.ObsID"]) + +class Slurm(object): + def __init__(self, headnode="head01.cep4"): + self.headnode = headnode + + def _runCommand(self, cmdline): + cmdline = "ssh %s %s" % (self.headnode, cmdline) + runCommand(cmdline) + + def schedule(self, jobName, cmdline, sbatch_params=None): + if sbatch_params is None: + sbatch_params = [] -def getSlurmJobInfo(): - stdout = runSlurmCommand("scontrol show job --oneliner") + self._runCommand("sbatch --job-name=%s %s bash -c '%s'" % (jobName, " ".join(sbatch_params), cmdline)) - if stdout == "No jobs in the system": - return {} + def cancel(self, jobName): + stdout = self._runCommand("scancel --jobname %s" % (jobName,)) - jobs = {} - for l in stdout.split("\n"): - # One line is one job - job_properties = {} + logger.debug("scancel output: %s" % (output,)) - # Information is in k=v pairs - for i in l.split(): - k,v = i.split("=", 1) - job_properties[k] = v + def jobs(self): + stdout = self._runCommand("scontrol show job --oneliner") - if "JobName" not in job_properties: - logger.warning("Could not find job name in line: %s" % (l,)) - continue + if stdout == "No jobs in the system": + return {} - name = job_properties["JobName"] - if name in jobs: - logger.warning("Duplicate job name: %s" % (name,)) - jobs[name] = job_properties + jobs = {} + for l in stdout.split("\n"): + # One line is one job + job_properties = {} - return jobs + # Information is in k=v pairs + for i in l.split(): + k,v = i.split("=", 1) + job_properties[k] = v + + if "JobName" not in job_properties: + logger.warning("Could not find job name in line: %s" % (l,)) + continue + + name = job_properties["JobName"] + if name in jobs: + logger.warning("Duplicate job name: %s" % (name,)) + jobs[name] = job_properties + + return jobs class PipelineStarter(OTDBBusListener): def __init__(self, otdb_busname=None, setStatus_busname=None, **kwargs): @@ -147,6 +165,8 @@ class PipelineStarter(OTDBBusListener): self.parset_rpc = RPC(service="TaskSpecification", busname=otdb_busname, ForwardExceptions=True) self.setStatus_busname = setStatus_busname if setStatus_busname else otdb_busname + self.slurm = Slurm() + def _setStatus(self, obsid, status): try: with RPC("StatusUpdateCmd", busname=self.setStatus_busname, timeout=10, ForwardExceptions=True) as status_rpc: @@ -181,27 +201,22 @@ class PipelineStarter(OTDBBusListener): def _slurmJobNames(self, parsets, allowed): names = [] - for obsid, p in parsets.iteritems(): - processType = p[PARSET_PREFIX + "Observation.processType"] + for p in parsets: jobName = p.slurmJobName() if jobName not in allowed: - raise KeyError("No SLURM job for predecessor %d (JobName '%s')." % (obsid, jobName)) + raise KeyError("No SLURM job for predecessor %d (JobName '%s')." % (p.treeId(), jobName)) - names.append(p.slurmJobName()) + names.append(jobName) return names def _getPredecessorParsets(self, parset): - obsIDs = parset.predecessors() - - logger.info("Obtaining predecessor parsets %s", obsIDs) + treeIds = parset.predecessors() - preparsets = {} - for obsid in obsIDs: - preparsets[obsid] = Parset(self.parset_rpc( OtdbID=obsid, timeout=10 )[0]) + logger.info("Obtaining predecessor parsets %s", treeIds) - return preparsets + return [Parset(self.parset_rpc( OtdbID=treeId, timeout=10 )[0]) for treeId in treeIds] def onObservationAborted(self, treeId, modificationTime): logger.info("***** STOP Tree ID %s *****", treeId) @@ -214,7 +229,7 @@ class PipelineStarter(OTDBBusListener): # Cancel corresponding SLURM job, causing any successors # to be cancelled as well. - stdout = self.runSlurmCommand("scancel --jobname %s" % (self._slurmJobName(parset),)) + self.slurm.cancel(parset.slurmJobName()) """ More statusses we want to abort on. @@ -223,18 +238,17 @@ class PipelineStarter(OTDBBusListener): onObservationHold = onObservationAborted @classmethod - def _minStartTime(self, preparsets): + def _minStartTime(self, preparsets, margin=datetime.timedelta(0, 60, 0)): result = None - for preparset in preparsets.values(): - processType = preparset[PARSET_PREFIX + "Observation.processType"] + for p in preparsets: + processType = p[PARSET_PREFIX + "Observation.processType"] - if processType == "Observation": - # If we depend on an observation, start 1 minute after it - obs_endtime = datetime.datetime.strptime(preparset[PARSET_PREFIX + "Observation.stopTime"], "%Y-%m-%d %H:%M:%S") - min_starttime = obs_endtime + datetime.timedelta(0, 60, 0) + # If we depend on an observation, start 1 minute after it + obs_endtime = datetime.datetime.strptime(p[PARSET_PREFIX + "Observation.stopTime"], "%Y-%m-%d %H:%M:%S") + min_starttime = obs_endtime + margin - result = max(result, min_starttime) if result else min_starttime + result = max(result, min_starttime) if result else min_starttime return result @@ -256,7 +270,7 @@ class PipelineStarter(OTDBBusListener): # Collect SLURM job information logger.info("Obtaining SLURM job list") - slurm_jobs = getSlurmJobInfo() + slurm_jobs = self.slurm.jobs() """ Schedule "docker-runPipeline.sh", which will fetch the parset and run the pipeline within @@ -264,8 +278,7 @@ class PipelineStarter(OTDBBusListener): """ # Determine SLURM parameters - sbatch_params = ["--job-name=%s" % (parset.slurmJobName(),), - + sbatch_params = [ # Only run job if all nodes are ready "--wait-all-nodes=1", @@ -284,56 +297,57 @@ class PipelineStarter(OTDBBusListener): # Define better places to write the output os.path.expandvars("--error=$LOFARROOT/var/log/docker-startPython-%s.stderr" % (treeId,)), os.path.expandvars("--output=$LOFARROOT/var/log/docker-startPython-%s.log" % (treeId,)), - ] - min_starttime = self._minStartTime(preparsets) + min_starttime = self._minStartTime([x for x in preparsets if x[PARSET_PREFIX + "Observation.processType"] == "Observation"]) if min_starttime: sbatch_params.append("--begin=%s" % (min_starttime.strftime("%FT%T"),)) - predecessor_jobs = self._slurmJobNames(preparsets, slurm_jobs.keys()) + predecessor_jobs = self._slurmJobNames([x for x in preparsets if x[PARSET_PREFIX + "Observation.processType"] != "Observation"], slurm_jobs.keys()) if predecessor_jobs: sbatch_params.append("--dependency=%s" % (",".join(("afterok:%s" % x for x in predecessor_jobs)),)) # Schedule runPipeline.sh logger.info("Scheduling SLURM job for runPipeline.sh") - slurm_job_id = runSlurmCommand( - "sbatch %s bash -c '%s'" % (" ".join(sbatch_params), + slurm_job_id = self.slurm.schedule(parset.slurmJobName(), - # Supply script to run on-the-fly to reduce dependencies on - # compute nodes. "docker run --rm lofar-pipeline:{tag}" - " --net=host" - " -v /data:/data" - " -e LUSER=$UID" - " -v $HOME/.ssh:/home/lofar/.ssh:ro" - " -e SLURM_JOB_ID=$SLURM_JOB_ID" - " runPipeline.sh {obsid}".format( + " --net=host" + " -v /data:/data" + " -e LUSER=$UID" + " -v $HOME/.ssh:/home/lofar/.ssh:ro" + " -e SLURM_JOB_ID=$SLURM_JOB_ID" + " runPipeline.sh {obsid}" + .format( obsid = treeId, tag = parset.dockerTag(), - ) - )) + ), + + sbatch_params=sbatch_params + ) logger.info("Scheduled SLURM job %s" % (slurm_job_id,)) # Schedule pipelineAborted.sh logger.info("Scheduling SLURM job for pipelineAborted.sh") - slurm_cancel_job_id = runSlurmCommand( - "sbatch" - " --job-name=%s-aborted" - " --cpus-per=task=1 --ntasks=1" - " --dependency=afternotok:%s" - " --kill-on-invalid-dep=yes" - " --requeue" - " bash -c '%s'" % (parset.slurmJobName(), slurm_job_id, + slurm_cancel_job_id = self.slurm.schedule("%s-aborted" % parset.slurmJobName(), "docker run --rm lofar-pipeline:{tag}" - " --net=host" - " -e LUSER=$UID" - " pipelineAborted.sh {obsid}".format( + " --net=host" + " -e LUSER=$UID" + " pipelineAborted.sh {obsid}" + .format( obsid = treeId, tag = parset.dockerTag(), - ) - )) + ), + + sbatch_params=[ + "--cpus-per=task=1", + "--ntasks=1" + "--dependency=afternotok:%s" % slurm_job_id, + "--kill-on-invalid-dep=yes", + "--requeue", + ] + ) logger.info("Scheduled SLURM job %s" % (slurm_cancel_job_id,)) # Set OTDB status to QUEUED diff --git a/MAC/Services/test/tPipelineStarter.py b/MAC/Services/test/tPipelineStarter.py index 1abc758c91c..127679521f5 100644 --- a/MAC/Services/test/tPipelineStarter.py +++ b/MAC/Services/test/tPipelineStarter.py @@ -54,30 +54,30 @@ class TestRunCommand(unittest.TestCase): output = runCommand("cat -", "yes") self.assertEqual(output, "yes") -class TestSlurmJobInfo(unittest.TestCase): +class TestSlurmJobs(unittest.TestCase): def test_no_jobs(self): """ Test 'scontrol show job' output if there are no jobs. """ - with patch('lofar.mac.PipelineStarter.runSlurmCommand') as MockRunSlurmCommand: + with patch('lofar.mac.PipelineStarter.Slurm._runCommand') as MockRunSlurmCommand: MockRunSlurmCommand.return_value = """No jobs in the system""" - self.assertEqual(getSlurmJobInfo(), {}) + self.assertEqual(Slurm().jobs(), {}) def test_one_job(self): """ Test 'scontrol show job' output for a single job. """ - with patch('lofar.mac.PipelineStarter.runSlurmCommand') as MockRunSlurmCommand: + with patch('lofar.mac.PipelineStarter.Slurm._runCommand') as MockRunSlurmCommand: MockRunSlurmCommand.return_value = """JobId=119 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901736 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:07 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:05:52 EligibleTime=2016-03-04T12:05:52 StartTime=2016-03-04T12:05:52 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7040 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" - jobs = getSlurmJobInfo() + jobs = Slurm().jobs() self.assertEqual(jobs["foo"]["JobName"], "foo") self.assertEqual(jobs["foo"]["JobId"], "119") def test_two_jobs(self): """ Test 'scontrol show job' output for multiple jobs. """ - with patch('lofar.mac.PipelineStarter.runSlurmCommand') as MockRunSlurmCommand: + with patch('lofar.mac.PipelineStarter.Slurm._runCommand') as MockRunSlurmCommand: MockRunSlurmCommand.return_value = """JobId=120 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901735 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:17 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:53 EligibleTime=2016-03-04T12:09:53 StartTime=2016-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0 JobId=121 JobName=bar UserId=mol(7261) GroupId=mol(7261) Priority=4294901734 Nice=0 Account=(null) QOS=(null) JobState=PENDING Reason=Resources Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:00 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:59 EligibleTime=2016-03-04T12:09:59 StartTime=2017-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=(null) NumNodes=2-2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=0 MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" - jobs = getSlurmJobInfo() + jobs = Slurm().jobs() self.assertEqual(jobs["foo"]["JobName"], "foo") self.assertEqual(jobs["foo"]["JobId"], "120") @@ -112,21 +112,28 @@ class TestPipelineStarter(unittest.TestCase): self.addCleanup(self.bus.close) # Patch SLURM - def _runSlurmCommand(cmdline, **kwargs): - print "SLURM call: %s" % cmdline + class MockSlurm(object): + def __init__(self, *args, **kwargs): + self.scheduled_jobs = {} - return "" + def schedule(self, jobName, *args, **kwargs): + print "Schedule SLURM job '%s': %s, %s" % (jobName, args, kwargs) - patcher = patch('lofar.mac.PipelineStarter.runSlurmCommand') - patcher.start().side_effect = _runSlurmCommand - self.addCleanup(patcher.stop) + self.scheduled_jobs[jobName] = (args, kwargs) + + # Return job ID + return "42" - patcher = patch('lofar.mac.PipelineStarter.getSlurmJobInfo') - patcher.start().return_value = { - "1": { "JobName": "1" }, - "2": { "JobName": "2" }, - "3": { "JobName": "3" }, - } + def jobs(self): + return { + "1": { "JobName": "1" }, + "2": { "JobName": "2" }, + "3": { "JobName": "3" }, + # "4" is an observation, so no SLURM job + } + + patcher = patch('lofar.mac.PipelineStarter.Slurm') + patcher.start().side_effect = MockSlurm self.addCleanup(patcher.stop) # Catch functions to prevent running executables @@ -142,11 +149,20 @@ class TestPipelineStarter(unittest.TestCase): print "***** TaskSpecificationService(%s) *****" % (OtdbID,) if OtdbID == 1: - predecessors = "[2,3]" + predecessors = "[2,3,4]" elif OtdbID == 2: predecessors = "[3]" elif OtdbID == 3: predecessors = "[]" + elif OtdbID == 4: + return { + "Version.number": "1", + PARSET_PREFIX + "Observation.ObsID": str(OtdbID), + PARSET_PREFIX + "Observation.Scheduler.predecessors": "[]", + PARSET_PREFIX + "Observation.processType": "Observation", + PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", + PARSET_PREFIX + "Observation.stopTime": "2016-01-01 01:00:00", + } else: raise Exception("Invalid OtdbID: %s" % OtdbID) @@ -199,7 +215,7 @@ class TestPipelineStarter(unittest.TestCase): def testNoPredecessors(self): """ - Request the resources for a simulated obsid 3, with the following predecessor tree: + Request to start a simulated obsid 3, with the following predecessor tree: 3 requires nothing """ @@ -213,11 +229,17 @@ class TestPipelineStarter(unittest.TestCase): # Verify message self.assertEqual(self.trigger.args[0], 3) # treeId + # Check if job was scheduled + self.assertIn("3", ps.slurm.scheduled_jobs) + self.assertIn("3-aborted", ps.slurm.scheduled_jobs) + def testPredecessors(self): """ - Request the resources for a simulated obsid 3, with the following predecessor tree: + Request to start a simulated obsid 1, with the following predecessor tree: - 1 requires 2, 3 + 1 requires 2, 3, 4 + 2 requires 3 + 4 is an observation """ with PipelineStarter(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: # Send fake status update @@ -229,6 +251,19 @@ class TestPipelineStarter(unittest.TestCase): # Verify message self.assertEqual(self.trigger.args[0], 1) # treeId + # Check if job was scheduled + self.assertIn("1", ps.slurm.scheduled_jobs) + self.assertIn("1-aborted", ps.slurm.scheduled_jobs) + + # Earliest start of this job > stop time of observation + for p in ps.slurm.scheduled_jobs["1"][1]["sbatch_params"]: + if p.startswith("--begin="): + begin = datetime.datetime.strptime(p, "--begin=%Y-%m-%dT%H:%M:%S") + self.assertGreater(begin, datetime.datetime(2016, 1, 1, 1, 0, 0)) + break + else: + self.assertTrue(False, "--begin parameter not given to SLURM job") + def main(argv): unittest.main(verbosity=2) -- GitLab From 739aa1d7b0dba611e7abf66e55cc97c01186682b Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 8 Mar 2016 14:11:46 +0000 Subject: [PATCH 039/933] Task #8437: Added MethodTrigger class to test function calls in asynchronous objects --- .gitattributes | 1 + LCS/PyCommon/CMakeLists.txt | 1 + LCS/PyCommon/methodtrigger.py | 68 +++++++++++++++ LCS/PyCommon/test/CMakeLists.txt | 1 + LCS/PyCommon/test/t_methodtrigger.py | 124 +++++++++++++++++++++++++++ LCS/PyCommon/test/t_methodtrigger.sh | 2 + 6 files changed, 197 insertions(+) create mode 100644 LCS/PyCommon/methodtrigger.py create mode 100644 LCS/PyCommon/test/t_methodtrigger.py create mode 100755 LCS/PyCommon/test/t_methodtrigger.sh diff --git a/.gitattributes b/.gitattributes index a3551495ed8..5cacfbd2c45 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2668,6 +2668,7 @@ LCS/PyCommon/postgres.py -text LCS/PyCommon/test/python-coverage.sh eol=lf LCS/PyCommon/test/t_dbcredentials.run eol=lf LCS/PyCommon/test/t_dbcredentials.sh eol=lf +LCS/PyCommon/test/t_methodtrigger.sh eol=lf LCS/PyCommon/util.py -text LCS/Tools/src/checkcomp.py -text LCS/Tools/src/countalllines -text diff --git a/LCS/PyCommon/CMakeLists.txt b/LCS/PyCommon/CMakeLists.txt index 42cf8512e89..6fc29b3e2de 100644 --- a/LCS/PyCommon/CMakeLists.txt +++ b/LCS/PyCommon/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory(test) set(_py_files dbcredentials.py factory.py + methodtrigger.py util.py postgres.py datetimeutils.py) diff --git a/LCS/PyCommon/methodtrigger.py b/LCS/PyCommon/methodtrigger.py new file mode 100644 index 00000000000..800d8d11ea1 --- /dev/null +++ b/LCS/PyCommon/methodtrigger.py @@ -0,0 +1,68 @@ +from threading import Lock, Condition + +__all__ = ["MethodTrigger"] + +class MethodTrigger: + """ + Set a flag when a specific method is called, possibly asynchronously. Caller can wait on this flag. + + Example: + + class Foo(object): + def bar(self): + pass + + foo = Foo() + trigger = MethodTrigger(foo, "bar") + + if trigger.wait(): # Waits for 10 seconds for foo.bar() to get called + print "foo.bar() got called" + else + # This will happen, as foo.bar() wasn't called + print "foo.bar() did not get called" + + Calls that were made before the trigger has been installed will not get recorded. + """ + + def __init__(self, obj, method): + assert isinstance(obj, object), "Object %s does not derive from object." % (obj,) + + self.obj = obj + self.method = method + self.old_func = obj.__getattribute__(method) + + self.called = False + self.args = [] + self.kwargs = {} + + self.lock = Lock() + self.cond = Condition(self.lock) + + # Patch the target method + obj.__setattr__(method, self.trigger) + + def trigger(self, *args, **kwargs): + # Save the call parameters + self.args = args + self.kwargs = kwargs + + # Call the original method + self.old_func(*args, **kwargs) + + # Restore the original method + self.obj.__setattr__(self.method, self.old_func) + + # Release waiting thread + with self.lock: + self.called = True + self.cond.notify() + + def wait(self, timeout=10.0): + # Wait for method to get called + with self.lock: + if self.called: + return True + + self.cond.wait(timeout) + + return self.called diff --git a/LCS/PyCommon/test/CMakeLists.txt b/LCS/PyCommon/test/CMakeLists.txt index a2abf73a98a..79c9b43bfa8 100644 --- a/LCS/PyCommon/test/CMakeLists.txt +++ b/LCS/PyCommon/test/CMakeLists.txt @@ -7,3 +7,4 @@ file(COPY DESTINATION ${CMAKE_BINARY_DIR}/bin) lofar_add_test(t_dbcredentials) +lofar_add_test(t_methodtrigger) diff --git a/LCS/PyCommon/test/t_methodtrigger.py b/LCS/PyCommon/test/t_methodtrigger.py new file mode 100644 index 00000000000..5b96ebcf6de --- /dev/null +++ b/LCS/PyCommon/test/t_methodtrigger.py @@ -0,0 +1,124 @@ +import unittest +from lofar.common.methodtrigger import MethodTrigger + +from threading import Thread +import time + +class TestMethodTrigger(unittest.TestCase): + def setUp(self): + # Create a basic object + class TestClass(object): + def func(self): + pass + + self.testobj = TestClass() + + # Install trigger + self.trigger = MethodTrigger(self.testobj, "func") + + def test_no_call(self): + """ Do not trigger. """ + + # Wait for trigger + self.assertFalse(self.trigger.wait(0.1)) + + def test_serial_call(self): + """ Trigger and wait serially. """ + + # Call function + self.testobj.func() + + # Wait for trigger + self.assertTrue(self.trigger.wait(0.1)) + + def test_parallel_call(self): + """ Trigger and wait in parallel. """ + + class wait_thread(Thread): + def __init__(self, trigger): + Thread.__init__(self) + self.result = None + self.trigger = trigger + + def run(self): + self.result = self.trigger.wait(1.0) + + class call_thread(Thread): + def __init__(self,func): + Thread.__init__(self) + self.func = func + + def run(self): + time.sleep(0.5) + self.func() + + # Start threads + t1 = wait_thread(self.trigger) + t1.start() + t2 = call_thread(self.testobj.func) + t2.start() + + # Wait for them to finish + t1.join() + t2.join() + + # Inspect result + self.assertTrue(t1.result) + +class TestArgs(unittest.TestCase): + def setUp(self): + # Create a basic object + class TestClass(object): + def func(self, a, b, c=None, d=None): + pass + + self.testobj = TestClass() + + # Install trigger + self.trigger = MethodTrigger(self.testobj, "func") + + def test_args(self): + """ Trigger and check args. """ + + # Call function + self.testobj.func(1, 2) + + # Wait for trigger + self.assertTrue(self.trigger.wait(0.1)) + + # Check stored arguments + self.assertEqual(self.trigger.args, (1, 2)) + + def test_kwargs(self): + """ Trigger and check kwargs. """ + + # Call function + self.testobj.func(a=1, b=2) + + # Wait for trigger + self.assertTrue(self.trigger.wait(0.1)) + + # Check stored arguments + self.assertEqual(self.trigger.kwargs, {"a": 1, "b": 2}) + + def test_full(self): + """ Trigger and check both args and kwargs. """ + + # Call function + self.testobj.func(1, 2, c=3, d=4) + + # Wait for trigger + self.assertTrue(self.trigger.wait(0.1)) + + # Check stored arguments + self.assertEqual(self.trigger.args, (1, 2)) + self.assertEqual(self.trigger.kwargs, {"c": 3, "d": 4}) + +def main(argv): + unittest.main(verbosity=2) + +if __name__ == "__main__": + # run all tests + import sys + main(sys.argv[1:]) + diff --git a/LCS/PyCommon/test/t_methodtrigger.sh b/LCS/PyCommon/test/t_methodtrigger.sh new file mode 100755 index 00000000000..c786a2cbcda --- /dev/null +++ b/LCS/PyCommon/test/t_methodtrigger.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh t_methodtrigger -- GitLab From 2b0770bc5e0f5a3a690dbabae9bfc925b982315d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 8 Mar 2016 14:19:06 +0000 Subject: [PATCH 040/933] Task #8437: Use MethodTrigger and addCleanup to reduce code --- .../Services/test/tRATaskSpecified.py | 63 +++++++------------ 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/SAS/ResourceAssignment/Services/test/tRATaskSpecified.py b/SAS/ResourceAssignment/Services/test/tRATaskSpecified.py index 94ca0643706..457028e2565 100644 --- a/SAS/ResourceAssignment/Services/test/tRATaskSpecified.py +++ b/SAS/ResourceAssignment/Services/test/tRATaskSpecified.py @@ -8,12 +8,12 @@ from RATaskSpecified import * from RABusListener import RATaskSpecifiedBusListener from lofar.parameterset import PyParameterSet from lofar.messaging import EventMessage, Service +from lofar.common.methodtrigger import MethodTrigger import unittest from glob import glob import uuid import datetime -from threading import Condition, Lock import logging logging.basicConfig(stream=sys.stdout, level=logging.INFO) @@ -73,6 +73,7 @@ class TestService(unittest.TestCase): self.busname = "%s-%s" % (sys.argv[0], str(uuid.uuid4())[:8]) self.bus = ToBus(self.busname, { "create": "always", "delete": "always", "node": { "type": "topic" } }) self.bus.open() + self.addCleanup(self.bus.close) # Define the services we use self.status_service = "%s/TaskStatus" % (self.busname,) @@ -102,44 +103,20 @@ class TestService(unittest.TestCase): PARSET_PREFIX + "Observation.Scheduler.predecessors": predecessors, } - self.parset_service = Service("TaskSpecification", TaskSpecificationService, busname=self.busname) - self.parset_service.start_listening() + parset_service = Service("TaskSpecification", TaskSpecificationService, busname=self.busname) + parset_service.start_listening() + self.addCleanup(parset_service.stop_listening) # ================================ # Setup listener to catch result # of our service # ================================ - class Listener(RATaskSpecifiedBusListener): - def __init__(self, **kwargs): - super(Listener, self).__init__(**kwargs) + listener = RATaskSpecifiedBusListener(busname=self.busname) + listener.start_listening() + self.addCleanup(listener.stop_listening) - self.messageReceived = False - self.lock = Lock() - self.cond = Condition(self.lock) - - def onTaskSpecified(self, sasId, modificationTime, resourceIndicators): - self.messageReceived = True - - self.sasID = sasId - self.resourceIndicators = resourceIndicators - - # Release waiting parent - with self.lock: - self.cond.notify() - - def waitForMessage(self): - with self.lock: - self.cond.wait(5.0) - return self.messageReceived - - self.listener = Listener(busname=self.busname) - self.listener.start_listening() - - def tearDown(self): - self.listener.stop_listening() - self.parset_service.stop_listening() - self.bus.close() + self.trigger = MethodTrigger(listener, "onTaskSpecified") def testNoPredecessors(self): """ @@ -158,13 +135,14 @@ class TestService(unittest.TestCase): tb.send(msg) # Wait for message to arrive - self.assertTrue(self.listener.waitForMessage()) + self.assertTrue(self.trigger.wait()) # Verify message - self.assertEqual(self.listener.sasID, 3) - self.assertNotIn("1", self.listener.resourceIndicators); - self.assertNotIn("2", self.listener.resourceIndicators); - self.assertIn("3", self.listener.resourceIndicators); + self.assertEqual(self.trigger.args[0], 3) + resourceIndicators = self.trigger.args[2] + self.assertNotIn("1", resourceIndicators) + self.assertNotIn("2", resourceIndicators) + self.assertIn("3", resourceIndicators); # Make sure we only requested one parset self.assertEqual(self.requested_parsets, 1) @@ -189,13 +167,14 @@ class TestService(unittest.TestCase): tb.send(msg) # Wait for message to arrive - self.assertTrue(self.listener.waitForMessage()) + self.assertTrue(self.trigger.wait()) # Verify message - self.assertEqual(self.listener.sasID, 1) - self.assertIn("1", self.listener.resourceIndicators); - self.assertIn("2", self.listener.resourceIndicators); - self.assertIn("3", self.listener.resourceIndicators); + self.assertEqual(self.trigger.args[0], 1) + resourceIndicators = self.trigger.args[2] + self.assertIn("1", resourceIndicators); + self.assertIn("2", resourceIndicators); + self.assertIn("3", resourceIndicators); # Make sure we only requested exactly three parsets self.assertEqual(self.requested_parsets, 3) -- GitLab From e6545afa16e616fa96c5517c642c993d39e094f8 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 8 Mar 2016 15:51:27 +0000 Subject: [PATCH 041/933] Task #8437: Use MethodTrigger from PyCommon --- MAC/Services/test/methodtrigger.py | 68 --------------------------- MAC/Services/test/tPipelineStarter.py | 2 +- 2 files changed, 1 insertion(+), 69 deletions(-) delete mode 100644 MAC/Services/test/methodtrigger.py diff --git a/MAC/Services/test/methodtrigger.py b/MAC/Services/test/methodtrigger.py deleted file mode 100644 index 800d8d11ea1..00000000000 --- a/MAC/Services/test/methodtrigger.py +++ /dev/null @@ -1,68 +0,0 @@ -from threading import Lock, Condition - -__all__ = ["MethodTrigger"] - -class MethodTrigger: - """ - Set a flag when a specific method is called, possibly asynchronously. Caller can wait on this flag. - - Example: - - class Foo(object): - def bar(self): - pass - - foo = Foo() - trigger = MethodTrigger(foo, "bar") - - if trigger.wait(): # Waits for 10 seconds for foo.bar() to get called - print "foo.bar() got called" - else - # This will happen, as foo.bar() wasn't called - print "foo.bar() did not get called" - - Calls that were made before the trigger has been installed will not get recorded. - """ - - def __init__(self, obj, method): - assert isinstance(obj, object), "Object %s does not derive from object." % (obj,) - - self.obj = obj - self.method = method - self.old_func = obj.__getattribute__(method) - - self.called = False - self.args = [] - self.kwargs = {} - - self.lock = Lock() - self.cond = Condition(self.lock) - - # Patch the target method - obj.__setattr__(method, self.trigger) - - def trigger(self, *args, **kwargs): - # Save the call parameters - self.args = args - self.kwargs = kwargs - - # Call the original method - self.old_func(*args, **kwargs) - - # Restore the original method - self.obj.__setattr__(self.method, self.old_func) - - # Release waiting thread - with self.lock: - self.called = True - self.cond.notify() - - def wait(self, timeout=10.0): - # Wait for method to get called - with self.lock: - if self.called: - return True - - self.cond.wait(timeout) - - return self.called diff --git a/MAC/Services/test/tPipelineStarter.py b/MAC/Services/test/tPipelineStarter.py index 127679521f5..4b3a7e1a43a 100644 --- a/MAC/Services/test/tPipelineStarter.py +++ b/MAC/Services/test/tPipelineStarter.py @@ -7,7 +7,7 @@ from lofar.mac.PipelineStarter import * from lofar.sas.otdb.OTDBBusListener import OTDBBusListener from lofar.messaging import ToBus, Service, EventMessage -from methodtrigger import MethodTrigger +from lofar.common.methodtrigger import MethodTrigger import subprocess import unittest -- GitLab From e88965d6b87bdb7fd44f01e3ba3879f132cee105 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 8 Mar 2016 16:11:18 +0000 Subject: [PATCH 042/933] Task #8437: Set to QUEUED before handing to SLURM (which can set obs to STARTED immediately), and some added logging --- MAC/Services/src/PipelineStarter.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/MAC/Services/src/PipelineStarter.py b/MAC/Services/src/PipelineStarter.py index f780c3088a9..bf43b3af8e0 100755 --- a/MAC/Services/src/PipelineStarter.py +++ b/MAC/Services/src/PipelineStarter.py @@ -21,8 +21,8 @@ # # $Id$ """ -Daemon that listens to OTDB status changes to SCHEDULED, requests -the parset of such jobs, and starts them using SLURM and Docker. +Daemon that listens to OTDB status changes to SCHEDULED, starts them +using SLURM and Docker, and puts the jobs to QUEUED. The execution chain is as follows: @@ -229,7 +229,9 @@ class PipelineStarter(OTDBBusListener): # Cancel corresponding SLURM job, causing any successors # to be cancelled as well. - self.slurm.cancel(parset.slurmJobName()) + jobName = parset.slurmJobName() + logger.info("Cancelling job %s", jobName) + self.slurm.cancel(jobName) """ More statusses we want to abort on. @@ -266,12 +268,22 @@ class PipelineStarter(OTDBBusListener): """ # Collect the parsets of predecessors + logger.info("Obtaining predecessor parsets") preparsets = self._getPredecessorParsets(parset) # Collect SLURM job information logger.info("Obtaining SLURM job list") slurm_jobs = self.slurm.jobs() + """ + Update OTDB before scheduling the SLURM jobs, + as the SLURM jobs will set the status too. + """ + + # Set OTDB status to QUEUED + logger.info("Setting status to QUEUED") + self._setStatus(treeId, "queued") + """ Schedule "docker-runPipeline.sh", which will fetch the parset and run the pipeline within a SLURM job. @@ -350,9 +362,5 @@ class PipelineStarter(OTDBBusListener): ) logger.info("Scheduled SLURM job %s" % (slurm_cancel_job_id,)) - # Set OTDB status to QUEUED - logger.info("Setting status to QUEUED") - self._setStatus(treeId, "queued") - logger.info("Pipeline processed.") -- GitLab From 8be0c8cfb331b12bfa851b50445d238b6e6e2977 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 8 Mar 2016 20:30:21 +0000 Subject: [PATCH 043/933] Task #8437: Added MAC_Services to scu001 build, and subdivided programs in OTDB/MAC/RA_Services groups in supervisord. --- SubSystems/RAServices/CMakeLists.txt | 1 + SubSystems/RAServices/RAServices.ini | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/SubSystems/RAServices/CMakeLists.txt b/SubSystems/RAServices/CMakeLists.txt index 6d170b92871..b3b14798951 100644 --- a/SubSystems/RAServices/CMakeLists.txt +++ b/SubSystems/RAServices/CMakeLists.txt @@ -2,6 +2,7 @@ lofar_package(RAServices DEPENDS OTDB_Services + MAC_Services MoMQueryService ResourceAssignmentDatabase ResourceAssignmentServices diff --git a/SubSystems/RAServices/RAServices.ini b/SubSystems/RAServices/RAServices.ini index 1426ab48b67..cf1dd05a9d0 100644 --- a/SubSystems/RAServices/RAServices.ini +++ b/SubSystems/RAServices/RAServices.ini @@ -1,3 +1,8 @@ +[group:OTDB] +programs=TreeService,TreeStatusEvents + [group:RA_Services] -programs=raewebservice,radbservice,radbpglistener,TreeService,TreeStatusEvents,RATaskSpecified -priority=200 +programs=raewebservice,radbservice,radbpglistener,RATaskSpecified + +[group:MAC] +programs=pipelinestarter -- GitLab From fa9d6aba9542e4970a942dd04d93b2a2998c43b0 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 9 Mar 2016 07:11:11 +0000 Subject: [PATCH 044/933] Task #8437: Minor cleanup --- MAC/Services/src/PipelineStarter.py | 26 +++++++++++++++++++------- MAC/Services/test/tPipelineStarter.py | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/MAC/Services/src/PipelineStarter.py b/MAC/Services/src/PipelineStarter.py index bf43b3af8e0..3046dec76f6 100755 --- a/MAC/Services/src/PipelineStarter.py +++ b/MAC/Services/src/PipelineStarter.py @@ -102,6 +102,15 @@ class Parset(dict): return result + def isObservation(self): + return self[PARSET_PREFIX + "Observation.processType"] == "Observation" + + def isPipeline(self): + return not self.isObservation() + + def processingCluster(self): + return self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] or "CEP2" + def dockerTag(self): # For now, return OUR tag return runCommand("docker-template", "${LOFAR_TAG}") @@ -187,11 +196,11 @@ class PipelineStarter(OTDBBusListener): @classmethod def _shouldHandle(self, parset): - if parset[PARSET_PREFIX + "Observation.processType"] != "Pipeline": + if not parset.isPipeline(): logger.info("Not processing tree: is not a pipeline") return False - if parset[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] in ["", "CEP2"]: + if parset.processingCluster() == "CEP2": logger.info("Not processing tree: is a CEP2 pipeline") return False @@ -211,18 +220,21 @@ class PipelineStarter(OTDBBusListener): return names + def _getParset(self, treeId): + return Parset(self.parset_rpc( OtdbID=treeId, timeout=10 )[0]) + def _getPredecessorParsets(self, parset): treeIds = parset.predecessors() logger.info("Obtaining predecessor parsets %s", treeIds) - return [Parset(self.parset_rpc( OtdbID=treeId, timeout=10 )[0]) for treeId in treeIds] + return [self._getParset(treeId) for treeId in treeIds] def onObservationAborted(self, treeId, modificationTime): logger.info("***** STOP Tree ID %s *****", treeId) # Request the parset - parset = Parset(self.parset_rpc( OtdbID=treeId, timeout=10 )[0]) + parset = self._getParset(treeId) if not self._shouldHandle(parset): return @@ -258,7 +270,7 @@ class PipelineStarter(OTDBBusListener): logger.info("***** QUEUE Tree ID %s *****", treeId) # Request the parset - parset = Parset(self.parset_rpc( OtdbID=treeId, timeout=10 )[0]) + parset = self._getParset(treeId) if not self._shouldHandle(parset): return @@ -311,11 +323,11 @@ class PipelineStarter(OTDBBusListener): os.path.expandvars("--output=$LOFARROOT/var/log/docker-startPython-%s.log" % (treeId,)), ] - min_starttime = self._minStartTime([x for x in preparsets if x[PARSET_PREFIX + "Observation.processType"] == "Observation"]) + min_starttime = self._minStartTime([x for x in preparsets if x.isObservation()]) if min_starttime: sbatch_params.append("--begin=%s" % (min_starttime.strftime("%FT%T"),)) - predecessor_jobs = self._slurmJobNames([x for x in preparsets if x[PARSET_PREFIX + "Observation.processType"] != "Observation"], slurm_jobs.keys()) + predecessor_jobs = self._slurmJobNames([x for x in preparsets if x.isPipeline()], slurm_jobs.keys()) if predecessor_jobs: sbatch_params.append("--dependency=%s" % (",".join(("afterok:%s" % x for x in predecessor_jobs)),)) diff --git a/MAC/Services/test/tPipelineStarter.py b/MAC/Services/test/tPipelineStarter.py index 4b3a7e1a43a..61ea9e143e9 100644 --- a/MAC/Services/test/tPipelineStarter.py +++ b/MAC/Services/test/tPipelineStarter.py @@ -101,7 +101,7 @@ class TestPipelineStarterClassMethods(unittest.TestCase): for t in trials: parset = { "ObsSW.Observation.processType": t["type"], "ObsSW.Observation.Cluster.ProcessingCluster.clusterName": t["cluster"] } - self.assertEqual(PipelineStarter._shouldHandle(parset), t["shouldHandle"]) + self.assertEqual(PipelineStarter._shouldHandle(Parset(parset)), t["shouldHandle"]) class TestPipelineStarter(unittest.TestCase): def setUp(self): -- GitLab From cdeff1a75b1d33475cfdb50765292d6c0daee4dd Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 9 Mar 2016 09:50:43 +0000 Subject: [PATCH 045/933] Task #8437: Renamed PipelineStarter to PipelineControl (since it also stops the pipelines) --- .gitattributes | 6 ++--- MAC/Services/src/CMakeLists.txt | 6 ++--- ...{PipelineStarter.py => PipelineControl.py} | 10 ++++---- .../src/{pipelinestarter => pipelinecontrol} | 0 ...ipelinestarter.ini => pipelinecontrol.ini} | 2 +- MAC/Services/test/CMakeLists.txt | 2 +- ...PipelineStarter.py => tPipelineControl.py} | 24 +++++++++---------- MAC/Services/test/tPipelineControl.sh | 2 ++ MAC/Services/test/tPipelineStarter.sh | 2 -- SubSystems/RAServices/RAServices.ini | 2 +- 10 files changed, 28 insertions(+), 28 deletions(-) rename MAC/Services/src/{PipelineStarter.py => PipelineControl.py} (97%) rename MAC/Services/src/{pipelinestarter => pipelinecontrol} (100%) rename MAC/Services/src/{pipelinestarter.ini => pipelinecontrol.ini} (70%) rename MAC/Services/test/{tPipelineStarter.py => tPipelineControl.py} (94%) create mode 100755 MAC/Services/test/tPipelineControl.sh delete mode 100755 MAC/Services/test/tPipelineStarter.sh diff --git a/.gitattributes b/.gitattributes index 5cacfbd2c45..ece505910f5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4128,9 +4128,9 @@ MAC/Navigator2/scripts/monitorStationAlarms.ctl -text MAC/Navigator2/scripts/readStationConfigs.ctl -text MAC/Navigator2/scripts/readStationConnections.ctl -text MAC/Navigator2/scripts/transferMPs.ctl -text -MAC/Services/src/pipelinestarter -text -MAC/Services/src/pipelinestarter.ini -text -MAC/Services/test/tPipelineStarter.sh eol=lf +MAC/Services/src/pipelinecontrol -text +MAC/Services/src/pipelinecontrol.ini -text +MAC/Services/test/tPipelineControl.sh eol=lf MAC/Test/APL/PVSSproject/colorDB/Lofar[!!-~]colors -text svneol=native#application/octet-stream MAC/Test/APL/PVSSproject/colorDB/colorDB_de -text svneol=native#application/octet-stream MAC/Test/APL/PVSSproject/config/config -text svneol=native#application/octet-stream diff --git a/MAC/Services/src/CMakeLists.txt b/MAC/Services/src/CMakeLists.txt index cf4cde0d7b6..692b7b5ce4a 100644 --- a/MAC/Services/src/CMakeLists.txt +++ b/MAC/Services/src/CMakeLists.txt @@ -1,15 +1,15 @@ # $Id$ lofar_add_bin_scripts( - pipelinestarter + pipelinecontrol ) python_install( - PipelineStarter.py + PipelineControl.py DESTINATION lofar/mac ) # supervisord config files install(FILES - pipelinestarter.ini + pipelinecontrol.ini DESTINATION etc/supervisord.d) diff --git a/MAC/Services/src/PipelineStarter.py b/MAC/Services/src/PipelineControl.py similarity index 97% rename from MAC/Services/src/PipelineStarter.py rename to MAC/Services/src/PipelineControl.py index 3046dec76f6..48e2bee4838 100755 --- a/MAC/Services/src/PipelineStarter.py +++ b/MAC/Services/src/PipelineControl.py @@ -26,7 +26,7 @@ using SLURM and Docker, and puts the jobs to QUEUED. The execution chain is as follows: -[SCHEDULED] -> PipelineStarter schedules +[SCHEDULED] -> PipelineControl schedules runPipeline.sh <obsid> || pipelineAborted.sh <obsid> @@ -167,9 +167,9 @@ class Slurm(object): return jobs -class PipelineStarter(OTDBBusListener): +class PipelineControl(OTDBBusListener): def __init__(self, otdb_busname=None, setStatus_busname=None, **kwargs): - super(PipelineStarter, self).__init__(busname=otdb_busname, **kwargs) + super(PipelineControl, self).__init__(busname=otdb_busname, **kwargs) self.parset_rpc = RPC(service="TaskSpecification", busname=otdb_busname, ForwardExceptions=True) self.setStatus_busname = setStatus_busname if setStatus_busname else otdb_busname @@ -187,10 +187,10 @@ class PipelineStarter(OTDBBusListener): def start_listening(self, **kwargs): self.parset_rpc.open() - super(PipelineStarter, self).start_listening(**kwargs) + super(PipelineControl, self).start_listening(**kwargs) def stop_listening(self, **kwargs): - super(PipelineStarter, self).stop_listening(**kwargs) + super(PipelineControl, self).stop_listening(**kwargs) self.parset_rpc.close() diff --git a/MAC/Services/src/pipelinestarter b/MAC/Services/src/pipelinecontrol similarity index 100% rename from MAC/Services/src/pipelinestarter rename to MAC/Services/src/pipelinecontrol diff --git a/MAC/Services/src/pipelinestarter.ini b/MAC/Services/src/pipelinecontrol.ini similarity index 70% rename from MAC/Services/src/pipelinestarter.ini rename to MAC/Services/src/pipelinecontrol.ini index 0d68a1f961b..c80c5381d5f 100644 --- a/MAC/Services/src/pipelinestarter.ini +++ b/MAC/Services/src/pipelinecontrol.ini @@ -1,5 +1,5 @@ [program:PipelineStarter] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;pipelinestarter' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;pipelinecontrol' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true diff --git a/MAC/Services/test/CMakeLists.txt b/MAC/Services/test/CMakeLists.txt index 86b9a1579b8..b1f0434a8f1 100644 --- a/MAC/Services/test/CMakeLists.txt +++ b/MAC/Services/test/CMakeLists.txt @@ -4,5 +4,5 @@ include(LofarCTest) lofar_find_package(Python REQUIRED) -lofar_add_test(tPipelineStarter) +lofar_add_test(tPipelineControl) diff --git a/MAC/Services/test/tPipelineStarter.py b/MAC/Services/test/tPipelineControl.py similarity index 94% rename from MAC/Services/test/tPipelineStarter.py rename to MAC/Services/test/tPipelineControl.py index 61ea9e143e9..4f388a788a2 100644 --- a/MAC/Services/test/tPipelineStarter.py +++ b/MAC/Services/test/tPipelineControl.py @@ -3,7 +3,7 @@ # Be able to find service python file import sys -from lofar.mac.PipelineStarter import * +from lofar.mac.PipelineControl import * from lofar.sas.otdb.OTDBBusListener import OTDBBusListener from lofar.messaging import ToBus, Service, EventMessage @@ -57,14 +57,14 @@ class TestRunCommand(unittest.TestCase): class TestSlurmJobs(unittest.TestCase): def test_no_jobs(self): """ Test 'scontrol show job' output if there are no jobs. """ - with patch('lofar.mac.PipelineStarter.Slurm._runCommand') as MockRunSlurmCommand: + with patch('lofar.mac.PipelineControl.Slurm._runCommand') as MockRunSlurmCommand: MockRunSlurmCommand.return_value = """No jobs in the system""" self.assertEqual(Slurm().jobs(), {}) def test_one_job(self): """ Test 'scontrol show job' output for a single job. """ - with patch('lofar.mac.PipelineStarter.Slurm._runCommand') as MockRunSlurmCommand: + with patch('lofar.mac.PipelineControl.Slurm._runCommand') as MockRunSlurmCommand: MockRunSlurmCommand.return_value = """JobId=119 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901736 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:07 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:05:52 EligibleTime=2016-03-04T12:05:52 StartTime=2016-03-04T12:05:52 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7040 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" jobs = Slurm().jobs() @@ -73,7 +73,7 @@ class TestSlurmJobs(unittest.TestCase): def test_two_jobs(self): """ Test 'scontrol show job' output for multiple jobs. """ - with patch('lofar.mac.PipelineStarter.Slurm._runCommand') as MockRunSlurmCommand: + with patch('lofar.mac.PipelineControl.Slurm._runCommand') as MockRunSlurmCommand: MockRunSlurmCommand.return_value = """JobId=120 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901735 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:17 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:53 EligibleTime=2016-03-04T12:09:53 StartTime=2016-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0 JobId=121 JobName=bar UserId=mol(7261) GroupId=mol(7261) Priority=4294901734 Nice=0 Account=(null) QOS=(null) JobState=PENDING Reason=Resources Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:00 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:59 EligibleTime=2016-03-04T12:09:59 StartTime=2017-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=(null) NumNodes=2-2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=0 MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" @@ -84,7 +84,7 @@ class TestSlurmJobs(unittest.TestCase): self.assertEqual(jobs["bar"]["JobName"], "bar") self.assertEqual(jobs["bar"]["JobId"], "121") -class TestPipelineStarterClassMethods(unittest.TestCase): +class TestPipelineControlClassMethods(unittest.TestCase): def test_shouldHandle(self): """ Test whether we filter the right OTDB trees. """ @@ -101,9 +101,9 @@ class TestPipelineStarterClassMethods(unittest.TestCase): for t in trials: parset = { "ObsSW.Observation.processType": t["type"], "ObsSW.Observation.Cluster.ProcessingCluster.clusterName": t["cluster"] } - self.assertEqual(PipelineStarter._shouldHandle(Parset(parset)), t["shouldHandle"]) + self.assertEqual(PipelineControl._shouldHandle(Parset(parset)), t["shouldHandle"]) -class TestPipelineStarter(unittest.TestCase): +class TestPipelineControl(unittest.TestCase): def setUp(self): # Create a random bus self.busname = "%s-%s" % (sys.argv[0], str(uuid.uuid4())[:8]) @@ -132,12 +132,12 @@ class TestPipelineStarter(unittest.TestCase): # "4" is an observation, so no SLURM job } - patcher = patch('lofar.mac.PipelineStarter.Slurm') + patcher = patch('lofar.mac.PipelineControl.Slurm') patcher.start().side_effect = MockSlurm self.addCleanup(patcher.stop) # Catch functions to prevent running executables - patcher = patch('lofar.mac.PipelineStarter.Parset.dockerTag') + patcher = patch('lofar.mac.PipelineControl.Parset.dockerTag') patcher.start().return_value = "trunk" self.addCleanup(patcher.stop) @@ -206,7 +206,7 @@ class TestPipelineStarter(unittest.TestCase): self.trigger = MethodTrigger(listener, "onObservationQueued") def test_setStatus(self): - with PipelineStarter(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: + with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: ps._setStatus(12345, "queued") # Wait for the staatus to propagate @@ -219,7 +219,7 @@ class TestPipelineStarter(unittest.TestCase): 3 requires nothing """ - with PipelineStarter(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: + with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: # Send fake status update ps._setStatus(3, "scheduled") @@ -241,7 +241,7 @@ class TestPipelineStarter(unittest.TestCase): 2 requires 3 4 is an observation """ - with PipelineStarter(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: + with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: # Send fake status update ps._setStatus(1, "scheduled") diff --git a/MAC/Services/test/tPipelineControl.sh b/MAC/Services/test/tPipelineControl.sh new file mode 100755 index 00000000000..b209e84048b --- /dev/null +++ b/MAC/Services/test/tPipelineControl.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh tPipelineControl diff --git a/MAC/Services/test/tPipelineStarter.sh b/MAC/Services/test/tPipelineStarter.sh deleted file mode 100755 index d3a7329b76f..00000000000 --- a/MAC/Services/test/tPipelineStarter.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -./runctest.sh tPipelineStarter diff --git a/SubSystems/RAServices/RAServices.ini b/SubSystems/RAServices/RAServices.ini index cf1dd05a9d0..a9700f3f999 100644 --- a/SubSystems/RAServices/RAServices.ini +++ b/SubSystems/RAServices/RAServices.ini @@ -5,4 +5,4 @@ programs=TreeService,TreeStatusEvents programs=raewebservice,radbservice,radbpglistener,RATaskSpecified [group:MAC] -programs=pipelinestarter +programs=pipelinecontrol -- GitLab From 931aeeba7c59107960da75630907988911773cd8 Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Wed, 9 Mar 2016 11:01:18 +0000 Subject: [PATCH 046/933] Task #9188: Creation of preRelease-2_16 branch -- GitLab From bfd8b0001c0ca6b221b55b0c09c3c8a65676f221 Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Thu, 10 Mar 2016 16:48:35 +0000 Subject: [PATCH 047/933] TAsk 9196: Adapted pipeline.cfg and tasks.cfg.in for new external aoflagger --- CEP/Pipeline/recipes/sip/pipeline.cfg.in | 1 + CEP/Pipeline/recipes/sip/tasks.cfg.in | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.in b/CEP/Pipeline/recipes/sip/pipeline.cfg.in index 01ae7714f0d..d4b175ad9e5 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.in +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.in @@ -4,6 +4,7 @@ casaroot = @CASACORE_ROOT_DIR@ pyraproot = @PYRAP_ROOT_DIR@ hdf5root = $ENV{HDF5_ROOT} wcsroot = @WCSLIB_ROOT_DIR@ +aoflaggerroot=@AOFLAGGER_ROOT_DIR@ pythonpath = @PYTHON_INSTALL_DIR@ runtime_directory = %(lofarroot)s/var/run/pipeline recipe_directories = [%(pythonpath)s/lofarpipe/recipes] diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.in b/CEP/Pipeline/recipes/sip/tasks.cfg.in index e2968e1bc5b..05e659aae3d 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.in @@ -50,7 +50,7 @@ mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/instrument.mapfile [rficonsole] recipe = rficonsole -executable = %(lofarroot)s/bin/rficonsole +executable = %(aoflaggerroot)s/bin/aoflagger [get_metadata] recipe = get_metadata @@ -61,7 +61,7 @@ ndppp_exec = %(lofarroot)s/bin/NDPPP asciistat_executable = %(lofarroot)s/bin/asciistats.py statplot_executable = %(lofarroot)s/bin/statsplot.py msselect_executable = %(casaroot)s/bin/msselect -rficonsole_executable = %(lofarroot)s/bin/rficonsole +rficonsole_executable = %(aoflaggerroot)s/bin/aoflagger [long_baseline] recipe = long_baseline @@ -69,7 +69,7 @@ ndppp_exec = %(lofarroot)s/bin/NDPPP asciistat_executable = %(lofarroot)s/bin/asciistats.py statplot_executable = %(lofarroot)s/bin/statsplot.py msselect_executable = %(casaroot)s/bin/msselect -rficonsole_executable = %(lofarroot)s/bin/rficonsole +rficonsole_executable = %(aoflaggerroot)s/bin/aoflagger nproc = 1 [imager_awimager] @@ -175,4 +175,4 @@ outputkey=image [rficonsole] recipe = executable_args -executable = %(lofarroot)s/bin/rficonsole +executable = %(aoflaggerroot)s/bin/aoflagger -- GitLab From d1a35edd25d98586ea8fa4c657e1298133fdd9f0 Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Thu, 10 Mar 2016 16:55:12 +0000 Subject: [PATCH 048/933] TAsk 9196: Adapted pipeline.cfg and tasks.cfg.in for new external aoflagger --- CEP/Pipeline/recipes/sip/pipeline.cfg.in | 1 - CEP/Pipeline/recipes/sip/tasks.cfg.in | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.in b/CEP/Pipeline/recipes/sip/pipeline.cfg.in index d4b175ad9e5..01ae7714f0d 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.in +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.in @@ -4,7 +4,6 @@ casaroot = @CASACORE_ROOT_DIR@ pyraproot = @PYRAP_ROOT_DIR@ hdf5root = $ENV{HDF5_ROOT} wcsroot = @WCSLIB_ROOT_DIR@ -aoflaggerroot=@AOFLAGGER_ROOT_DIR@ pythonpath = @PYTHON_INSTALL_DIR@ runtime_directory = %(lofarroot)s/var/run/pipeline recipe_directories = [%(pythonpath)s/lofarpipe/recipes] diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.in b/CEP/Pipeline/recipes/sip/tasks.cfg.in index 05e659aae3d..e2968e1bc5b 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.in @@ -50,7 +50,7 @@ mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/instrument.mapfile [rficonsole] recipe = rficonsole -executable = %(aoflaggerroot)s/bin/aoflagger +executable = %(lofarroot)s/bin/rficonsole [get_metadata] recipe = get_metadata @@ -61,7 +61,7 @@ ndppp_exec = %(lofarroot)s/bin/NDPPP asciistat_executable = %(lofarroot)s/bin/asciistats.py statplot_executable = %(lofarroot)s/bin/statsplot.py msselect_executable = %(casaroot)s/bin/msselect -rficonsole_executable = %(aoflaggerroot)s/bin/aoflagger +rficonsole_executable = %(lofarroot)s/bin/rficonsole [long_baseline] recipe = long_baseline @@ -69,7 +69,7 @@ ndppp_exec = %(lofarroot)s/bin/NDPPP asciistat_executable = %(lofarroot)s/bin/asciistats.py statplot_executable = %(lofarroot)s/bin/statsplot.py msselect_executable = %(casaroot)s/bin/msselect -rficonsole_executable = %(aoflaggerroot)s/bin/aoflagger +rficonsole_executable = %(lofarroot)s/bin/rficonsole nproc = 1 [imager_awimager] @@ -175,4 +175,4 @@ outputkey=image [rficonsole] recipe = executable_args -executable = %(aoflaggerroot)s/bin/aoflagger +executable = %(lofarroot)s/bin/rficonsole -- GitLab From d30024b0bc858036f9f14cc612ada4b6588dcb5c Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Thu, 10 Mar 2016 16:55:58 +0000 Subject: [PATCH 049/933] TAsk #9196: Adapted pipeline.cfg and tasks.cfg.in for new external aoflagger --- CEP/Pipeline/recipes/sip/pipeline.cfg.in | 1 + CEP/Pipeline/recipes/sip/tasks.cfg.in | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.in b/CEP/Pipeline/recipes/sip/pipeline.cfg.in index 01ae7714f0d..d4b175ad9e5 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.in +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.in @@ -4,6 +4,7 @@ casaroot = @CASACORE_ROOT_DIR@ pyraproot = @PYRAP_ROOT_DIR@ hdf5root = $ENV{HDF5_ROOT} wcsroot = @WCSLIB_ROOT_DIR@ +aoflaggerroot=@AOFLAGGER_ROOT_DIR@ pythonpath = @PYTHON_INSTALL_DIR@ runtime_directory = %(lofarroot)s/var/run/pipeline recipe_directories = [%(pythonpath)s/lofarpipe/recipes] diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.in b/CEP/Pipeline/recipes/sip/tasks.cfg.in index e2968e1bc5b..05e659aae3d 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.in @@ -50,7 +50,7 @@ mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/instrument.mapfile [rficonsole] recipe = rficonsole -executable = %(lofarroot)s/bin/rficonsole +executable = %(aoflaggerroot)s/bin/aoflagger [get_metadata] recipe = get_metadata @@ -61,7 +61,7 @@ ndppp_exec = %(lofarroot)s/bin/NDPPP asciistat_executable = %(lofarroot)s/bin/asciistats.py statplot_executable = %(lofarroot)s/bin/statsplot.py msselect_executable = %(casaroot)s/bin/msselect -rficonsole_executable = %(lofarroot)s/bin/rficonsole +rficonsole_executable = %(aoflaggerroot)s/bin/aoflagger [long_baseline] recipe = long_baseline @@ -69,7 +69,7 @@ ndppp_exec = %(lofarroot)s/bin/NDPPP asciistat_executable = %(lofarroot)s/bin/asciistats.py statplot_executable = %(lofarroot)s/bin/statsplot.py msselect_executable = %(casaroot)s/bin/msselect -rficonsole_executable = %(lofarroot)s/bin/rficonsole +rficonsole_executable = %(aoflaggerroot)s/bin/aoflagger nproc = 1 [imager_awimager] @@ -175,4 +175,4 @@ outputkey=image [rficonsole] recipe = executable_args -executable = %(lofarroot)s/bin/rficonsole +executable = %(aoflaggerroot)s/bin/aoflagger -- GitLab From 697beeefd63c55bbf8df97527f1b0f5fde9d90fa Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Fri, 11 Mar 2016 10:46:55 +0000 Subject: [PATCH 050/933] Task #8353: Trial change for building aoflagger module separately --- CEP/DP3/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/DP3/CMakeLists.txt b/CEP/DP3/CMakeLists.txt index 7c693c95b83..0a0dd7fac75 100644 --- a/CEP/DP3/CMakeLists.txt +++ b/CEP/DP3/CMakeLists.txt @@ -5,5 +5,5 @@ lofar_add_package(TestDynDPPP) lofar_add_package(PythonDPPP) lofar_add_package(DPPP_AOFlag) lofar_add_package(SPW_Combine SPWCombine) -lofar_add_package(AOFlagger) +#lofar_add_package(AOFlagger) -- GitLab From 2a9a9330a40a3b6f5c6f0142ac6eadf6ed54b3b6 Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Mon, 14 Mar 2016 07:44:29 +0000 Subject: [PATCH 051/933] TAsk #9189: Renaming preRelease branch to Release branch -- GitLab From 22d576246d8556a4df6e2a8aee2e006e36f3475d Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Mon, 14 Mar 2016 13:40:46 +0000 Subject: [PATCH 052/933] TAsk #9189: Add install files for changed ppstune layout --- .gitattributes | 1 + LCU/PPSTune/CMakeLists.txt | 2 ++ LCU/PPSTune/ppstune/CMakeLists.txt | 6 ++++++ 3 files changed, 9 insertions(+) create mode 100644 LCU/PPSTune/ppstune/CMakeLists.txt diff --git a/.gitattributes b/.gitattributes index 252016504a8..d874326c4ee 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2718,6 +2718,7 @@ LCU/PPSTune/doc/source/instructions-menno.rst -text LCU/PPSTune/doc/source/ppstune.rst -text LCU/PPSTune/doc/source/readme.rst -text LCU/PPSTune/ppstune.sh -text svneol=unset#text/x-shellscript +LCU/PPSTune/ppstune/CMakeLists.txt -text LCU/PPSTune/ppstune/__init__.py -text LCU/PPSTune/ppstune/plots.py -text LCU/PPSTune/ppstune/ppstune.py -text diff --git a/LCU/PPSTune/CMakeLists.txt b/LCU/PPSTune/CMakeLists.txt index a6ad8cee00d..e70eac3a439 100644 --- a/LCU/PPSTune/CMakeLists.txt +++ b/LCU/PPSTune/CMakeLists.txt @@ -2,6 +2,8 @@ lofar_package(PPSTune 1.0) +add_subdirectory(ppstune) + # Install files matching regex pattern in current directory and below install(DIRECTORY . DESTINATION sbin diff --git a/LCU/PPSTune/ppstune/CMakeLists.txt b/LCU/PPSTune/ppstune/CMakeLists.txt new file mode 100644 index 00000000000..65333225528 --- /dev/null +++ b/LCU/PPSTune/ppstune/CMakeLists.txt @@ -0,0 +1,6 @@ +# $Id: CMakeLists.txt 26657 2013-09-24 11:35:59Z schoenmakers $ + +# Install files matching regex pattern in current directory and below +install(FILES + ppstune.py + DESTINATION sbin) -- GitLab From 713124b6336d09542f098498e62e118feb92bbf3 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 14 Mar 2016 15:21:03 +0000 Subject: [PATCH 053/933] Task #8437: Minor documentation update --- MAC/Services/src/PipelineControl.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 48e2bee4838..f40ca7d0613 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -21,10 +21,13 @@ # # $Id$ """ -Daemon that listens to OTDB status changes to SCHEDULED, starts them -using SLURM and Docker, and puts the jobs to QUEUED. +Daemon that starts/stops pipelines based on their status in OTDB. -The execution chain is as follows: +The execution chains are as follows: + +----------------------------- + Starting a pipeline +----------------------------- [SCHEDULED] -> PipelineControl schedules @@ -42,13 +45,20 @@ The execution chain is as follows: (runPipeline.sh) -> Calls - state <- [ACTIVE] - getParset - - (run pipeline) + - (run pipeline) (which, for CEP2 compatibility, still calls state <- [FINISHED/ABORTED]) - state <- [COMPLETING] - (wrap up) - state <- [FINISHED] (pipelineAborted.sh) -> Calls - state <- [ABORTED] + +----------------------------- + Stopping a pipeline +----------------------------- + +[ABORTED] -> Cancels SLURM job associated with pipeline, causing + a cascade of job terminations of successor pipelines. """ from lofar.messaging import FromBus, ToBus, RPC, EventMessage -- GitLab From 81fc0a7b77fc7470fd03cf214440f911c5ba1f28 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 14 Mar 2016 18:44:56 +0000 Subject: [PATCH 054/933] Task #8437: Default pipeline image is "lofar-pipeline" --- CEP/Pipeline/framework/lofarpipe/support/remotecommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index 82950986177..dbd145f0ee0 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -211,7 +211,7 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config try: image = config.get('docker', 'image') except: - image = "lofar" + image = "lofar-pipeline" # Construct the full command line, except for {command}, as that itself # can contain spaces which we don't want to split on. -- GitLab From 738ef2a42b9232181ec11ff720734edad8d4da60 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 14 Mar 2016 18:47:14 +0000 Subject: [PATCH 055/933] Task #8437: Generate pipeline.cfg.CEP4, and propagate this file in PipelineControl --- .gitattributes | 2 +- CEP/Pipeline/recipes/sip/CMakeLists.txt | 24 +++++++++++++++++++ CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 8 +++---- ...cep4 => pipeline.cfg.CEP4.docker-template} | 11 ++++----- MAC/Services/src/PipelineControl.py | 3 ++- 5 files changed, 34 insertions(+), 14 deletions(-) rename CEP/Pipeline/recipes/sip/{pipeline.cfg.thead01.cep4 => pipeline.cfg.CEP4.docker-template} (85%) diff --git a/.gitattributes b/.gitattributes index ae8a01b027f..e2359c94fd9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1614,8 +1614,8 @@ CEP/Pipeline/recipes/sip/nodes/selfcal_finalize.py eol=lf CEP/Pipeline/recipes/sip/nodes/setupparmdb.py eol=lf CEP/Pipeline/recipes/sip/nodes/setupsourcedb.py eol=lf CEP/Pipeline/recipes/sip/nodes/vdsmaker.py eol=lf +CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.docker-template -text CEP/Pipeline/recipes/sip/pipeline.cfg.in eol=lf -CEP/Pipeline/recipes/sip/pipeline.cfg.thead01.cep4 -text CEP/Pipeline/recipes/sip/plugins/PipelineStep_addMapfile.py -text CEP/Pipeline/recipes/sip/plugins/PipelineStep_changeMapfile.py -text CEP/Pipeline/recipes/sip/plugins/PipelineStep_combineParsets.py -text diff --git a/CEP/Pipeline/recipes/sip/CMakeLists.txt b/CEP/Pipeline/recipes/sip/CMakeLists.txt index f3654d95a5b..dedd7ed4922 100644 --- a/CEP/Pipeline/recipes/sip/CMakeLists.txt +++ b/CEP/Pipeline/recipes/sip/CMakeLists.txt @@ -113,6 +113,7 @@ install(FILES install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pipeline.cfg + ${CMAKE_CURRENT_BINARY_DIR}/pipeline.cfg.CEP4 ${CMAKE_CURRENT_BINARY_DIR}/tasks.cfg DESTINATION share/pipeline) @@ -127,6 +128,29 @@ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/pipeline.cfg.in ${CMAKE_CURRENT_BINARY_DIR}/pipeline.cfg) +# Convert configuration files through docker-template +foreach(_file ${CMAKE_CURRENT_SOURCE_DIR}/pipeline.cfg.CEP4) + # _src -> _dst + set(_src ${CMAKE_CURRENT_SOURCE_DIR}/${_file}.in_docker-template) + set(_dst ${CMAKE_CURRENT_BINARY_DIR}/${_file}) + + # add generating command, and (any) target to force the generation + # when "all" is build. + add_custom_command( + OUTPUT ${_dst} + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/docker-template < ${_src} > ${_dst} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/docker-template ${_src} ${CMAKE_CURRENT_BINARY_DIR}/versiondocker + ) + add_custom_target(${_file}_target ALL DEPENDS ${_dst}) + + # install resulting file + install(FILES + ${_dst} + DESTINATION share/pipeline + RENAME Dockerfile + ) +endforeach() + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/tasks.cfg.in ${CMAKE_CURRENT_BINARY_DIR}/tasks.cfg) diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh index 5c5de91a59e..2fdfe4fabdc 100755 --- a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -15,9 +15,10 @@ # runPipeline.sh <obsid> || pipelineAborted.sh <obsid> OBSID=$1 +shift if [ -z "$OBSID" ]; then - echo "Usage: $0 <obsid>" + echo "Usage: $0 <obsid> <pipeline parameters>" exit 1 fi @@ -35,10 +36,7 @@ getParset.py -o $OBSID >$PARSET PROGRAM_NAME=$(getparsetvalue $PARSET "ObsSW.Observation.ObservationControl.PythonControl.programName") # Run pipeline -OPTIONS=" \ - -d \ - -c ${LOFARROOT}/share/pipeline/pipeline.cfg \ - -t ${LOFARROOT}/share/pipeline/tasks.cfg" +OPTIONS=" -d $@" # Set up the environment (information to propagate to the node scripts for monitoring and logging) export LOFAR_OBSID="$OBSID" diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.thead01.cep4 b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.docker-template similarity index 85% rename from CEP/Pipeline/recipes/sip/pipeline.cfg.thead01.cep4 rename to CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.docker-template index 62ad298207d..552bfb33abf 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.thead01.cep4 +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.docker-template @@ -22,10 +22,10 @@ hdf5root = wcsroot = /opt/wcslib pythonpath = /opt/lofar/lib/python2.7/site-packages # runtime dir is a global FS (nfs, lustre) to exchange small files (parsets, vds, map files, etc) -runtime_directory = /shared/mol/regression_test/rundir +runtime_directory = /data/share/pipeline recipe_directories = [%(pythonpath)s/lofarpipe/recipes] # working dir is the local dir in which input/output dataproducts reside -working_directory = /globalhome/mol/regression_test/working_dir +working_directory = /data/scratch task_files = [%(lofarroot)s/share/pipeline/tasks.cfg] [layout] @@ -50,11 +50,9 @@ xml_stat_file = %(runtime_directory)s/%(job_name)s/logs/%(start_time)s/statistic method = none [docker] -image = lofar-patched +image = lofar-pipeline:${LOFAR_TAG} [remote] -#method = slurm_srun_cep3 -#method = ssh_docker method = custom_cmdline max_per_node = 1 @@ -85,5 +83,4 @@ max_per_node = 1 # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -#cmdline = ssh -n -tt -x localhost srun -w {host} -N 1 -n 1 --jobid={slurm_job_id} docker run -t --rm -e LUSER={uid} -w g -v /home/mol/.ssh:/home/lofar/.ssh:ro -v /globalhome/mol/regression_test:/globalhome/mol/regression_test -v /shared:/shared --net=host {docker_image} /bin/bash -c -cmdline = ssh -n -tt -x localhost srun -w {host} -N 1 -n 1 --jobid={slurm_job_id} docker run -t --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v %(working_directory)s:%(working_directory)s --net=host {docker_image} /bin/bash -c "\"{command}\"" +cmdline = ssh -n -tt -x localhost srun -w {host} -N 1 -n 1 --jobid={slurm_job_id} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v working_directory)s:%(working_directory)s -v /data:/data --net=host {docker_image} /bin/bash -c "\"{command}\"" diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index f40ca7d0613..b8afe302d2a 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -351,10 +351,11 @@ class PipelineControl(OTDBBusListener): " -e LUSER=$UID" " -v $HOME/.ssh:/home/lofar/.ssh:ro" " -e SLURM_JOB_ID=$SLURM_JOB_ID" - " runPipeline.sh {obsid}" + " runPipeline.sh {obsid} --config /opt/lofar/share/pipeline/pipeline.cfg.{cluster}" .format( obsid = treeId, tag = parset.dockerTag(), + cluster = parset.processingCluster() ), sbatch_params=sbatch_params -- GitLab From 750981197ce1aa77362d51a4f6827eb608a504ec Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 14 Mar 2016 18:48:10 +0000 Subject: [PATCH 056/933] Task #8437: Use more common .tmpl extention --- .gitattributes | 2 +- CEP/Pipeline/recipes/sip/CMakeLists.txt | 2 +- ...pipeline.cfg.CEP4.docker-template => pipeline.cfg.CEP4.tmpl} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename CEP/Pipeline/recipes/sip/{pipeline.cfg.CEP4.docker-template => pipeline.cfg.CEP4.tmpl} (100%) diff --git a/.gitattributes b/.gitattributes index e2359c94fd9..73ed20da7ce 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1614,7 +1614,7 @@ CEP/Pipeline/recipes/sip/nodes/selfcal_finalize.py eol=lf CEP/Pipeline/recipes/sip/nodes/setupparmdb.py eol=lf CEP/Pipeline/recipes/sip/nodes/setupsourcedb.py eol=lf CEP/Pipeline/recipes/sip/nodes/vdsmaker.py eol=lf -CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.docker-template -text +CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl -text CEP/Pipeline/recipes/sip/pipeline.cfg.in eol=lf CEP/Pipeline/recipes/sip/plugins/PipelineStep_addMapfile.py -text CEP/Pipeline/recipes/sip/plugins/PipelineStep_changeMapfile.py -text diff --git a/CEP/Pipeline/recipes/sip/CMakeLists.txt b/CEP/Pipeline/recipes/sip/CMakeLists.txt index dedd7ed4922..bf6eac9da74 100644 --- a/CEP/Pipeline/recipes/sip/CMakeLists.txt +++ b/CEP/Pipeline/recipes/sip/CMakeLists.txt @@ -131,7 +131,7 @@ configure_file( # Convert configuration files through docker-template foreach(_file ${CMAKE_CURRENT_SOURCE_DIR}/pipeline.cfg.CEP4) # _src -> _dst - set(_src ${CMAKE_CURRENT_SOURCE_DIR}/${_file}.in_docker-template) + set(_src ${CMAKE_CURRENT_SOURCE_DIR}/${_file}.tmpl) set(_dst ${CMAKE_CURRENT_BINARY_DIR}/${_file}) # add generating command, and (any) target to force the generation diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.docker-template b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl similarity index 100% rename from CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.docker-template rename to CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl -- GitLab From 1a323e75d0b79c821e7f56444718818d953931cd Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 15 Mar 2016 07:46:06 +0000 Subject: [PATCH 057/933] Task #8437: Fixed generation of pipeline.cfg.CEP4 --- CEP/Pipeline/recipes/sip/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/CMakeLists.txt b/CEP/Pipeline/recipes/sip/CMakeLists.txt index bf6eac9da74..e3dd9365fe6 100644 --- a/CEP/Pipeline/recipes/sip/CMakeLists.txt +++ b/CEP/Pipeline/recipes/sip/CMakeLists.txt @@ -129,7 +129,7 @@ configure_file( ${CMAKE_CURRENT_BINARY_DIR}/pipeline.cfg) # Convert configuration files through docker-template -foreach(_file ${CMAKE_CURRENT_SOURCE_DIR}/pipeline.cfg.CEP4) +foreach(_file pipeline.cfg.CEP4) # _src -> _dst set(_src ${CMAKE_CURRENT_SOURCE_DIR}/${_file}.tmpl) set(_dst ${CMAKE_CURRENT_BINARY_DIR}/${_file}) -- GitLab From bf5a5a3b283e6c183aa9637e90eef99bdf98b083 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 15 Mar 2016 09:48:22 +0000 Subject: [PATCH 058/933] Task #8437: Fixed calls to docker-template and versiondocker --- CEP/Pipeline/recipes/sip/CMakeLists.txt | 4 ++-- Docker/CMakeLists.txt | 2 +- Docker/docker-template | 29 +++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/CMakeLists.txt b/CEP/Pipeline/recipes/sip/CMakeLists.txt index e3dd9365fe6..1ef8cc9a778 100644 --- a/CEP/Pipeline/recipes/sip/CMakeLists.txt +++ b/CEP/Pipeline/recipes/sip/CMakeLists.txt @@ -138,8 +138,8 @@ foreach(_file pipeline.cfg.CEP4) # when "all" is build. add_custom_command( OUTPUT ${_dst} - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/docker-template < ${_src} > ${_dst} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/docker-template ${_src} ${CMAKE_CURRENT_BINARY_DIR}/versiondocker + COMMAND ${CMAKE_SOURCE_DIR}/Docker/docker-template -v ${CMAKE_BINARY_DIR}/Docker/versiondocker < ${_src} > ${_dst} + DEPENDS ${CMAKE_SOURCE_DIR}/Docker/docker-template ${_src} ${CMAKE_BINARY_DIR}/Docker/versiondocker ) add_custom_target(${_file}_target ALL DEPENDS ${_dst}) diff --git a/Docker/CMakeLists.txt b/Docker/CMakeLists.txt index 060eea1d0c4..8558bb5d9d0 100644 --- a/Docker/CMakeLists.txt +++ b/Docker/CMakeLists.txt @@ -42,7 +42,7 @@ foreach(_dir ${DOCKER_TEMPLATE_DIRS}) # when "all" is build. add_custom_command( OUTPUT ${_dst} - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/docker-template < ${_src} > ${_dst} + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/docker-template -v ${CMAKE_CURRENT_BINARY_DIR}/versiondocker < ${_src} > ${_dst} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/docker-template ${_src} ${CMAKE_CURRENT_BINARY_DIR}/versiondocker ) add_custom_target(${_dir}_Dockerfile_target ALL DEPENDS ${_dst}) diff --git a/Docker/docker-template b/Docker/docker-template index 61bd6cabfe1..ea50b2d002a 100755 --- a/Docker/docker-template +++ b/Docker/docker-template @@ -18,9 +18,34 @@ # ----- LOFAR_BRANCH_NAME = tags/LOFAR-Release-2_15_1 ----- # ----- LOFAR_BRANCH_NAME = UNKNOWN ----- +function usage() { + echo "$0 [-v VERSIONDOCKER]" + echo "" + echo " -v VERSIONDOCKER Provides location of 'versiondocker' executable" + echo "" + exit 1 +} + +# Defaults +VERSION_DOCKER="versiondocker" + +# Parse options +while getopts "hv:" opt; do + case $opt in + h) usage + ;; + v) VERSION_DOCKER="$OPTARG" + ;; + \?) error "Invalid option: -$OPTARG" + ;; + :) error "Option requires an argument: -$OPTARG" + ;; + esac +done +[ $OPTIND -eq 1 ] && usage + # Make sure we obtain info about the project source! -#PATH=$PATH:. -VERSION_INFO=`(versiondocker || ./versiondocker) 2>/dev/null` # in cmake, executable is in . +VERSION_INFO=`$VERSION_DOCKER` # Extract branch name w.r.t. repository root, e.g. branches/LOFAR-Task1234 export LOFAR_BRANCH_NAME=`echo "$VERSION_INFO" | perl -ne 'print "$1" if /branch += +(.+)/;'` -- GitLab From bc8fbb37ac3574c94ac65a17d7d51a36ea474446 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 15 Mar 2016 09:56:46 +0000 Subject: [PATCH 059/933] Task #8473: Do not require arguments --- Docker/docker-template | 1 - 1 file changed, 1 deletion(-) diff --git a/Docker/docker-template b/Docker/docker-template index ea50b2d002a..e2ab8ee3d5a 100755 --- a/Docker/docker-template +++ b/Docker/docker-template @@ -42,7 +42,6 @@ while getopts "hv:" opt; do ;; esac done -[ $OPTIND -eq 1 ] && usage # Make sure we obtain info about the project source! VERSION_INFO=`$VERSION_DOCKER` -- GitLab From 2b14f238f33d42cb342cf0a036f2fdfadb85163c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 15 Mar 2016 10:26:21 +0000 Subject: [PATCH 060/933] Task #8437: Proper command-line parsing in runPipeline.sh and pipelineAborted.sh, and allow parset to be provided manually --- .../recipes/sip/bin/pipelineAborted.sh | 38 +++++++++-- CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 66 +++++++++++++++---- MAC/Services/src/PipelineControl.py | 8 ++- 3 files changed, 92 insertions(+), 20 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh b/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh index f4a2bc76f2d..68031f76983 100755 --- a/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh +++ b/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh @@ -8,18 +8,44 @@ # # Syntax: # -# runPipeline.sh <obsid> || pipelineAborted.sh <obsid> +# runPipeline.sh -o <obsid> || pipelineAborted.sh -o <obsid> -OBSID=$1 +# ======= Defaults -if [ -z "$OBSID" ]; then - echo "Usage: $0 <obsid>" - exit 1 -fi +# Obs ID +OBSID= # Queue on which to post status changes SETSTATUS_BUS=lofar.otdb.setStatus +# ======= Parse command-line parameters + +function usage() { + echo "$0 -o OBSID [options]" + echo "" + echo " -o OBSID Task identifier" + echo " -b busname Bus name to post status changes on (default: $SETSTATUS_BUS)" + exit 1 +} + +while getopts "o:c:p:b:" opt; do + case $opt in + h) usage + ;; + o) OBSID="$OPTARG" + ;; + b) SETSTATUS_BUS="$OPTARG" + ;; + \?) error "Invalid option: -$OPTARG" + ;; + :) error "Option requires an argument: -$OPTARG" + ;; + esac +done +[ -z "$OBSID" ] && usage + +# ======= Run + # Mark as aborted setStatus.py -o $OBSID -s aborted -b $SETSTATUS_BUS || true diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh index 2fdfe4fabdc..74e28c521df 100755 --- a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -12,31 +12,72 @@ # # Syntax: # -# runPipeline.sh <obsid> || pipelineAborted.sh <obsid> +# runPipeline.sh -o <obsid> || pipelineAborted.sh -o <obsid> -OBSID=$1 -shift +# ======= Defaults -if [ -z "$OBSID" ]; then - echo "Usage: $0 <obsid> <pipeline parameters>" - exit 1 -fi +# Obs ID +OBSID= + +# Parset (will be requested if not set) +PARSET= + +# Location of pipeline-framework configuration file +PIPELINE_CONFIG=$LOFARROOT/share/pipeline/pipeline.cfg # Queue on which to post status changes SETSTATUS_BUS=lofar.otdb.setStatus +# ======= Parse command-line parameters + +function usage() { + echo "$0 -o OBSID [options]" + echo "" + echo " -o OBSID Task identifier" + echo " -c pipeline.cfg Override pipeline configuration file (default: $PIPELINE_CONFIG)" + echo " -p pipeline.parset Provide parset (default: request through QPID)" + echo " -b busname Bus name to post status changes on (default: $SETSTATUS_BUS)" + exit 1 +} + +while getopts "o:c:p:b:" opt; do + case $opt in + h) usage + ;; + o) OBSID="$OPTARG" + ;; + c) PIPELINE_CONFIG="$OPTARG" + ;; + p) PARSET="$OPTARG" + ;; + b) SETSTATUS_BUS="$OPTARG" + ;; + \?) error "Invalid option: -$OPTARG" + ;; + :) error "Option requires an argument: -$OPTARG" + ;; + esac +done +[ -z "$OBSID" ] && usage + +# ======= Init + # Mark as started setStatus.py -o $OBSID -s active -b $SETSTATUS_BUS || true -# Fetch parset -PARSET=${LOFARROOT}/var/run/Observation${OBSID}.parset -getParset.py -o $OBSID >$PARSET +if [ -z "$PARSET" ]; then + # Fetch parset + PARSET=${LOFARROOT}/var/run/Observation${OBSID}.parset + getParset.py -o $OBSID >$PARSET +fi + +# ======= Run # Fetch parameters from parset PROGRAM_NAME=$(getparsetvalue $PARSET "ObsSW.Observation.ObservationControl.PythonControl.programName") # Run pipeline -OPTIONS=" -d $@" +OPTIONS=" -d -c $PIPELINE_CONFIG" # Set up the environment (information to propagate to the node scripts for monitoring and logging) export LOFAR_OBSID="$OBSID" @@ -47,6 +88,8 @@ echo "Executing: ${PROGRAM_NAME} ${OPTIONS} ${PARSET}" ${PROGRAM_NAME} ${OPTIONS} ${PARSET} RESULT=$? +# ======= Fini + # Process the result setStatus.py -o $OBSID -s completing -b $SETSTATUS_BUS || true @@ -60,3 +103,4 @@ fi # Propagate result to caller exit $RESULT + diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index b8afe302d2a..c530cdbb22b 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -351,11 +351,12 @@ class PipelineControl(OTDBBusListener): " -e LUSER=$UID" " -v $HOME/.ssh:/home/lofar/.ssh:ro" " -e SLURM_JOB_ID=$SLURM_JOB_ID" - " runPipeline.sh {obsid} --config /opt/lofar/share/pipeline/pipeline.cfg.{cluster}" + " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -b {status_bus}" .format( obsid = treeId, tag = parset.dockerTag(), - cluster = parset.processingCluster() + cluster = parset.processingCluster(), + status_bus = self.setStatus_busname, ), sbatch_params=sbatch_params @@ -369,10 +370,11 @@ class PipelineControl(OTDBBusListener): "docker run --rm lofar-pipeline:{tag}" " --net=host" " -e LUSER=$UID" - " pipelineAborted.sh {obsid}" + " pipelineAborted.sh -o {obsid} -b {status_bus}" .format( obsid = treeId, tag = parset.dockerTag(), + status_bus = self.setStatus_busname, ), sbatch_params=[ -- GitLab From d813a03387836ffb65101b01e6c13b17ffe0c871 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 15 Mar 2016 10:29:46 +0000 Subject: [PATCH 061/933] Task #8437: Added Docker dependency for pipeline framework, to allow querying Docker image information during CMake build --- CEP/Pipeline/recipes/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/CMakeLists.txt b/CEP/Pipeline/recipes/CMakeLists.txt index 608dee21588..8951603a883 100644 --- a/CEP/Pipeline/recipes/CMakeLists.txt +++ b/CEP/Pipeline/recipes/CMakeLists.txt @@ -1,6 +1,6 @@ # $Id$ -lofar_package(Pipeline-Recipes 0.1) +lofar_package(Pipeline-Recipes 0.1 DEPENDS Docker) # The pipeline.cfg needs to know whether QPID is installed include(LofarFindPackage) -- GitLab From 331fd9dc26000bd178609af8b0f82ba2af759515 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 15 Mar 2016 12:31:10 +0000 Subject: [PATCH 062/933] Task #8437: Install gettext-base to add "envsubst" command, needed by "docker-template" --- Docker/lofar-pipeline/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 7bffe246927..abb3be74ed4 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -6,7 +6,7 @@ FROM lofar-base:${LOFAR_TAG} ENV AOFLAGGER_VERSION=2.7.1 # Run-time dependencies -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 && \ +RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base && \ sudo apt-get -y install python-pip python-dev && \ sudo pip install pyfits pywcs python-monetdb && \ sudo apt-get -y purge python-pip python-dev && \ -- GitLab From 5d17d3c625d538546ddf8dea5c156d479589f26a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 16 Mar 2016 11:57:21 +0000 Subject: [PATCH 063/933] Task #8437: Use "sacct" to get historical job information, and fix race condition where QUEUED is set before pipeline is actually queued --- MAC/Services/src/PipelineControl.py | 80 ++++++++++++--------------- MAC/Services/test/tPipelineControl.py | 36 ++++-------- 2 files changed, 46 insertions(+), 70 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index c530cdbb22b..de2990b039a 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -150,32 +150,37 @@ class Slurm(object): logger.debug("scancel output: %s" % (output,)) - def jobs(self): - stdout = self._runCommand("scontrol show job --oneliner") + def jobs(self, maxage=datetime.timedelta(365)): + starttime = (datetime.datetime.utcnow() - maxage).strftime("%FT%T") - if stdout == "No jobs in the system": - return {} + stdout = self._runCommand("sacct --starttime=%s --noheader --parsable2 --format=jobid,jobname" % (starttime,)) jobs = {} for l in stdout.split("\n"): # One line is one job - job_properties = {} + jobid, jobname = l.split("|") + jobs_properties = { "JobId": jobid, "JobName": jobname } - # Information is in k=v pairs - for i in l.split(): - k,v = i.split("=", 1) - job_properties[k] = v + # warn of duplicate names + if jobname in jobs: + logger.warning("Duplicate job name: %s" % (jobname,)) + jobs[jobname] = job_properties - if "JobName" not in job_properties: - logger.warning("Could not find job name in line: %s" % (l,)) - continue + return jobs - name = job_properties["JobName"] - if name in jobs: - logger.warning("Duplicate job name: %s" % (name,)) - jobs[name] = job_properties + def jobid(self, jobname): + stdout = self._runCommand("sacct --starttime=2016-01-01 --noheader --parsable2 --format=jobid --name=%s" % (jobname,)) - return jobs + if stdout == "": + return None + + lines = stdout.split("\n") + + if len(lines) > 1: + logger.warning("Duplicate job name: %s" % (jobname,)) + + # Use last occurance if there are multiple + return lines[-1] class PipelineControl(OTDBBusListener): def __init__(self, otdb_busname=None, setStatus_busname=None, **kwargs): @@ -216,19 +221,8 @@ class PipelineControl(OTDBBusListener): return True - @classmethod - def _slurmJobNames(self, parsets, allowed): - names = [] - - for p in parsets: - jobName = p.slurmJobName() - - if jobName not in allowed: - raise KeyError("No SLURM job for predecessor %d (JobName '%s')." % (p.treeId(), jobName)) - - names.append(jobName) - - return names + def _slurmJobIds(self, parsets): + return [self.slurm.jobid(p.slurmJobName()) for p in parsets] def _getParset(self, treeId): return Parset(self.parset_rpc( OtdbID=treeId, timeout=10 )[0]) @@ -293,19 +287,6 @@ class PipelineControl(OTDBBusListener): logger.info("Obtaining predecessor parsets") preparsets = self._getPredecessorParsets(parset) - # Collect SLURM job information - logger.info("Obtaining SLURM job list") - slurm_jobs = self.slurm.jobs() - - """ - Update OTDB before scheduling the SLURM jobs, - as the SLURM jobs will set the status too. - """ - - # Set OTDB status to QUEUED - logger.info("Setting status to QUEUED") - self._setStatus(treeId, "queued") - """ Schedule "docker-runPipeline.sh", which will fetch the parset and run the pipeline within a SLURM job. @@ -337,7 +318,7 @@ class PipelineControl(OTDBBusListener): if min_starttime: sbatch_params.append("--begin=%s" % (min_starttime.strftime("%FT%T"),)) - predecessor_jobs = self._slurmJobNames([x for x in preparsets if x.isPipeline()], slurm_jobs.keys()) + predecessor_jobs = self._slurmJobIds([x for x in preparsets if x.isPipeline()]) if predecessor_jobs: sbatch_params.append("--dependency=%s" % (",".join(("afterok:%s" % x for x in predecessor_jobs)),)) @@ -387,5 +368,16 @@ class PipelineControl(OTDBBusListener): ) logger.info("Scheduled SLURM job %s" % (slurm_cancel_job_id,)) + """ + Update OTDB status. Note the possible race condition + as the SLURM jobs will set the status too. + """ + + # Set OTDB status to QUEUED + # TODO: How to avoid race condition with runPipeline.sh setting the status to STARTED + # when the SLURM job starts running? + logger.info("Setting status to QUEUED") + self._setStatus(treeId, "queued") + logger.info("Pipeline processed.") diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index 4f388a788a2..c672e786938 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -58,31 +58,16 @@ class TestSlurmJobs(unittest.TestCase): def test_no_jobs(self): """ Test 'scontrol show job' output if there are no jobs. """ with patch('lofar.mac.PipelineControl.Slurm._runCommand') as MockRunSlurmCommand: - MockRunSlurmCommand.return_value = """No jobs in the system""" + MockRunSlurmCommand.return_value = "" - self.assertEqual(Slurm().jobs(), {}) + self.assertEqual(Slurm().jobid("foo"), None) def test_one_job(self): """ Test 'scontrol show job' output for a single job. """ with patch('lofar.mac.PipelineControl.Slurm._runCommand') as MockRunSlurmCommand: - MockRunSlurmCommand.return_value = """JobId=119 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901736 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:07 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:05:52 EligibleTime=2016-03-04T12:05:52 StartTime=2016-03-04T12:05:52 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7040 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" + MockRunSlurmCommand.return_value = """119""" - jobs = Slurm().jobs() - self.assertEqual(jobs["foo"]["JobName"], "foo") - self.assertEqual(jobs["foo"]["JobId"], "119") - - def test_two_jobs(self): - """ Test 'scontrol show job' output for multiple jobs. """ - with patch('lofar.mac.PipelineControl.Slurm._runCommand') as MockRunSlurmCommand: - MockRunSlurmCommand.return_value = """JobId=120 JobName=foo UserId=mol(7261) GroupId=mol(7261) Priority=4294901735 Nice=0 Account=(null) QOS=(null) JobState=RUNNING Reason=None Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:17 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:53 EligibleTime=2016-03-04T12:09:53 StartTime=2016-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=tcpu[01-02] BatchHost=tcpu01 NumNodes=2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,mem=6000,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=3000M MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0 - JobId=121 JobName=bar UserId=mol(7261) GroupId=mol(7261) Priority=4294901734 Nice=0 Account=(null) QOS=(null) JobState=PENDING Reason=Resources Dependency=(null) Requeue=1 Restarts=0 BatchFlag=0 Reboot=0 ExitCode=0:0 RunTime=00:00:00 TimeLimit=UNLIMITED TimeMin=N/A SubmitTime=2016-03-04T12:09:59 EligibleTime=2016-03-04T12:09:59 StartTime=2017-03-04T12:09:53 EndTime=Unknown PreemptTime=None SuspendTime=None SecsPreSuspend=0 Partition=cpu AllocNode:Sid=thead01:7250 ReqNodeList=(null) ExcNodeList=(null) NodeList=(null) NumNodes=2-2 NumCPUs=2 CPUs/Task=1 ReqB:S:C:T=0:0:*:* TRES=cpu=2,node=2 Socks/Node=* NtasksPerN:B:S:C=0:0:*:* CoreSpec=* MinCPUsNode=1 MinMemoryNode=0 MinTmpDiskNode=0 Features=(null) Gres=(null) Reservation=(null) Shared=OK Contiguous=0 Licenses=(null) Network=(null) Command=(null) WorkDir=/home/mol Power= SICP=0""" - - jobs = Slurm().jobs() - self.assertEqual(jobs["foo"]["JobName"], "foo") - self.assertEqual(jobs["foo"]["JobId"], "120") - - self.assertEqual(jobs["bar"]["JobName"], "bar") - self.assertEqual(jobs["bar"]["JobId"], "121") + self.assertEqual(Slurm().jobid("foo"), "119") class TestPipelineControlClassMethods(unittest.TestCase): def test_shouldHandle(self): @@ -124,13 +109,12 @@ class TestPipelineControl(unittest.TestCase): # Return job ID return "42" - def jobs(self): - return { - "1": { "JobName": "1" }, - "2": { "JobName": "2" }, - "3": { "JobName": "3" }, - # "4" is an observation, so no SLURM job - } + def jobid(self, jobname): + if jobname in ["1", "2", "3"]: + return jobname + + # "4" is an observation, so no SLURM job + return None patcher = patch('lofar.mac.PipelineControl.Slurm') patcher.start().side_effect = MockSlurm -- GitLab From 4a3a0fc395e4067bdc4a7e9ba2842e6f95d2f905 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 23 Mar 2016 13:46:20 +0000 Subject: [PATCH 064/933] Task #8437: Added OTDB_Services dependency to Offline to provide setStatus.py and getParset.py to runPipeline.py --- SubSystems/Offline/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SubSystems/Offline/CMakeLists.txt b/SubSystems/Offline/CMakeLists.txt index f11418570ff..1866393aea4 100644 --- a/SubSystems/Offline/CMakeLists.txt +++ b/SubSystems/Offline/CMakeLists.txt @@ -1,4 +1,4 @@ # $Id$ -lofar_package(Offline DEPENDS CEP MSLofar StaticMetaData MessageBus) +lofar_package(Offline DEPENDS CEP MSLofar StaticMetaData MessageBus OTDB_Services) -- GitLab From 6f0114a7be94e2b81f860f2bf8b431ef1d3517d6 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 23 Mar 2016 14:11:09 +0000 Subject: [PATCH 065/933] Task #8437: Actually allow -h option --- CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh index 74e28c521df..8746f119cae 100755 --- a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -40,7 +40,7 @@ function usage() { exit 1 } -while getopts "o:c:p:b:" opt; do +while getopts "ho:c:p:b:" opt; do case $opt in h) usage ;; -- GitLab From 1855c4d1c31f6f2e45fa5f7e10cdc9ac5f313b50 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 23 Mar 2016 14:33:07 +0000 Subject: [PATCH 066/933] Task #8437: Corrected key name for python program to start --- CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh index 8746f119cae..780ef6ab669 100755 --- a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -74,7 +74,7 @@ fi # ======= Run # Fetch parameters from parset -PROGRAM_NAME=$(getparsetvalue $PARSET "ObsSW.Observation.ObservationControl.PythonControl.programName") +PROGRAM_NAME=$(getparsetvalue $PARSET "ObsSW.Observation.ObservationControl.PythonControl.pythonProgram") # Run pipeline OPTIONS=" -d -c $PIPELINE_CONFIG" -- GitLab From e6b3f1f28251e2b0e72a4d82d563c100f3b491f4 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 23 Mar 2016 14:33:30 +0000 Subject: [PATCH 067/933] Task #8437: Fixes for docker command line, and use of correct key for obs id --- MAC/Services/src/PipelineControl.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index de2990b039a..930181c8a80 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -129,7 +129,7 @@ class Parset(dict): return str(self.treeId()) def treeId(self): - return int(self[PARSET_PREFIX + "Observation.ObsID"]) + return int(self[PARSET_PREFIX + "Observation.otdbID"]) class Slurm(object): def __init__(self, headnode="head01.cep4"): @@ -326,12 +326,13 @@ class PipelineControl(OTDBBusListener): logger.info("Scheduling SLURM job for runPipeline.sh") slurm_job_id = self.slurm.schedule(parset.slurmJobName(), - "docker run --rm lofar-pipeline:{tag}" + "docker run --rm" " --net=host" " -v /data:/data" " -e LUSER=$UID" " -v $HOME/.ssh:/home/lofar/.ssh:ro" " -e SLURM_JOB_ID=$SLURM_JOB_ID" + " lofar-pipeline:{tag}" " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -b {status_bus}" .format( obsid = treeId, @@ -348,9 +349,10 @@ class PipelineControl(OTDBBusListener): logger.info("Scheduling SLURM job for pipelineAborted.sh") slurm_cancel_job_id = self.slurm.schedule("%s-aborted" % parset.slurmJobName(), - "docker run --rm lofar-pipeline:{tag}" + "docker run --rm" " --net=host" " -e LUSER=$UID" + " lofar-pipeline:{tag}" " pipelineAborted.sh -o {obsid} -b {status_bus}" .format( obsid = treeId, -- GitLab From 8965313ac7c581cdc29817c2373ad0a75af5bb53 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 23 Mar 2016 15:36:23 +0000 Subject: [PATCH 068/933] Task #8437: Do not specify target host on CEP4, since the target host is symbolic for global fs systems --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 552bfb33abf..90d8a0c633f 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -69,8 +69,9 @@ max_per_node = 1 # * pseudo-tty to prevent buffering logs (ssh -tt, docker -t) # -# host -> worker node: srun -w {host} -N 1 -n 1 --jobid={slurm_job_id} -# Needs the specific host, because the test system does not have a global file system. +# host -> worker node: srun -N 1 -n 1 --jobid={slurm_job_id} +# (Add -w {host} for systems that do not have a global file system, to force job +# execution on the host that contains the data) # # worker node -> container: docker run -t --rm -e LUSER={uid} -w g -v /home/mol/.ssh:/home/lofar/.ssh:ro -v /globalhome/mol/regression_test:/globalhome/mol/regression_test -v /shared:/shared --net=host {docker_image} # Starts the container on the worker node, with pretty much the same parameters as the master container: @@ -83,4 +84,4 @@ max_per_node = 1 # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun -w {host} -N 1 -n 1 --jobid={slurm_job_id} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v working_directory)s:%(working_directory)s -v /data:/data --net=host {docker_image} /bin/bash -c "\"{command}\"" +cmdline = ssh -n -tt -x localhost srun -N 1 -n 1 --jobid={slurm_job_id} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v working_directory)s:%(working_directory)s -v /data:/data --net=host {docker_image} /bin/bash -c "\"{command}\"" -- GitLab From f4b839f71666aed548fc1303792ee5817fa8efd6 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 23 Mar 2016 15:39:03 +0000 Subject: [PATCH 069/933] Task #8437: Fixed typo in marshalling working directory through docker+slurm --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 90d8a0c633f..a7ba4205870 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -84,4 +84,4 @@ max_per_node = 1 # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun -N 1 -n 1 --jobid={slurm_job_id} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v working_directory)s:%(working_directory)s -v /data:/data --net=host {docker_image} /bin/bash -c "\"{command}\"" +cmdline = ssh -n -tt -x localhost srun -N 1 -n 1 --jobid={slurm_job_id} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v %(working_directory)s:%(working_directory)s -v /data:/data --net=host {docker_image} /bin/bash -c "\"{command}\"" -- GitLab From 821b237d0dafde3573754ed0b1612343187ee1ba Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 23 Mar 2016 21:16:12 +0000 Subject: [PATCH 070/933] Task #8437: Use multiple nodes (since all node names are currently replaced by "CEP4") --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 1 - 1 file changed, 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index a7ba4205870..54fe75a7d24 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -54,7 +54,6 @@ image = lofar-pipeline:${LOFAR_TAG} [remote] method = custom_cmdline -max_per_node = 1 # We take the following path to start a remote container: # -- GitLab From 62bc3e19cc9f6638efe6fb718b9031a6246049aa Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 23 Mar 2016 21:20:16 +0000 Subject: [PATCH 071/933] Task #8437: Do not fill /data/scratch directly, but annotate our dirs instead --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 54fe75a7d24..3ffec34db53 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -25,7 +25,7 @@ pythonpath = /opt/lofar/lib/python2.7/site-packages runtime_directory = /data/share/pipeline recipe_directories = [%(pythonpath)s/lofarpipe/recipes] # working dir is the local dir in which input/output dataproducts reside -working_directory = /data/scratch +working_directory = /data/scratch/pipeline task_files = [%(lofarroot)s/share/pipeline/tasks.cfg] [layout] -- GitLab From 8769e8461c53032652631ac52c16861206822106 Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Thu, 24 Mar 2016 10:05:45 +0000 Subject: [PATCH 072/933] Task #9189: Merging forgotten changes from branch 2.15 from trunk into release branch --- CEP/DP3/AOFlagger/src/CMakeLists.txt | 2 +- .../StaticMetaData/RSPConnections_Cobalt.dat | 19 +++++-------------- .../StaticMetaData/RSPConnections_local.dat | 14 ++++++++++++++ .../default/StationStreams.parset | 12 ++++++------ SAS/OTDB/sql/create_OTDB.sql | 2 +- SAS/XML_generator/src/xmlgen.py | 2 ++ 6 files changed, 29 insertions(+), 22 deletions(-) diff --git a/CEP/DP3/AOFlagger/src/CMakeLists.txt b/CEP/DP3/AOFlagger/src/CMakeLists.txt index e1bbc56a294..831daed3c8f 100644 --- a/CEP/DP3/AOFlagger/src/CMakeLists.txt +++ b/CEP/DP3/AOFlagger/src/CMakeLists.txt @@ -22,7 +22,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2") include(CheckIncludeFileCXX) -FIND_PATH(BOOST_ASIO_H_FOUND "boost/asio.hpp HINTS ${BOOST_ROOT_DIR} PATH_SUFFIXES include") +FIND_PATH(BOOST_ASIO_H_FOUND "boost/asio.hpp" HINTS ${BOOST_ROOT_DIR} PATH_SUFFIXES include) if(BOOST_ASIO_H_FOUND) message(STATUS "Boost ASIO library found.") else() diff --git a/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat b/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat index af07cb061d3..d019ae7d8bf 100644 --- a/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat +++ b/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat @@ -173,17 +173,8 @@ UK608 RSP_0 cbt005-10GB04 10.214.1.105 A0:36:9F:1F:79:E2 DE609 RSP_0 cbt008-10GB04 10.200.91.108 A0:36:9F:1F:7B:6A DE609 RSP_0 cbt008-10GB04 10.200.92.108 A0:36:9F:1F:7B:6A -PL610 RSP_0 cbt003-10GB04 10.211.1.103 A0:36:9F:1F:7B:42 -PL610 RSP_0 cbt003-10GB04 10.212.1.103 A0:36:9F:1F:7B:42 -PL610 RSP_0 cbt003-10GB04 10.213.1.103 A0:36:9F:1F:7B:42 -PL610 RSP_0 cbt003-10GB04 10.214.1.103 A0:36:9F:1F:7B:42 - -PL611 RSP_0 cbt004-10GB04 10.211.1.104 A0:36:9F:1F:7A:06 -PL611 RSP_0 cbt004-10GB04 10.212.1.104 A0:36:9F:1F:7A:06 -PL611 RSP_0 cbt004-10GB04 10.213.1.104 A0:36:9F:1F:7A:06 -PL611 RSP_0 cbt004-10GB04 10.214.1.104 A0:36:9F:1F:7A:06 - -PL612 RSP_0 cbt005-10GB04 10.211.1.105 A0:36:9F:1F:79:E2 -PL612 RSP_0 cbt005-10GB04 10.212.1.105 A0:36:9F:1F:79:E2 -PL612 RSP_0 cbt005-10GB04 10.213.1.105 A0:36:9F:1F:79:E2 -PL612 RSP_0 cbt005-10GB04 10.214.1.105 A0:36:9F:1F:79:E2 +PL610 RSP_0 cbt003-10GB04 10.220.11.103 A0:36:9F:1F:7B:42 + +PL611 RSP_0 cbt004-10GB04 10.220.41.104 A0:36:9F:1F:7A:06 + +PL612 RSP_0 cbt005-10GB04 10.220.61.105 A0:36:9F:1F:79:E2 diff --git a/MAC/Deployment/data/StaticMetaData/RSPConnections_local.dat b/MAC/Deployment/data/StaticMetaData/RSPConnections_local.dat index 9137dc62491..1d106225d4d 100644 --- a/MAC/Deployment/data/StaticMetaData/RSPConnections_local.dat +++ b/MAC/Deployment/data/StaticMetaData/RSPConnections_local.dat @@ -60,3 +60,17 @@ FI609_01 10.212.9.42 00:1B:21:B5:31:25 eth1-PC1 FI609_02 10.213.9.42 00:1B:21:B5:31:26 eth5-PC2 FI609_03 10.214.9.42 00:1B:21:B5:31:27 eth4-PC3 +PL610_00 10.170.0.30 00:12:F2:C6:BB:00 R00-BG1-PL610 +PL610_01 10.170.0.30 00:12:F2:C6:BB:00 R00-BG1-PL610 +PL610_02 10.170.0.30 00:12:F2:C6:BB:00 R00-BG1-PL610 +PL610_03 10.170.0.30 00:12:F2:C6:BB:00 R00-BG1-PL610 + +PL611_00 10.170.0.30 00:12:F2:C6:BB:00 R00-BG1-PL611 +PL611_01 10.170.0.30 00:12:F2:C6:BB:00 R00-BG1-PL611 +PL611_02 10.170.0.30 00:12:F2:C6:BB:00 R00-BG1-PL611 +PL611_03 10.170.0.30 00:12:F2:C6:BB:00 R00-BG1-PL611 + +PL612_00 10.170.0.30 90:1B:0E:60:33:F4 LOFARPL612_1 +PL612_01 10.170.0.30 90:1B:0E:60:33:F4 LOFARPL612_1 +PL612_02 10.170.0.30 90:1B:0E:60:33:F4 LOFARPL612_1 +PL612_03 10.170.0.30 90:1B:0E:60:33:F4 LOFARPL612_1 \ No newline at end of file diff --git a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset index cdc1ca47a95..a300bea9bc7 100644 --- a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset +++ b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset @@ -219,17 +219,17 @@ PIC.Core.FR606HBA.RSP.ports = [udp:10.211.1.105:16060, udp:10.212.1.105:1606 PIC.Core.FR606HBA.RSP.receiver = cbt005_1 PIC.Core.FR606LBA.RSP.ports = [udp:10.211.1.105:16060, udp:10.212.1.105:16061, udp:10.213.1.105:16062, udp:10.214.1.105:16063] PIC.Core.FR606LBA.RSP.receiver = cbt005_1 -PIC.Core.PL610HBA.RSP.ports = [udp:10.211.1.103:16100, udp:10.212.1.103:16101, udp:10.213.1.103:16102, udp:10.214.1.103:16103] +PIC.Core.PL610HBA.RSP.ports = [udp:10.220.11.103:16100, udp:10.220.11.103:16101, udp:10.220.11.103:16102, udp:10.220.11.103:16103] PIC.Core.PL610HBA.RSP.receiver = cbt003_1 -PIC.Core.PL610LBA.RSP.ports = [udp:10.211.1.103:16100, udp:10.212.1.103:16101, udp:10.213.1.103:16102, udp:10.214.1.103:16103] +PIC.Core.PL610LBA.RSP.ports = [udp:10.220.11.103:16100, udp:10.220.11.103:16101, udp:10.220.11.103:16102, udp:10.220.11.103:16103] PIC.Core.PL610LBA.RSP.receiver = cbt003_1 -PIC.Core.PL611HBA.RSP.ports = [udp:10.211.1.104:16110, udp:10.212.1.104:16111, udp:10.213.1.104:16112, udp:10.214.1.104:16113] +PIC.Core.PL611HBA.RSP.ports = [udp:10.220.41.104:16110, udp:10.220.41.104:16111, udp:10.220.41.104:16112, udp:10.220.41.104:16113] PIC.Core.PL611HBA.RSP.receiver = cbt004_1 -PIC.Core.PL611LBA.RSP.ports = [udp:10.211.1.104:16110, udp:10.212.1.104:16111, udp:10.213.1.104:16112, udp:10.214.1.104:16113] +PIC.Core.PL611LBA.RSP.ports = [udp:10.220.41.104:16110, udp:10.220.41.104:16111, udp:10.220.41.104:16112, udp:10.220.41.104:16113] PIC.Core.PL611LBA.RSP.receiver = cbt004_1 -PIC.Core.PL612HBA.RSP.ports = [udp:10.211.1.105:16120, udp:10.212.1.105:16121, udp:10.213.1.105:16122, udp:10.214.1.105:16123] +PIC.Core.PL612HBA.RSP.ports = [udp:10.220.61.105:16120, udp:10.220.61.105:16121, udp:10.220.61.105:16122, udp:10.220.61.105:16123] PIC.Core.PL612HBA.RSP.receiver = cbt005_1 -PIC.Core.PL612LBA.RSP.ports = [udp:10.211.1.105:16120, udp:10.212.1.105:16121, udp:10.213.1.105:16122, udp:10.214.1.105:16123] +PIC.Core.PL612LBA.RSP.ports = [udp:10.220.61.105:16120, udp:10.220.61.105:16121, udp:10.220.61.105:16122, udp:10.220.61.105:16123] PIC.Core.PL612LBA.RSP.receiver = cbt005_1 PIC.Core.RS104HBA.RSP.ports = [udp:cbt002-10GB03:11040, udp:cbt002-10GB03:11041, udp:cbt002-10GB03:11042, udp:cbt002-10GB03:11043] PIC.Core.RS104HBA.RSP.receiver = cbt002_1 diff --git a/SAS/OTDB/sql/create_OTDB.sql b/SAS/OTDB/sql/create_OTDB.sql index ac7c834dade..63fcb2d414a 100644 --- a/SAS/OTDB/sql/create_OTDB.sql +++ b/SAS/OTDB/sql/create_OTDB.sql @@ -7,7 +7,6 @@ \i create_types.sql \i create_log_system.sql - \i security_func.sql \i misc_func.sql @@ -15,6 +14,7 @@ \i setTreeState_func.sql \i addTreeState_func.sql \i getStateList_func.sql +\i getStateChanges_func.sql -- OTDBConnection \i getTreeList_func.sql diff --git a/SAS/XML_generator/src/xmlgen.py b/SAS/XML_generator/src/xmlgen.py index 33ce1777e5a..04884462d4f 100755 --- a/SAS/XML_generator/src/xmlgen.py +++ b/SAS/XML_generator/src/xmlgen.py @@ -1659,6 +1659,8 @@ def checkSettings(settings, blockNr): settings["do_imaging"] = False if settings["processing"] == "LongBaseline": #TODO issue 8357, needs better function name + if (settings["calibration_mode"] == "none"): + raise GenException("LongBaseline does not work with calibration=none for BLOCK: %i" % blockNr) determineNrImages(settings["targetBeams"], settings["subbandsPerSubbandGroup"], "subbandsPerSubbandGroup") determineNrImages(settings["targetBeams"], settings["subbandGroupsPerMS"], "subbandGroupsPerMS") -- GitLab From 48c60ad298561c9c1315a4e70b7471d5f69cbdf4 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 24 Mar 2016 16:08:49 +0000 Subject: [PATCH 073/933] Task #8437: Create /opt/lofar/var/{log,run} in the Docker image, and transfer ownership of /opt to calling user to allow him to write to /opt/lofar/var. --- Docker/lofar-base/chuser.sh | 3 +++ Docker/lofar-outputproc/Dockerfile.tmpl | 1 + Docker/lofar-pipeline/Dockerfile.tmpl | 1 + 3 files changed, 5 insertions(+) diff --git a/Docker/lofar-base/chuser.sh b/Docker/lofar-base/chuser.sh index 294e58c7d30..53911fb5a0e 100755 --- a/Docker/lofar-base/chuser.sh +++ b/Docker/lofar-base/chuser.sh @@ -16,6 +16,9 @@ if [ -n "${LUSER}" ]; then # Set ownership of home dir to new user chown --from=${OLDID} -R ${LUSER}:${LGROUP} ${HOME} + + # Set ownership of installed software to new user + chown --from=${OLDID} -R ${LUSER}:${LGROUP} /opt fi # Switch to the updated user diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index 348423a922f..af9037e5657 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -26,6 +26,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y su cd ${INSTALLDIR}/lofar/build/gnu_opt && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make install && \ + bash -c "mkdir -p ${INSTALLDIR}/lofar/var/{log,run}" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ sudo apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev && \ diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index abb3be74ed4..c9037d3c814 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -51,6 +51,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y su cd ${INSTALLDIR}/lofar/build/gnu_opt && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make install && \ + bash -c "mkdir -p ${INSTALLDIR}/lofar/var/{log,run}" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ sudo apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ -- GitLab From 847c82c8f9bc59d8269dd4b2923de5dd3c53d004 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 25 Mar 2016 06:24:15 +0000 Subject: [PATCH 074/933] Task #8437: Parse job id returned by sbatch correctly --- MAC/Services/src/PipelineControl.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 930181c8a80..06dc9ee314b 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -70,6 +70,7 @@ from lofar.messaging.RPC import RPC, RPCTimeoutException import subprocess import datetime import os +import re import logging logger = logging.getLogger(__name__) @@ -139,11 +140,18 @@ class Slurm(object): cmdline = "ssh %s %s" % (self.headnode, cmdline) runCommand(cmdline) - def schedule(self, jobName, cmdline, sbatch_params=None): + def submit(self, jobName, cmdline, sbatch_params=None): if sbatch_params is None: sbatch_params = [] - self._runCommand("sbatch --job-name=%s %s bash -c '%s'" % (jobName, " ".join(sbatch_params), cmdline)) + stdout = self._runCommand("sbatch --job-name=%s %s bash -c '%s'" % (jobName, " ".join(sbatch_params), cmdline)) + + # Returns "Submitted batch job 3" -- extract ID + match = re.search("Submitted batch job (\d+)", stdout) + if not match: + return None + + return match.group(1) def cancel(self, jobName): stdout = self._runCommand("scancel --jobname %s" % (jobName,)) @@ -324,7 +332,7 @@ class PipelineControl(OTDBBusListener): # Schedule runPipeline.sh logger.info("Scheduling SLURM job for runPipeline.sh") - slurm_job_id = self.slurm.schedule(parset.slurmJobName(), + slurm_job_id = self.slurm.submit(parset.slurmJobName(), "docker run --rm" " --net=host" @@ -347,7 +355,7 @@ class PipelineControl(OTDBBusListener): # Schedule pipelineAborted.sh logger.info("Scheduling SLURM job for pipelineAborted.sh") - slurm_cancel_job_id = self.slurm.schedule("%s-aborted" % parset.slurmJobName(), + slurm_cancel_job_id = self.slurm.submit("%s-aborted" % parset.slurmJobName(), "docker run --rm" " --net=host" -- GitLab From 86cd04ccc554539ca1004bc12595ffb70fa3af13 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 25 Mar 2016 07:25:31 +0000 Subject: [PATCH 075/933] Task #8437: Allow more parallellism on CEP4 by using a special tasks.cfg --- .gitattributes | 1 + CEP/Pipeline/recipes/sip/CMakeLists.txt | 5 + .../recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in | 182 ++++++++++++++++++ 4 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in diff --git a/.gitattributes b/.gitattributes index 73ed20da7ce..489abf44b28 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1629,6 +1629,7 @@ CEP/Pipeline/recipes/sip/skymodels/3C380.skymodel -text CEP/Pipeline/recipes/sip/skymodels/3C48.skymodel eol=lf CEP/Pipeline/recipes/sip/skymodels/Ateam_LBA_CC.skymodel eol=lf CEP/Pipeline/recipes/sip/skymodels/CygA.skymodel -text +CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in eol=lf CEP/Pipeline/recipes/sip/tasks.cfg.in eol=lf CEP/Pipeline/test/CMakeLists.txt eol=lf CEP/Pipeline/test/__init__.py eol=lf diff --git a/CEP/Pipeline/recipes/sip/CMakeLists.txt b/CEP/Pipeline/recipes/sip/CMakeLists.txt index 1ef8cc9a778..b034415be1d 100644 --- a/CEP/Pipeline/recipes/sip/CMakeLists.txt +++ b/CEP/Pipeline/recipes/sip/CMakeLists.txt @@ -115,6 +115,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pipeline.cfg ${CMAKE_CURRENT_BINARY_DIR}/pipeline.cfg.CEP4 ${CMAKE_CURRENT_BINARY_DIR}/tasks.cfg + ${CMAKE_CURRENT_BINARY_DIR}/tasks.cfg.CEP4 DESTINATION share/pipeline) # PIPELINE_FEEDBACK_METHOD is used in pipeline.cfg.in to enable/disable qpid feedback @@ -155,3 +156,7 @@ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/tasks.cfg.in ${CMAKE_CURRENT_BINARY_DIR}/tasks.cfg) +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/tasks.cfg.CEP4.in + ${CMAKE_CURRENT_BINARY_DIR}/tasks.cfg.CEP4) + diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 3ffec34db53..afcd2a70cd0 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -26,7 +26,7 @@ runtime_directory = /data/share/pipeline recipe_directories = [%(pythonpath)s/lofarpipe/recipes] # working dir is the local dir in which input/output dataproducts reside working_directory = /data/scratch/pipeline -task_files = [%(lofarroot)s/share/pipeline/tasks.cfg] +task_files = [%(lofarroot)s/share/pipeline/tasks.cfg.CEP4] [layout] job_directory = %(runtime_directory)s/%(job_name)s diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in new file mode 100644 index 00000000000..c1cb1da86e1 --- /dev/null +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in @@ -0,0 +1,182 @@ +[ndppp] +recipe = dppp +executable = %(lofarroot)s/bin/NDPPP +dry_run = False +mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/dppp.mapfile +parset = %(runtime_directory)s/%(job_name)s/parsets/NDPPP.parset +nproc = 0 +nthreads = 8 +clobber = False + +[vdsreader] +recipe = vdsreader +gvds = %(runtime_directory)s/%(job_name)s/vds/%(job_name)s.gvds + +[setupparmdb] +recipe = setupparmdb +executable = %(lofarroot)s/bin/parmdbm +mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/parmdb.mapfile +nproc = 0 + +[setupsourcedb] +recipe = setupsourcedb +executable = %(lofarroot)s/bin/makesourcedb +mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/sky.mapfile +nproc = 0 + +[vdsmaker] +recipe = vdsmaker +directory = %(runtime_directory)s/%(job_name)s/vds +gvds = %(runtime_directory)s/%(job_name)s/vds/%(job_name)s.gvds +makevds = %(lofarroot)s/bin/makevds +combinevds = %(lofarroot)s/bin/combinevds +nproc = 0 + +[new_bbs] +recipe = new_bbs +control_exec = %(lofarroot)s/bin/GlobalControl +kernel_exec = %(lofarroot)s/bin/KernelControl +parset = %(runtime_directory)s/%(job_name)s/parsets/BBS.parset +gvds = %(runtime_directory)s/%(job_name)s/vds/%(job_name)s.gvds +db_key = %(job_name)s +db_host = ldb001 +db_user = postgres +db_name = $ENV{USER} +instrument_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/instrument.mapfile +sky_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/sky.mapfile +data_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/bbs.mapfile + +[gainoutliercorrection] +recipe = gainoutliercorrection +executable = %(lofarroot)s/bin/parmexportcal +mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/instrument.mapfile + +[rficonsole] +recipe = rficonsole +executable = %(lofarroot)s/bin/rficonsole +nproc = 0 + +[get_metadata] +recipe = get_metadata + +[imager_prepare] +recipe = imager_prepare +ndppp_exec = %(lofarroot)s/bin/NDPPP +asciistat_executable = %(lofarroot)s/bin/asciistats.py +statplot_executable = %(lofarroot)s/bin/statsplot.py +msselect_executable = %(casaroot)s/bin/msselect +rficonsole_executable = %(lofarroot)s/bin/rficonsole + +[long_baseline] +recipe = long_baseline +ndppp_exec = %(lofarroot)s/bin/NDPPP +asciistat_executable = %(lofarroot)s/bin/asciistats.py +statplot_executable = %(lofarroot)s/bin/statsplot.py +msselect_executable = %(casaroot)s/bin/msselect +rficonsole_executable = %(lofarroot)s/bin/rficonsole +nproc = 0 + +[imager_awimager] +recipe = imager_awimager +executable = %(lofarroot)s/bin/awimager + +[imager_create_dbs] +recipe = imager_create_dbs +parmdb_executable = %(lofarroot)s/bin/parmdbm +makesourcedb_path = %(lofarroot)s/bin/makesourcedb + +[imager_bbs] +recipe = imager_bbs +bbs_executable = %(lofarroot)s/bin/bbs-reducer + +[imager_source_finding] +recipe = imager_source_finding +makesourcedb_path = %(lofarroot)s/bin/makesourcedb + +[imager_finalize] +recipe = imager_finalize +fillrootimagegroup_exec = %(lofarroot)s/bin/fillRootImageGroup + +[copier] +recipe = copier +mapfiles_dir = %(runtime_directory)s/%(job_name)s/mapfiles + +[bbs_reducer] +recipe = bbs_reducer +executable = %(lofarroot)s/bin/bbs-reducer +parset = %(runtime_directory)s/%(job_name)s/parsets/bbs.parset +instrument_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/instrument.mapfile +sky_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/sky.mapfile +data_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/bbs.mapfile + +[selfcal_awimager] +recipe = selfcal_awimager +executable = %(lofarroot)s/bin/awimager + +[selfcal_bbs] +recipe = selfcal_bbs +bbs_executable = %(lofarroot)s/bin/bbs-reducer + +[selfcal_finalize] +recipe = selfcal_finalize +fillrootimagegroup_exec = %(lofarroot)s/bin/fillRootImageGroup +msselect_executable = %(casaroot)s/bin/msselect + +# below are tasks for the generic pipeline +[executable_args] +recipe = executable_args +#executable = /over/write/me +arguments = [] +#parset = parsetifyouhaveone +inputkey = +outputkey = +#mapfile_in = '' +mapfile_out = +skip_infile = False +skip_outfile = False +inplace = False +outputsuffixes = [] +parsetasfile = False +#args_format=gnu + +[casapy-imager] +recipe = executable_args +parsetasfile = True +#executable = /path/to/your/casapy/bin/casa +outputsuffixes = [flux,image,model,residual,psf] +nodescript = executable_casa + +[pythonplugin] +recipe = executable_args +nodescript = python_plugin + +[python-calibrate-stand-alone] +recipe=executable_args +nodescript=calibrate-stand-alone +executable=%(lofarroot)s/bin/bbs-reducer +parsetasfile=True + +[calibrate-stand-alone] +recipe = executable_args +executable = %(lofarroot)s/bin/calibrate-stand-alone + +[dppp] +recipe = executable_args +parsetasfile = False +executable = %(lofarroot)s/bin/NDPPP +outputsuffixes = [] +args_format=lofar +outputkey=msout + +[awimager] +recipe = executable_args +parsetasfile = True +executable = %(lofarroot)s/bin/awimager +outputsuffixes = [.model, .model.corr, .residual, .residual.corr, .restored, .restored.corr, .psf] +max_per_node = 0 +args_format=lofar +outputkey=image + +[rficonsole] +recipe = executable_args +executable = %(lofarroot)s/bin/rficonsole -- GitLab From 5092934dc08e29502e9758cb2591bc4b7b201329 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 25 Mar 2016 07:32:02 +0000 Subject: [PATCH 076/933] Task #8437: Support release builds from branches/ dir --- Docker/docker-template | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Docker/docker-template b/Docker/docker-template index e2ab8ee3d5a..a5eed91ba54 100755 --- a/Docker/docker-template +++ b/Docker/docker-template @@ -61,7 +61,9 @@ export LOFAR_BRANCH_URL="https://svn.astron.nl/LOFAR/${LOFAR_BRANCH_NAME}" case "${LOFAR_BRANCH_NAME}" in trunk) export LOFAR_TAG=trunk ;; - tags/*) export LOFAR_TAG=${LOFAR_BRANCH_NAME##tags/LOFAR-Release-} ;; + */LOFAR-Release-*) + # support releases in both tags/ and branches/ + export LOFAR_TAG=${LOFAR_BRANCH_NAME##*/LOFAR-Release-} ;; branches/*) export LOFAR_TAG=${LOFAR_BRANCH_NAME##branches/*Task} ;; *) export LOFAR_TAG=latest ;; esac -- GitLab From 40cfaa015a9ef982fef4641f0eb150afdcb9c343 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 25 Mar 2016 08:18:19 +0000 Subject: [PATCH 077/933] Task #8437: Only put overriding keys in tasks.cfg.CEP4 --- .../recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in | 159 +----------------- 2 files changed, 3 insertions(+), 158 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index afcd2a70cd0..9bb7b776c8d 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -26,7 +26,7 @@ runtime_directory = /data/share/pipeline recipe_directories = [%(pythonpath)s/lofarpipe/recipes] # working dir is the local dir in which input/output dataproducts reside working_directory = /data/scratch/pipeline -task_files = [%(lofarroot)s/share/pipeline/tasks.cfg.CEP4] +task_files = [%(lofarroot)s/share/pipeline/tasks.cfg, %(lofarroot)s/share/pipeline/tasks.cfg.CEP4] [layout] job_directory = %(runtime_directory)s/%(job_name)s diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in index c1cb1da86e1..e706c1afdc3 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in @@ -1,182 +1,27 @@ [ndppp] -recipe = dppp -executable = %(lofarroot)s/bin/NDPPP -dry_run = False -mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/dppp.mapfile -parset = %(runtime_directory)s/%(job_name)s/parsets/NDPPP.parset nproc = 0 nthreads = 8 -clobber = False - -[vdsreader] -recipe = vdsreader -gvds = %(runtime_directory)s/%(job_name)s/vds/%(job_name)s.gvds [setupparmdb] -recipe = setupparmdb -executable = %(lofarroot)s/bin/parmdbm -mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/parmdb.mapfile nproc = 0 [setupsourcedb] -recipe = setupsourcedb -executable = %(lofarroot)s/bin/makesourcedb -mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/sky.mapfile nproc = 0 [vdsmaker] -recipe = vdsmaker -directory = %(runtime_directory)s/%(job_name)s/vds -gvds = %(runtime_directory)s/%(job_name)s/vds/%(job_name)s.gvds -makevds = %(lofarroot)s/bin/makevds -combinevds = %(lofarroot)s/bin/combinevds nproc = 0 -[new_bbs] -recipe = new_bbs -control_exec = %(lofarroot)s/bin/GlobalControl -kernel_exec = %(lofarroot)s/bin/KernelControl -parset = %(runtime_directory)s/%(job_name)s/parsets/BBS.parset -gvds = %(runtime_directory)s/%(job_name)s/vds/%(job_name)s.gvds -db_key = %(job_name)s -db_host = ldb001 -db_user = postgres -db_name = $ENV{USER} -instrument_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/instrument.mapfile -sky_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/sky.mapfile -data_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/bbs.mapfile - -[gainoutliercorrection] -recipe = gainoutliercorrection -executable = %(lofarroot)s/bin/parmexportcal -mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/instrument.mapfile - [rficonsole] -recipe = rficonsole -executable = %(lofarroot)s/bin/rficonsole nproc = 0 -[get_metadata] -recipe = get_metadata - -[imager_prepare] -recipe = imager_prepare -ndppp_exec = %(lofarroot)s/bin/NDPPP -asciistat_executable = %(lofarroot)s/bin/asciistats.py -statplot_executable = %(lofarroot)s/bin/statsplot.py -msselect_executable = %(casaroot)s/bin/msselect -rficonsole_executable = %(lofarroot)s/bin/rficonsole - [long_baseline] -recipe = long_baseline -ndppp_exec = %(lofarroot)s/bin/NDPPP -asciistat_executable = %(lofarroot)s/bin/asciistats.py -statplot_executable = %(lofarroot)s/bin/statsplot.py -msselect_executable = %(casaroot)s/bin/msselect -rficonsole_executable = %(lofarroot)s/bin/rficonsole nproc = 0 -[imager_awimager] -recipe = imager_awimager -executable = %(lofarroot)s/bin/awimager - -[imager_create_dbs] -recipe = imager_create_dbs -parmdb_executable = %(lofarroot)s/bin/parmdbm -makesourcedb_path = %(lofarroot)s/bin/makesourcedb - -[imager_bbs] -recipe = imager_bbs -bbs_executable = %(lofarroot)s/bin/bbs-reducer - -[imager_source_finding] -recipe = imager_source_finding -makesourcedb_path = %(lofarroot)s/bin/makesourcedb - -[imager_finalize] -recipe = imager_finalize -fillrootimagegroup_exec = %(lofarroot)s/bin/fillRootImageGroup - -[copier] -recipe = copier -mapfiles_dir = %(runtime_directory)s/%(job_name)s/mapfiles - -[bbs_reducer] -recipe = bbs_reducer -executable = %(lofarroot)s/bin/bbs-reducer -parset = %(runtime_directory)s/%(job_name)s/parsets/bbs.parset -instrument_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/instrument.mapfile -sky_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/sky.mapfile -data_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/bbs.mapfile - -[selfcal_awimager] -recipe = selfcal_awimager -executable = %(lofarroot)s/bin/awimager - -[selfcal_bbs] -recipe = selfcal_bbs -bbs_executable = %(lofarroot)s/bin/bbs-reducer - -[selfcal_finalize] -recipe = selfcal_finalize -fillrootimagegroup_exec = %(lofarroot)s/bin/fillRootImageGroup -msselect_executable = %(casaroot)s/bin/msselect - -# below are tasks for the generic pipeline -[executable_args] -recipe = executable_args -#executable = /over/write/me -arguments = [] -#parset = parsetifyouhaveone -inputkey = -outputkey = -#mapfile_in = '' -mapfile_out = -skip_infile = False -skip_outfile = False -inplace = False -outputsuffixes = [] -parsetasfile = False -#args_format=gnu - -[casapy-imager] -recipe = executable_args -parsetasfile = True -#executable = /path/to/your/casapy/bin/casa -outputsuffixes = [flux,image,model,residual,psf] -nodescript = executable_casa - -[pythonplugin] -recipe = executable_args -nodescript = python_plugin - -[python-calibrate-stand-alone] -recipe=executable_args -nodescript=calibrate-stand-alone -executable=%(lofarroot)s/bin/bbs-reducer -parsetasfile=True - -[calibrate-stand-alone] -recipe = executable_args -executable = %(lofarroot)s/bin/calibrate-stand-alone - [dppp] -recipe = executable_args -parsetasfile = False -executable = %(lofarroot)s/bin/NDPPP -outputsuffixes = [] -args_format=lofar -outputkey=msout +max_per_node = 0 [awimager] -recipe = executable_args -parsetasfile = True -executable = %(lofarroot)s/bin/awimager -outputsuffixes = [.model, .model.corr, .residual, .residual.corr, .restored, .restored.corr, .psf] max_per_node = 0 -args_format=lofar -outputkey=image [rficonsole] -recipe = executable_args -executable = %(lofarroot)s/bin/rficonsole +max_per_node = 0 -- GitLab From d36201df81184b9a995179eb9edd33d8f286e9be Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 25 Mar 2016 12:49:09 +0000 Subject: [PATCH 078/933] Task #8437: Provide SLURM partition name when scheduling jobs, to prevent scheduling to wrong partition --- MAC/Services/src/PipelineControl.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 06dc9ee314b..93d575236d1 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -136,6 +136,9 @@ class Slurm(object): def __init__(self, headnode="head01.cep4"): self.headnode = headnode + # TODO: Derive SLURM partition name + self.partition = "cpu" + def _runCommand(self, cmdline): cmdline = "ssh %s %s" % (self.headnode, cmdline) runCommand(cmdline) @@ -144,7 +147,7 @@ class Slurm(object): if sbatch_params is None: sbatch_params = [] - stdout = self._runCommand("sbatch --job-name=%s %s bash -c '%s'" % (jobName, " ".join(sbatch_params), cmdline)) + stdout = self._runCommand("sbatch --partition=%s --job-name=%s %s bash -c '%s'" % (self.partition, jobName, " ".join(sbatch_params), cmdline)) # Returns "Submitted batch job 3" -- extract ID match = re.search("Submitted batch job (\d+)", stdout) -- GitLab From f20039af1833ceba29087fba7791a4eb4830db12 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 25 Mar 2016 13:09:47 +0000 Subject: [PATCH 079/933] Task #8437: Also install nano for convenience --- Docker/lofar-base/Dockerfile.tmpl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index d15366ba060..af8599032ca 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -39,7 +39,8 @@ ENV J=4 #RUN sed -i 's/archive.ubuntu.com/osmirror.rug.nl/' /etc/apt/sources.list RUN apt-get update && apt-get upgrade -y RUN apt-get install -y sudo python2.7 libpython2.7 && \ - apt-get install -y libblas3 liblapacke python-numpy libcfitsio3 libwcs4 libfftw3-bin libhdf5-7 libboost-python${BOOST_VERSION}.0 + apt-get install -y libblas3 liblapacke python-numpy libcfitsio3 libwcs4 libfftw3-bin libhdf5-7 libboost-python${BOOST_VERSION}.0 && \ + apt-get install -y nano # # setup-account -- GitLab From cf4b24f2abb8d2537692014a19fec52da89a387d Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Fri, 25 Mar 2016 15:13:01 +0000 Subject: [PATCH 080/933] TAsk #9189: Commit hook test --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 59c82bd7bf3..89b36ddaf5a 100644 --- a/README +++ b/README @@ -13,7 +13,7 @@ Repository web interface: https://svn.astron.nl/viewvc/LOFAR/ Top-level LOFAR Project Content (incomplete summary) -------------------------------- +-------------------------------- CEP/ CEntral Processing software CEP/Calibration/ Calibration: BBS, antenna and station responses -- GitLab From 716edde5feb4176cf8669303da39b0f33ae79267 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 29 Mar 2016 11:47:47 +0000 Subject: [PATCH 081/933] Task #8437: Allocate 8 dedicated cores per step --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 9bb7b776c8d..9cfd39ad1a7 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -83,4 +83,4 @@ method = custom_cmdline # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun -N 1 -n 1 --jobid={slurm_job_id} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v %(working_directory)s:%(working_directory)s -v /data:/data --net=host {docker_image} /bin/bash -c "\"{command}\"" +cmdline = ssh -n -tt -x localhost srun --exclusive --cpus-per-task=8 --ntasks=1 --jobid={slurm_job_id} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v %(working_directory)s:%(working_directory)s -v /data:/data --net=host {docker_image} /bin/bash -c "\"{command}\"" -- GitLab From 763b336f055dc5a24da0989516e8f00b9590812a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 29 Mar 2016 12:22:47 +0000 Subject: [PATCH 082/933] Task #8437: Fixed name of mock Slurm method --- MAC/Services/test/tPipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index c672e786938..c3899f508ec 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -101,7 +101,7 @@ class TestPipelineControl(unittest.TestCase): def __init__(self, *args, **kwargs): self.scheduled_jobs = {} - def schedule(self, jobName, *args, **kwargs): + def submit(self, jobName, *args, **kwargs): print "Schedule SLURM job '%s': %s, %s" % (jobName, args, kwargs) self.scheduled_jobs[jobName] = (args, kwargs) -- GitLab From 1a68ae2f26cfa68bc77eca1fb31c4368b0165153 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 30 Mar 2016 07:40:41 +0000 Subject: [PATCH 083/933] Task #8437: Propagate name of script/executable as SLURM job name. Removed superfluous docker.image option. --- .../lofarpipe/support/remotecommand.py | 18 +++++++++++------- .../recipes/sip/pipeline.cfg.CEP4.tmpl | 5 +---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index dbd145f0ee0..445d8a60fb7 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -199,8 +199,8 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config {command} := bash command line to be executed {uid} := uid of the calling user - {image} := docker.image configuration option {slurm_job_id} := the SLURM job id to allocate resources in + {job_name} := name of this executable or script """ commandArray = ["%s=%s" % (key, value) for key, value in environment.items()] @@ -208,19 +208,23 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config commandArray.extend(re.escape(str(arg)) for arg in arguments) commandStr = " ".join(commandArray) - try: - image = config.get('docker', 'image') - except: - image = "lofar-pipeline" + # Determine job name + def jobname(commandstr): + args = [os.path.basename(x) for x in commandstr.split()] + + if len(args) == 1: + return args[0] + + return args[1] if args[0] == "python" else args[0] # Construct the full command line, except for {command}, as that itself # can contain spaces which we don't want to split on. full_command_line = config.get('remote', 'cmdline').format( uid = os.geteuid(), slurm_job_id = os.environ.get("SLURM_JOB_ID"), - docker_image = image, host = host, - command = "{command}" + command = "{command}", + job_name = jobname(command), ).split(' ') # Fill in {command} somewhere diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 9cfd39ad1a7..41a5b96c7f4 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -49,9 +49,6 @@ xml_stat_file = %(runtime_directory)s/%(job_name)s/logs/%(start_time)s/statistic # none Do NOT send feedback and status method = none -[docker] -image = lofar-pipeline:${LOFAR_TAG} - [remote] method = custom_cmdline @@ -83,4 +80,4 @@ method = custom_cmdline # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun --exclusive --cpus-per-task=8 --ntasks=1 --jobid={slurm_job_id} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v %(working_directory)s:%(working_directory)s -v /data:/data --net=host {docker_image} /bin/bash -c "\"{command}\"" +cmdline = ssh -n -tt -x localhost srun --exclusive --cpus-per-task=8 --ntasks=1 --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v %(working_directory)s:%(working_directory)s -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} /bin/bash -c "\"{command}\"" -- GitLab From b2c6e6d3249aa0cef0acc40d4d7a683a5e2b555a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 30 Mar 2016 08:03:25 +0000 Subject: [PATCH 084/933] Task #8437: Propagate nr cores to use in job allocation --- .../framework/lofarpipe/support/remotecommand.py | 15 ++++++++++----- CEP/Pipeline/recipes/sip/master/dppp.py | 5 ++++- CEP/Pipeline/recipes/sip/master/rficonsole.py | 5 ++++- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index 445d8a60fb7..7ac394c699e 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -61,7 +61,7 @@ class ParamikoWrapper(object): def kill(self): self.chan.close() -def run_remote_command(config, logger, host, command, env, arguments = None): +def run_remote_command(config, logger, host, command, env, arguments = None, resources = {}): """ Run command on host, passing it arguments from the arguments list and exporting key/value pairs from env(a dictionary). @@ -97,7 +97,7 @@ def run_remote_command(config, logger, host, command, env, arguments = None): elif method == "slurm_srun_cep3": return run_via_slurm_srun_cep3(logger, command, arguments, host) elif method == "custom_cmdline": - return run_via_custom_cmdline(logger, host, command, env, arguments, config) + return run_via_custom_cmdline(logger, host, command, env, arguments, config, resources) else: return run_via_ssh(logger, host, command, env, arguments) @@ -185,7 +185,7 @@ def run_via_ssh(logger, host, command, environment, arguments): process.kill = lambda : os.kill(process.pid, signal.SIGKILL) return process -def run_via_custom_cmdline(logger, host, command, environment, arguments, config): +def run_via_custom_cmdline(logger, host, command, environment, arguments, config, resources): """ Dispatch a remote command via a customisable command line @@ -202,6 +202,8 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config {slurm_job_id} := the SLURM job id to allocate resources in {job_name} := name of this executable or script + {nr_cores} := number of cores to allocate for this job + """ commandArray = ["%s=%s" % (key, value) for key, value in environment.items()] commandArray.append(command) @@ -225,6 +227,7 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config host = host, command = "{command}", job_name = jobname(command), + nr_cores = resources.get("cores", 1), ).split(' ') # Fill in {command} somewhere @@ -288,10 +291,11 @@ class ComputeJob(object): :param command: Full path to command to be run on target host :param arguments: List of arguments which will be passed to command """ - def __init__(self, host, command, arguments = []): + def __init__(self, host, command, arguments = [], resources = {}): self.host = host self.command = command self.arguments = arguments + self.resources = resources self.results = {} self.results['returncode'] = 123456 # Default to obscure code to allow # test of failing ssh connections @@ -329,7 +333,8 @@ class ComputeJob(object): "LOFARROOT" : os.environ.get('LOFARROOT'), "QUEUE_PREFIX" : os.environ.get('QUEUE_PREFIX','') }, - arguments = [id, jobhost, jobport] + arguments = [id, jobhost, jobport], + resources = self.resources ) # Wait for process to finish. In the meantime, if the killswitch # is set (by an exception in the main thread), forcibly kill our diff --git a/CEP/Pipeline/recipes/sip/master/dppp.py b/CEP/Pipeline/recipes/sip/master/dppp.py index f8a4467eff3..5fa53690af9 100644 --- a/CEP/Pipeline/recipes/sip/master/dppp.py +++ b/CEP/Pipeline/recipes/sip/master/dppp.py @@ -236,7 +236,10 @@ class dppp(BaseRecipe, RemoteCommandRecipeMixIn): self.inputs['data_end_time'], self.inputs['nthreads'], self.inputs['clobber'] - ] + ], + resources={ + "cores": self.inputs['nthreads'] + } ) ) self._schedule_jobs(jobs, max_per_node=self.inputs['nproc']) diff --git a/CEP/Pipeline/recipes/sip/master/rficonsole.py b/CEP/Pipeline/recipes/sip/master/rficonsole.py index a87a9d4dd17..047d78ab205 100644 --- a/CEP/Pipeline/recipes/sip/master/rficonsole.py +++ b/CEP/Pipeline/recipes/sip/master/rficonsole.py @@ -112,7 +112,10 @@ class rficonsole(BaseRecipe, RemoteCommandRecipeMixIn): self.inputs['indirect_read'], self.inputs['skip_flagged'], self.inputs['working_dir'] - ] + file_list + ] + file_list, + resources={ + "cores": self.inputs['nthreads'] + } ) ) self._schedule_jobs(jobs, max_per_node=self.inputs['nproc']) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 41a5b96c7f4..6ff3730d548 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -80,4 +80,4 @@ method = custom_cmdline # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun --exclusive --cpus-per-task=8 --ntasks=1 --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v %(working_directory)s:%(working_directory)s -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} /bin/bash -c "\"{command}\"" +cmdline = ssh -n -tt -x localhost srun --exclusive --distribution=cyclic --cpus-per-task={nr_cores} --ntasks=1 --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v %(working_directory)s:%(working_directory)s -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} /bin/bash -c "\"{command}\"" -- GitLab From 002bbd2d08bab0b870ea4d5c31d8617921f44690 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 1 Apr 2016 09:49:17 +0000 Subject: [PATCH 085/933] Task #8437: Fixed busname parameter to be -B in setStatus.py, runPipeline.sh, pipelineAborted.sh --- CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh | 8 ++++---- CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 12 ++++++------ MAC/Services/src/PipelineControl.py | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh b/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh index 68031f76983..cd4acf54d37 100755 --- a/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh +++ b/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh @@ -24,17 +24,17 @@ function usage() { echo "$0 -o OBSID [options]" echo "" echo " -o OBSID Task identifier" - echo " -b busname Bus name to post status changes on (default: $SETSTATUS_BUS)" + echo " -B busname Bus name to post status changes on (default: $SETSTATUS_BUS)" exit 1 } -while getopts "o:c:p:b:" opt; do +while getopts "o:c:p:B:" opt; do case $opt in h) usage ;; o) OBSID="$OPTARG" ;; - b) SETSTATUS_BUS="$OPTARG" + B) SETSTATUS_BUS="$OPTARG" ;; \?) error "Invalid option: -$OPTARG" ;; @@ -47,6 +47,6 @@ done # ======= Run # Mark as aborted -setStatus.py -o $OBSID -s aborted -b $SETSTATUS_BUS || true +setStatus.py -o $OBSID -s aborted -B $SETSTATUS_BUS || true exit 0 diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh index 780ef6ab669..c7d0c8a8a9f 100755 --- a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -36,11 +36,11 @@ function usage() { echo " -o OBSID Task identifier" echo " -c pipeline.cfg Override pipeline configuration file (default: $PIPELINE_CONFIG)" echo " -p pipeline.parset Provide parset (default: request through QPID)" - echo " -b busname Bus name to post status changes on (default: $SETSTATUS_BUS)" + echo " -B busname Bus name to post status changes on (default: $SETSTATUS_BUS)" exit 1 } -while getopts "ho:c:p:b:" opt; do +while getopts "ho:c:p:B:" opt; do case $opt in h) usage ;; @@ -50,7 +50,7 @@ while getopts "ho:c:p:b:" opt; do ;; p) PARSET="$OPTARG" ;; - b) SETSTATUS_BUS="$OPTARG" + B) SETSTATUS_BUS="$OPTARG" ;; \?) error "Invalid option: -$OPTARG" ;; @@ -63,7 +63,7 @@ done # ======= Init # Mark as started -setStatus.py -o $OBSID -s active -b $SETSTATUS_BUS || true +setStatus.py -o $OBSID -s active -B $SETSTATUS_BUS || true if [ -z "$PARSET" ]; then # Fetch parset @@ -91,14 +91,14 @@ RESULT=$? # ======= Fini # Process the result -setStatus.py -o $OBSID -s completing -b $SETSTATUS_BUS || true +setStatus.py -o $OBSID -s completing -B $SETSTATUS_BUS || true if [ $RESULT -eq 0 ]; then # Wait for feedback to propagate sleep 60 # Mark as succesful - setStatus.py -o $OBSID -s finished -b $SETSTATUS_BUS || true + setStatus.py -o $OBSID -s finished -B $SETSTATUS_BUS || true fi # Propagate result to caller diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 93d575236d1..1a4be9f4283 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -344,7 +344,7 @@ class PipelineControl(OTDBBusListener): " -v $HOME/.ssh:/home/lofar/.ssh:ro" " -e SLURM_JOB_ID=$SLURM_JOB_ID" " lofar-pipeline:{tag}" - " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -b {status_bus}" + " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -B {status_bus}" .format( obsid = treeId, tag = parset.dockerTag(), @@ -364,7 +364,7 @@ class PipelineControl(OTDBBusListener): " --net=host" " -e LUSER=$UID" " lofar-pipeline:{tag}" - " pipelineAborted.sh -o {obsid} -b {status_bus}" + " pipelineAborted.sh -o {obsid} -B {status_bus}" .format( obsid = treeId, tag = parset.dockerTag(), -- GitLab From 02b176a61cf21c158f1eead40a5784e188a64899 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 1 Apr 2016 12:06:59 +0000 Subject: [PATCH 086/933] Task #8437: Imaging pipeline requires rsync --- Docker/lofar-pipeline/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index c9037d3c814..80d41c9fa68 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -6,7 +6,7 @@ FROM lofar-base:${LOFAR_TAG} ENV AOFLAGGER_VERSION=2.7.1 # Run-time dependencies -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base && \ +RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync && \ sudo apt-get -y install python-pip python-dev && \ sudo pip install pyfits pywcs python-monetdb && \ sudo apt-get -y purge python-pip python-dev && \ -- GitLab From 706dd95cc6a1450ac37b7076c679f958b2ccb732 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 1 Apr 2016 12:13:21 +0000 Subject: [PATCH 087/933] Task #8437: Corrected path to AOFlagger in CEP4 docker image --- CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in index e706c1afdc3..87982d209d3 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in @@ -16,6 +16,7 @@ nproc = 0 [long_baseline] nproc = 0 +rficonsole_executable = /opt/aoflagger/bin/rficonsole [dppp] max_per_node = 0 @@ -24,4 +25,8 @@ max_per_node = 0 max_per_node = 0 [rficonsole] +executable = /opt/aoflagger/bin/rficonsole max_per_node = 0 + +[imager_prepare] +rficonsole_executable = /opt/aoflagger/bin/rficonsole -- GitLab From 63d8f36d5d12338829743e4270ed63b80ce96e9c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 1 Apr 2016 14:08:19 +0000 Subject: [PATCH 088/933] Task #8437: Add support for global fs in node scripts to prevent rsyncing files within the same filesystem --- CEP/Pipeline/recipes/sip/master/copier.py | 6 ++++-- CEP/Pipeline/recipes/sip/master/imager_prepare.py | 5 ++++- CEP/Pipeline/recipes/sip/master/long_baseline.py | 3 +++ CEP/Pipeline/recipes/sip/nodes/copier.py | 5 +++-- CEP/Pipeline/recipes/sip/nodes/imager_prepare.py | 5 +++-- CEP/Pipeline/recipes/sip/nodes/long_baseline.py | 5 +++-- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 1 + 7 files changed, 21 insertions(+), 9 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/master/copier.py b/CEP/Pipeline/recipes/sip/master/copier.py index 4846c7a5beb..0f9f8405a82 100644 --- a/CEP/Pipeline/recipes/sip/master/copier.py +++ b/CEP/Pipeline/recipes/sip/master/copier.py @@ -236,6 +236,8 @@ class copier(MasterNodeInterface): self.logger.info("Starting copier run") super(copier, self).go() + globalfs = self.config.has_option("remote", "globalfs") and self.config.getboolean("remote", "globalfs") + # Load data from mapfiles self.source_map = DataMap.load(self.inputs['mapfile_source']) self.target_map = DataMap.load(self.inputs['mapfile_target']) @@ -246,8 +248,8 @@ class copier(MasterNodeInterface): # Run the compute nodes with the node specific mapfiles for source, target in zip(self.source_map, self.target_map): - args = [source.host, source.file, target.file] - self.append_job(target.host, args) + args = [source.host, source.file, target.file, globalfs] + self.append_job(target.host args) # start the jobs, return the exit status. return self.run_jobs() diff --git a/CEP/Pipeline/recipes/sip/master/imager_prepare.py b/CEP/Pipeline/recipes/sip/master/imager_prepare.py index 3d06ab9dcd5..dc44bad684d 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_prepare.py +++ b/CEP/Pipeline/recipes/sip/master/imager_prepare.py @@ -160,6 +160,8 @@ class imager_prepare(BaseRecipe, RemoteCommandRecipeMixIn): paths_to_image_mapfiles = [] n_subband_groups = len(output_map) # needed for subsets in sb list + globalfs = self.config.has_option("remote", "globalfs") and self.config.getboolean("remote", "globalfs") + for idx_sb_group, item in enumerate(output_map): #create the input files for this node self.logger.debug("Creating input data subset for processing" @@ -203,7 +205,8 @@ class imager_prepare(BaseRecipe, RemoteCommandRecipeMixIn): self.inputs['msselect_executable'], self.inputs['rficonsole_executable'], self.inputs['do_rficonsole'], - self.inputs['add_beam_tables']] + self.inputs['add_beam_tables'], + globalfs] jobs.append(ComputeJob(item.host, node_command, arguments)) diff --git a/CEP/Pipeline/recipes/sip/master/long_baseline.py b/CEP/Pipeline/recipes/sip/master/long_baseline.py index f61764bc78b..9e2c6f9a16e 100644 --- a/CEP/Pipeline/recipes/sip/master/long_baseline.py +++ b/CEP/Pipeline/recipes/sip/master/long_baseline.py @@ -166,6 +166,8 @@ class long_baseline(BaseRecipe, RemoteCommandRecipeMixIn): paths_to_image_mapfiles = [] n_subband_groups = len(output_map) + globalfs = self.config.has_option("remote", "globalfs") and self.config.getboolean("remote", "globalfs") + output_map.iterator = final_output_map.iterator = DataMap.SkipIterator for idx_sb_group, (output_item, final_item) in enumerate(zip(output_map, final_output_map)): @@ -204,6 +206,7 @@ class long_baseline(BaseRecipe, RemoteCommandRecipeMixIn): self.inputs['msselect_executable'], self.inputs['rficonsole_executable'], self.inputs['add_beam_tables'], + globalfs, final_item.file] jobs.append(ComputeJob(output_item.host, node_command, arguments)) diff --git a/CEP/Pipeline/recipes/sip/nodes/copier.py b/CEP/Pipeline/recipes/sip/nodes/copier.py index a35f66e5d1c..b972b06c004 100644 --- a/CEP/Pipeline/recipes/sip/nodes/copier.py +++ b/CEP/Pipeline/recipes/sip/nodes/copier.py @@ -21,7 +21,8 @@ class copier(LOFARnodeTCP): """ Node script for copying files between nodes. See master script for full public interface """ - def run(self, source_node, source_path, target_path): + def run(self, source_node, source_path, target_path, globalfs): + self.globalfs = globalfs # Time execution of this job with log_time(self.logger): return self._copy_single_file_using_rsync( @@ -51,7 +52,7 @@ class copier(LOFARnodeTCP): # construct copy command: Copy to the dir # if process runs on local host use a simple copy command. - if source_node=="localhost": + if self.globalfs or source_node=="localhost": command = ["cp", "-r","{0}".format(source_path),"{0}".format(target_path)] else: command = ["rsync", "-r", diff --git a/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py b/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py index 63328ef3f2a..e1ed5c185d1 100644 --- a/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py +++ b/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py @@ -44,11 +44,12 @@ class imager_prepare(LOFARnodeTCP): ndppp_executable, output_measurement_set, time_slices_per_image, subbands_per_group, input_ms_mapfile, asciistat_executable, statplot_executable, msselect_executable, - rficonsole_executable, do_rficonsole, add_beam_tables): + rficonsole_executable, do_rficonsole, add_beam_tables, globalfs): """ Entry point for the node recipe """ self.environment.update(environment) + self.globalfs = globalfs with log_time(self.logger): input_map = DataMap.load(input_ms_mapfile) @@ -178,7 +179,7 @@ class imager_prepare(LOFARnodeTCP): command = ["rsync", "-r", "{0}:{1}".format( input_item.host, input_item.file), "{0}".format(processed_ms_dir)] - if input_item.host == "localhost": + if self.globalfs or input_item.host == "localhost": command = ["cp", "-r", "{0}".format(input_item.file), "{0}".format(processed_ms_dir)] diff --git a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py index cea1e3c7752..60826d9fcab 100644 --- a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py +++ b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py @@ -45,11 +45,12 @@ class long_baseline(LOFARnodeTCP): ndppp_executable, output_measurement_set, subbandgroups_per_ms, subbands_per_subbandgroup, ms_mapfile, asciistat_executable, statplot_executable, msselect_executable, - rficonsole_executable, add_beam_tables, final_output_path): + rficonsole_executable, add_beam_tables, globalfs, final_output_path): """ Entry point for the node recipe """ self.environment.update(environment) + self.globalfs = globalfs with log_time(self.logger): input_map = DataMap.load(ms_mapfile) @@ -194,7 +195,7 @@ class long_baseline(LOFARnodeTCP): command = ["rsync", "-r", "{0}:{1}".format( input_item.host, input_item.file), "{0}".format(processed_ms_dir)] - if input_item.host == "localhost": + if self.globalfs or input_item.host == "localhost": command = ["cp", "-r", "{0}".format(input_item.file), "{0}".format(processed_ms_dir)] diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 6ff3730d548..105f03b84e2 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -51,6 +51,7 @@ method = none [remote] method = custom_cmdline +globalfs = yes # We take the following path to start a remote container: # -- GitLab From ebbddd302d28b29e32bf1b26bcad927ab41cab81 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 1 Apr 2016 14:41:37 +0000 Subject: [PATCH 089/933] Task #8437: Fixed accidental removal of , --- CEP/Pipeline/recipes/sip/master/copier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/master/copier.py b/CEP/Pipeline/recipes/sip/master/copier.py index 0f9f8405a82..c15dba135c9 100644 --- a/CEP/Pipeline/recipes/sip/master/copier.py +++ b/CEP/Pipeline/recipes/sip/master/copier.py @@ -249,7 +249,7 @@ class copier(MasterNodeInterface): # Run the compute nodes with the node specific mapfiles for source, target in zip(self.source_map, self.target_map): args = [source.host, source.file, target.file, globalfs] - self.append_job(target.host args) + self.append_job(target.host, args) # start the jobs, return the exit status. return self.run_jobs() -- GitLab From 97806b00114110f187cdbdaa29a69e7abc036e83 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 2 Apr 2016 07:07:35 +0000 Subject: [PATCH 090/933] Tsk #8437: Use aoflagger binary instead of rficonsole --- CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in index 87982d209d3..895a78d7a96 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in @@ -16,7 +16,7 @@ nproc = 0 [long_baseline] nproc = 0 -rficonsole_executable = /opt/aoflagger/bin/rficonsole +rficonsole_executable = /opt/aoflagger/bin/aoflagger [dppp] max_per_node = 0 @@ -25,8 +25,8 @@ max_per_node = 0 max_per_node = 0 [rficonsole] -executable = /opt/aoflagger/bin/rficonsole +executable = /opt/aoflagger/bin/aoflagger max_per_node = 0 [imager_prepare] -rficonsole_executable = /opt/aoflagger/bin/rficonsole +rficonsole_executable = /opt/aoflagger/bin/aoflagger -- GitLab From 8ce5ef2516c8f129350360e2385b97b38a1d139a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 2 Apr 2016 07:30:51 +0000 Subject: [PATCH 091/933] Task #8437: Cleanup, and do not map working_directory anymore since it does not exist, and thus will be created as root by docker --- .../recipes/sip/pipeline.cfg.CEP4.tmpl | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 105f03b84e2..90db5bcf7ab 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -1,18 +1,4 @@ -# This pipeline.cfg was used on the CEP4 test system to show how Docker and SLURM -# can be used to distribute and run jobs. -# -# Is called as follows (inside a SLURM job allocation, f.e. "salloc -N 2"). Replace directory names -# where needed: -# -# # Create this on every node -# CONFIGDIR=/globalhome/mol/regression_test -# HOSTS=(`scontrol show hostnames $SLURM_JOB_NODELIST`) -# -# # $CONFIGDIR is local, and ocntains both the working_dir (for input/output data) and the config files -# # /share is a shared disk for the vds files, map files, logs. -# -# docker run --rm -e SLURM_JOB_ID=${SLURM_JOB_ID} -e LUSER=${UID} -v $HOME/.ssh:/home/lofar/.ssh:ro -v $CONFIGDIR:$CONFIGDIR -v /shared:/shared --net=host lofar-patched /bin/bash -c "\"$EXTRA_CMDS;python $CONFIGDIR/regression_test_runner.py preprocessing_pipeline --pipelinecfg $CONFIGDIR/pipeline.cfg --testdata $CONFIGDIR/data --computehost1 ${HOSTS[0]} --computehost2 ${HOSTS[1]} --workdir $CONFIGDIR/working_dir --rundir $CONFIGDIR/rundir\"" - +# This pipeline.cfg is used on CEP4, to run jobs through Docker and SLURM. [DEFAULT] lofarroot = /opt/lofar @@ -66,19 +52,19 @@ globalfs = yes # * pseudo-tty to prevent buffering logs (ssh -tt, docker -t) # -# host -> worker node: srun -N 1 -n 1 --jobid={slurm_job_id} +# host -> worker node: srun ... # (Add -w {host} for systems that do not have a global file system, to force job # execution on the host that contains the data) # -# worker node -> container: docker run -t --rm -e LUSER={uid} -w g -v /home/mol/.ssh:/home/lofar/.ssh:ro -v /globalhome/mol/regression_test:/globalhome/mol/regression_test -v /shared:/shared --net=host {docker_image} +# worker node -> container: docker run ... # Starts the container on the worker node, with pretty much the same parameters as the master container: # # --rm: don't linger resources when the container exits # -e LUSER=(uid): set the user to run as (the calling user) # -h {host}: set container hostname (for easier debugging) (doesnt work yet, using --net=host instead) -# -v: map the directories for input/output data, shared storage (parsets, vds files, etc) +# -v /data:/data: map CEP4's global fs. This includes %(working_directory)s and %(runtime_directory)s so those do not have to be mapped as well. # # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun --exclusive --distribution=cyclic --cpus-per-task={nr_cores} --ntasks=1 --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v %(runtime_directory)s:%(runtime_directory)s -v %(working_directory)s:%(working_directory)s -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} /bin/bash -c "\"{command}\"" +cmdline = ssh -n -tt -x localhost srun --exclusive --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} /bin/bash -c "\"{command}\"" -- GitLab From 5ffb4162f1d265f7db54ac3014f85d9240451de8 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 2 Apr 2016 10:31:53 +0000 Subject: [PATCH 092/933] Task #8437: Added --ntasks, required for --exclusive --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 90db5bcf7ab..6c1c35a244a 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -67,4 +67,4 @@ globalfs = yes # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun --exclusive --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} /bin/bash -c "\"{command}\"" +cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} /bin/bash -c "\"{command}\"" -- GitLab From 45ae22c64e4e7a6dde08eed3daa27c1da93dfdcd Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 4 Apr 2016 09:43:40 +0000 Subject: [PATCH 093/933] Task #8437: Use dedicated working directories for imaging node scripts to prevent cross contamination --- CEP/Pipeline/recipes/sip/master/imager_prepare.py | 5 ++++- CEP/Pipeline/recipes/sip/master/long_baseline.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/master/imager_prepare.py b/CEP/Pipeline/recipes/sip/master/imager_prepare.py index dc44bad684d..f17ce182724 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_prepare.py +++ b/CEP/Pipeline/recipes/sip/master/imager_prepare.py @@ -191,9 +191,12 @@ class imager_prepare(BaseRecipe, RemoteCommandRecipeMixIn): paths_to_image_mapfiles.append( tuple([item.host, inputs_for_image_mapfile_path, False])) + # use a unique working directory per job, to prevent interference between jobs on a global fs + working_dir = os.path.join(self.inputs['working_directory'], "ms_per_image_{0}".format(idx_sb_group)) + arguments = [self.environment, self.inputs['parset'], - self.inputs['working_directory'], + working_dir, self.inputs['processed_ms_dir'], self.inputs['ndppp_exec'], item.file, diff --git a/CEP/Pipeline/recipes/sip/master/long_baseline.py b/CEP/Pipeline/recipes/sip/master/long_baseline.py index 9e2c6f9a16e..45734bd28e8 100644 --- a/CEP/Pipeline/recipes/sip/master/long_baseline.py +++ b/CEP/Pipeline/recipes/sip/master/long_baseline.py @@ -192,9 +192,12 @@ class long_baseline(BaseRecipe, RemoteCommandRecipeMixIn): paths_to_image_mapfiles.append( tuple([output_item.host, inputs_for_image_mapfile_path, False])) + # use a unique working directory per job, to prevent interference between jobs on a global fs + working_dir = os.path.join(self.inputs['working_directory'], "ms_per_image_{0}".format(idx_sb_group)) + arguments = [self.environment, self.inputs['parset'], - self.inputs['working_directory'], + working_dir, self.inputs['processed_ms_dir'], self.inputs['ndppp_exec'], output_item.file, -- GitLab From 2cbea4c4a8ed1821f69f2be783cb4acfa3d9ae9b Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 4 Apr 2016 18:18:19 +0000 Subject: [PATCH 094/933] Task #8437: Specify nthreads for recipes that may benefit from it --- CEP/Pipeline/recipes/sip/master/executable_args.py | 10 +++++++++- CEP/Pipeline/recipes/sip/master/imager_prepare.py | 12 ++++++++++-- CEP/Pipeline/recipes/sip/master/long_baseline.py | 10 +++++++++- CEP/Pipeline/recipes/sip/tasks.cfg.in | 10 ++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/master/executable_args.py b/CEP/Pipeline/recipes/sip/master/executable_args.py index 4c07727745f..2bec87956bb 100644 --- a/CEP/Pipeline/recipes/sip/master/executable_args.py +++ b/CEP/Pipeline/recipes/sip/master/executable_args.py @@ -35,6 +35,11 @@ class executable_args(BaseRecipe, RemoteCommandRecipeMixIn): default='', optional=True ), + 'nthreads': ingredient.IntField( + '--nthreads', + default=8, + help="Number of threads per process" + ), 'nodescript': ingredient.StringField( '--nodescript', help="Name of the node script to execute", @@ -369,7 +374,10 @@ class executable_args(BaseRecipe, RemoteCommandRecipeMixIn): self.inputs['parsetasfile'], args_format, self.environment - ] + ], + resources={ + "cores": self.inputs['nthreads'] + } ) ) max_per_node = self.inputs['max_per_node'] diff --git a/CEP/Pipeline/recipes/sip/master/imager_prepare.py b/CEP/Pipeline/recipes/sip/master/imager_prepare.py index f17ce182724..3ba13b86007 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_prepare.py +++ b/CEP/Pipeline/recipes/sip/master/imager_prepare.py @@ -57,6 +57,11 @@ class imager_prepare(BaseRecipe, RemoteCommandRecipeMixIn): '-w', '--working-directory', help="Working directory used by the nodes: local data" ), + 'nthreads': ingredient.IntField( + '--nthreads', + default=8, + help="Number of threads per process" + ), 'target_mapfile': ingredient.StringField( '--target-mapfile', help="Contains the node and path to target files, defines" @@ -191,7 +196,7 @@ class imager_prepare(BaseRecipe, RemoteCommandRecipeMixIn): paths_to_image_mapfiles.append( tuple([item.host, inputs_for_image_mapfile_path, False])) - # use a unique working directory per job, to prevent interference between jobs on a global fs + # use unique working directories per job, to prevent interference between jobs on a global fs working_dir = os.path.join(self.inputs['working_directory'], "ms_per_image_{0}".format(idx_sb_group)) arguments = [self.environment, @@ -211,7 +216,10 @@ class imager_prepare(BaseRecipe, RemoteCommandRecipeMixIn): self.inputs['add_beam_tables'], globalfs] - jobs.append(ComputeJob(item.host, node_command, arguments)) + jobs.append(ComputeJob(item.host, node_command, arguments, + resources={ + "cores": self.inputs['nthreads'] + })) # Hand over the job(s) to the pipeline scheduler self._schedule_jobs(jobs) diff --git a/CEP/Pipeline/recipes/sip/master/long_baseline.py b/CEP/Pipeline/recipes/sip/master/long_baseline.py index 45734bd28e8..fe399f5263c 100644 --- a/CEP/Pipeline/recipes/sip/master/long_baseline.py +++ b/CEP/Pipeline/recipes/sip/master/long_baseline.py @@ -63,6 +63,11 @@ class long_baseline(BaseRecipe, RemoteCommandRecipeMixIn): '-w', '--working-directory', help="Working directory used by the nodes: local data" ), + 'nthreads': ingredient.IntField( + '--nthreads', + default=8, + help="Number of threads per process" + ), 'target_mapfile': ingredient.StringField( '--target-mapfile', help="Contains the node and path to target files, defines" @@ -212,7 +217,10 @@ class long_baseline(BaseRecipe, RemoteCommandRecipeMixIn): globalfs, final_item.file] - jobs.append(ComputeJob(output_item.host, node_command, arguments)) + jobs.append(ComputeJob(output_item.host, node_command, arguments, + resources={ + "cores": self.inputs['nthreads'] + })) # Hand over the job(s) to the pipeline scheduler self._schedule_jobs(jobs, max_per_node=self.inputs['nproc']) diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.in b/CEP/Pipeline/recipes/sip/tasks.cfg.in index e2968e1bc5b..ab6fcb96cbe 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.in @@ -62,6 +62,7 @@ asciistat_executable = %(lofarroot)s/bin/asciistats.py statplot_executable = %(lofarroot)s/bin/statsplot.py msselect_executable = %(casaroot)s/bin/msselect rficonsole_executable = %(lofarroot)s/bin/rficonsole +nthreads = 8 [long_baseline] recipe = long_baseline @@ -71,6 +72,7 @@ statplot_executable = %(lofarroot)s/bin/statsplot.py msselect_executable = %(casaroot)s/bin/msselect rficonsole_executable = %(lofarroot)s/bin/rficonsole nproc = 1 +nthreads = 8 [imager_awimager] recipe = imager_awimager @@ -134,6 +136,7 @@ inplace = False outputsuffixes = [] parsetasfile = False #args_format=gnu +nthreads = 8 [casapy-imager] recipe = executable_args @@ -141,20 +144,24 @@ parsetasfile = True #executable = /path/to/your/casapy/bin/casa outputsuffixes = [flux,image,model,residual,psf] nodescript = executable_casa +nthreads = 8 [pythonplugin] recipe = executable_args nodescript = python_plugin +nthreads = 8 [python-calibrate-stand-alone] recipe=executable_args nodescript=calibrate-stand-alone executable=%(lofarroot)s/bin/bbs-reducer parsetasfile=True +nthreads = 8 [calibrate-stand-alone] recipe = executable_args executable = %(lofarroot)s/bin/calibrate-stand-alone +nthreads = 8 [dppp] recipe = executable_args @@ -163,6 +170,7 @@ executable = %(lofarroot)s/bin/NDPPP outputsuffixes = [] args_format=lofar outputkey=msout +nthreads = 8 [awimager] recipe = executable_args @@ -172,7 +180,9 @@ outputsuffixes = [.model, .model.corr, .residual, .residual.corr, .restored, .re max_per_node = 1 args_format=lofar outputkey=image +nthreads = 8 [rficonsole] recipe = executable_args executable = %(lofarroot)s/bin/rficonsole +nthreads = 8 -- GitLab From 93ecd50ceb3001cff5804a3991e1180c8e0bdcb5 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 5 Apr 2016 15:04:26 +0000 Subject: [PATCH 095/933] Task #8437: Support commands that output >4k of logging --- .../lofarpipe/support/subprocessgroup.py | 157 ++++++++++++------ .../test/support/subprocessgroup_test.py | 15 ++ 2 files changed, 122 insertions(+), 50 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index 5eb731e7c41..d394d7a8b1e 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -1,7 +1,87 @@ import subprocess +import select import time from lofarpipe.support.lofarexceptions import PipelineException +class SubProcess(object): + STDOUT = 1 + STDERR = 2 + + def __init__(self, logger, cmd, cwd): + """ + Start a subprocess for `cmd' in working directory `cwd'. + + Output is sent to `logger'. + """ + + self.cmd = cmd + self.completed = False + + self.process = subprocess.Popen( + cmd, + cwd=cwd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + self.pid = self.process.pid + self.exit_status = None + + self.output_buffers = { self.STDOUT: "", + self.STDERR: "" } + + def print_logger(line): + print line + + self.output_loggers = { self.STDOUT: logger.debug if logger else print_logger, + self.STDERR: logger.warn if logger else print_logger } + self.logger = logger.info if logger else print_logger + + self.logger("Subprocess started: %s" % self.cmd) + + def done(self): + if self.completed: + return True + + if self.process.poll() is None: + return False + + # Process is finished, read remaining data and exit code + (stdout, stderr) = self.process.communicate() + self.exit_status = self.process.returncode + + self._addoutput(self.STDOUT, stdout, flush=True) + self._addoutput(self.STDERR, stderr, flush=True) + + self.completed = True + + self.logger("Subprocess completed: %s" % self.cmd) + + return True + + def fds(self): + return [self.process.stdout, self.process.stderr] + + def read(self, fds): + for fd in fds: + if fd == self.process.stdout: + self._addoutput(self.STDOUT, fd.read(4096)) + if fd == self.process.stderr: + self._addoutput(self.STDERR, fd.read(4096)) + + def _addoutput(self, stdtype, output, flush=False): + buf = self.output_buffers[stdtype] + output + lines = buf.split("\n") + remainder = lines.pop() if lines else "" + + for l in lines: + self.output_loggers[stdtype](l) + + if flush: + self.output_loggers[stdtype](remainder) + remainder = "" + + self.output_buffers[stdtype] = remainder + class SubProcessGroup(object): """ @@ -34,26 +114,15 @@ class SubProcessGroup(object): self.running_process_count += 1 # Run subprocess - process = subprocess.Popen( - cmd, - cwd=cwd, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + process = SubProcess(self.logger, cmd, cwd) # save the process - self.process_group.append((False, (cmd, process))) + self.process_group.append(process) # add to resource monitor if available if self.usageStats: self.usageStats.addPID(process.pid) - if self.logger == None: - print "Subprocess started: {0}".format(cmd) - else: - self.logger.info("Subprocess started: {0}".format(cmd)) - - def run(self, cmd_in, unsave=False, cwd=None): """ Add the cmd as a subprocess to the current group: The process is @@ -88,65 +157,53 @@ class SubProcessGroup(object): """ collected_exit_status = [] - while (True): - # The exit test - if (self.running_process_count == 0 and - len(self.processes_waiting_for_execution) == 0): - break # the while loop + while self.running_process_count or self.processes_waiting_for_execution: + # collect all unfinished processes + processes = [p for p in self.process_group if not p.completed] + + # collect fds we need to poll + fds = [] + for process in processes: + fds.extend(process.fds()) + + # poll for data + rlist, _, _ = select.select(fds, [], [], self.polling_interval) - # start with waiting - time.sleep(self.polling_interval) + # let processed read their data + for process in processes: + process.read(rlist) # check all the running processes for completion - for idx, (completed, (cmd, process)) in \ - enumerate(self.process_group): - if completed: + for process in self.process_group: + if process.completed: + # process completed earlier continue - # poll returns null if the process is not completed - if process.poll() == None: + if not process.done(): + # process still running continue # We have a completed process - # communicate with the process - # TODO: This would be the best place to create a - # non mem caching interaction with the processes! - (stdoutdata, stderrdata) = process.communicate() - exit_status = process.returncode + exit_status = process.exit_status # get the exit status if exit_status != 0: collected_exit_status.append((cmd, exit_status)) - # log the std out and err - if self.logger != None: - self.logger.info( - "Subprocesses group, completed command: ") - self.logger.info(cmd) - self.logger.debug(stdoutdata) - self.logger.warn(stderrdata) - else: - print "Subprocesses group, completed command: " - print cmd - print stdoutdata - print stderrdata - # Now update the state of the internal state - self.process_group[idx] = (True, (cmd, process)) self.running_process_count -= 1 - - + # if there are less then the allowed processes running and # we have waiting processes start another on - while (self.running_process_count < self.max_concurrent_processes - and - len(self.processes_waiting_for_execution) != 0): + while self.running_process_count < self.max_concurrent_processes and self.processes_waiting_for_execution: # Get the last process - cmd , cwd = self.processes_waiting_for_execution.pop() + cmd, cwd = self.processes_waiting_for_execution.pop() + # start it self._start_process(cmd, cwd) # If none of the processes return with error status if len(collected_exit_status) == 0: collected_exit_status = None + return collected_exit_status diff --git a/CEP/Pipeline/test/support/subprocessgroup_test.py b/CEP/Pipeline/test/support/subprocessgroup_test.py index 290be2967ce..b7b0a6589f5 100644 --- a/CEP/Pipeline/test/support/subprocessgroup_test.py +++ b/CEP/Pipeline/test/support/subprocessgroup_test.py @@ -25,6 +25,21 @@ class SubProcessGroupTest(unittest.TestCase): """ + def test_output_bigger_than_pipe(self): + process_group = SubProcessGroup(polling_interval=1) + + # print a lot of numbers + cmd = 'seq -s, 1 4096' + start_time = time.time() + + # Start it multiple times + for idx in range(2): + process_group.run(cmd) + + process_group.wait_for_finish() + end_time = time.time() + self.assertTrue((end_time - start_time) < 1) + def test_limit_number_of_proc(self): process_group = SubProcessGroup(polling_interval=1) -- GitLab From b58e303bdec3620ae9e3c02f55990b193117498d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 5 Apr 2016 17:23:50 +0000 Subject: [PATCH 096/933] Task #8437: Bugfix --- CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index d394d7a8b1e..f250f6f07ff 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -187,8 +187,8 @@ class SubProcessGroup(object): exit_status = process.exit_status # get the exit status - if exit_status != 0: - collected_exit_status.append((cmd, exit_status)) + if exit_status != 0: + collected_exit_status.append((process.cmd, exit_status)) # Now update the state of the internal state self.running_process_count -= 1 @@ -207,3 +207,4 @@ class SubProcessGroup(object): collected_exit_status = None return collected_exit_status + -- GitLab From dbecb13adcb19cd6be3cd03fc205bedfc4191adc Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 5 Apr 2016 17:34:34 +0000 Subject: [PATCH 097/933] Task #8437: Indentation fix --- CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index f250f6f07ff..0aa286e7fb3 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -36,7 +36,7 @@ class SubProcess(object): self.STDERR: logger.warn if logger else print_logger } self.logger = logger.info if logger else print_logger - self.logger("Subprocess started: %s" % self.cmd) + self.logger("Subprocess started: %s" % self.cmd) def done(self): if self.completed: -- GitLab From 8a295dfb6630d3c10b803110f1851963ea0dbad2 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 5 Apr 2016 17:34:59 +0000 Subject: [PATCH 098/933] Task #8437: Indentation fix --- CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index 0aa286e7fb3..aa3867b4855 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -36,7 +36,7 @@ class SubProcess(object): self.STDERR: logger.warn if logger else print_logger } self.logger = logger.info if logger else print_logger - self.logger("Subprocess started: %s" % self.cmd) + self.logger("Subprocess started: %s" % self.cmd) def done(self): if self.completed: @@ -54,7 +54,7 @@ class SubProcess(object): self.completed = True - self.logger("Subprocess completed: %s" % self.cmd) + self.logger("Subprocess completed: %s" % self.cmd) return True -- GitLab From 00f5d011ddf8d48686ce0267866b931545c9f106 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 5 Apr 2016 18:35:18 +0000 Subject: [PATCH 099/933] Task #8437: Harden output gathering --- .../lofarpipe/support/subprocessgroup.py | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index aa3867b4855..286bb823e3b 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -17,25 +17,42 @@ class SubProcess(object): self.cmd = cmd self.completed = False + # start process self.process = subprocess.Popen( cmd, cwd=cwd, - stdin=subprocess.PIPE, + + # Set buffering parameters + bufsize=1, # 1 = line buffering + universal_newlines=True, # translate ^M output by ssh -tt + + # Don't inherit our fds after fork() + close_fds=True, + + # I/O redirection: block stdin, read stdout/stderr separately + stdin=file("/dev/null"), stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.pid = self.process.pid self.exit_status = None + # output source streams + self.output_streams = { self.STDOUT: self.process.stdout, + self.STDERR: self.process.stderr } + + # output buffer self.output_buffers = { self.STDOUT: "", self.STDERR: "" } + # output sink def print_logger(line): print line self.output_loggers = { self.STDOUT: logger.debug if logger else print_logger, self.STDERR: logger.warn if logger else print_logger } - self.logger = logger.info if logger else print_logger + # report we started + self.logger = logger.info if logger else print_logger self.logger("Subprocess started: %s" % self.cmd) def done(self): @@ -59,7 +76,7 @@ class SubProcess(object): return True def fds(self): - return [self.process.stdout, self.process.stderr] + return self.output_streams.values() def read(self, fds): for fd in fds: @@ -82,6 +99,12 @@ class SubProcess(object): self.output_buffers[stdtype] = remainder + # 0-byte read means they closed the fd + if not output: + if stdtype in self.output_streams: + # Don't close (subprocess doesn't like that), + # but do registera to prevent further select()s. + del self.output_streams[stdtype] class SubProcessGroup(object): """ -- GitLab From c73e659adce6da5c9e40d4fe6a0dcfb48514fde7 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 5 Apr 2016 20:02:36 +0000 Subject: [PATCH 100/933] Task #8437: Support alternating stdout/stderr output using non-blocking reads --- .gitattributes | 1 + .../lofarpipe/support/subprocessgroup.py | 25 +++++++++++++------ .../test/support/output_stderr_stdout.sh | 14 +++++++++++ .../test/support/subprocessgroup_test.py | 4 +-- 4 files changed, 34 insertions(+), 10 deletions(-) create mode 100755 CEP/Pipeline/test/support/output_stderr_stdout.sh diff --git a/.gitattributes b/.gitattributes index e042858d9b1..320a36841a6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1672,6 +1672,7 @@ CEP/Pipeline/test/regression_tests/target_pipeline.py -text CEP/Pipeline/test/regression_tests/trunk_imaging_regression.parset -text CEP/Pipeline/test/support/__init__.py eol=lf CEP/Pipeline/test/support/loggingdecorators_test.py -text +CEP/Pipeline/test/support/output_stderr_stdout.sh eol=lf CEP/Pipeline/test/support/subprocessgroup_test.py eol=lf CEP/Pipeline/test/support/xmllogging_test.py -text CEP/Pipeline/test/test_framework/CMakeLists.txt eol=lf diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index 286bb823e3b..7dc11f4b406 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -1,5 +1,7 @@ import subprocess import select +import fcntl +import os import time from lofarpipe.support.lofarexceptions import PipelineException @@ -11,11 +13,18 @@ class SubProcess(object): """ Start a subprocess for `cmd' in working directory `cwd'. - Output is sent to `logger'. + Output is sent to `logger', or stdout if logger is None. """ + def print_logger(line): + print line + self.cmd = cmd self.completed = False + self.logger = logger.info if logger else print_logger + + # report we are starting + self.logger("Subprocess starting: %s" % self.cmd) # start process self.process = subprocess.Popen( @@ -45,15 +54,15 @@ class SubProcess(object): self.STDERR: "" } # output sink - def print_logger(line): - print line - self.output_loggers = { self.STDOUT: logger.debug if logger else print_logger, self.STDERR: logger.warn if logger else print_logger } - # report we started - self.logger = logger.info if logger else print_logger - self.logger("Subprocess started: %s" % self.cmd) + + # Set fds to non-blocking to allow <4k reads. This is needed if the process + # alternates between stdout and stderr output. + for f in self.output_streams.values(): + flag = fcntl.fcntl(f, fcntl.F_GETFL) + fcntl.fcntl(f, fcntl.F_SETFL, flag | os.O_NONBLOCK) def done(self): if self.completed: @@ -96,7 +105,7 @@ class SubProcess(object): if flush: self.output_loggers[stdtype](remainder) remainder = "" - + self.output_buffers[stdtype] = remainder # 0-byte read means they closed the fd diff --git a/CEP/Pipeline/test/support/output_stderr_stdout.sh b/CEP/Pipeline/test/support/output_stderr_stdout.sh new file mode 100755 index 00000000000..31b86045230 --- /dev/null +++ b/CEP/Pipeline/test/support/output_stderr_stdout.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Spam stderr, but not enough to fill a 4k buffer. +# Reading from stderr should not block. +for i in `seq 1 10`; do + echo e${i}e 1>&2 +done + +# Now spam stdout, filling any buffer. +# If reading from stderr blocks in the previuos loop, +# we can't finish this loop. +for i in `seq 1 4096`; do + echo o${i}o o${i}o o${i}o o${i}o o${i}o o${i}o +done diff --git a/CEP/Pipeline/test/support/subprocessgroup_test.py b/CEP/Pipeline/test/support/subprocessgroup_test.py index b7b0a6589f5..816d7fe4628 100644 --- a/CEP/Pipeline/test/support/subprocessgroup_test.py +++ b/CEP/Pipeline/test/support/subprocessgroup_test.py @@ -25,11 +25,11 @@ class SubProcessGroupTest(unittest.TestCase): """ - def test_output_bigger_than_pipe(self): + def test_alternating_output(self): process_group = SubProcessGroup(polling_interval=1) # print a lot of numbers - cmd = 'seq -s, 1 4096' + cmd = '%s/output_stderr_stdout.sh' % (os.path.dirname(__file__) or ".",) start_time = time.time() # Start it multiple times -- GitLab From d7ed8044a8b3f0279a044cf39386304f33dc9a68 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 5 Apr 2016 22:15:03 +0000 Subject: [PATCH 101/933] Task #8437: Added python-matplotlib for statsplot.py --- Docker/lofar-pipeline/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 80d41c9fa68..531b41e58e8 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -6,7 +6,7 @@ FROM lofar-base:${LOFAR_TAG} ENV AOFLAGGER_VERSION=2.7.1 # Run-time dependencies -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync && \ +RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ sudo apt-get -y install python-pip python-dev && \ sudo pip install pyfits pywcs python-monetdb && \ sudo apt-get -y purge python-pip python-dev && \ -- GitLab From 35f5e92063ed79e86bdb3d7d1b61faa65db48e59 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 12:56:15 +0000 Subject: [PATCH 102/933] Task #8437: Propagate SIGTERM/SIGINT to pipeline program --- CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh index c7d0c8a8a9f..b184a52b33d 100755 --- a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -85,7 +85,15 @@ export LOFAR_OBSID="$OBSID" # Start the Python program echo "**** $(date) ****" echo "Executing: ${PROGRAM_NAME} ${OPTIONS} ${PARSET}" -${PROGRAM_NAME} ${OPTIONS} ${PARSET} + +# Run and propagate SIGTERM, SIGINT +trap 'kill -TERM $PID' TERM INT +${PROGRAM_NAME} ${OPTIONS} ${PARSET} & +PID=$! +wait $PID # This will return >128 if this shell is killed +trap - TERM INT +wait $PID # This will return the actual exit status of our program + RESULT=$? # ======= Fini -- GitLab From febe889a0f83a1f5f17ed046f255867793790853 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 13:23:53 +0000 Subject: [PATCH 103/933] Task #8437: Propagate signals through chuser.sh --- Docker/lofar-base/chuser.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Docker/lofar-base/chuser.sh b/Docker/lofar-base/chuser.sh index 53911fb5a0e..0b034ddec1a 100755 --- a/Docker/lofar-base/chuser.sh +++ b/Docker/lofar-base/chuser.sh @@ -21,7 +21,10 @@ if [ -n "${LUSER}" ]; then chown --from=${OLDID} -R ${LUSER}:${LGROUP} /opt fi -# Switch to the updated user +# Update environment for updated user export HOME=/home/${USER} touch -a $HOME/.bashrc -sudo -u ${USER} -E -s /bin/bash -c "source $HOME/.bashrc;$*" +source $HOME/.bashrc + +# Use exec to make sure we propagate signals +exec sudo -u ${USER} -E "$@" -- GitLab From 28add1a7096a305447cbcae28df2910585e7ce7c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 14:11:14 +0000 Subject: [PATCH 104/933] Task #8437: Remove bash from docker command line to make signal propagation easier --- .../lofarpipe/support/remotecommand.py | 30 ++++++++++++------- .../recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index 7ac394c699e..4edf2d52a2c 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -196,7 +196,9 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config with the following strings replaced: {host} := host to execute command on - {command} := bash command line to be executed + {env} := "A=B C=D" string, the environment to set + {docker_env} := "-e A=B -e C=D" string, the environment to set + {command} := command to be executed {uid} := uid of the calling user {slurm_job_id} := the SLURM job id to allocate resources in @@ -205,9 +207,17 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config {nr_cores} := number of cores to allocate for this job """ - commandArray = ["%s=%s" % (key, value) for key, value in environment.items()] - commandArray.append(command) - commandArray.extend(re.escape(str(arg)) for arg in arguments) + + # construct {env} + envPairs = ["%s=%s" % (key, value) for key, value in environment.items()] + envStr = " ".join(envPairs) + + # construct {docker-env} + dockerEnvPairs = ["-e %s=%s" % (key, value) for key, value in environment.items()] + dockerEnvStr = " ".join(dockerEnvPairs) + + # construct {command} + commandArray = [command] + [re.escape(str(arg)) for arg in arguments] commandStr = " ".join(commandArray) # Determine job name @@ -219,20 +229,20 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config return args[1] if args[0] == "python" else args[0] - # Construct the full command line, except for {command}, as that itself - # can contain spaces which we don't want to split on. + # Construct the full command line full_command_line = config.get('remote', 'cmdline').format( uid = os.geteuid(), slurm_job_id = os.environ.get("SLURM_JOB_ID"), host = host, - command = "{command}", + + env = envStr, + docker_env = dockerEnvStr, + + command = commandStr, job_name = jobname(command), nr_cores = resources.get("cores", 1), ).split(' ') - # Fill in {command} somewhere - full_command_line = [x.format(command = commandStr) for x in full_command_line] - logger.debug("Dispatching command to %s with custom_cmdline: %s" % (host, full_command_line)) process = spawn_process(full_command_line, logger) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 6c1c35a244a..473eb91eeaf 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -67,4 +67,4 @@ globalfs = yes # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} /bin/bash -c "\"{command}\"" +cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} {docker-env} {command} -- GitLab From ec75765a23d5f1929832104d25e1b12abdd34001 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 14:16:58 +0000 Subject: [PATCH 105/933] Task #8437: Drop paramiko support to make refactoring process spawning easier --- .../lofarpipe/support/remotecommand.py | 59 +------------------ 1 file changed, 2 insertions(+), 57 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index 4edf2d52a2c..984fbc4ecb4 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -17,7 +17,7 @@ import time import xml.dom.minidom as xml from lofarpipe.support.pipelinelogging import log_process_output -from lofarpipe.support.utilities import spawn_process +from lofarpipe.support.subprocess import spawn_process from lofarpipe.support.lofarexceptions import PipelineQuit from lofarpipe.support.jobserver import job_server import lofarpipe.support.lofaringredient as ingredient @@ -27,39 +27,6 @@ from lofarpipe.support.xmllogging import add_child # frame. When multiplexing lots of threads, that will cause memory issues. threading.stack_size(1048576) -class ParamikoWrapper(object): - """ - Sends an SSH command to a host using paramiko, then emulates a Popen-like - interface so that we can pass it back to pipeline recipes. - """ - def __init__(self, paramiko_client, command): - self.returncode = None - self.client = paramiko_client - self.chan = paramiko_client.get_transport().open_session() - self.chan.get_pty() - self.chan.exec_command(command) - self.stdout = self.chan.makefile('rb', -1) - self.stderr = self.chan.makefile_stderr('rb', -1) - - def communicate(self): - if not self.returncode: - self.returncode = self.chan.recv_exit_status() - stdout = "\n".join(line.strip() for line in self.stdout.readlines()) + "\n" - stderr = "\n".join(line.strip() for line in self.stdout.readlines()) + "\n" - return stdout, stderr - - def poll(self): - if not self.returncode and self.chan.exit_status_ready(): - self.returncode = self.chan.recv_exit_status() - return self.returncode - - def wait(self): - if not self.returncode: - self.returncode = self.chan.recv_exit_status() - return self.returncode - - def kill(self): - self.chan.close() def run_remote_command(config, logger, host, command, env, arguments = None, resources = {}): """ @@ -80,13 +47,7 @@ def run_remote_command(config, logger, host, command, env, arguments = None, res logger.info("********************** Remote method is %s" % method) - if method == "paramiko": - try: - key_filename = config.get('remote', 'key_filename') - except: - key_filename = None - return run_via_paramiko(logger, host, command, env, arguments, key_filename) - elif method == "mpirun": + if method == "mpirun": return run_via_mpirun(logger, host, command, env, arguments) elif method == "local": return run_via_local(logger, command, arguments) @@ -249,22 +210,6 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config process.kill = lambda : os.kill(process.pid, signal.SIGKILL) return process -def run_via_paramiko(logger, host, command, environment, arguments, key_filename): - """ - Dispatch a remote command via paramiko. - - We return an instance of ParamikoWrapper. - """ - logger.debug("Dispatching command to %s with paramiko" % host) - import paramiko - client = paramiko.SSHClient() - client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - client.connect(host, key_filename = key_filename) - commandstring = ["%s=%s" % (key, value) for key, value in environment.items()] - commandstring.append(command) - commandstring.extend(re.escape(str(arg)) for arg in arguments) - return ParamikoWrapper(client, " ".join(commandstring)) - class ProcessLimiter(defaultdict): """ Provide a dictionary-like structure of bounded semaphores with arbitrary -- GitLab From 7474fec916006d1bbd7fd11cccc49345169f39f7 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 14:41:22 +0000 Subject: [PATCH 106/933] Task #8437: Use SubProcessGroup to spawn processes wherever possible. --- .../lofarpipe/support/remotecommand.py | 70 ++++++------------- .../lofarpipe/support/subprocessgroup.py | 26 ++++++- .../framework/lofarpipe/support/utilities.py | 6 ++ 3 files changed, 52 insertions(+), 50 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index 984fbc4ecb4..4609d07b719 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -17,7 +17,7 @@ import time import xml.dom.minidom as xml from lofarpipe.support.pipelinelogging import log_process_output -from lofarpipe.support.subprocess import spawn_process +from lofarpipe.support.subprocessgroup import SubProcessGroup from lofarpipe.support.lofarexceptions import PipelineQuit from lofarpipe.support.jobserver import job_server import lofarpipe.support.lofaringredient as ingredient @@ -33,8 +33,7 @@ def run_remote_command(config, logger, host, command, env, arguments = None, res Run command on host, passing it arguments from the arguments list and exporting key/value pairs from env(a dictionary). - Returns an object with poll() and communicate() methods, similar to - subprocess.Popen. + Returns an array of command line arguments to start. This is a generic interface to potentially multiple ways of running commands (SSH, mpirun, etc). The appropriate method is chosen from the @@ -66,16 +65,14 @@ def run_via_slurm_srun_cep3(logger, command, arguments, host): logger.debug("Dispatching command to %s with srun" % host) for arg in arguments: command = command + " " + str(arg) - commandstring = ["srun","-N 1","-n 1","-w",host, "/bin/sh", "-c", "hostname && " + command] + commandarray = ["srun","-N 1","-n 1","-w",host, "/bin/sh", "-c", "hostname && " + command] #commandstring = ["srun","-N 1","--cpu_bind=map_cpu:none","-w",host, "/bin/sh", "-c", "hostname && " + command] # we have a bug that crashes jobs when too many get startet at the same time # temporary NOT 100% reliable workaround #from random import randint #time.sleep(randint(0,10)) ########################## - process = spawn_process(commandstring, logger) - process.kill = lambda : os.kill(process.pid, signal.SIGKILL) - return process + return commandarray def run_via_mpirun(logger, host, command, environment, arguments): """ @@ -86,47 +83,35 @@ def run_via_mpirun(logger, host, command, environment, arguments): """ logger.debug("Dispatching command to %s with mpirun" % host) mpi_cmd = ["/usr/bin/mpirun", "-host", host] - for key in environment.keys(): - mpi_cmd.extend(["-x", key]) + for key,value in environment.iteritems(): + mpi_cmd.extend(["-x", "%s=%s" % (key,value)]) mpi_cmd.append("--") mpi_cmd.extend(command.split()) # command is split into (python, script) mpi_cmd.extend(str(arg) for arg in arguments) - env = os.environ - env.update(environment) - process = spawn_process(mpi_cmd, logger, env = env) - # mpirun should be killed with a SIGTERM to enable it to shut down the - # remote command. - process.kill = lambda : os.kill(process.pid, signal.SIGTERM) - return process + return mpi_cmd # let the mpi demon manage free resources to start jobs def run_via_mpiexec(logger, command, arguments, host): for arg in arguments: command = command + " " + str(arg) - commandstring = ["mpiexec", "-x", "-np=1", "/bin/sh", "-c", "hostname && " + command] - process = spawn_process(commandstring, logger) - process.kill = lambda : os.kill(process.pid, signal.SIGKILL) - return process + commandarray = ["mpiexec", "-x", "-np=1", "/bin/sh", "-c", "hostname && " + command] + return commandarray # start mpi run on cep # TODO: rsync fails on missing ssh key?? def run_via_mpiexec_cep(logger, command, arguments, host): for arg in arguments: command = command + " " + str(arg) - commandstring = ["mpiexec", "-x", "PYTHONPATH", "-x", "LD_LIBRARY_PATH", "-x", "PATH", "-H", host, "/bin/sh", "-c", "hostname ; " + command] - process = spawn_process(commandstring, logger) - process.kill = lambda : os.kill(process.pid, signal.SIGKILL) - return process + commandarray = ["mpiexec", "-x", "PYTHONPATH", "-x", "LD_LIBRARY_PATH", "-x", "PATH", "-H", host, "/bin/sh", "-c", "hostname ; " + command] + return commandarray def run_via_local(logger, command, arguments): - commandstring = ["/bin/sh", "-c"] + commandarray = ["/bin/sh", "-c"] for arg in arguments: command = command + " " + str(arg) - commandstring.append(command) - process = spawn_process(commandstring, logger) - process.kill = lambda : os.kill(process.pid, signal.SIGKILL) - return process + commandarray.append(command) + return commandarray def run_via_ssh(logger, host, command, environment, arguments): """ @@ -142,9 +127,7 @@ def run_via_ssh(logger, host, command, environment, arguments): commandstring.append(command) commandstring.extend(re.escape(str(arg)) for arg in arguments) ssh_cmd.append('"' + " ".join(commandstring) + '"') - process = spawn_process(ssh_cmd, logger) - process.kill = lambda : os.kill(process.pid, signal.SIGKILL) - return process + return ssh_cmd def run_via_custom_cmdline(logger, host, command, environment, arguments, config, resources): """ @@ -205,10 +188,7 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config ).split(' ') logger.debug("Dispatching command to %s with custom_cmdline: %s" % (host, full_command_line)) - - process = spawn_process(full_command_line, logger) - process.kill = lambda : os.kill(process.pid, signal.SIGKILL) - return process + return full_command_line class ProcessLimiter(defaultdict): """ @@ -276,7 +256,7 @@ class ComputeJob(object): self.results['returncode'] = 1 error.set() return 1 - process = run_remote_command( + cmdarray = run_remote_command( config, logger, self.host, @@ -291,18 +271,12 @@ class ComputeJob(object): arguments = [id, jobhost, jobport], resources = self.resources ) - # Wait for process to finish. In the meantime, if the killswitch - # is set (by an exception in the main thread), forcibly kill our - # job off. - while process.poll() == None: - if killswitch.isSet(): - process.kill() - else: - time.sleep(1) - sout, serr = process.communicate() - serr = serr.replace("Connection to %s closed.\r\n" % self.host, "") - log_process_output("Remote command", sout, serr, logger) + # Run and wait for process to finish. + pg = SubProcessGroup(logger=logger, killSwitch=killswitch) + pg.run(cmdarray) + pg.wait_for_finish() + except Exception, e: logger.exception("Failed to run remote process %s (%s)" % (self.command, str(e))) self.results['returncode'] = 1 diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index 7dc11f4b406..d5bb3292586 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -1,7 +1,8 @@ import subprocess import select -import fcntl import os +import signal +import fcntl import time from lofarpipe.support.lofarexceptions import PipelineException @@ -20,6 +21,7 @@ class SubProcess(object): print line self.cmd = cmd + self.killed = False self.completed = False self.logger = logger.info if logger else print_logger @@ -84,6 +86,13 @@ class SubProcess(object): return True + def kill(self): + if self.killed: + return + + os.signal(self.pid, signal.SIGTERM) + self.killed = True + def fds(self): return self.output_streams.values() @@ -126,7 +135,8 @@ class SubProcessGroup(object): max_concurrent_processes = 8, # poll each 10 seconds: we have a mix of short and long # running processes - polling_interval = 10): + polling_interval = 10, + killSwitch = None): self.process_group = [] self.logger = logger self.usageStats = usageStats @@ -138,10 +148,17 @@ class SubProcessGroup(object): self.processes_waiting_for_execution = [] self.polling_interval = polling_interval + self.killSwitch = killSwitch + def _start_process(self, cmd, cwd): """ Helper function collection all the coded needed to start a process """ + + # Do nothing if we're stopping + if self.killSwitch and self.killSwitch.isSet(): + return + # About to start a process, increase the counter self.running_process_count += 1 @@ -193,6 +210,11 @@ class SubProcessGroup(object): # collect all unfinished processes processes = [p for p in self.process_group if not p.completed] + # check whether we're stopping + if self.killSwitch and self.killSwitch.isSet(): + for process in processes: + process.kill() + # collect fds we need to poll fds = [] for process in processes: diff --git a/CEP/Pipeline/framework/lofarpipe/support/utilities.py b/CEP/Pipeline/framework/lofarpipe/support/utilities.py index de7ec3e2795..37c883f128c 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/utilities.py +++ b/CEP/Pipeline/framework/lofarpipe/support/utilities.py @@ -217,6 +217,9 @@ def string_to_list(my_string): def spawn_process(cmd, logger, cwd = None, env = None, max_tries = 2, max_timeout = 30): """ + DEPRECATED -- spawn_process leads to custom, and thus bad, output handling. Use + support.subprocessgroup.SubProcessGroup instead. + Tries to spawn a process. If it hits an OSError due to lack of memory or too many open files, it @@ -225,6 +228,9 @@ def spawn_process(cmd, logger, cwd = None, env = None, max_tries = 2, max_timeou If successful, the process object is returned. Otherwise, we eventually propagate the exception. """ + + logger.error("support.utilities.spawn_process is DEPRECATED. Please use support.subprocessgroup.SubProcessGroup") + trycounter = 0 while True: logger.debug( -- GitLab From 52ccf7c3a35a52882a1b1cdb53274339715ce623 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 17:16:33 +0000 Subject: [PATCH 107/933] Task #8437: Move bashrc to /opt, and include and propagate it to user-specified command --- Docker/lofar-base/Dockerfile.tmpl | 2 +- Docker/lofar-base/bashrc | 1 + Docker/lofar-base/chuser.sh | 8 +++++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index af8599032ca..96b70375c3b 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -164,7 +164,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y gi # # config # -COPY bashrc /home/${USER}/.bashrc +COPY bashrc /opt/bashrc # # entry diff --git a/Docker/lofar-base/bashrc b/Docker/lofar-base/bashrc index 74dbbeef125..0a421af1a7d 100644 --- a/Docker/lofar-base/bashrc +++ b/Docker/lofar-base/bashrc @@ -2,6 +2,7 @@ # lofar [ -r ${INSTALLDIR}/lofar/lofarinit.sh ] && source ${INSTALLDIR}/lofar/lofarinit.sh +export PATH PYTHONPATH LD_LIBRARY_PATH LOFARROOT # qpid source ${INSTALLDIR}/qpid/.profile diff --git a/Docker/lofar-base/chuser.sh b/Docker/lofar-base/chuser.sh index 0b034ddec1a..3c4e9d3ab0f 100755 --- a/Docker/lofar-base/chuser.sh +++ b/Docker/lofar-base/chuser.sh @@ -23,8 +23,10 @@ fi # Update environment for updated user export HOME=/home/${USER} -touch -a $HOME/.bashrc -source $HOME/.bashrc + +# Import bashrc for software in /opt +source /opt/bashrc # Use exec to make sure we propagate signals -exec sudo -u ${USER} -E "$@" +# `env' is needed to propagate PATH variables through sudo. +exec sudo -u ${USER} -E env "PATH=$PATH" "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" "PYTHONPATH=$PYTHONPATH" "$@" -- GitLab From e5dc6fa1dcf3f5448b5347d2b8b918fe910f42b5 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 18:52:00 +0000 Subject: [PATCH 108/933] Task #8437: Fixed typo --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 473eb91eeaf..de3060b235d 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -67,4 +67,4 @@ globalfs = yes # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} {docker-env} {command} +cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} {docker_env} {command} -- GitLab From 58638e2c072cd4f401a3d57d94dee65ede730d58 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 18:57:42 +0000 Subject: [PATCH 109/933] Task #8437: Avoid empty parameters --- CEP/Pipeline/framework/lofarpipe/support/remotecommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index 4609d07b719..c1993194c5d 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -185,7 +185,7 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config command = commandStr, job_name = jobname(command), nr_cores = resources.get("cores", 1), - ).split(' ') + ).split() logger.debug("Dispatching command to %s with custom_cmdline: %s" % (host, full_command_line)) return full_command_line -- GitLab From 62134b1c30ee9c0cc1b98b3679290c947d321e8c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 20:12:00 +0000 Subject: [PATCH 110/933] Task #8437: All docker args need to be put before the image name --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index de3060b235d..8465c065719 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -67,4 +67,4 @@ globalfs = yes # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v /data:/data --net=host lofar-pipeline:${LOFAR_TAG} {docker_env} {command} +cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v /data:/data --net=host {docker_env} lofar-pipeline:${LOFAR_TAG} {command} -- GitLab From e691616cb851abc324a593a942072fe3776a50ff Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 20:48:46 +0000 Subject: [PATCH 111/933] Task #8437: Use globally unique location for concat.ms to support scratch dir on globalfs --- CEP/Pipeline/recipes/sip/bin/imaging_pipeline.py | 4 ++-- CEP/Pipeline/recipes/sip/bin/long_baseline_pipeline.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/imaging_pipeline.py b/CEP/Pipeline/recipes/sip/bin/imaging_pipeline.py index acac777e345..93d394a9709 100755 --- a/CEP/Pipeline/recipes/sip/bin/imaging_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/imaging_pipeline.py @@ -265,8 +265,8 @@ class imaging_pipeline(control): # MS per image. It must be stored on the same host as the final image. self.target_data = copy.deepcopy(self.output_data) - for item in self.target_data: - item.file = os.path.join(self.scratch_directory, 'concat.ms') + for idx, item in enumerate(self.target_data): + item.file = os.path.join(self.scratch_directory, 'ms_per_image_%d' % idx, 'concat.ms') @xml_node diff --git a/CEP/Pipeline/recipes/sip/bin/long_baseline_pipeline.py b/CEP/Pipeline/recipes/sip/bin/long_baseline_pipeline.py index 3375b5ae7a5..104aeea30bf 100644 --- a/CEP/Pipeline/recipes/sip/bin/long_baseline_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/long_baseline_pipeline.py @@ -206,8 +206,8 @@ class longbaseline_pipeline(control): # MS per image. It must be stored on the same host as the final image. self.target_data = copy.deepcopy(self.output_data) - for item in self.target_data: - item.file = os.path.join(self.scratch_directory, 'concat.ms') + for idx, item in enumerate(self.target_data): + item.file = os.path.join(self.scratch_directory, 'ms_per_image_%d' % idx, 'concat.ms') @xml_node -- GitLab From 996d0b19d1c4d2f3ec267c734b6ae65458533ea9 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 20:53:48 +0000 Subject: [PATCH 112/933] Task #8437: Improved logging for subprocess entry/exit --- CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index d5bb3292586..e31c618c01d 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -26,7 +26,7 @@ class SubProcess(object): self.logger = logger.info if logger else print_logger # report we are starting - self.logger("Subprocess starting: %s" % self.cmd) + self.logger("Subprocess starting: %s (%s)" % (" ".join(self.cmd), self.cmd) # start process self.process = subprocess.Popen( @@ -82,7 +82,7 @@ class SubProcess(object): self.completed = True - self.logger("Subprocess completed: %s" % self.cmd) + self.logger("Subprocess completed with exit status %d: %s" % (self.exit_status, " ".join(self.cmd))) return True -- GitLab From d8b872a6e3b08f4c5a32b8dae60d5fcd8412fc81 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 21:08:35 +0000 Subject: [PATCH 113/933] Task #8437: Bugfix: corrected return of process exit code --- .../framework/lofarpipe/support/remotecommand.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index c1993194c5d..7e885b8eacf 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -275,7 +275,7 @@ class ComputeJob(object): # Run and wait for process to finish. pg = SubProcessGroup(logger=logger, killSwitch=killswitch) pg.run(cmdarray) - pg.wait_for_finish() + job_successful = (pg.wait_for_finish() is None) except Exception, e: logger.exception("Failed to run remote process %s (%s)" % (self.command, str(e))) @@ -285,10 +285,10 @@ class ComputeJob(object): finally: limiter[self.host].release() - if process.returncode != 0: + if not job_successful: logger.error( - "Remote process %s %s failed on %s (status: %d)" % \ - (self.command, self.arguments, self.host, process.returncode) + "Remote process %s %s failed on %s" % \ + (self.command, self.arguments, self.host) ) error.set() @@ -296,13 +296,13 @@ class ComputeJob(object): # add the duration of time_info_end = time.time() self.results["job_duration"] = str(time_info_end - time_info_start) - self.results['returncode'] = process.returncode + self.results['returncode'] = 0 if job_successful else 1 logger.debug( "compute.dispatch results job {0}: {1}: {2}, {3}: {4} ".format( self.id, "job_duration", self.results["job_duration"], "returncode", self.results["returncode"] )) - return process.returncode + return self.results["returncode"] def threadwatcher(threadpool, logger, killswitch): -- GitLab From b074f8e5ccf2e9f7f4e6297a821ada765a17c5b5 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 21:23:50 +0000 Subject: [PATCH 114/933] Task #8437: Faster builds for head0X.cep4 --- Docker/lofar-base/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 96b70375c3b..a228d6defa6 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -31,7 +31,7 @@ ENV UID=${BUILD_UID} # # set-build-options # -ENV J=4 +ENV J=6 # # Base and runtime dependencies -- GitLab From a9addc16c17959080e82634e7271ce0f4e5f5161 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Apr 2016 21:33:51 +0000 Subject: [PATCH 115/933] Task #8437: Fixed typo --- CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index e31c618c01d..d20f67aaa99 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -26,7 +26,7 @@ class SubProcess(object): self.logger = logger.info if logger else print_logger # report we are starting - self.logger("Subprocess starting: %s (%s)" % (" ".join(self.cmd), self.cmd) + self.logger("Subprocess starting: %s (%s)" % (" ".join(self.cmd), self.cmd)) # start process self.process = subprocess.Popen( -- GitLab From 1cf31217c22e0680840096fec1e549bce52239ca Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 7 Apr 2016 12:13:02 +0000 Subject: [PATCH 116/933] Task #8437: Use globally unique location for concat.ms to support scratch dir on globalfs --- CEP/Pipeline/recipes/sip/bin/msss_imager_pipeline.py | 4 ++-- CEP/Pipeline/recipes/sip/bin/selfcal_imager_pipeline.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/msss_imager_pipeline.py b/CEP/Pipeline/recipes/sip/bin/msss_imager_pipeline.py index 8b075b094d8..f2ce16de7ca 100755 --- a/CEP/Pipeline/recipes/sip/bin/msss_imager_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/msss_imager_pipeline.py @@ -273,8 +273,8 @@ class msss_imager_pipeline(control): # MS per image. It must be stored on the same host as the final image. self.target_data = copy.deepcopy(self.output_data) - for item in self.target_data: - item.file = os.path.join(self.scratch_directory, 'concat.ms') + for idx, item in enumerate(self.target_data): + item.file = os.path.join(self.scratch_directory, 'ms_per_image_%d' % idx, 'concat.ms') @xml_node diff --git a/CEP/Pipeline/recipes/sip/bin/selfcal_imager_pipeline.py b/CEP/Pipeline/recipes/sip/bin/selfcal_imager_pipeline.py index 0d1e725ddf7..040e51e7b92 100644 --- a/CEP/Pipeline/recipes/sip/bin/selfcal_imager_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/selfcal_imager_pipeline.py @@ -446,8 +446,8 @@ class selfcal_imager_pipeline(control): # MS per image. It must be stored on the same host as the final image. self.target_data = copy.deepcopy(self.output_data) - for item in self.target_data: - item.file = os.path.join(self.scratch_directory, 'concat.ms') + for idx, item in enumerate(self.target_data): + item.file = os.path.join(self.scratch_directory, 'ms_per_image_%d' % idx, 'concat.ms') @xml_node -- GitLab From 575405a0793143c7aefa40ec0380e88386a8a69c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 13 Apr 2016 10:03:10 +0000 Subject: [PATCH 117/933] Task #8437: Use 8 cores for BBS --- CEP/Pipeline/recipes/sip/master/bbs_reducer.py | 10 +++++++++- CEP/Pipeline/recipes/sip/master/imager_bbs.py | 10 +++++++++- CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in | 1 - CEP/Pipeline/recipes/sip/tasks.cfg.in | 2 ++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/master/bbs_reducer.py b/CEP/Pipeline/recipes/sip/master/bbs_reducer.py index 5fda848e56d..25bab0248d2 100644 --- a/CEP/Pipeline/recipes/sip/master/bbs_reducer.py +++ b/CEP/Pipeline/recipes/sip/master/bbs_reducer.py @@ -26,6 +26,11 @@ class bbs_reducer(BaseRecipe, RemoteCommandRecipeMixIn): '-p', '--parset', help="BBS configuration parset" ), + 'nthreads': ingredient.IntField( + '--nthreads', + default=8, + help="Number of threads per process" + ), 'executable': ingredient.ExecField( '--executable', help="The full path to the BBS-reducer executable" @@ -115,7 +120,10 @@ class bbs_reducer(BaseRecipe, RemoteCommandRecipeMixIn): self.inputs['executable'], self.inputs['parset'], self.environment - ] + ], + resources={ + "cores": self.inputs['nthreads'] + } ) ) self._schedule_jobs(self.jobs) diff --git a/CEP/Pipeline/recipes/sip/master/imager_bbs.py b/CEP/Pipeline/recipes/sip/master/imager_bbs.py index c17450c4bff..809c2f182fe 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_bbs.py +++ b/CEP/Pipeline/recipes/sip/master/imager_bbs.py @@ -36,6 +36,11 @@ class imager_bbs(BaseRecipe, RemoteCommandRecipeMixIn): '-p', '--parset', help="BBS configuration parset" ), + 'nthreads': ingredient.IntField( + '--nthreads', + default=8, + help="Number of threads per process" + ), 'bbs_executable': ingredient.StringField( '--bbs-executable', help="BBS standalone executable (bbs-reducer)" @@ -128,7 +133,10 @@ class imager_bbs(BaseRecipe, RemoteCommandRecipeMixIn): arguments = [self.inputs['bbs_executable'], self.inputs['parset'], ms_list_path, parmdb_list_path, sourcedb_list_path] - jobs.append(ComputeJob(host, node_command, arguments)) + jobs.append(ComputeJob(host, node_command, arguments, + resources={ + "cores": self.inputs['nthreads'] + })) # start and wait till all are finished self._schedule_jobs(jobs) diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in index 895a78d7a96..c6708c69332 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in @@ -1,6 +1,5 @@ [ndppp] nproc = 0 -nthreads = 8 [setupparmdb] nproc = 0 diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.in b/CEP/Pipeline/recipes/sip/tasks.cfg.in index ab6fcb96cbe..5d6d46b8fb8 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.in @@ -86,6 +86,7 @@ makesourcedb_path = %(lofarroot)s/bin/makesourcedb [imager_bbs] recipe = imager_bbs bbs_executable = %(lofarroot)s/bin/bbs-reducer +nthreads = 8 [imager_source_finding] recipe = imager_source_finding @@ -106,6 +107,7 @@ parset = %(runtime_directory)s/%(job_name)s/parsets/bbs.parset instrument_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/instrument.mapfile sky_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/sky.mapfile data_mapfile = %(runtime_directory)s/%(job_name)s/mapfiles/bbs.mapfile +nthreads = 8 [selfcal_awimager] recipe = selfcal_awimager -- GitLab From 6647fe9284214a87401e637a54a12e4161f3a53c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 13 Apr 2016 17:02:29 +0000 Subject: [PATCH 118/933] Task #8437: Globally unique input files for imager_bbs --- CEP/Pipeline/recipes/sip/master/imager_bbs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/master/imager_bbs.py b/CEP/Pipeline/recipes/sip/master/imager_bbs.py index 809c2f182fe..90d400c51e8 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_bbs.py +++ b/CEP/Pipeline/recipes/sip/master/imager_bbs.py @@ -111,22 +111,22 @@ class imager_bbs(BaseRecipe, RemoteCommandRecipeMixIn): ms_map.iterator = parmdb_map.iterator = sourcedb_map.iterator = \ DataMap.SkipIterator - for (ms, parmdb, sourcedb) in zip(ms_map, parmdb_map, sourcedb_map): + for (idx, (ms, parmdb, sourcedb)) in enumerate(zip(ms_map, parmdb_map, sourcedb_map)): #host is same for each entry (validate_data_maps) host, ms_list = ms.host, ms.file # Write data maps to MultaDataMaps ms_list_path = os.path.join( - map_dir, host + "_ms_" + run_id + ".map") + map_dir, "%s-%s_map_%s.map" % (host, idx, run_id)) MultiDataMap([tuple([host, ms_list, False])]).save(ms_list_path) parmdb_list_path = os.path.join( - map_dir, host + "_parmdb_" + run_id + ".map") + map_dir, "%s-%s_parmdb_%s.map" % (host, idx, run_id)) MultiDataMap( [tuple([host, parmdb.file, False])]).save(parmdb_list_path) sourcedb_list_path = os.path.join( - map_dir, host + "_sky_" + run_id + ".map") + map_dir, "%s-%s_sky_%s.map" % (host, idx, run_id)) MultiDataMap( [tuple([host, [sourcedb.file], False])]).save(sourcedb_list_path) -- GitLab From 5ae3dc9bc057fd9f7ec39c595d1a6fe9d3d75b80 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 14 Apr 2016 06:05:23 +0000 Subject: [PATCH 119/933] Task #8437: Use globally unique temp file names in imager_awimager and imager_source_finding recipes --- CEP/Pipeline/recipes/sip/master/imager_awimager.py | 4 ++-- CEP/Pipeline/recipes/sip/master/imager_source_finding.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/master/imager_awimager.py b/CEP/Pipeline/recipes/sip/master/imager_awimager.py index 1e88c5ecb27..bc4cc5a1861 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_awimager.py +++ b/CEP/Pipeline/recipes/sip/master/imager_awimager.py @@ -120,7 +120,7 @@ class imager_awimager(BaseRecipe, RemoteCommandRecipeMixIn): sourcedb_map.iterator = input_map.iterator = output_map.iterator = \ DataMap.SkipIterator - for measurement_item, source_item in zip(input_map, sourcedb_map): + for idx, (measurement_item, source_item) in enumerate(zip(input_map, sourcedb_map)): if measurement_item.skip or source_item.skip: jobs.append(None) continue @@ -134,7 +134,7 @@ class imager_awimager(BaseRecipe, RemoteCommandRecipeMixIn): self.environment, self.inputs['parset'], self.inputs['working_directory'], - self.inputs['output_image'], + "%s-%s" % (self.inputs['output_image'], idx), measurement_path, sourcedb_path, self.inputs['mask_patch_size'], diff --git a/CEP/Pipeline/recipes/sip/master/imager_source_finding.py b/CEP/Pipeline/recipes/sip/master/imager_source_finding.py index 59fd13cfed4..40bb1cf6c0b 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_source_finding.py +++ b/CEP/Pipeline/recipes/sip/master/imager_source_finding.py @@ -91,15 +91,15 @@ class imager_source_finding(BaseRecipe, RemoteCommandRecipeMixIn): node_command = " python %s" % (self.__file__.replace("master", "nodes")) jobs = [] input_map.iterator = DataMap.SkipIterator - for item in input_map: + for idx, item in enumerate(input_map): arguments = [item.file, self.inputs["bdsm_parset_file_run1"], self.inputs["bdsm_parset_file_run2x"], - catalog_output_path, + "%s-%s" % (catalog_output_path, idx), os.path.join( self.inputs["working_directory"], - "bdsm_output.img"), - self.inputs['sourcedb_target_path'], + "bdsm_output-%s.img" % (idx, )), + "%s-%s" % (self.inputs['sourcedb_target_path'], idx), self.environment, self.inputs['working_directory'], self.inputs['makesourcedb_path'] -- GitLab From 004298d4d792f51746be78a585946696466e7ee8 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 14 Apr 2016 08:50:03 +0000 Subject: [PATCH 120/933] Task #8437: Use lofar.otdb.command bus to set statusses --- CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh | 2 +- CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh b/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh index cd4acf54d37..bf835676ca8 100755 --- a/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh +++ b/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh @@ -16,7 +16,7 @@ OBSID= # Queue on which to post status changes -SETSTATUS_BUS=lofar.otdb.setStatus +SETSTATUS_BUS=lofar.otdb.command # ======= Parse command-line parameters diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh index b184a52b33d..f9502dbe4d1 100755 --- a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -26,7 +26,7 @@ PARSET= PIPELINE_CONFIG=$LOFARROOT/share/pipeline/pipeline.cfg # Queue on which to post status changes -SETSTATUS_BUS=lofar.otdb.setStatus +SETSTATUS_BUS=lofar.otdb.command # ======= Parse command-line parameters -- GitLab From 05c01dcb8cca58e4955ed222aaf3bbcd512ff963 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 14 Apr 2016 10:09:04 +0000 Subject: [PATCH 121/933] Task #8437: Compile HDF5 support into casacore for imager_finalize recipe --- Docker/lofar-base/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index a228d6defa6..ef26437fa5d 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -71,7 +71,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y wg if [ "${CASACORE_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casacore/src && git checkout tags/v${CASACORE_VERSION}; fi && \ cd ${INSTALLDIR}/casacore/data && wget --retry-connrefused ftp://ftp.astron.nl/outgoing/Measures/WSRT_Measures.ztar && \ cd ${INSTALLDIR}/casacore/data && tar xf WSRT_Measures.ztar && rm -f WSRT_Measures.ztar && \ - cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DUSE_OPENMP=True -DUSE_FFTW3=TRUE ../src/ && \ + cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DUSE_OPENMP=True -DUSE_FFTW3=TRUE -DUSE_HDF5=ON ../src/ && \ cd ${INSTALLDIR}/casacore/build && make -j ${J} && \ cd ${INSTALLDIR}/casacore/build && make install && \ bash -c "strip ${INSTALLDIR}/casacore/{lib,bin}/* || true" && \ -- GitLab From 5686ce393439a64e9cc328aa8f960f426ba96e76 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 14 Apr 2016 18:12:25 +0000 Subject: [PATCH 122/933] Task #8437: Put images of awimager in private directory, to allow node scripts to put files next to it --- CEP/Pipeline/recipes/sip/master/imager_awimager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/master/imager_awimager.py b/CEP/Pipeline/recipes/sip/master/imager_awimager.py index bc4cc5a1861..90bda7e4d31 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_awimager.py +++ b/CEP/Pipeline/recipes/sip/master/imager_awimager.py @@ -134,7 +134,8 @@ class imager_awimager(BaseRecipe, RemoteCommandRecipeMixIn): self.environment, self.inputs['parset'], self.inputs['working_directory'], - "%s-%s" % (self.inputs['output_image'], idx), + # put in unique dir, as node script wants to put private .par files next to it + "%s_%s/image" % (self.inputs['output_image'], idx), measurement_path, sourcedb_path, self.inputs['mask_patch_size'], -- GitLab From f42c16ddfcf735f8a0af2d817b2568c49324aaba Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 14 Apr 2016 19:45:11 +0000 Subject: [PATCH 123/933] Task #8437: Allow multithreading in imager_awimager --- CEP/Pipeline/recipes/sip/master/imager_awimager.py | 10 +++++++++- CEP/Pipeline/recipes/sip/tasks.cfg.in | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/master/imager_awimager.py b/CEP/Pipeline/recipes/sip/master/imager_awimager.py index 90bda7e4d31..ef8c926e061 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_awimager.py +++ b/CEP/Pipeline/recipes/sip/master/imager_awimager.py @@ -38,6 +38,11 @@ class imager_awimager(BaseRecipe, RemoteCommandRecipeMixIn): '-p', '--parset', help = "The full path to a awimager configuration parset." ), + 'nthreads': ingredient.IntField( + '--nthreads', + default=8, + help="Number of threads per process" + ), 'working_directory': ingredient.StringField( '-w', '--working-directory', help = "Working directory used on output nodes. Results location" @@ -144,7 +149,10 @@ class imager_awimager(BaseRecipe, RemoteCommandRecipeMixIn): self.inputs['fov'], ] - jobs.append(ComputeJob(host, node_command, arguments)) + jobs.append(ComputeJob(host, node_command, arguments, + resources={ + "cores": self.inputs['nthreads'] + })) self._schedule_jobs(jobs) # ********************************************************************* diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.in b/CEP/Pipeline/recipes/sip/tasks.cfg.in index 5d6d46b8fb8..0cb43042787 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.in @@ -77,6 +77,7 @@ nthreads = 8 [imager_awimager] recipe = imager_awimager executable = %(lofarroot)s/bin/awimager +nthreads = 8 [imager_create_dbs] recipe = imager_create_dbs -- GitLab From 45f6a9dd343ea04b2bdf6f220a273c9408743032 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 15 Apr 2016 06:45:44 +0000 Subject: [PATCH 124/933] Task #8437: Change docker tags to full name of branch, for easier marshalling of branch ID between subsystems --- Docker/docker-template | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Docker/docker-template b/Docker/docker-template index a5eed91ba54..7b3987abe45 100755 --- a/Docker/docker-template +++ b/Docker/docker-template @@ -55,16 +55,14 @@ export LOFAR_BRANCH_NAME=`echo "$VERSION_INFO" | perl -ne 'print "$1" if /branch export LOFAR_BRANCH_URL="https://svn.astron.nl/LOFAR/${LOFAR_BRANCH_NAME}" -# ----- LOFAR_TAG = 1234 ----- +# ----- LOFAR_TAG = LOFAR-Task1234 ----- # ----- LOFAR_TAG = trunk ----- -# ----- LOFAR_TAG = 2_15_1 ----- +# ----- LOFAR_TAG = LOFAR-Release-2_15_1 ----- case "${LOFAR_BRANCH_NAME}" in trunk) export LOFAR_TAG=trunk ;; - */LOFAR-Release-*) - # support releases in both tags/ and branches/ - export LOFAR_TAG=${LOFAR_BRANCH_NAME##*/LOFAR-Release-} ;; - branches/*) export LOFAR_TAG=${LOFAR_BRANCH_NAME##branches/*Task} ;; + branches/*) export LOFAR_TAG=${LOFAR_BRANCH_NAME##branches/} ;; + tags/*) export LOFAR_TAG=${LOFAR_BRANCH_NAME##tags/} ;; *) export LOFAR_TAG=latest ;; esac -- GitLab From b76ddf0d4b95a765230a9a77b85d6ca41ab877cc Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 15 Apr 2016 12:57:18 +0000 Subject: [PATCH 125/933] Task #8437: Read docker tag from parset, fall back to same version as PipelineControl --- MAC/Services/src/PipelineControl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 1a4be9f4283..f41e74a2810 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -123,8 +123,9 @@ class Parset(dict): return self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] or "CEP2" def dockerTag(self): - # For now, return OUR tag - return runCommand("docker-template", "${LOFAR_TAG}") + # Return the version set in the parset, and fall back to our own version. + return (self[PARSET_PREFIX + "Observation.ObservationControl.PythonControl.softwareVersion"] or + runCommand("docker-template", "${LOFAR_TAG}")) def slurmJobName(self): return str(self.treeId()) -- GitLab From 563e3c57f546cce27a756d05f72dacfa6d84124f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 15 Apr 2016 13:53:21 +0000 Subject: [PATCH 126/933] Task #9189: Temp fix for generating LOFAR-Release docker tags --- Docker/docker-template | 1 + 1 file changed, 1 insertion(+) diff --git a/Docker/docker-template b/Docker/docker-template index 61bd6cabfe1..a6f44b17fcb 100755 --- a/Docker/docker-template +++ b/Docker/docker-template @@ -38,6 +38,7 @@ export LOFAR_BRANCH_URL="https://svn.astron.nl/LOFAR/${LOFAR_BRANCH_NAME}" case "${LOFAR_BRANCH_NAME}" in trunk) export LOFAR_TAG=trunk ;; tags/*) export LOFAR_TAG=${LOFAR_BRANCH_NAME##tags/LOFAR-Release-} ;; + branches/LOFAR-Release-*) export LOFAR_TAG=${LOFAR_BRANCH_NAME##branches/LOFAR-Release-} ;; branches/*) export LOFAR_TAG=${LOFAR_BRANCH_NAME##branches/*Task} ;; *) export LOFAR_TAG=latest ;; esac -- GitLab From ff90936f81d0a7227a585030c77e3e06e85a87e7 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 15 Apr 2016 18:00:17 +0000 Subject: [PATCH 127/933] Task #8437: Provide unique working directories for node scripts systematically --- CEP/Pipeline/recipes/sip/master/imager_awimager.py | 5 ++++- CEP/Pipeline/recipes/sip/master/imager_create_dbs.py | 9 ++++++--- CEP/Pipeline/recipes/sip/master/imager_prepare.py | 2 +- CEP/Pipeline/recipes/sip/master/imager_source_finding.py | 5 ++++- CEP/Pipeline/recipes/sip/master/long_baseline.py | 2 +- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/master/imager_awimager.py b/CEP/Pipeline/recipes/sip/master/imager_awimager.py index ef8c926e061..06b4a9a7afd 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_awimager.py +++ b/CEP/Pipeline/recipes/sip/master/imager_awimager.py @@ -134,11 +134,14 @@ class imager_awimager(BaseRecipe, RemoteCommandRecipeMixIn): host , measurement_path = measurement_item.host, measurement_item.file host2 , sourcedb_path = source_item.host, source_item.file + # use unique working directories per job, to prevent interference between jobs on a global fs + working_dir = os.path.join(self.inputs['working_directory'], "imager_awimager_{0}".format(idx)) + # construct and save the output name arguments = [self.inputs['executable'], self.environment, self.inputs['parset'], - self.inputs['working_directory'], + working_dir, # put in unique dir, as node script wants to put private .par files next to it "%s_%s/image" % (self.inputs['output_image'], idx), measurement_path, diff --git a/CEP/Pipeline/recipes/sip/master/imager_create_dbs.py b/CEP/Pipeline/recipes/sip/master/imager_create_dbs.py index 10783788c55..0c26faaf965 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_create_dbs.py +++ b/CEP/Pipeline/recipes/sip/master/imager_create_dbs.py @@ -190,8 +190,8 @@ class imager_create_dbs(BaseRecipe, RemoteCommandRecipeMixIn): source_list_map.iterator = slice_paths_map.iterator = \ input_map.iterator = DataMap.SkipIterator - for (input_item, slice_item, source_list_item) in zip( - input_map, slice_paths_map,source_list_map): + for idx, (input_item, slice_item, source_list_item) in enumerate(zip( + input_map, slice_paths_map,source_list_map)): host_ms, concat_ms = input_item.host, input_item.file host_slice, slice_paths = slice_item.host, slice_item.file @@ -199,6 +199,9 @@ class imager_create_dbs(BaseRecipe, RemoteCommandRecipeMixIn): sourcedb_target_path = os.path.join( concat_ms + self.inputs["sourcedb_suffix"]) + # use unique working directories per job, to prevent interference between jobs on a global fs + working_dir = os.path.join(self.inputs['working_directory'], "imager_create_dbs_{0}".format(idx)) + # The actual call for the node script arguments = [concat_ms, sourcedb_target_path, @@ -212,7 +215,7 @@ class imager_create_dbs(BaseRecipe, RemoteCommandRecipeMixIn): slice_paths, self.inputs["parmdb_suffix"], self.environment, - self.inputs["working_directory"], + working_dir, self.inputs["makesourcedb_path"], source_list_item.file, self.inputs["major_cycle"]] diff --git a/CEP/Pipeline/recipes/sip/master/imager_prepare.py b/CEP/Pipeline/recipes/sip/master/imager_prepare.py index 3ba13b86007..898fbe9a51c 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_prepare.py +++ b/CEP/Pipeline/recipes/sip/master/imager_prepare.py @@ -197,7 +197,7 @@ class imager_prepare(BaseRecipe, RemoteCommandRecipeMixIn): tuple([item.host, inputs_for_image_mapfile_path, False])) # use unique working directories per job, to prevent interference between jobs on a global fs - working_dir = os.path.join(self.inputs['working_directory'], "ms_per_image_{0}".format(idx_sb_group)) + working_dir = os.path.join(self.inputs['working_directory'], "imager_prepare_{0}".format(idx_sb_group)) arguments = [self.environment, self.inputs['parset'], diff --git a/CEP/Pipeline/recipes/sip/master/imager_source_finding.py b/CEP/Pipeline/recipes/sip/master/imager_source_finding.py index 40bb1cf6c0b..aaa7aa00111 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_source_finding.py +++ b/CEP/Pipeline/recipes/sip/master/imager_source_finding.py @@ -92,6 +92,9 @@ class imager_source_finding(BaseRecipe, RemoteCommandRecipeMixIn): jobs = [] input_map.iterator = DataMap.SkipIterator for idx, item in enumerate(input_map): + # use unique working directories per job, to prevent interference between jobs on a global fs + working_dir = os.path.join(self.inputs['working_directory'], "imager_source_finding_{0}".format(idx)) + arguments = [item.file, self.inputs["bdsm_parset_file_run1"], self.inputs["bdsm_parset_file_run2x"], @@ -101,7 +104,7 @@ class imager_source_finding(BaseRecipe, RemoteCommandRecipeMixIn): "bdsm_output-%s.img" % (idx, )), "%s-%s" % (self.inputs['sourcedb_target_path'], idx), self.environment, - self.inputs['working_directory'], + working_dir, self.inputs['makesourcedb_path'] ] diff --git a/CEP/Pipeline/recipes/sip/master/long_baseline.py b/CEP/Pipeline/recipes/sip/master/long_baseline.py index fe399f5263c..d5f734b2e34 100644 --- a/CEP/Pipeline/recipes/sip/master/long_baseline.py +++ b/CEP/Pipeline/recipes/sip/master/long_baseline.py @@ -198,7 +198,7 @@ class long_baseline(BaseRecipe, RemoteCommandRecipeMixIn): tuple([output_item.host, inputs_for_image_mapfile_path, False])) # use a unique working directory per job, to prevent interference between jobs on a global fs - working_dir = os.path.join(self.inputs['working_directory'], "ms_per_image_{0}".format(idx_sb_group)) + working_dir = os.path.join(self.inputs['working_directory'], "long_baseline_{0}".format(idx_sb_group)) arguments = [self.environment, self.inputs['parset'], -- GitLab From acaeb18875b3ad639b9f20784e83c0b4b57d508f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 15 Apr 2016 18:55:23 +0000 Subject: [PATCH 128/933] Task #8437: Create directory containing concat.ms, as it might not exist --- CEP/Pipeline/recipes/sip/nodes/imager_prepare.py | 1 + 1 file changed, 1 insertion(+) diff --git a/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py b/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py index e1ed5c185d1..5abce8b6e7c 100644 --- a/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py +++ b/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py @@ -56,6 +56,7 @@ class imager_prepare(LOFARnodeTCP): #****************************************************************** # 1. Create the directories used in this recipe create_directory(processed_ms_dir) + create_directory(os.path.dirname(output_measurement_set)) # time slice dir_to_remove: assure empty directory: Stale data # is problematic for dppp -- GitLab From 9f63ab1cabfccc1309e89fc2a3281e8743813ac6 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 15 Apr 2016 18:55:39 +0000 Subject: [PATCH 129/933] Task #8437: Give map files .map extension --- CEP/Pipeline/recipes/sip/master/imager_prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/master/imager_prepare.py b/CEP/Pipeline/recipes/sip/master/imager_prepare.py index 898fbe9a51c..8152f610693 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_prepare.py +++ b/CEP/Pipeline/recipes/sip/master/imager_prepare.py @@ -179,7 +179,7 @@ class imager_prepare(BaseRecipe, RemoteCommandRecipeMixIn): # Save the mapfile inputs_for_image_mapfile_path = os.path.join( job_directory, "mapfiles", - "ms_per_image_{0}".format(idx_sb_group)) + "ms_per_image_{0}.map".format(idx_sb_group)) self._store_data_map(inputs_for_image_mapfile_path, inputs_for_image_map, "inputmap for location") -- GitLab From 861591d1ff109e3af70c340f722ec2a765b09821 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 15 Apr 2016 21:03:59 +0000 Subject: [PATCH 130/933] Task #8437: Let node recipes create their own working directory --- CEP/Pipeline/recipes/sip/nodes/imager_awimager.py | 5 +++++ CEP/Pipeline/recipes/sip/nodes/imager_create_dbs.py | 5 +++++ CEP/Pipeline/recipes/sip/nodes/imager_source_finding.py | 5 +++++ CEP/Pipeline/recipes/sip/nodes/long_baseline.py | 1 + 4 files changed, 16 insertions(+) diff --git a/CEP/Pipeline/recipes/sip/nodes/imager_awimager.py b/CEP/Pipeline/recipes/sip/nodes/imager_awimager.py index e11471d3d89..8c894ef291d 100644 --- a/CEP/Pipeline/recipes/sip/nodes/imager_awimager.py +++ b/CEP/Pipeline/recipes/sip/nodes/imager_awimager.py @@ -25,6 +25,7 @@ from lofarpipe.support.pipelinelogging import log_time from lofarpipe.support.utilities import patch_parset from lofarpipe.support.utilities import get_parset from lofarpipe.support.utilities import catch_segfaults +from lofarpipe.support.utilities import create_directory from lofarpipe.support.lofarexceptions import PipelineException import pyrap.tables as pt # @UnresolvedImport from subprocess import CalledProcessError @@ -66,6 +67,10 @@ class imager_awimager(LOFARnodeTCP): # Read the parameters as specified in the parset parset_object = get_parset(parset) + #****************************************************************** + # 0. Create the directories used in this recipe + create_directory(working_directory) + # ************************************************************* # 1. Calculate awimager parameters that depend on measurement set # and the parset diff --git a/CEP/Pipeline/recipes/sip/nodes/imager_create_dbs.py b/CEP/Pipeline/recipes/sip/nodes/imager_create_dbs.py index 0fe1189ac51..30e60ab2a09 100644 --- a/CEP/Pipeline/recipes/sip/nodes/imager_create_dbs.py +++ b/CEP/Pipeline/recipes/sip/nodes/imager_create_dbs.py @@ -16,6 +16,7 @@ from lofarpipe.support.lofarnode import LOFARnodeTCP from lofarpipe.support.pipelinelogging import log_process_output from lofarpipe.support.pipelinelogging import CatchLog4CPlus from lofarpipe.support.utilities import catch_segfaults +from lofarpipe.support.utilities import create_directory import monetdb.sql as db import lofar.gsm.gsmutils as gsm @@ -65,6 +66,10 @@ class imager_create_dbs(LOFARnodeTCP): self.logger.info("Starting imager_create_dbs Node") self.environment.update(environment) + #****************************************************************** + # 0. Create the directories used in this recipe + create_directory(working_directory) + #******************************************************************* # 1. get a sourcelist: from gsm or from file source_list, append = self._create_source_list( diff --git a/CEP/Pipeline/recipes/sip/nodes/imager_source_finding.py b/CEP/Pipeline/recipes/sip/nodes/imager_source_finding.py index 07612e98991..2d5cc1fe13b 100644 --- a/CEP/Pipeline/recipes/sip/nodes/imager_source_finding.py +++ b/CEP/Pipeline/recipes/sip/nodes/imager_source_finding.py @@ -8,6 +8,7 @@ from lofarpipe.support.lofarnode import LOFARnodeTCP from lofarpipe.support.pipelinelogging import CatchLog4CPlus from lofarpipe.support.utilities import catch_segfaults +from lofarpipe.support.utilities import create_directory class imager_source_finding(LOFARnodeTCP): @@ -53,6 +54,10 @@ class imager_source_finding(LOFARnodeTCP): """ + #****************************************************************** + # 0. Create the directories used in this recipe + create_directory(working_directory) + import lofar.bdsm as bdsm#@UnresolvedImport self.logger.info("Starting imager_source_finding") self.environment.update(environment) diff --git a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py index 60826d9fcab..244107a677c 100644 --- a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py +++ b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py @@ -57,6 +57,7 @@ class long_baseline(LOFARnodeTCP): #****************************************************************** # I. Create the directories used in this recipe create_directory(processed_ms_dir) + create_directory(working_dir) # time slice dir_to_remove: assure empty directory: Stale data # is problematic for dppp -- GitLab From b36d7db30192f44e2ee9115f24d638fcaafc6fc8 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 15 Apr 2016 22:06:40 +0000 Subject: [PATCH 131/933] Task #8437: Added missing import --- CEP/Pipeline/recipes/sip/master/imager_awimager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/CEP/Pipeline/recipes/sip/master/imager_awimager.py b/CEP/Pipeline/recipes/sip/master/imager_awimager.py index 06b4a9a7afd..b1b2365feab 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_awimager.py +++ b/CEP/Pipeline/recipes/sip/master/imager_awimager.py @@ -5,6 +5,7 @@ # swinbank@transientskp.org # ------------------------------------------------------------------------------ import sys +import os import copy import lofarpipe.support.lofaringredient as ingredient from lofarpipe.support.baserecipe import BaseRecipe -- GitLab From f7e5c776555e2cb774a6d34291538cb7f85f1ce1 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 16 Apr 2016 07:48:47 +0000 Subject: [PATCH 132/933] Task #8437: Added casacore/bin to PATH --- Docker/lofar-base/bashrc | 1 + 1 file changed, 1 insertion(+) diff --git a/Docker/lofar-base/bashrc b/Docker/lofar-base/bashrc index 0a421af1a7d..ef75d16a73f 100644 --- a/Docker/lofar-base/bashrc +++ b/Docker/lofar-base/bashrc @@ -11,4 +11,5 @@ source ${INSTALLDIR}/qpid/.profile export PYTHONPATH=${PYTHONPATH}:${INSTALLDIR}/python-casacore/lib/python2.7/site-packages # casacore +export PATH=${PATH}:${INSTALLDIR}/casacore/bin export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${INSTALLDIR}/casacore/lib -- GitLab From 2db881fa080486586014e703f1dee54fd3f0ebac Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sun, 17 Apr 2016 08:30:18 +0000 Subject: [PATCH 133/933] Task #8437: Install /opt/lofar/var als a symlink to /home/lofar/lofar/var and allow only $HOME to change ownership at start. This reduces both image boot time and any changes committed from a container --- Docker/lofar-base/chuser.sh | 3 --- Docker/lofar-outputproc/Dockerfile.tmpl | 3 ++- Docker/lofar-pipeline/Dockerfile.tmpl | 3 ++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Docker/lofar-base/chuser.sh b/Docker/lofar-base/chuser.sh index 3c4e9d3ab0f..76ba3ca7eca 100755 --- a/Docker/lofar-base/chuser.sh +++ b/Docker/lofar-base/chuser.sh @@ -16,9 +16,6 @@ if [ -n "${LUSER}" ]; then # Set ownership of home dir to new user chown --from=${OLDID} -R ${LUSER}:${LGROUP} ${HOME} - - # Set ownership of installed software to new user - chown --from=${OLDID} -R ${LUSER}:${LGROUP} /opt fi # Update environment for updated user diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index 85d51129122..1a77d72d68b 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -26,7 +26,8 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y su cd ${INSTALLDIR}/lofar/build/gnu_opt && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make install && \ - bash -c "mkdir -p ${INSTALLDIR}/lofar/var/{log,run}" && \ + bash -c "mkdir -p /home/${USER}/lofar/var/{log,run}" && \ + bash -c "ln -sfT ${INSTALLDIR}/lofar/var /home/${USER}/lofar/var" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ sudo setcap cap_sys_nice,cap_ipc_lock=ep ${INSTALLDIR}/lofar/bin/outputProc && \ diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 531b41e58e8..d2d27f09615 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -51,7 +51,8 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y su cd ${INSTALLDIR}/lofar/build/gnu_opt && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make install && \ - bash -c "mkdir -p ${INSTALLDIR}/lofar/var/{log,run}" && \ + bash -c "mkdir -p /home/${USER}/lofar/var/{log,run}" && \ + bash -c "ln -sfT ${INSTALLDIR}/lofar/var /home/${USER}/lofar/var" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ sudo apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ -- GitLab From 66c24ffb06c9064428f34e51e2ff0bb5c246e970 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 19 Apr 2016 07:01:34 +0000 Subject: [PATCH 134/933] Task #8437: Do not run CEP4 pipelines through MACScheduler. We will use PipelineControl instead. --- MAC/APL/MainCU/src/MACScheduler/MACScheduler.conf.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/APL/MainCU/src/MACScheduler/MACScheduler.conf.in b/MAC/APL/MainCU/src/MACScheduler/MACScheduler.conf.in index ed9c486a187..bbccffe8341 100644 --- a/MAC/APL/MainCU/src/MACScheduler/MACScheduler.conf.in +++ b/MAC/APL/MainCU/src/MACScheduler/MACScheduler.conf.in @@ -33,4 +33,4 @@ ParsetQueuename = lofar.task.specification.system # Pipelines on cluster X can be ignored by the MACScheduler with this key. # use e.g. 'CEP2' or 'CEP4' -excludePipelinesOnThisCluster = '' +excludePipelinesOnThisCluster = '!CEP4' -- GitLab From fa5ae441ab24e43168e47568c9259d32ad70ed4f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 19 Apr 2016 07:04:20 +0000 Subject: [PATCH 135/933] Task #8437: Propagate SKIP values all the way to the end --- CEP/Pipeline/recipes/sip/bin/preprocessing_pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/preprocessing_pipeline.py b/CEP/Pipeline/recipes/sip/bin/preprocessing_pipeline.py index 345fba64f1a..cc4644ea4a8 100755 --- a/CEP/Pipeline/recipes/sip/bin/preprocessing_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/preprocessing_pipeline.py @@ -185,7 +185,7 @@ class preprocessing_pipeline(control): # Run the Default Pre-Processing Pipeline (DPPP); with duration(self, "ndppp"): - self.run_task("ndppp", + output_data_mapfile = self.run_task("ndppp", (input_data_mapfile, output_data_mapfile), data_start_time=vdsinfo['start_time'], data_end_time=vdsinfo['end_time'], @@ -196,7 +196,7 @@ class preprocessing_pipeline(control): parset=ndppp_parset, parmdb_mapfile=parmdb_mapfile, sourcedb_mapfile=sourcedb_mapfile - ) + )['mapfile'] # ********************************************************************* # 6. Create feedback file for further processing by the LOFAR framework -- GitLab From 8da99b20bf9b55d5e99b2e492935bd44b138b116 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 19 Apr 2016 08:55:57 +0000 Subject: [PATCH 136/933] Task #8437: After all these years, I still swap the parameters to ln occasionally... --- Docker/lofar-outputproc/Dockerfile.tmpl | 2 +- Docker/lofar-pipeline/Dockerfile.tmpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index 1a77d72d68b..1db1c15f8fa 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -27,7 +27,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y su cd ${INSTALLDIR}/lofar/build/gnu_opt && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make install && \ bash -c "mkdir -p /home/${USER}/lofar/var/{log,run}" && \ - bash -c "ln -sfT ${INSTALLDIR}/lofar/var /home/${USER}/lofar/var" && \ + bash -c "ln -sfT /home/${USER}/lofar/var ${INSTALLDIR}/lofar/var" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ sudo setcap cap_sys_nice,cap_ipc_lock=ep ${INSTALLDIR}/lofar/bin/outputProc && \ diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index d2d27f09615..b17d117b796 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -52,7 +52,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y su cd ${INSTALLDIR}/lofar/build/gnu_opt && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make install && \ bash -c "mkdir -p /home/${USER}/lofar/var/{log,run}" && \ - bash -c "ln -sfT ${INSTALLDIR}/lofar/var /home/${USER}/lofar/var" && \ + bash -c "ln -sfT /home/${USER}/lofar/var ${INSTALLDIR}/lofar/var" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ sudo apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ -- GitLab From f61a72e3a4483fc5f87c31a32f20f094b0d3ed92 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 19 Apr 2016 09:25:17 +0000 Subject: [PATCH 137/933] Task #8437: Pipeline already installs $LOFARROOT/var, so move it instead of creating it. --- Docker/lofar-pipeline/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index b17d117b796..81948dcf203 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -51,7 +51,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y su cd ${INSTALLDIR}/lofar/build/gnu_opt && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && make install && \ - bash -c "mkdir -p /home/${USER}/lofar/var/{log,run}" && \ + bash -c "mv ${INSTALLDIR}/lofar/var /home/${USER}/" && \ bash -c "ln -sfT /home/${USER}/lofar/var ${INSTALLDIR}/lofar/var" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ -- GitLab From eb55cd7527fec7d27a4278ea6298e92eda203601 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 21 Apr 2016 07:56:47 +0000 Subject: [PATCH 138/933] Task #8589: Created task branch -- GitLab From 6f6dda5b8f2448f787f214599e3d80570c754389 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 21 Apr 2016 08:03:26 +0000 Subject: [PATCH 139/933] Task #8589: Added lofar-tbbwriter Docker files --- .gitattributes | 3 + Docker/CMakeLists.txt | 1 + Docker/docker-build-all.sh | 3 +- Docker/lofar-tbbwriter/Dockerfile | 118 ++++++++++++++++++++++++++++++ Docker/lofar-tbbwriter/bashrc | 11 +++ Docker/lofar-tbbwriter/chuser.sh | 32 ++++++++ 6 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 Docker/lofar-tbbwriter/Dockerfile create mode 100644 Docker/lofar-tbbwriter/bashrc create mode 100755 Docker/lofar-tbbwriter/chuser.sh diff --git a/.gitattributes b/.gitattributes index 985f3647b9e..07c8d2ad3ed 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2351,6 +2351,9 @@ Docker/lofar-base/bashrc -text Docker/lofar-base/chuser.sh -text Docker/lofar-outputproc/Dockerfile.tmpl -text Docker/lofar-pipeline/Dockerfile.tmpl -text +Docker/lofar-tbbwriter/Dockerfile -text +Docker/lofar-tbbwriter/bashrc -text +Docker/lofar-tbbwriter/chuser.sh -text JAVA/GUI/Plotter/dist/lib/sgt.jar -text svneol=unset#unset JAVA/GUI/Plotter/doc/Plotter.EAP -text JAVA/GUI/Plotter/doc/javadoc/resources/inherit.gif -text diff --git a/Docker/CMakeLists.txt b/Docker/CMakeLists.txt index 060eea1d0c4..d1ba2e38b0d 100644 --- a/Docker/CMakeLists.txt +++ b/Docker/CMakeLists.txt @@ -60,6 +60,7 @@ install(DIRECTORY lofar-base lofar-pipeline lofar-outputproc + lofar-tbbwriter DESTINATION share/docker USE_SOURCE_PERMISSIONS PATTERN Dockerfile.tmpl EXCLUDE) diff --git a/Docker/docker-build-all.sh b/Docker/docker-build-all.sh index 71eb409a42c..fbd69c3bb1e 100755 --- a/Docker/docker-build-all.sh +++ b/Docker/docker-build-all.sh @@ -11,5 +11,6 @@ cd ${LOFARROOT}/share/docker build lofar-base && \ build lofar-pipeline && \ -build lofar-outputproc +build lofar-outputproc && \ +build lofar-tbb diff --git a/Docker/lofar-tbbwriter/Dockerfile b/Docker/lofar-tbbwriter/Dockerfile new file mode 100644 index 00000000000..955064aa755 --- /dev/null +++ b/Docker/lofar-tbbwriter/Dockerfile @@ -0,0 +1,118 @@ +# +# base +# +FROM ubuntu:12.04 + +# +# common-environment +# +ENV USER=lofar +ENV INSTALLDIR=/opt + +# +# environment +# +ENV DEBIAN_FRONTEND=noninteractive \ + PYTHON_VERSION=2.7 + +# +# versions +# Requires boost 1.48 +# Remove casacore? +# +ENV CASACORE_VERSION=2.0.3 \ + CASAREST_VERSION=1.4.1 \ + PYTHON_CASACORE_VERSION=2.0.1 \ + BOOST_VERSION=1.48 + +# +# set-uid +# +ENV UID=1000 + +# +# set-build-options +# +ENV J=6 + +# +# Base and runtime dependencies +# +#RUN sed -i 's/archive.ubuntu.com/osmirror.rug.nl/' /etc/apt/sources.list +RUN apt-get update && apt-get upgrade -y +RUN apt-get install -y sudo +#python2.7 libpython2.7 +# apt-get install -y libblas3 liblapacke python-numpy libcfitsio3 libwcs4 libfftw3-bin libhdf5-7 libboost-python${BOOST_VERSION}.0 && \ +# apt-get install -y nano + +# +# setup-account +# +RUN (getent group sudo &>/dev/null || groupadd sudo) && \ + echo "useradd -m ${USERADD_FLAGS} ${USER}" && \ + useradd -m -u ${UID} ${USER} && \ + usermod -a -G sudo ${USER} && \ + echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \ + sed -i 's/requiretty/!requiretty/g' /etc/sudoers + +# +# setup install dir +# +RUN mkdir -p ${INSTALLDIR} && chown ${USER}.${USER} ${INSTALLDIR} + +USER ${USER} + + +# +# ******************* +# Lofar User Software +# ******************* +# +RUN sudo apt-get update && sudo apt-get upgrade -y && \ + sudo apt-get install -y g++ gfortran flex swig bison subversion \ + zlib1g-dev libatlas-base-dev liblapack-dev \ + libncurses5-dev libfreetype6-dev libpng12-dev \ + python-dev python-tk python-pyfits tk8.5-dev fftw3-dev \ + libbz2-dev libghc-readline-dev \ + git git git-core git-doc git-man git-svn \ + valgrind + +RUN sudo apt-get install -y libboost${BOOST_VERSION}-all-dev \ + wcslib-dev \ + cmake cmake-doc cmake-curses-gui make \ + libgsl0-dev \ + python-matplotlib \ + python-sphinx \ + libcfitsio3-dev \ + python-numpy \ + num-utils \ + python-scipy \ + libblas-dev \ + python-sip-dev \ + openmpi-bin openmpi-common \ + ipython + +RUN cd $INSTALLDIR && svn co http://usg.lofar.org/svn/code/trunk lofarsoft && \ + export LOFARSOFT=${INSTALLDIR}/lofarsoft && . $LOFARSOFT/devel_common/scripts/init.sh && \ + cd $LOFARSOFT && ./bootstrap && cd build && cmake -DCASACORE_FROM_LATEST_SVN_REVISION=ON . && make rebuild_cache + +ENV LOFARSOFT=${INSTALLDIR}/lofarsoft + +RUN . $LOFARSOFT/devel_common/scripts/init.sh && cd $LOFARSOFT/build && make hdf5 + +RUN . $LOFARSOFT/devel_common/scripts/init.sh && cd $LOFARSOFT/build && make dal1 + + + +# +# config +# +COPY bashrc /opt/bashrc + +# +# entry +# +COPY chuser.sh /usr/local/bin/chuser.sh +WORKDIR /home/${USER} +ENTRYPOINT ["sudo","-E","/usr/local/bin/chuser.sh"] + diff --git a/Docker/lofar-tbbwriter/bashrc b/Docker/lofar-tbbwriter/bashrc new file mode 100644 index 00000000000..66b9cd7d8e9 --- /dev/null +++ b/Docker/lofar-tbbwriter/bashrc @@ -0,0 +1,11 @@ +#!/bin/bash + +# lofar +[ -r ${INSTALLDIR}/lofar/lofarinit.sh ] && source ${INSTALLDIR}/lofar/lofarinit.sh +export PATH PYTHONPATH LD_LIBRARY_PATH LOFARROOT + +# qpid +#source ${INSTALLDIR}/qpid/.profile + +# lofarsoft +source ${LOFARSOFT}/devel_common/scripts/init.sh diff --git a/Docker/lofar-tbbwriter/chuser.sh b/Docker/lofar-tbbwriter/chuser.sh new file mode 100755 index 00000000000..3c4e9d3ab0f --- /dev/null +++ b/Docker/lofar-tbbwriter/chuser.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# Fetch the user name used in this container +export USER=${SUDO_USER} + +if [ -n "${LUSER}" ]; then + if [ -z "${LGROUP}" ]; then + LGROUP=${LUSER} + fi + + OLDID=`id -u ${USER}` + + # Replace USER by LUSER:LGROUP + sed -i -e "s/${USER}:x:[0-9]\+:[0-9]\+/${USER}:x:${LUSER}:${LGROUP}/g" /etc/passwd + sed -i -e "s/${USER}:x:[0-9]\+:/${USER}:x:${LGROUP}:/g" /etc/group + + # Set ownership of home dir to new user + chown --from=${OLDID} -R ${LUSER}:${LGROUP} ${HOME} + + # Set ownership of installed software to new user + chown --from=${OLDID} -R ${LUSER}:${LGROUP} /opt +fi + +# Update environment for updated user +export HOME=/home/${USER} + +# Import bashrc for software in /opt +source /opt/bashrc + +# Use exec to make sure we propagate signals +# `env' is needed to propagate PATH variables through sudo. +exec sudo -u ${USER} -E env "PATH=$PATH" "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" "PYTHONPATH=$PYTHONPATH" "$@" -- GitLab From 082ad3d0d17187d65aa8c98c5927cde9ecdb8557 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 22 Apr 2016 13:54:38 +0000 Subject: [PATCH 140/933] Task #9346: Created working branch -- GitLab From de2a68b6cbda2ceb22f88577588c496d75302d3c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 22 Apr 2016 14:05:36 +0000 Subject: [PATCH 141/933] Task #9346: Added test for docker 1.9.1 workaround (#9311) --- .gitattributes | 1 + .../validation/cep4/docker/cgroupdriver.test | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100755 SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test diff --git a/.gitattributes b/.gitattributes index 985f3647b9e..bd6e2f01ab9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5475,6 +5475,7 @@ SubSystems/Online_Cobalt/test/tgenerateStationStreams.sh eol=lf SubSystems/Online_Cobalt/test/tstartBGL.in_parset -text SubSystems/Online_Cobalt/test/tstartBGL.run eol=lf SubSystems/Online_Cobalt/test/tstartBGL.sh eol=lf +SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test -text SubSystems/Online_Cobalt/validation/cluster/c3/cexec -text SubSystems/Online_Cobalt/validation/cluster/connectivity/cobalt2cep.test eol=lf SubSystems/Online_Cobalt/validation/cluster/connectivity/cobalt2cobalt.test eol=lf diff --git a/SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test b/SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test new file mode 100755 index 00000000000..19a0650dbd8 --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test @@ -0,0 +1,11 @@ +#/bin/bash -e + +# Test for cgroupdriver=cgroupfs workaround for Docker 1.9.1 + +DOCKER_VERSION=`docker --version | awk '{ print $3; }'` + +[ "$DOCKER_VERSION" == "1.9.1," ] || exit 0 + +# Docker startup line must contain "--exec-opt=native.cgroupdriver=cgroupfs" +fgrep -q "ExecStart" /usr/lib/systemd/system/docker.service | fgrep -q "--exec-opt=native.cgroupdriver=cgroupfs" + -- GitLab From 45daaae2785add3923c3c3ef9777e830186e245d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 22 Apr 2016 14:40:41 +0000 Subject: [PATCH 142/933] Task #9346: Added more CEP4 tests --- .gitattributes | 8 ++++++++ .../validation/cep4/docker/cgroupdriver.test | 2 +- .../validation/cep4/lofarsys/docker.root.test | 3 +++ .../validation/cep4/lofarsys/ssh_localhost.root.test | 3 +++ .../Online_Cobalt/validation/cep4/os/lustre-mounted.test | 3 +++ .../Online_Cobalt/validation/cep4/os/services.test | 4 ++++ SubSystems/Online_Cobalt/validation/cep4/os/utc.test | 3 +++ .../Online_Cobalt/validation/cep4/packages/iperf.test | 2 ++ .../validation/cep4/packages/jenkins-reqs.test | 9 +++++++++ .../Online_Cobalt/validation/cep4/qpid/no_auth.test | 3 +++ 10 files changed, 39 insertions(+), 1 deletion(-) create mode 100755 SubSystems/Online_Cobalt/validation/cep4/lofarsys/docker.root.test create mode 100755 SubSystems/Online_Cobalt/validation/cep4/lofarsys/ssh_localhost.root.test create mode 100755 SubSystems/Online_Cobalt/validation/cep4/os/lustre-mounted.test create mode 100644 SubSystems/Online_Cobalt/validation/cep4/os/services.test create mode 100755 SubSystems/Online_Cobalt/validation/cep4/os/utc.test create mode 100644 SubSystems/Online_Cobalt/validation/cep4/packages/iperf.test create mode 100755 SubSystems/Online_Cobalt/validation/cep4/packages/jenkins-reqs.test create mode 100755 SubSystems/Online_Cobalt/validation/cep4/qpid/no_auth.test diff --git a/.gitattributes b/.gitattributes index bd6e2f01ab9..49e6ca85e4e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5476,6 +5476,14 @@ SubSystems/Online_Cobalt/test/tstartBGL.in_parset -text SubSystems/Online_Cobalt/test/tstartBGL.run eol=lf SubSystems/Online_Cobalt/test/tstartBGL.sh eol=lf SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test -text +SubSystems/Online_Cobalt/validation/cep4/lofarsys/docker.root.test -text +SubSystems/Online_Cobalt/validation/cep4/lofarsys/ssh_localhost.root.test -text +SubSystems/Online_Cobalt/validation/cep4/os/lustre-mounted.test -text +SubSystems/Online_Cobalt/validation/cep4/os/services.test -text +SubSystems/Online_Cobalt/validation/cep4/os/utc.test -text +SubSystems/Online_Cobalt/validation/cep4/packages/iperf.test -text +SubSystems/Online_Cobalt/validation/cep4/packages/jenkins-reqs.test -text +SubSystems/Online_Cobalt/validation/cep4/qpid/no_auth.test -text SubSystems/Online_Cobalt/validation/cluster/c3/cexec -text SubSystems/Online_Cobalt/validation/cluster/connectivity/cobalt2cep.test eol=lf SubSystems/Online_Cobalt/validation/cluster/connectivity/cobalt2cobalt.test eol=lf diff --git a/SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test b/SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test index 19a0650dbd8..03d3e45e879 100755 --- a/SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test +++ b/SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test @@ -7,5 +7,5 @@ DOCKER_VERSION=`docker --version | awk '{ print $3; }'` [ "$DOCKER_VERSION" == "1.9.1," ] || exit 0 # Docker startup line must contain "--exec-opt=native.cgroupdriver=cgroupfs" -fgrep -q "ExecStart" /usr/lib/systemd/system/docker.service | fgrep -q "--exec-opt=native.cgroupdriver=cgroupfs" +fgrep -q "ExecStart" /usr/lib/systemd/system/docker.service | fgrep -q "--exec-opt=native.cgroupdriver=cgroupfs" || exit 1 diff --git a/SubSystems/Online_Cobalt/validation/cep4/lofarsys/docker.root.test b/SubSystems/Online_Cobalt/validation/cep4/lofarsys/docker.root.test new file mode 100755 index 00000000000..6830d60c8cc --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/lofarsys/docker.root.test @@ -0,0 +1,3 @@ +#!/bin/bash + +sudo -u lofarsys docker run --rm -it hello-world || exit 1 diff --git a/SubSystems/Online_Cobalt/validation/cep4/lofarsys/ssh_localhost.root.test b/SubSystems/Online_Cobalt/validation/cep4/lofarsys/ssh_localhost.root.test new file mode 100755 index 00000000000..1f58c732d0c --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/lofarsys/ssh_localhost.root.test @@ -0,0 +1,3 @@ +#!/bin/bash + +sudo -u lofarsys ssh localhost /bin/true || exit 1 diff --git a/SubSystems/Online_Cobalt/validation/cep4/os/lustre-mounted.test b/SubSystems/Online_Cobalt/validation/cep4/os/lustre-mounted.test new file mode 100755 index 00000000000..03ccef9e2a6 --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/os/lustre-mounted.test @@ -0,0 +1,3 @@ +#!/bin/bash + +mount -t lustre | fgrep -q "/data" || exit 1 diff --git a/SubSystems/Online_Cobalt/validation/cep4/os/services.test b/SubSystems/Online_Cobalt/validation/cep4/os/services.test new file mode 100644 index 00000000000..f9760a5e6b8 --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/os/services.test @@ -0,0 +1,4 @@ +#!/bin/bash + +# No services should have failed, except for network.service +systemctl | fgrep -v "network.service" | grep failed diff --git a/SubSystems/Online_Cobalt/validation/cep4/os/utc.test b/SubSystems/Online_Cobalt/validation/cep4/os/utc.test new file mode 100755 index 00000000000..ebbfe9d0761 --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/os/utc.test @@ -0,0 +1,3 @@ +#!/bin/bash + +[ "`date +%Z`" == "UTC" ] || exit 1 diff --git a/SubSystems/Online_Cobalt/validation/cep4/packages/iperf.test b/SubSystems/Online_Cobalt/validation/cep4/packages/iperf.test new file mode 100644 index 00000000000..7885c278818 --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/packages/iperf.test @@ -0,0 +1,2 @@ +#!/bin/bash +yum -C list installed iperf || exit 1 diff --git a/SubSystems/Online_Cobalt/validation/cep4/packages/jenkins-reqs.test b/SubSystems/Online_Cobalt/validation/cep4/packages/jenkins-reqs.test new file mode 100755 index 00000000000..d52bcd6af1e --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/packages/jenkins-reqs.test @@ -0,0 +1,9 @@ +#!/bin/bash + +# Requirements to do a Jenkins build. Only relevant for head0X +[[ `hostname` =~ "head" ]] || exit 0 + +yum -C list installed | grep "^java-" || exit 1 +yum -C list installed subversion || exit 1 +yum -C list installed cmake || exit 1 +yum -C list installed gcc-c++ || exit 1 diff --git a/SubSystems/Online_Cobalt/validation/cep4/qpid/no_auth.test b/SubSystems/Online_Cobalt/validation/cep4/qpid/no_auth.test new file mode 100755 index 00000000000..7de7af4bf40 --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/qpid/no_auth.test @@ -0,0 +1,3 @@ +#!/bin/bash + +grep "auth=no" /etc/qpid/qpidd.conf || exit 1 -- GitLab From c1efa5ff3aa0b2913a054532aa10602b6a7b8fb3 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 22 Apr 2016 14:43:04 +0000 Subject: [PATCH 143/933] Task #9346: Added test if SLURM can be reached --- .gitattributes | 1 + SubSystems/Online_Cobalt/validation/cep4/slurm/available.test | 3 +++ 2 files changed, 4 insertions(+) create mode 100755 SubSystems/Online_Cobalt/validation/cep4/slurm/available.test diff --git a/.gitattributes b/.gitattributes index 49e6ca85e4e..32a5057360d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5484,6 +5484,7 @@ SubSystems/Online_Cobalt/validation/cep4/os/utc.test -text SubSystems/Online_Cobalt/validation/cep4/packages/iperf.test -text SubSystems/Online_Cobalt/validation/cep4/packages/jenkins-reqs.test -text SubSystems/Online_Cobalt/validation/cep4/qpid/no_auth.test -text +SubSystems/Online_Cobalt/validation/cep4/slurm/available.test -text SubSystems/Online_Cobalt/validation/cluster/c3/cexec -text SubSystems/Online_Cobalt/validation/cluster/connectivity/cobalt2cep.test eol=lf SubSystems/Online_Cobalt/validation/cluster/connectivity/cobalt2cobalt.test eol=lf diff --git a/SubSystems/Online_Cobalt/validation/cep4/slurm/available.test b/SubSystems/Online_Cobalt/validation/cep4/slurm/available.test new file mode 100755 index 00000000000..9b7a190d6f5 --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/slurm/available.test @@ -0,0 +1,3 @@ +#!/bin/bash + +sinfo || exit 1 -- GitLab From 2774bf2a56eb7ef723b6fcef3d07c84128ecc3fa Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 22 Apr 2016 14:48:45 +0000 Subject: [PATCH 144/933] Task #9346: Added IPoIB test for connected mode --- .gitattributes | 1 + .../validation/cep4/network/ipoib.test | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100755 SubSystems/Online_Cobalt/validation/cep4/network/ipoib.test diff --git a/.gitattributes b/.gitattributes index 32a5057360d..29bc3f4b159 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5478,6 +5478,7 @@ SubSystems/Online_Cobalt/test/tstartBGL.sh eol=lf SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test -text SubSystems/Online_Cobalt/validation/cep4/lofarsys/docker.root.test -text SubSystems/Online_Cobalt/validation/cep4/lofarsys/ssh_localhost.root.test -text +SubSystems/Online_Cobalt/validation/cep4/network/ipoib.test -text SubSystems/Online_Cobalt/validation/cep4/os/lustre-mounted.test -text SubSystems/Online_Cobalt/validation/cep4/os/services.test -text SubSystems/Online_Cobalt/validation/cep4/os/utc.test -text diff --git a/SubSystems/Online_Cobalt/validation/cep4/network/ipoib.test b/SubSystems/Online_Cobalt/validation/cep4/network/ipoib.test new file mode 100755 index 00000000000..27d5b1c0b1a --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/network/ipoib.test @@ -0,0 +1,15 @@ +#!/bin/bash + +for IFACE in ib0 +do + echo Testing interface $IFACE... + + # Interface should exist + ip link show $IFACE || exit 1 + + # Interface should be up + ip link show $IFACE | grep -q "state UP" || exit 1 + + # Connected mode should be set + [ "`cat /sys/class/net/$IFACE/mode`" == "connected" ] || exit 1 +done -- GitLab From 51cfa03ee49e0368becf96029e133544a83ac492 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 22 Apr 2016 14:53:47 +0000 Subject: [PATCH 145/933] Task #9346: Added check for user restrictions --- .gitattributes | 1 + .../Online_Cobalt/validation/cep4/os/users.test | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100755 SubSystems/Online_Cobalt/validation/cep4/os/users.test diff --git a/.gitattributes b/.gitattributes index 29bc3f4b159..2a2e3425f2c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5481,6 +5481,7 @@ SubSystems/Online_Cobalt/validation/cep4/lofarsys/ssh_localhost.root.test -text SubSystems/Online_Cobalt/validation/cep4/network/ipoib.test -text SubSystems/Online_Cobalt/validation/cep4/os/lustre-mounted.test -text SubSystems/Online_Cobalt/validation/cep4/os/services.test -text +SubSystems/Online_Cobalt/validation/cep4/os/users.test -text SubSystems/Online_Cobalt/validation/cep4/os/utc.test -text SubSystems/Online_Cobalt/validation/cep4/packages/iperf.test -text SubSystems/Online_Cobalt/validation/cep4/packages/jenkins-reqs.test -text diff --git a/SubSystems/Online_Cobalt/validation/cep4/os/users.test b/SubSystems/Online_Cobalt/validation/cep4/os/users.test new file mode 100755 index 00000000000..8935075222c --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/os/users.test @@ -0,0 +1,11 @@ +#!/bin/bash + +# Restrict the users allowed on the CPU nodes +[[ `hostname` =~ "cpu" ]] || exit 0 + +# lofarsys should exist +id lofarsys || exit 1 + +# mol should not exist +id mol && exit 1 + -- GitLab From 1f94391d0889191d6c2531463cb6623d9a483d16 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 25 Apr 2016 06:16:51 +0000 Subject: [PATCH 146/933] Task #9346: Put redmine ticket # in test name --- .gitattributes | 8 ++++---- .../docker/{cgroupdriver.test => 9311-cgroupdriver.test} | 0 .../cep4/network/{ipoib.test => 9228-ipoib.test} | 0 .../validation/cep4/os/{utc.test => 9226-utc.test} | 0 .../cep4/qpid/{no_auth.test => 9315-no_auth.test} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename SubSystems/Online_Cobalt/validation/cep4/docker/{cgroupdriver.test => 9311-cgroupdriver.test} (100%) rename SubSystems/Online_Cobalt/validation/cep4/network/{ipoib.test => 9228-ipoib.test} (100%) rename SubSystems/Online_Cobalt/validation/cep4/os/{utc.test => 9226-utc.test} (100%) rename SubSystems/Online_Cobalt/validation/cep4/qpid/{no_auth.test => 9315-no_auth.test} (100%) diff --git a/.gitattributes b/.gitattributes index 2a2e3425f2c..7fa16a16230 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5475,17 +5475,17 @@ SubSystems/Online_Cobalt/test/tgenerateStationStreams.sh eol=lf SubSystems/Online_Cobalt/test/tstartBGL.in_parset -text SubSystems/Online_Cobalt/test/tstartBGL.run eol=lf SubSystems/Online_Cobalt/test/tstartBGL.sh eol=lf -SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test -text +SubSystems/Online_Cobalt/validation/cep4/docker/9311-cgroupdriver.test -text SubSystems/Online_Cobalt/validation/cep4/lofarsys/docker.root.test -text SubSystems/Online_Cobalt/validation/cep4/lofarsys/ssh_localhost.root.test -text -SubSystems/Online_Cobalt/validation/cep4/network/ipoib.test -text +SubSystems/Online_Cobalt/validation/cep4/network/9228-ipoib.test -text +SubSystems/Online_Cobalt/validation/cep4/os/9226-utc.test -text SubSystems/Online_Cobalt/validation/cep4/os/lustre-mounted.test -text SubSystems/Online_Cobalt/validation/cep4/os/services.test -text SubSystems/Online_Cobalt/validation/cep4/os/users.test -text -SubSystems/Online_Cobalt/validation/cep4/os/utc.test -text SubSystems/Online_Cobalt/validation/cep4/packages/iperf.test -text SubSystems/Online_Cobalt/validation/cep4/packages/jenkins-reqs.test -text -SubSystems/Online_Cobalt/validation/cep4/qpid/no_auth.test -text +SubSystems/Online_Cobalt/validation/cep4/qpid/9315-no_auth.test -text SubSystems/Online_Cobalt/validation/cep4/slurm/available.test -text SubSystems/Online_Cobalt/validation/cluster/c3/cexec -text SubSystems/Online_Cobalt/validation/cluster/connectivity/cobalt2cep.test eol=lf diff --git a/SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test b/SubSystems/Online_Cobalt/validation/cep4/docker/9311-cgroupdriver.test similarity index 100% rename from SubSystems/Online_Cobalt/validation/cep4/docker/cgroupdriver.test rename to SubSystems/Online_Cobalt/validation/cep4/docker/9311-cgroupdriver.test diff --git a/SubSystems/Online_Cobalt/validation/cep4/network/ipoib.test b/SubSystems/Online_Cobalt/validation/cep4/network/9228-ipoib.test similarity index 100% rename from SubSystems/Online_Cobalt/validation/cep4/network/ipoib.test rename to SubSystems/Online_Cobalt/validation/cep4/network/9228-ipoib.test diff --git a/SubSystems/Online_Cobalt/validation/cep4/os/utc.test b/SubSystems/Online_Cobalt/validation/cep4/os/9226-utc.test similarity index 100% rename from SubSystems/Online_Cobalt/validation/cep4/os/utc.test rename to SubSystems/Online_Cobalt/validation/cep4/os/9226-utc.test diff --git a/SubSystems/Online_Cobalt/validation/cep4/qpid/no_auth.test b/SubSystems/Online_Cobalt/validation/cep4/qpid/9315-no_auth.test similarity index 100% rename from SubSystems/Online_Cobalt/validation/cep4/qpid/no_auth.test rename to SubSystems/Online_Cobalt/validation/cep4/qpid/9315-no_auth.test -- GitLab From 0d36b200c485a6d8e08575490439a72557095a4c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 25 Apr 2016 10:13:09 +0000 Subject: [PATCH 147/933] Task #9346: Fixed docker test --- .../Online_Cobalt/validation/cep4/docker/9311-cgroupdriver.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SubSystems/Online_Cobalt/validation/cep4/docker/9311-cgroupdriver.test b/SubSystems/Online_Cobalt/validation/cep4/docker/9311-cgroupdriver.test index 03d3e45e879..bc87ad3e5fa 100755 --- a/SubSystems/Online_Cobalt/validation/cep4/docker/9311-cgroupdriver.test +++ b/SubSystems/Online_Cobalt/validation/cep4/docker/9311-cgroupdriver.test @@ -7,5 +7,5 @@ DOCKER_VERSION=`docker --version | awk '{ print $3; }'` [ "$DOCKER_VERSION" == "1.9.1," ] || exit 0 # Docker startup line must contain "--exec-opt=native.cgroupdriver=cgroupfs" -fgrep -q "ExecStart" /usr/lib/systemd/system/docker.service | fgrep -q "--exec-opt=native.cgroupdriver=cgroupfs" || exit 1 +fgrep "ExecStart" /usr/lib/systemd/system/docker.service | fgrep -- "--exec-opt=native.cgroupdriver=cgroupfs" || exit 1 -- GitLab From ec2d0f5ded383035f059683066783f49bcdc38fa Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 26 Apr 2016 10:18:14 +0000 Subject: [PATCH 148/933] Task #8589: Added CEP4 cpu nodes to Storage+MAC.dat for TBB dumps --- .../data/StaticMetaData/Storage+MAC.dat | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/MAC/Deployment/data/StaticMetaData/Storage+MAC.dat b/MAC/Deployment/data/StaticMetaData/Storage+MAC.dat index f0d0d44e323..2b1667fd953 100644 --- a/MAC/Deployment/data/StaticMetaData/Storage+MAC.dat +++ b/MAC/Deployment/data/StaticMetaData/Storage+MAC.dat @@ -103,3 +103,53 @@ locus097 00:02:c9:0e:37:fe 10.135.255.22 locus098 00:02:c9:0e:37:fe 10.135.255.23 locus099 00:02:c9:0e:37:fe 10.135.255.24 locus100 00:02:c9:0e:37:fe 10.135.255.25 +cpu01 44:a8:42:32:3d:1e 10.168.153.1 +cpu02 44:a8:42:36:9f:6a 10.168.153.2 +cpu03 44:a8:42:36:61:ea 10.168.153.3 +cpu04 44:a8:42:2f:e7:ae 10.168.153.4 +cpu05 44:a8:42:36:88:3f 10.168.153.5 +cpu06 44:a8:42:38:76:c7 10.168.153.6 +cpu07 44:a8:42:2f:ed:60 10.168.153.7 +cpu08 44:a8:42:2f:6d:d3 10.168.153.8 +cpu09 44:a8:42:2f:d0:58 10.168.153.9 +cpu10 44:a8:42:2f:76:4d 10.168.153.10 +cpu11 44:a8:42:2f:cc:f4 10.168.153.11 +cpu12 44:a8:42:2f:76:71 10.168.153.12 +cpu13 44:a8:42:2f:dc:e4 10.168.153.13 +cpu14 44:a8:42:36:a2:0a 10.168.153.14 +cpu15 44:a8:42:2f:c9:24 10.168.153.15 +cpu16 44:a8:42:2f:77:1d 10.168.153.16 +cpu17 44:a8:42:36:3e:05 10.168.153.17 +cpu18 44:a8:42:2f:97:34 10.168.153.18 +cpu19 44:a8:42:36:65:da 10.168.153.19 +cpu20 44:a8:42:36:67:9b 10.168.153.20 +cpu21 44:a8:42:36:67:ef 10.168.153.21 +cpu22 44:a8:42:36:5a:71 10.168.153.22 +cpu23 44:a8:42:31:df:b3 10.168.153.23 +cpu24 44:a8:42:36:41:35 10.168.153.24 +cpu25 44:a8:42:36:4b:c6 10.168.153.25 +cpu26 44:a8:42:36:9f:22 10.168.153.26 +cpu27 44:a8:42:2f:b8:ce 10.168.153.27 +cpu28 44:a8:42:31:ea:1a 10.168.153.28 +#cpu29 44:a8:00:00:00:00 10.168.153.29 # <--- MAC address TBD +cpu30 44:a8:42:36:55:d3 10.168.153.30 +cpu31 44:a8:42:2f:eb:08 10.168.153.31 +cpu32 44:a8:42:36:ad:90 10.168.153.32 +cpu33 44:a8:42:36:86:b3 10.168.153.33 +cpu34 44:a8:42:36:83:36 10.168.153.34 +cpu35 44:a8:42:31:dc:ce 10.168.153.35 +cpu36 44:a8:42:2f:e7:74 10.168.153.36 +cpu37 44:a8:42:2f:98:b9 10.168.153.37 +cpu38 44:a8:42:36:59:bd 10.168.153.38 +cpu39 44:a8:42:36:42:01 10.168.153.39 +cpu40 44:a8:42:36:3c:25 10.168.153.40 +cpu41 44:a8:42:2f:ed:8e 10.168.153.41 +cpu42 44:a8:42:32:0c:e1 10.168.153.42 +cpu43 44:a8:42:36:a5:0a 10.168.153.43 +cpu44 44:a8:42:36:39:cd 10.168.153.44 +cpu45 44:a8:42:36:83:4e 10.168.153.45 +cpu46 44:a8:42:36:68:2b 10.168.153.46 +cpu47 44:a8:42:36:b0:b4 10.168.153.47 +cpu48 44:a8:42:36:b0:e4 10.168.153.48 +cpu49 44:a8:42:36:84:97 10.168.153.49 +cpu50 44:a8:42:36:a2:16 10.168.153.50 -- GitLab From 48211c621fac741a00d3948639d5b4c5a6685d9a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 29 Apr 2016 07:19:21 +0000 Subject: [PATCH 149/933] Task #9346: Added check for fqdn hostname --- .gitattributes | 1 + .../validation/cep4/os/9376-hostname.test | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100755 SubSystems/Online_Cobalt/validation/cep4/os/9376-hostname.test diff --git a/.gitattributes b/.gitattributes index 7fa16a16230..2d17b535274 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5480,6 +5480,7 @@ SubSystems/Online_Cobalt/validation/cep4/lofarsys/docker.root.test -text SubSystems/Online_Cobalt/validation/cep4/lofarsys/ssh_localhost.root.test -text SubSystems/Online_Cobalt/validation/cep4/network/9228-ipoib.test -text SubSystems/Online_Cobalt/validation/cep4/os/9226-utc.test -text +SubSystems/Online_Cobalt/validation/cep4/os/9376-hostname.test -text SubSystems/Online_Cobalt/validation/cep4/os/lustre-mounted.test -text SubSystems/Online_Cobalt/validation/cep4/os/services.test -text SubSystems/Online_Cobalt/validation/cep4/os/users.test -text diff --git a/SubSystems/Online_Cobalt/validation/cep4/os/9376-hostname.test b/SubSystems/Online_Cobalt/validation/cep4/os/9376-hostname.test new file mode 100755 index 00000000000..e57b2d94c95 --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/os/9376-hostname.test @@ -0,0 +1,14 @@ +#!/bin/bash + +FQDN=`hostname --fqdn` + +# FQDN hostname must give full-length name +case $FQDN in + *.cep4.control.lofar) + exit 0 + ;; + *) + exit 1 + ;; +esac + -- GitLab From d9f18f051435810da7498259f358f0417da122db Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 29 Apr 2016 08:42:20 +0000 Subject: [PATCH 150/933] Task #9346: Added NHC to list of checks --- .gitattributes | 1 + SubSystems/Online_Cobalt/validation/cep4/slurm/nhc.test | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100755 SubSystems/Online_Cobalt/validation/cep4/slurm/nhc.test diff --git a/.gitattributes b/.gitattributes index 2d17b535274..87a8bbabcfe 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5488,6 +5488,7 @@ SubSystems/Online_Cobalt/validation/cep4/packages/iperf.test -text SubSystems/Online_Cobalt/validation/cep4/packages/jenkins-reqs.test -text SubSystems/Online_Cobalt/validation/cep4/qpid/9315-no_auth.test -text SubSystems/Online_Cobalt/validation/cep4/slurm/available.test -text +SubSystems/Online_Cobalt/validation/cep4/slurm/nhc.test -text SubSystems/Online_Cobalt/validation/cluster/c3/cexec -text SubSystems/Online_Cobalt/validation/cluster/connectivity/cobalt2cep.test eol=lf SubSystems/Online_Cobalt/validation/cluster/connectivity/cobalt2cobalt.test eol=lf diff --git a/SubSystems/Online_Cobalt/validation/cep4/slurm/nhc.test b/SubSystems/Online_Cobalt/validation/cep4/slurm/nhc.test new file mode 100755 index 00000000000..6e448e0654d --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/slurm/nhc.test @@ -0,0 +1,6 @@ +#!/bin/bash -v + +# Run only on cpu nodes +[[ `hostname` =~ "cpu" ]] || exit 0 + +/usr/sbin/nhc || exit 1 -- GitLab From 20b8e1ad89768bfdb1dccaa4fc323ec99cc9be84 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 29 Apr 2016 14:14:55 +0000 Subject: [PATCH 151/933] Task #9349: feature branch for mom copytask in webscheduler -- GitLab From fcc6385620048ea3a3f3e6214783ef25bb9d93a4 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 29 Apr 2016 14:32:16 +0000 Subject: [PATCH 152/933] Task #9349: added verbose (logging) option to rpcwrapper --- LCS/Messaging/python/messaging/RPC.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/LCS/Messaging/python/messaging/RPC.py b/LCS/Messaging/python/messaging/RPC.py index f67f4c86c38..4262ce36e0c 100644 --- a/LCS/Messaging/python/messaging/RPC.py +++ b/LCS/Messaging/python/messaging/RPC.py @@ -282,11 +282,13 @@ class RPCWrapper(object): def __init__(self, busname=None, servicename=None, broker=None, - timeout=10): + timeout=10, + verbose=False): self.busname = busname self.servicename = servicename self.broker = broker self.timeout = timeout + self.verbose = verbose self._serviceRPCs = {} #cache of rpc's for each service @@ -313,7 +315,8 @@ class RPCWrapper(object): '''execute the rpc call on the <bus>/<service>.<method> and return the result''' try: if self.timeout: - rpckwargs = {'timeout': self.timeout} + rpckwargs = {'timeout': self.timeout, + 'Verbose': self.verbose} service_method = (self.servicename + '.' + method) if self.servicename and method \ else self.servicename if self.servicename else method -- GitLab From 44a21a32cd6facdb2990aa37a96d1b3b13d95364 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 29 Apr 2016 14:33:00 +0000 Subject: [PATCH 153/933] Task #9349: inital python mom rpc client for copy task and more. Talks to Java MoM services. --- .gitattributes | 1 + SAS/MoM/MoMQueryService/CMakeLists.txt | 1 + SAS/MoM/MoMQueryService/config.py | 3 ++ SAS/MoM/MoMQueryService/momrpc.py | 42 ++++++++++++++++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 SAS/MoM/MoMQueryService/momrpc.py diff --git a/.gitattributes b/.gitattributes index 985f3647b9e..34409cbffb7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4726,6 +4726,7 @@ SAS/MoM/MoMQueryService/momqueryrpc.py -text SAS/MoM/MoMQueryService/momqueryservice -text SAS/MoM/MoMQueryService/momqueryservice.ini -text SAS/MoM/MoMQueryService/momqueryservice.py -text +SAS/MoM/MoMQueryService/momrpc.py -text SAS/MoM/MoMQueryService/test/CMakeLists.txt -text SAS/MoM/MoMQueryService/test/test_momqueryservice.py -text SAS/MoM/MoMQueryService/test/test_momqueryservice.run -text diff --git a/SAS/MoM/MoMQueryService/CMakeLists.txt b/SAS/MoM/MoMQueryService/CMakeLists.txt index bb355d1c6eb..7e6a3118ab4 100644 --- a/SAS/MoM/MoMQueryService/CMakeLists.txt +++ b/SAS/MoM/MoMQueryService/CMakeLists.txt @@ -9,6 +9,7 @@ set(_py_files config.py momqueryservice.py momqueryrpc.py + momrpc.py ) python_install(${_py_files} DESTINATION lofar/mom/momqueryservice) diff --git a/SAS/MoM/MoMQueryService/config.py b/SAS/MoM/MoMQueryService/config.py index a5b66e022d8..00986504667 100644 --- a/SAS/MoM/MoMQueryService/config.py +++ b/SAS/MoM/MoMQueryService/config.py @@ -5,3 +5,6 @@ from lofar.messaging import adaptNameToEnvironment DEFAULT_MOMQUERY_BUSNAME = adaptNameToEnvironment('lofar.ra.command') DEFAULT_MOMQUERY_SERVICENAME = 'momqueryservice' + +DEFAULT_MOM_BUSNAME = adaptNameToEnvironment('lofar.mom.bus') +DEFAULT_MOM_SERVICENAME = '' diff --git a/SAS/MoM/MoMQueryService/momrpc.py b/SAS/MoM/MoMQueryService/momrpc.py new file mode 100644 index 00000000000..94d01c9aeaa --- /dev/null +++ b/SAS/MoM/MoMQueryService/momrpc.py @@ -0,0 +1,42 @@ +#!/usr/bin/python + +import sys +import logging +from optparse import OptionParser +from lofar.messaging.RPC import RPC, RPCException, RPCWrapper +from lofar.mom.momqueryservice.config import DEFAULT_MOM_BUSNAME, DEFAULT_MOM_SERVICENAME + +logger = logging.getLogger(__file__) + +class MoMRPC(RPCWrapper): + def copyTask(self, mom2id, newTaskName=None, newTaskDescription=None): + logger.info("calling copyTask rpc for mom2id %s" % (mom2id)) + new_task_mom2id = self.rpc('TaskCopy', mom2Id=mom2id) #, newTaskName=newTaskName, newTaskDescription=newTaskDescription) + logger.info("mom2id of copied task = %s" % (new_task_mom2id)) + return new_task_mom2id + +def main(): + # Check the invocation arguments + parser = OptionParser('%prog [options]', + description='do rpc calls to the momservice from the commandline') + parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') + parser.add_option('-b', '--busname', dest='busname', type='string', default=DEFAULT_MOM_BUSNAME, help='Name of the bus exchange on the qpid broker [default: %default]') + parser.add_option('-s', '--servicename', dest='servicename', type='string', default=DEFAULT_MOM_SERVICENAME, help='Name for this service [default: %default]') + parser.add_option('--mom2id', dest='mom2id_to_copy', type='int', help='[REQUIRED] mom2id of the task to copy.') + parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') + (options, args) = parser.parse_args() + + if options.mom2id_to_copy == None: + parser.print_help() + parser.error('Missing required option mom2id') + + verbose = bool(options.verbose) + + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.DEBUG if verbose else logging.INFO) + + with MoMRPC(busname=options.busname, servicename=options.servicename, broker=options.broker, verbose=verbose) as rpc: + print rpc.copyTask(options.mom2id_to_copy) + +if __name__ == '__main__': + main() -- GitLab From 11c5bf7fc4ecae6ebc3bc941dc98e8762e71506d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 29 Apr 2016 15:10:26 +0000 Subject: [PATCH 154/933] Task #9346: Check data location for docker data on head nodes --- .gitattributes | 1 + .../validation/cep4/docker/datalocation.test | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100755 SubSystems/Online_Cobalt/validation/cep4/docker/datalocation.test diff --git a/.gitattributes b/.gitattributes index 87a8bbabcfe..3eef3eec50a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5476,6 +5476,7 @@ SubSystems/Online_Cobalt/test/tstartBGL.in_parset -text SubSystems/Online_Cobalt/test/tstartBGL.run eol=lf SubSystems/Online_Cobalt/test/tstartBGL.sh eol=lf SubSystems/Online_Cobalt/validation/cep4/docker/9311-cgroupdriver.test -text +SubSystems/Online_Cobalt/validation/cep4/docker/datalocation.test -text SubSystems/Online_Cobalt/validation/cep4/lofarsys/docker.root.test -text SubSystems/Online_Cobalt/validation/cep4/lofarsys/ssh_localhost.root.test -text SubSystems/Online_Cobalt/validation/cep4/network/9228-ipoib.test -text diff --git a/SubSystems/Online_Cobalt/validation/cep4/docker/datalocation.test b/SubSystems/Online_Cobalt/validation/cep4/docker/datalocation.test new file mode 100755 index 00000000000..0d4666d30e4 --- /dev/null +++ b/SubSystems/Online_Cobalt/validation/cep4/docker/datalocation.test @@ -0,0 +1,9 @@ +#/bin/bash -e + +# Test for location of Docker's data: head nodes need to use /localdata/docker + +[[ `hostname` =~ "head" ]] || exit 0 + +# Docker startup line must contain "--graph=/localdata/docker" +fgrep "ExecStart" /usr/lib/systemd/system/docker.service | fgrep -- "--graph=/localdata/docker" || exit 1 + -- GitLab From 890ff2ee112f06bdc7deedfcbc846042f07aac5e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 2 May 2016 07:10:40 +0000 Subject: [PATCH 155/933] Task #9349: only block editing in production env --- .../ResourceAssignmentEditor/lib/webservice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 221c4a04064..1b6c142fcc0 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -50,6 +50,7 @@ from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAUL from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME from lofar.sas.resourceassignment.resourceassignmenteditor.mom import updateTaskMomDetails +from lofar.common import isProductionEnvironment, isTestEnvironment #from lofar.sas.resourceassignment.resourceassigner. import updateTaskMomDetails logger = logging.getLogger(__name__) @@ -203,7 +204,8 @@ def getTask(task_id): @app.route('/rest/tasks/<int:task_id>', methods=['PUT']) def putTask(task_id): - abort(403, 'Editing of tasks is by users is not yet approved') + if isProductionEnvironment(): + abort(403, 'Editing of tasks is by users is not yet approved') if 'Content-Type' in request.headers and \ request.headers['Content-Type'].startswith('application/json'): -- GitLab From 03be3e4510839505f360cbbeed89717fec2b751b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 2 May 2016 08:41:16 +0000 Subject: [PATCH 156/933] Task #9349: added copy task url and method --- .../lib/radbchangeshandler.py | 6 +-- .../lib/webservice.py | 49 +++++++++++++++---- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py index f1d9cf31031..c16481807e6 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py @@ -44,7 +44,7 @@ CHANGE_INSERT_TYPE = 'insert' CHANGE_DELETE_TYPE = 'delete' class RADBChangesHandler(RADBBusListener): - def __init__(self, busname=DEFAULT_NOTIFICATION_BUSNAME, subjects=DEFAULT_NOTIFICATION_SUBJECTS, broker=None, momrpc=None, **kwargs): + def __init__(self, busname=DEFAULT_NOTIFICATION_BUSNAME, subjects=DEFAULT_NOTIFICATION_SUBJECTS, broker=None, momqueryrpc=None, **kwargs): """ RADBChangesHandler listens on the lofar notification message bus and keeps track of all the change notifications. :param broker: valid Qpid broker host (default: None, which means localhost) @@ -60,7 +60,7 @@ class RADBChangesHandler(RADBBusListener): self._lock = Lock() self._changedCondition = Condition() self._changeNumber = 0L - self._momrpc = momrpc + self._momqueryrpc = momqueryrpc def _handleChange(self, change): '''_handleChange appends a change in the changes list and calls the onChangedCallback. @@ -89,7 +89,7 @@ class RADBChangesHandler(RADBBusListener): :param task: dictionary with the inserted task''' task['starttime'] = task['starttime'].datetime() task['endtime'] = task['endtime'].datetime() - updateTaskMomDetails(task, self._momrpc) + updateTaskMomDetails(task, self._momqueryrpc) task_change = {'changeType':CHANGE_INSERT_TYPE, 'objectType':'task', 'value':task} self._handleChange(task_change) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 1b6c142fcc0..f8e2a1f2183 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -49,6 +49,8 @@ from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAUL from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as DEFAULT_RADB_SERVICENAME from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME +from lofar.mom.momqueryservice.momrpc import MoMRPC +from lofar.mom.momqueryservice.config import DEFAULT_MOM_BUSNAME, DEFAULT_MOM_SERVICENAME from lofar.sas.resourceassignment.resourceassignmenteditor.mom import updateTaskMomDetails from lofar.common import isProductionEnvironment, isTestEnvironment #from lofar.sas.resourceassignment.resourceassigner. import updateTaskMomDetails @@ -94,6 +96,7 @@ app.json_encoder = CustomJSONEncoder rarpc = None momrpc = None +momqueryrpc = None radbchangeshandler = None @app.route('/') @@ -182,7 +185,7 @@ def getTasksFromUntil(fromTimestamp=None, untilTimestamp=None): # there are no task names in the database yet. # will they come from spec/MoM? # add Task <id> as name for now - updateTaskMomDetails(tasks, momrpc) + updateTaskMomDetails(tasks, momqueryrpc) return jsonify({'tasks': tasks}) @@ -195,7 +198,7 @@ def getTask(task_id): abort(404) task['name'] = 'Task %d' % task['id'] - updateTaskMomDetails(task, momrpc) + updateTaskMomDetails(task, momqueryrpc) return jsonify({'task': task}) except Exception as e: abort(404) @@ -238,6 +241,27 @@ def putTask(task_id): abort(404) abort(406) +@app.route('/rest/tasks/<int:task_id>/copy', methods=['PUT']) +def copyTask(task_id): + if isProductionEnvironment(): + abort(403, 'Copying of tasks is by users is not yet approved') + + try: + task = rarpc.getTask(task_id) + + if not task: + logger.error('Could not find task %s' % task_id) + abort(404, 'Could not find task %s' % task_id) + + mom2id = task['mom_id'] + new_task_mom2id = momrpc.copyTask(mom2id=mom2id) + return jsonify({'copied':True, 'new_task_mom2id':new_task_mom2id, 'old_task_mom2id':mom2id}) + except Exception as e: + logger.error(e) + abort(404, str(e)) + + return jsonify({'copied':False}) + @app.route('/rest/tasks/<int:task_id>/resourceclaims') def taskResourceClaims(task_id): return jsonify({'taskResourceClaims': rarpc.getResourceClaims(task_id=task_id, include_properties=True)}) @@ -270,7 +294,7 @@ def resourceclaimpropertytypes(): def getMoMProjects(): projects = [] try: - projects = momrpc.getProjects() + projects = momqueryrpc.getProjects() projects = [x for x in projects if x['status_id'] in [1, 7]] for project in projects: project['mom_id'] = project.pop('mom2id') @@ -283,7 +307,7 @@ def getMoMProjects(): @app.route('/rest/momobjectdetails/<int:mom2id>') def getMoMObjectDetails(mom2id): - details = momrpc.getProjectDetails(mom2id) + details = momqueryrpc.getProjectDetails(mom2id) details = details.values()[0] if details else None if details: details['project_mom_id'] = details.pop('project_mom2id') @@ -323,8 +347,11 @@ def main(): parser.add_option('--radb_servicename', dest='radb_servicename', type='string', default=DEFAULT_RADB_SERVICENAME, help='Name of the radbservice, default: %default') parser.add_option('--radb_notification_busname', dest='radb_notification_busname', type='string', default=DEFAULT_RADB_CHANGES_BUSNAME, help='Name of the notification bus exchange on the qpid broker on which the radb notifications are published, default: %default') parser.add_option('--radb_notification_subjects', dest='radb_notification_subjects', type='string', default=DEFAULT_RADB_CHANGES_SUBJECTS, help='Subject(s) to listen for on the radb notification bus exchange on the qpid broker, default: %default') - parser.add_option('--mom_busname', dest='mom_busname', type='string', default=DEFAULT_MOMQUERY_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momservice listens, default: %default') - parser.add_option('--mom_servicename', dest='mom_servicename', type='string', default=DEFAULT_MOMQUERY_SERVICENAME, help='Name of the momservice, default: %default') + parser.add_option('--mom_busname', dest='mom_busname', type='string', default=DEFAULT_MOM_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momservice listens, default: %default') + parser.add_option('--mom_servicename', dest='mom_servicename', type='string', default=DEFAULT_MOM_SERVICENAME, help='Name of the momservice, default: %default') + parser.add_option('--mom_broker', dest='mom_broker', type='string', default=None, help='Address of the qpid broker for the mom service, default: localhost') + parser.add_option('--mom_query_busname', dest='mom_query_busname', type='string', default=DEFAULT_MOMQUERY_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momqueryservice listens, default: %default') + parser.add_option('--mom_query_servicename', dest='mom_query_servicename', type='string', default=DEFAULT_MOMQUERY_SERVICENAME, help='Name of the momqueryservice, default: %default') parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() @@ -332,13 +359,15 @@ def main(): level=logging.DEBUG if options.verbose else logging.INFO) global rarpc - rarpc = RARPC(busname=DEFAULT_RADB_BUSNAME, servicename=DEFAULT_RADB_SERVICENAME, broker=options.broker) + rarpc = RARPC(busname=options.radb_busname, servicename=options.radb_servicename, broker=options.broker) global momrpc - momrpc = MoMQueryRPC(busname=DEFAULT_MOMQUERY_BUSNAME, servicename=DEFAULT_MOMQUERY_SERVICENAME, timeout=2.5, broker=options.broker) + momrpc = MoMRPC(busname=options.mom_busname, servicename=options.mom_servicename, broker=options.mom_broker) + global momqueryrpc + momqueryrpc = MoMQueryRPC(busname=options.mom_query_busname, servicename=options.mom_query_servicename, timeout=2.5, broker=options.broker) global radbchangeshandler - radbchangeshandler = RADBChangesHandler(DEFAULT_RADB_CHANGES_BUSNAME, broker=options.broker, momrpc=momrpc) + radbchangeshandler = RADBChangesHandler(options.radb_notification_busname, subjects=options.radb_notification_subjects, broker=options.broker, momqueryrpc=momqueryrpc) - with radbchangeshandler, rarpc, momrpc: + with radbchangeshandler, rarpc, momrpc, momqueryrpc: '''Start the webserver''' app.run(debug=options.verbose, threaded=True, host='0.0.0.0', port=options.port) -- GitLab From 3e236e786a933be49578e4f7063acd9adda6a123 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 2 May 2016 12:49:38 +0000 Subject: [PATCH 157/933] Task #9189: commented out w.i.p. --- .../ResourceAssignmentEstimator/service.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index 68950c21324..82dd53dde97 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -18,10 +18,10 @@ class ResourceEstimatorHandler(MessageHandlerInterface): def __init__(self, **kwargs): super(ResourceEstimatorHandler, self).__init__(**kwargs) self.observation = ObservationResourceEstimator() - self.longbaseline_pipeline = LongBaselinePipelineResourceEstimator() - self.calibration_pipeline = CalibrationPipelineResourceEstimator() - self.pulsar_pipeline = PulsarPipelineResourceEstimator() - self.imaging_pipeline = ImagePipelineResourceEstimator() + #self.longbaseline_pipeline = LongBaselinePipelineResourceEstimator() + #self.calibration_pipeline = CalibrationPipelineResourceEstimator() + #self.pulsar_pipeline = PulsarPipelineResourceEstimator() + #self.imaging_pipeline = ImagePipelineResourceEstimator() def handle_message(self, content): specification_tree = content["specification_tree"] -- GitLab From 8b634eda7ae220d0e974f3320af4b912fe7d50ec Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 2 May 2016 13:53:13 +0000 Subject: [PATCH 158/933] Task #8437 #9386: Propagate $LOFAERENV to Pipeline Framework --- MAC/Services/src/PipelineControl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index f41e74a2810..59139913d82 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -342,6 +342,7 @@ class PipelineControl(OTDBBusListener): " --net=host" " -v /data:/data" " -e LUSER=$UID" + " -e LOFARENV=$LOFARENV" " -v $HOME/.ssh:/home/lofar/.ssh:ro" " -e SLURM_JOB_ID=$SLURM_JOB_ID" " lofar-pipeline:{tag}" @@ -364,6 +365,7 @@ class PipelineControl(OTDBBusListener): "docker run --rm" " --net=host" " -e LUSER=$UID" + " -e LOFARENV=$LOFARENV" " lofar-pipeline:{tag}" " pipelineAborted.sh -o {obsid} -B {status_bus}" .format( -- GitLab From 5ef0ef5d5791bc40f6d0500d226abf0dc2853512 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 2 May 2016 13:54:57 +0000 Subject: [PATCH 159/933] Task #8437 #9386: Propagate $LOFARENV to Pipeline Framework --- MAC/Services/src/PipelineControl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 59139913d82..bd2f4dd5746 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -342,12 +342,13 @@ class PipelineControl(OTDBBusListener): " --net=host" " -v /data:/data" " -e LUSER=$UID" - " -e LOFARENV=$LOFARENV" + " -e LOFARENV={lofarenv}" " -v $HOME/.ssh:/home/lofar/.ssh:ro" " -e SLURM_JOB_ID=$SLURM_JOB_ID" " lofar-pipeline:{tag}" " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -B {status_bus}" .format( + lofarenv = os.environ.get("LOFARENV", ""), obsid = treeId, tag = parset.dockerTag(), cluster = parset.processingCluster(), @@ -365,10 +366,11 @@ class PipelineControl(OTDBBusListener): "docker run --rm" " --net=host" " -e LUSER=$UID" - " -e LOFARENV=$LOFARENV" + " -e LOFARENV={lofarenv}" " lofar-pipeline:{tag}" " pipelineAborted.sh -o {obsid} -B {status_bus}" .format( + lofarenv = os.environ.get("LOFARENV", ""), obsid = treeId, tag = parset.dockerTag(), status_bus = self.setStatus_busname, -- GitLab From 6ba0ce66f911e14e0ee5eb55b987f97cc9dabe2a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 3 May 2016 06:54:00 +0000 Subject: [PATCH 160/933] Task #8887: Disable queue prefixesd for LCS/MessageBus in 2.16 release branch --- LCS/MessageBus/src/Util.cc | 3 ++- LCS/MessageBus/src/messagebus.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/LCS/MessageBus/src/Util.cc b/LCS/MessageBus/src/Util.cc index 9c18c62c28a..d0eb919700f 100644 --- a/LCS/MessageBus/src/Util.cc +++ b/LCS/MessageBus/src/Util.cc @@ -26,7 +26,8 @@ namespace LOFAR { std::string queue_prefix() { - string lofarenv = getenv_str("LOFARENV"); + // disable LOFARENV for 2.16 release + string lofarenv = "PRODUCTION"; //getenv_str("LOFARENV"); string queueprefix = getenv_str("QUEUE_PREFIX"); if (lofarenv == "PRODUCTION") { diff --git a/LCS/MessageBus/src/messagebus.py b/LCS/MessageBus/src/messagebus.py index f6c1de6a37d..99cd046e97d 100644 --- a/LCS/MessageBus/src/messagebus.py +++ b/LCS/MessageBus/src/messagebus.py @@ -97,7 +97,8 @@ class Session: return "%s%s; {%s}" % (self._queue_prefix(), queue, options) def _queue_prefix(self): - lofarenv = os.environ.get("LOFARENV", "") + # disable LOFARENV for 2.16 release + lofarenv = "PRODUCTION" # os.environ.get("LOFARENV", "") queueprefix = os.environ.get("QUEUE_PREFIX", "") if lofarenv == "PRODUCTION": -- GitLab From eff913f0a4cf29c7f561580e5e345854b6996f7a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 4 May 2016 10:04:12 +0000 Subject: [PATCH 161/933] Task #9078: Created working branch -- GitLab From 36703de91d048c852c607bc6512117c978dc93a1 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 4 May 2016 10:06:31 +0000 Subject: [PATCH 162/933] Task #9078: Added cbm00X queues to infra --- SAS/QPIDInfrastructure/bin/populateDB.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/SAS/QPIDInfrastructure/bin/populateDB.sh b/SAS/QPIDInfrastructure/bin/populateDB.sh index e24f0454a77..b3be1783c5b 100755 --- a/SAS/QPIDInfrastructure/bin/populateDB.sh +++ b/SAS/QPIDInfrastructure/bin/populateDB.sh @@ -60,7 +60,19 @@ else fi # ----------------------------------------- -# Cobalt & Pipelines -> MessageRouter +# Cobalt GPUProc -> MessageRouter +# ----------------------------------------- + +for fnode in $COBALT +do + addtoQPIDDB.py --broker $fnode --queue ${PREFIX}lofar.task.feedback.dataproducts --federation $CCU + addtoQPIDDB.py --broker $fnode --queue ${PREFIX}lofar.task.feedback.processing --federation $CCU + addtoQPIDDB.py --broker $fnode --queue ${PREFIX}lofar.task.feedback.state --federation $CCU +done + + +# ----------------------------------------- +# Cobalt OutputProc & Pipelines -> MessageRouter # ----------------------------------------- for tnode in $CEP4HEAD -- GitLab From 059f6fc05fcc2727fdc7b4c40c41a60e534bdc14 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 4 May 2016 12:52:14 +0000 Subject: [PATCH 163/933] Task #9078: Use cbm00X instead of cbt00X --- SAS/QPIDInfrastructure/bin/populateDB.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/QPIDInfrastructure/bin/populateDB.sh b/SAS/QPIDInfrastructure/bin/populateDB.sh index b3be1783c5b..8e5099f7d17 100755 --- a/SAS/QPIDInfrastructure/bin/populateDB.sh +++ b/SAS/QPIDInfrastructure/bin/populateDB.sh @@ -34,7 +34,7 @@ if $PROD; then MOM_USER=lcs023.control.lofar MOM_INGEST=lcs029.control.lofar - COBALT="`seq -f cbt%03.0f.control.lofar 1 8`" + COBALT="`seq -f cbm%03.0f.control.lofar 1 8`" CEP2="`seq -f locus%03.0f.cep2.lofar 1 94`" CEP2HEAD=lhn001.cep2.lofar @@ -50,7 +50,7 @@ else MOM_USER=lcs028.control.lofar MOM_INGEST=lcs028.control.lofar - COBALT="cbt009.control.lofar" + COBALT="cbm009.control.lofar" CEP2="locus098.cep2.lofar locus099.cep2.lofar" CEP2HEAD=locus102.cep2.lofar -- GitLab From 3fd09dafa8cf34485989a739e06eec19894c13e8 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 10 May 2016 12:02:25 +0000 Subject: [PATCH 164/933] Task #9078: Add lofar.mom.command and lofar.mom.notification, and route lofar.otdb.notification through CEP4 --- SAS/QPIDInfrastructure/bin/populateDB.sh | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/SAS/QPIDInfrastructure/bin/populateDB.sh b/SAS/QPIDInfrastructure/bin/populateDB.sh index 8e5099f7d17..ccf9206c301 100755 --- a/SAS/QPIDInfrastructure/bin/populateDB.sh +++ b/SAS/QPIDInfrastructure/bin/populateDB.sh @@ -148,6 +148,15 @@ addtoQPIDDB.py --broker $MOM_USER --queue mom-otdb-adapter.importxml --federatio # ----------------------------------------- addtoQPIDDB.py --broker $MOM_USER --exchange lofar.mom.bus addtoQPIDDB.py --broker $MOM_INGEST --exchange lofar.mom.bus +addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.command +addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.notification + +# ----------------------------------------- +# MoM Services <-> ResourceAssignment +# ----------------------------------------- + +addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.mom.command --federation $MOM_USER +addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.notification --federation $SCU # ----------------------------------------- # ResourceAssignment @@ -160,14 +169,16 @@ addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.otdb.notification addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.ssdb.command addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.ssdb.notification -# TODO: messages will end up at $SCU twice? -for tnode in head{01..02}.cep4 +# TODO: messages will be duplicated? +for head in head{01..02}.cep4 do - for fnode in cpu{01..50}.cep4 + for cpu in cpu{01..50}.cep4 do - addtoQPIDDB.py --broker $fnode --exchange ${PREFIX}lofar.otdb.command --federation $tnode + addtoQPIDDB.py --broker $cpu --exchange ${PREFIX}lofar.otdb.command --federation $head + addtoQPIDDB.py --broker $head --exchange ${PREFIX}lofar.otdb.notification --federation $cpu done - addtoQPIDDB.py --broker $tnode --exchange ${PREFIX}lofar.otdb.command --federation $SCU + addtoQPIDDB.py --broker $head --exchange ${PREFIX}lofar.otdb.command --federation $SCU + addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.otdb.notification --federation $head done -- GitLab From 1603bdb01173b1017edbe56916f501310547640f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 10 May 2016 12:20:18 +0000 Subject: [PATCH 165/933] Task #9078: Prefix lofar.mom.bus with test., and route it from scu0XX --- SAS/QPIDInfrastructure/bin/populateDB.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SAS/QPIDInfrastructure/bin/populateDB.sh b/SAS/QPIDInfrastructure/bin/populateDB.sh index ccf9206c301..6b972899f59 100755 --- a/SAS/QPIDInfrastructure/bin/populateDB.sh +++ b/SAS/QPIDInfrastructure/bin/populateDB.sh @@ -146,8 +146,8 @@ addtoQPIDDB.py --broker $MOM_USER --queue mom-otdb-adapter.importxml --federatio # ----------------------------------------- # MoM Services # ----------------------------------------- -addtoQPIDDB.py --broker $MOM_USER --exchange lofar.mom.bus -addtoQPIDDB.py --broker $MOM_INGEST --exchange lofar.mom.bus +addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.bus +addtoQPIDDB.py --broker $MOM_INGEST --exchange ${PREFIX}lofar.mom.bus addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.command addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.notification @@ -155,6 +155,7 @@ addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.notification # MoM Services <-> ResourceAssignment # ----------------------------------------- +addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.mom.bus --federation $MOM_USER addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.mom.command --federation $MOM_USER addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.notification --federation $SCU -- GitLab From 0ada100fe0ecde98f23d66f4b11b2a04111d4f43 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 10 May 2016 13:06:27 +0000 Subject: [PATCH 166/933] Task #9349: made contextmenu plugin for gantt. call copyTask via dataservice via webservice on menu click. --- .gitattributes | 1 + .../lib/CMakeLists.txt | 1 + .../static/app/controllers/datacontroller.js | 7 ++ .../app/controllers/ganttprojectcontroller.js | 56 ++++++++++-- .../controllers/ganttresourcecontroller.js | 52 +++++++++-- .../angular-gantt-contextmenu-plugin.js | 86 +++++++++++++++++++ .../lib/templates/index.html | 3 + 7 files changed, 192 insertions(+), 14 deletions(-) create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js diff --git a/.gitattributes b/.gitattributes index 34409cbffb7..137391a8366 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5060,6 +5060,7 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datac SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/bootstrap.min.css -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/favicon.ico -text diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt index 0e9c1f37a64..ffbddce6f5c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt @@ -35,6 +35,7 @@ set(app_files static/app/controllers/ganttresourcecontroller.js static/app/controllers/chartresourceusagecontroller.js static/app/controllers/ganttprojectcontroller.js + static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js static/css/main.css templates/index.html) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 0d773e2e956..f851a446570 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -254,6 +254,13 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }) }; + self.copyTask = function(task) { + $http.put('/rest/tasks/' + task.id + '/copy').error(function(result) { + console.log("Error. Could not copy task. " + result); + alert("Error: Could not copy task with mom id " + task.mom_id); + }) + }; + self.computeMinMaxTaskTimes = function() { var starttimes = self.filteredTasks.map(function(t) { return t.starttime;}); var endtimes = self.filteredTasks.map(function(t) { return t.endtime;}); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index 4ad95c83910..85782ee0c7e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -13,7 +13,8 @@ var ganttProjectControllerMod = angular.module('GanttProjectControllerMod', [ 'gantt.groups', 'gantt.dependencies', 'gantt.overlap', - 'gantt.resizeSensor']).config(['$compileProvider', function($compileProvider) { + 'gantt.resizeSensor', + 'gantt.contextmenu']).config(['$compileProvider', function($compileProvider) { $compileProvider.debugInfoEnabled(false); // Remove debug info (angularJS >= 1.3) }]); @@ -48,34 +49,73 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS } ); - api.directives.on.new($scope, function(directiveName, directiveScope, element) { + api.directives.on.new($scope, function(directiveName, directiveScope, directiveElement) { if (directiveName === 'ganttRow' || directiveName === 'ganttRowLabel' ) { - element.bind('click', function(event) { + directiveElement.bind('click', function(event) { if(directiveScope.row.model.project) { $scope.dataService.selected_project_id = directiveScope.row.model.project.id; } }); } else if (directiveName === 'ganttTask') { - element.bind('click', function(event) { + directiveElement.bind('click', function(event) { if(directiveScope.task.model.raTask) { $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; } }); - element.bind('dblclick', function(event) { + directiveElement.bind('dblclick', function(event) { if(directiveScope.task.model.raTask) { $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; $scope.jumpToSelectedTasks(); } }); +// directiveElement.bind('contextmenu', function(event) { +// if(directiveScope.task.model.raTask) { +// $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; +// } +// +// //search for already existing contextmenu element +// if(directiveElement.find('#gantt-project-context-menu').length) { +// //found, remove it, so we can create a fresh one +// directiveElement.find('#gantt-project-context-menu')[0].remove(); +// } +// +// //create contextmenu element +// //with list of menu items, +// //each with it's own action +// var contextmenuElement = angular.element('<div id="gantt-project-context-menu"></div>'); +// ulElement = angular.element('<ul style="z-index:10000; position:fixed; top:initial; left:initial; display:block;" role="menu" class="dropdown-menu"></ul>'); +// contextmenuElement.append(ulElement); +// liElement = angular.element('<li><a href="#">Copy Task</a></li>'); +// ulElement.append(liElement); +// liElement.on('click', function() { +// $scope.dataService.copyTask(directiveScope.task.model.raTask); +// closeContextMenu(); +// }); +// +// var closeContextMenu = function() { +// contextmenuElement.remove(); +// angular.element(document).unbind('click', closeContextMenu); +// }; +// +// //click anywhere to remove the contextmenu +// angular.element(document).bind('click', closeContextMenu); +// +// //add contextmenu to clicked element +// directiveElement.append(contextmenuElement); +// +// //prevent bubbling event upwards +// return false; +// }); } }); - api.directives.on.destroy($scope, function(directiveName, directiveScope, element) { + api.directives.on.destroy($scope, function(directiveName, directiveScope, directiveElement) { if (directiveName === 'ganttRow' || directiveName === 'ganttRowLabel' || directiveName === 'ganttTask') { - element.unbind('click'); + directiveElement.unbind('click'); } if (directiveName === 'ganttTask') { - element.unbind('dblclick'); + directiveElement.unbind('dblclick'); +// directiveElement.unbind('contextmenu'); } }); } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js index f75da077ea7..a59b2b741f4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js @@ -11,7 +11,8 @@ var ganttResourceControllerMod = angular.module('GanttResourceControllerMod', [ 'gantt.tree', 'gantt.groups', 'gantt.overlap', - 'gantt.resizeSensor']).config(['$compileProvider', function($compileProvider) { + 'gantt.resizeSensor', + 'gantt.contextmenu']).config(['$compileProvider', function($compileProvider) { $compileProvider.debugInfoEnabled(false); // Remove debug info (angularJS >= 1.3) }]); @@ -45,9 +46,9 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat api.tasks.on.resizeEnd($scope, moveHandler); }); - api.directives.on.new($scope, function(directiveName, directiveScope, element) { + api.directives.on.new($scope, function(directiveName, directiveScope, directiveElement) { if (directiveName === 'ganttRow' || directiveName === 'ganttRowLabel' ) { - element.bind('click', function(event) { + directiveElement.bind('click', function(event) { if(directiveScope.row.model.resource) { $scope.dataService.selected_resource_id = directiveScope.row.model.resource.id; } else if(directiveScope.row.model.resourceGroup) { @@ -55,7 +56,7 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat } }); } else if (directiveName === 'ganttTask') { - element.bind('click', function(event) { + directiveElement.bind('click', function(event) { if(directiveScope.task.model.raTask) { $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; } @@ -63,12 +64,51 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat $scope.dataService.selected_resourceClaim_id = directiveScope.task.model.claim.id; } }); + directiveElement.bind('contextmenu', function(event) { + if(directiveScope.task.model.raTask) { + $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; + } + + //search for already existing contextmenu element + if(directiveElement.find('#gantt-resource-context-menu').length) { + //found, remove it, so we can create a fresh one + directiveElement.find('#gantt-resource-context-menu')[0].remove(); + } + + //create contextmenu element + //with list of menu items, + //each with it's own action + var contextmenuElement = angular.element('<div id="gantt-resource-context-menu"></div>'); + ulElement = angular.element('<ul style="z-index:10000; position:fixed; top:initial; left:initial; display:block;" role="menu" class="dropdown-menu"></ul>'); + contextmenuElement.append(ulElement); + liElement = angular.element('<li><a href="#">Copy Task</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + $scope.dataService.copyTask(directiveScope.task.model.raTask); + closeContextMenu(); + }); + + var closeContextMenu = function() { + contextmenuElement.remove(); + angular.element(document).unbind('click', closeContextMenu); + }; + + //click anywhere to remove the contextmenu + angular.element(document).bind('click', closeContextMenu); + + //add contextmenu to clicked element + directiveElement.append(contextmenuElement); + + //prevent bubbling event upwards + return false; + }); } }); - api.directives.on.destroy($scope, function(directiveName, directiveScope, element) { + api.directives.on.destroy($scope, function(directiveName, directiveScope, directiveElement) { if (directiveName === 'ganttRow' || directiveName === 'ganttRowLabel' || directiveName === 'ganttTask') { - element.unbind('click'); + directiveElement.unbind('click'); + directiveElement.unbind('contextmenu'); } }); } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js new file mode 100644 index 00000000000..19280914c28 --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -0,0 +1,86 @@ +(function(){ + 'use strict'; + angular.module('gantt.contextmenu', ['gantt', 'gantt.contextmenu.templates']).directive('ganttContextmenu', ['$compile', '$document', function($compile, $document) { + return { + restrict: 'E', + require: '^gantt', + scope: { + enabled: '=?' + }, + link: function(scope, element, attrs, ganttCtrl) { + var api = ganttCtrl.gantt.api; + + // Load options from global options attribute. + if (scope.options && typeof(scope.options.contextmenu) === 'object') { + for (var option in scope.options.contextmenu) { + scope[option] = scope.options[option]; + } + } + + if (scope.enabled === undefined) { + scope.enabled = true; + } + + api.directives.on.new(scope, function(dName, dScope, dElement, dAttrs, dController) { + //for each new ganttTask + if (dName === 'ganttTask') { + dElement.bind('contextmenu', function(event) { + //TODO: remove link to dataService in this generic plugin + var dataService = dScope.scope.dataService; + + if(dScope.task.model.raTask) { + dataService.selected_task_id = dScope.task.model.raTask.id; + } + + //search for already existing contextmenu element + if(dElement.find('#gantt-context-menu').length) { + //found, remove it, so we can create a fresh one + dElement.find('#gantt-context-menu')[0].remove(); + } + + //create contextmenu element + //with list of menu items, + //each with it's own action + var contextmenuElement = angular.element('<div id="gantt-context-menu"></div>'); + var ulElement = angular.element('<ul style="z-index:10000; position:fixed; top:initial; left:initial; display:block;" role="menu" class="dropdown-menu"></ul>'); + contextmenuElement.append(ulElement); + var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + //TODO: remove link to dataService in this generic plugin + dataService.copyTask(dScope.task.model.raTask); + closeContextMenu(); + }); + + var closeContextMenu = function() { + contextmenuElement.remove(); + angular.element(document).unbind('click', closeContextMenu); + }; + + //click anywhere to remove the contextmenu + angular.element(document).bind('click', closeContextMenu); + + //add contextmenu to clicked element + dElement.append(contextmenuElement); + + //prevent bubbling event upwards + return false; + }); + } + }); + + api.directives.on.destroy(scope, function(dName, dScope, dElement, dAttrs, dController) { + //for each destroyed ganttTask + if (dName === 'ganttTask') { + dElement.unbind('contextmenu'); + } + }); + } + }; + }]); +}()); + +angular.module('gantt.contextmenu.templates', []).run(['$templateCache', function($templateCache) { + +}]); + diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index 214ea165b3b..4c1655f2932 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -40,6 +40,7 @@ <script src="/static/app/controllers/gridcontroller.js"></script> <script src="/static/app/controllers/ganttresourcecontroller.js"></script> <script src="/static/app/controllers/ganttprojectcontroller.js"></script> + <script src="/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js"></script> <script src="/static/app/controllers/chartresourceusagecontroller.js"></script> </head> <body> @@ -124,6 +125,7 @@ </gantt-movable> <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> <gantt-dependencies enabled="true"></gantt-dependencies> + <gantt-contextmenu enabled="true"></gantt-contextmenu> </div> </div> </uib-tab> @@ -144,6 +146,7 @@ allow-resizing="true" allow-row-switching="false"></gantt-movable> <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> + <gantt-contextmenu enabled="true"></gantt-contextmenu> </div> </div> </uib-tab> -- GitLab From 8ff22deabd7cffe4f447d992dc320641bd7ac9e2 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 10 May 2016 13:29:57 +0000 Subject: [PATCH 167/933] Task #9349: expose mom base url via config from webservice depending on lofarenv. Use mom base url in mom links in webscheduler --- .../lib/static/app/controllers/datacontroller.js | 16 +++++++++++++++- .../lib/static/app/controllers/gridcontroller.js | 8 +++++--- .../ResourceAssignmentEditor/lib/webservice.py | 12 ++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index f851a446570..668013c4d13 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -29,6 +29,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.taskTimes = {}; self.resourceClaimTimes = {}; + self.config = {}; + self.selected_resource_id; self.selected_resourceGroup_id; self.selected_task_id; @@ -449,6 +451,17 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }); }; + self.getConfig = function() { + var defer = $q.defer(); + $http.get('/rest/config').success(function(result) { + self.config = result.config; + defer.resolve(); + }); + + return defer.promise; + }; + + //start with local client time //lofarTime will be synced with server, //because local machine might have incorrect clock @@ -475,7 +488,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.lastUpdateChangeNumber = result.mostRecentChangeNumber; } - var nrOfItemsToLoad = 6; + var nrOfItemsToLoad = 7; var nrOfItemsLoaded = 0; var checkInitialLoadCompleteness = function() { nrOfItemsLoaded += 1; @@ -484,6 +497,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } }; + self.getConfig().then(checkInitialLoadCompleteness); self.getMoMProjects().then(checkInitialLoadCompleteness); self.getTaskTypes().then(checkInitialLoadCompleteness); self.getTaskStatusTypes().then(checkInitialLoadCompleteness); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index b1926986e92..7d2aa29e73c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -18,7 +18,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid { field: 'project_name', displayName:'Project', enableCellEdit: false, - cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}">{{row.entity[col.field]}}</a>', + cellTemplate:'<a target="_blank" href="{{row.grid.appScope.dataService.config.mom_base_url}}/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}">{{row.entity[col.field]}}</a>', width: '15%', filter: { type: uiGridConstants.filter.SELECT, @@ -28,7 +28,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid { field: 'mom_id', displayName: 'MoM ID', enableCellEdit: false, - cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}">{{row.entity[col.field]}}</a>', + cellTemplate:'<a target="_blank" href="{{row.grid.appScope.dataService.config.mom_base_url}}/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}">{{row.entity[col.field]}}</a>', width: '7.5%' }, { field: 'otdb_id', @@ -152,7 +152,9 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid starttime: task.starttime, endtime: task.endtime, status: task.status, - type: task.type + type: task.type, + project_mom2object_id: task.project_mom2object_id, + mom2object_id: task.mom2object_id }; tasks.push(gridTask); } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index f8e2a1f2183..8d242a136cb 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -106,6 +106,18 @@ def index(): '''Serves the ResourceAssignmentEditor's index page''' return render_template('index.html', title='Scheduler') +@app.route('/rest/config') +@gzipped +def config(): + config = {'mom_base_url':''} + + if isProductionEnvironment(): + config['mom_base_url'] = 'https://lofar.astron.nl/mom3' + elif isTestEnvironment(): + config['mom_base_url'] = 'http://lofartest.control.lofar:8080/mom3' + + return jsonify({'config': config}) + @app.route('/rest/resources') @gzipped def resources(): -- GitLab From e0fe75db0cdc11193c0aef6bd09b6d65c59b96af Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 11 May 2016 06:18:12 +0000 Subject: [PATCH 168/933] Task #8437: Split up bashrc in Dockerfile, and replaced -e LUSER=$UID to the Docker-native -u $UID --- .gitattributes | 4 ++ .../framework/lofarpipe/support/utilities.py | 3 ++ .../recipes/sip/pipeline.cfg.CEP4.tmpl | 6 +-- Docker/lofar-base/Dockerfile.tmpl | 32 +++++-------- Docker/lofar-base/bashrc | 18 ++----- Docker/lofar-base/bashrc.d/00-casacore | 3 ++ Docker/lofar-base/bashrc.d/00-qpid | 2 + Docker/lofar-base/bashrc.d/01-python-casacore | 2 + Docker/lofar-base/bashrc.d/50-lofar | 3 ++ Docker/lofar-base/chuser.sh | 37 ++++++--------- MAC/Services/src/PipelineControl.py | 47 +++++++++++++++++-- .../GPUProc/src/scripts/runObservation.sh | 2 +- 12 files changed, 95 insertions(+), 64 deletions(-) create mode 100644 Docker/lofar-base/bashrc.d/00-casacore create mode 100644 Docker/lofar-base/bashrc.d/00-qpid create mode 100644 Docker/lofar-base/bashrc.d/01-python-casacore create mode 100644 Docker/lofar-base/bashrc.d/50-lofar diff --git a/.gitattributes b/.gitattributes index 0dd7521feb9..dfc7754cbe3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2352,6 +2352,10 @@ CMake/variants/variants.sharkbay -text Docker/docker-build-all.sh -text Docker/lofar-base/Dockerfile.tmpl -text Docker/lofar-base/bashrc -text +Docker/lofar-base/bashrc.d/00-casacore -text +Docker/lofar-base/bashrc.d/00-qpid -text +Docker/lofar-base/bashrc.d/01-python-casacore -text +Docker/lofar-base/bashrc.d/50-lofar -text Docker/lofar-base/chuser.sh -text Docker/lofar-outputproc/Dockerfile.tmpl -text Docker/lofar-pipeline/Dockerfile.tmpl -text diff --git a/CEP/Pipeline/framework/lofarpipe/support/utilities.py b/CEP/Pipeline/framework/lofarpipe/support/utilities.py index 37c883f128c..c445578c03c 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/utilities.py +++ b/CEP/Pipeline/framework/lofarpipe/support/utilities.py @@ -231,6 +231,9 @@ def spawn_process(cmd, logger, cwd = None, env = None, max_tries = 2, max_timeou logger.error("support.utilities.spawn_process is DEPRECATED. Please use support.subprocessgroup.SubProcessGroup") + # Make sure the working directory exists. + create_directory(cwd); + trycounter = 0 while True: logger.debug( diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 8465c065719..7d644173706 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -47,7 +47,7 @@ globalfs = yes # (SLURM config files, Munge keys, correct Munge user ID, munged). # # Throughout this path, we maintain: -# * userid, which is set to the user that started the master script container (-e LUSER=`id -u`) +# * userid, which is set to the user that started the master script container (-u `id -u`) # * environment (sudo -p, docker -e K=V, etc) # * pseudo-tty to prevent buffering logs (ssh -tt, docker -t) @@ -60,11 +60,11 @@ globalfs = yes # Starts the container on the worker node, with pretty much the same parameters as the master container: # # --rm: don't linger resources when the container exits -# -e LUSER=(uid): set the user to run as (the calling user) +# -u (uid): set the user to run as (the calling user) # -h {host}: set container hostname (for easier debugging) (doesnt work yet, using --net=host instead) # -v /data:/data: map CEP4's global fs. This includes %(working_directory)s and %(runtime_directory)s so those do not have to be mapped as well. # # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -e LUSER={uid} -v /data:/data --net=host {docker_env} lofar-pipeline:${LOFAR_TAG} {command} +cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -u {uid} -v /data:/data --net=host {docker_env} lofar-pipeline:${LOFAR_TAG} {command} diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index ef26437fa5d..4c306034bee 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -37,19 +37,15 @@ ENV J=6 # Base and runtime dependencies # #RUN sed -i 's/archive.ubuntu.com/osmirror.rug.nl/' /etc/apt/sources.list -RUN apt-get update && apt-get upgrade -y -RUN apt-get install -y sudo python2.7 libpython2.7 && \ +RUN apt-get update && \ + apt-get install -y sudo python2.7 libpython2.7 && \ apt-get install -y libblas3 liblapacke python-numpy libcfitsio3 libwcs4 libfftw3-bin libhdf5-7 libboost-python${BOOST_VERSION}.0 && \ apt-get install -y nano # # setup-account # -RUN (getent group sudo &>/dev/null || groupadd sudo) && \ - echo "useradd -m ${USERADD_FLAGS} ${USER}" && \ - useradd -m -u ${UID} ${USER} && \ - usermod -a -G sudo ${USER} && \ - echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \ +RUN echo 'ALL ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \ sed -i 's/requiretty/!requiretty/g' /etc/sudoers # @@ -64,7 +60,7 @@ USER ${USER} # Casacore # ******************* # -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ +RUN sudo apt-get update && sudo apt-get install -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/casacore/build && \ mkdir -p ${INSTALLDIR}/casacore/data && \ cd ${INSTALLDIR}/casacore && git clone https://github.com/casacore/casacore.git src && \ @@ -84,7 +80,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y wg # Casarest # ******************* # -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ +RUN sudo apt-get update && sudo apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/casarest/build && \ cd ${INSTALLDIR}/casarest && git clone https://github.com/casacore/casarest.git src && \ if [ "${CASAREST_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casarest/src && git checkout tags/v${CASAREST_VERSION}; fi && \ @@ -101,7 +97,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y gi # Pyrap # ******************* # -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y git make g++ python-setuptools libboost-python-dev libcfitsio3-dev wcslib-dev && \ +RUN sudo apt-get update && sudo apt-get install -y git make g++ python-setuptools libboost-python-dev libcfitsio3-dev wcslib-dev && \ mkdir ${INSTALLDIR}/python-casacore && \ cd ${INSTALLDIR}/python-casacore && git clone https://github.com/casacore/python-casacore && \ if [ "$PYTHON_CASACORE_VERSION" != "latest" ]; then cd ${INSTALLDIR}/python-casacore/python-casacore && git checkout tags/v${PYTHON_CASACORE_VERSION}; fi && \ @@ -133,11 +129,11 @@ store-plaintext-passwords = yes \n\ # Run-time dependencies # QPID daemon legacy store would require: libaio1 libdb5.1++ -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y sasl2-bin libuuid1 libnss3 libnspr4 xqilla libboost-program-options${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 +RUN sudo apt-get update && sudo apt-get install -y sasl2-bin libuuid1 libnss3 libnspr4 xqilla libboost-program-options${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 # Install # QPID daemon legacy store would require: libaio-dev libdb5.1++-dev -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y subversion swig ruby ruby-dev python-dev libsasl2-dev pkg-config cmake libtool uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man fakeroot build-essential debhelper libsslcommon2-dev libxqilla-dev python-setuptools libboost-program-options${BOOST_VERSION}-dev libboost-filesystem${BOOST_VERSION}-dev && \ +RUN sudo apt-get update && sudo apt-get install -y subversion swig ruby ruby-dev python-dev libsasl2-dev pkg-config cmake libtool uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man fakeroot build-essential debhelper libsslcommon2-dev libxqilla-dev python-setuptools libboost-program-options${BOOST_VERSION}-dev libboost-filesystem${BOOST_VERSION}-dev && \ mkdir /opt/qpid && \ svn --non-interactive -q --username lofar-guest --password lofar-guest co ${LOFAR_BRANCH_URL}/LCS/MessageBus/qpid/ /opt/qpid; \ /opt/qpid/local/sbin/build_qpid && \ @@ -150,7 +146,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y su # DAL # ******************* # -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y git cmake g++ swig python-dev libhdf5-dev && \ +RUN sudo apt-get update && sudo apt-get install -y git cmake g++ swig python-dev libhdf5-dev && \ mkdir ${INSTALLDIR}/DAL && \ cd ${INSTALLDIR}/DAL && git clone https://github.com/nextgen-astrodata/DAL.git src && \ mkdir ${INSTALLDIR}/DAL/build && cd ${INSTALLDIR}/DAL/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/DAL ../src && \ @@ -161,15 +157,9 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y gi sudo apt-get purge -y git cmake g++ swig python-dev libhdf5-dev && \ sudo apt-get autoremove -y -# -# config -# -COPY bashrc /opt/bashrc - # # entry # -COPY chuser.sh /usr/local/bin/chuser.sh -WORKDIR /home/${USER} -ENTRYPOINT ["sudo","-E","/usr/local/bin/chuser.sh"] +COPY ["bashrc", "bashrc.d", "${INSTALLDIR}/"] +ENTRYPOINT ["/usr/local/bin/chuser.sh"] diff --git a/Docker/lofar-base/bashrc b/Docker/lofar-base/bashrc index ef75d16a73f..b73fc3d6d3d 100644 --- a/Docker/lofar-base/bashrc +++ b/Docker/lofar-base/bashrc @@ -1,15 +1,7 @@ #!/bin/bash -# lofar -[ -r ${INSTALLDIR}/lofar/lofarinit.sh ] && source ${INSTALLDIR}/lofar/lofarinit.sh -export PATH PYTHONPATH LD_LIBRARY_PATH LOFARROOT - -# qpid -source ${INSTALLDIR}/qpid/.profile - -# python-casacore -export PYTHONPATH=${PYTHONPATH}:${INSTALLDIR}/python-casacore/lib/python2.7/site-packages - -# casacore -export PATH=${PATH}:${INSTALLDIR}/casacore/bin -export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${INSTALLDIR}/casacore/lib +# Read all profiles +shopt -s nullglob +for rc in ${INSTALLDIR}/bashrc.d/*; do + source $rc +done diff --git a/Docker/lofar-base/bashrc.d/00-casacore b/Docker/lofar-base/bashrc.d/00-casacore new file mode 100644 index 00000000000..dbda097bf8a --- /dev/null +++ b/Docker/lofar-base/bashrc.d/00-casacore @@ -0,0 +1,3 @@ +#!/bin/bash +export PATH=${PATH}:${INSTALLDIR}/casacore/bin +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${INSTALLDIR}/casacore/lib diff --git a/Docker/lofar-base/bashrc.d/00-qpid b/Docker/lofar-base/bashrc.d/00-qpid new file mode 100644 index 00000000000..bf491f5fbcf --- /dev/null +++ b/Docker/lofar-base/bashrc.d/00-qpid @@ -0,0 +1,2 @@ +#!/bin/bash +source ${INSTALLDIR}/qpid/.profile diff --git a/Docker/lofar-base/bashrc.d/01-python-casacore b/Docker/lofar-base/bashrc.d/01-python-casacore new file mode 100644 index 00000000000..d58385ad518 --- /dev/null +++ b/Docker/lofar-base/bashrc.d/01-python-casacore @@ -0,0 +1,2 @@ +#!/bin/bash +export PYTHONPATH=${PYTHONPATH}:${INSTALLDIR}/python-casacore/lib/python2.7/site-packages diff --git a/Docker/lofar-base/bashrc.d/50-lofar b/Docker/lofar-base/bashrc.d/50-lofar new file mode 100644 index 00000000000..10ba02c20fd --- /dev/null +++ b/Docker/lofar-base/bashrc.d/50-lofar @@ -0,0 +1,3 @@ +#!/bin/bash +[ -r ${INSTALLDIR}/lofar/lofarinit.sh ] && source ${INSTALLDIR}/lofar/lofarinit.sh +export PATH PYTHONPATH LD_LIBRARY_PATH LOFARROOT diff --git a/Docker/lofar-base/chuser.sh b/Docker/lofar-base/chuser.sh index 76ba3ca7eca..ca90f0e5b1f 100755 --- a/Docker/lofar-base/chuser.sh +++ b/Docker/lofar-base/chuser.sh @@ -1,29 +1,20 @@ #!/usr/bin/env bash -# Fetch the user name used in this container -export USER=${SUDO_USER} - -if [ -n "${LUSER}" ]; then - if [ -z "${LGROUP}" ]; then - LGROUP=${LUSER} - fi +# Configrue user +export USER=${UID} +export HOME=/home/${USER} +mkdir -p $HOME && cd $HOME - OLDID=`id -u ${USER}` +# Add user to system +fgrep -q ":x:${UID}:" /etc/passwd || echo "${USER}:x:${UID}:${UID}::${HOME}:/bin/bash" >> /etc/passwd +fgrep -q ":x:${UID}:" /etc/group || echo "${USER}:x:${UID}:" >> /etc/group - # Replace USER by LUSER:LGROUP - sed -i -e "s/${USER}:x:[0-9]\+:[0-9]\+/${USER}:x:${LUSER}:${LGROUP}/g" /etc/passwd - sed -i -e "s/${USER}:x:[0-9]\+:/${USER}:x:${LGROUP}:/g" /etc/group +# Set the environment +[ -e /opt/bashrc ] && source /opt/bashrc - # Set ownership of home dir to new user - chown --from=${OLDID} -R ${LUSER}:${LGROUP} ${HOME} +# Run the requested command +if [ -z "$*" ]; then + exec /bin/bash +else + exec "$@" fi - -# Update environment for updated user -export HOME=/home/${USER} - -# Import bashrc for software in /opt -source /opt/bashrc - -# Use exec to make sure we propagate signals -# `env' is needed to propagate PATH variables through sudo. -exec sudo -u ${USER} -E env "PATH=$PATH" "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" "PYTHONPATH=$PYTHONPATH" "$@" diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index bd2f4dd5746..9dddc258c17 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -101,6 +101,39 @@ def runCommand(cmdline, input=None): """ Prefix that is common to all parset keys, depending on the exact source. """ PARSET_PREFIX="ObsSW." +class ProcessingClusterTask(object): + def __init__(self): + self.name = "" + self.partition = "" + + self.maxDuration = 0 # seconds + + self.nrCoresPerTask = 1 + self.nrTasks = 1 + self.minMemPerTask = 1024 # MB + +class CEP2Task(ProcessingClusterTask): + def __init__(self): + super(CEP2Task).__init__(self) + + self.name = "CEP2" + +class CEP4Task(ProcessingClusterTask): + def __init__(self): + super(CEP4Task).__init__(self) + + self.name = "CEP4" + self.partition = "cpu" + + self.nrCoresPerTask = 8 + self.minMemPerTask = self.nrCoresPerTask * 10 * 1024 # ~240 GB free for 24 cores -> ~10GB/core + +clusterTaskFactory = { + "": CEP2Task, + "CEP2": CEP2Task, + "CEP4": CEP4Task, +} + class Parset(dict): def predecessors(self): """ Extract the list of predecessor obs IDs from the given parset. """ @@ -120,7 +153,15 @@ class Parset(dict): return not self.isObservation() def processingCluster(self): - return self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] or "CEP2" + clusterName = self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] or "CEP2" + task = clusterTaskFactory[clusterName]() + + # override defaults if provided by parset + task.partition = self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterPartition"] or task.partition + try: + task.nrCoresPerTask = int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfCoresPerTask"] or task.nrCoresPerTask + except ValueError: + pass def dockerTag(self): # Return the version set in the parset, and fall back to our own version. @@ -341,7 +382,7 @@ class PipelineControl(OTDBBusListener): "docker run --rm" " --net=host" " -v /data:/data" - " -e LUSER=$UID" + " -u $UID" " -e LOFARENV={lofarenv}" " -v $HOME/.ssh:/home/lofar/.ssh:ro" " -e SLURM_JOB_ID=$SLURM_JOB_ID" @@ -365,7 +406,7 @@ class PipelineControl(OTDBBusListener): "docker run --rm" " --net=host" - " -e LUSER=$UID" + " -u $UID" " -e LOFARENV={lofarenv}" " lofar-pipeline:{tag}" " pipelineAborted.sh -o {obsid} -B {status_bus}" diff --git a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh index d6c20981d5e..5d94b7236f0 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh @@ -342,7 +342,7 @@ OUTPUTPROC_CMDLINE="$OUTPUTPROC_VARS $OUTPUT_PROC_EXECUTABLE $OBSERVATIONID" if $DOCKER; then TAG="`echo '${LOFAR_TAG}' | docker-template`" - OUTPUTPROC_CMDLINE="docker run --rm --privileged -e LUSER=`id -u $SSH_USER_NAME` --net=host -v $GLOBALFS_DIR:$GLOBALFS_DIR lofar-outputproc:$TAG bash -c \"$OUTPUTPROC_CMDLINE\"" + OUTPUTPROC_CMDLINE="docker run --rm --privileged -u `id -u $SSH_USER_NAME` --net=host -v $GLOBALFS_DIR:$GLOBALFS_DIR lofar-outputproc:$TAG bash -c \"$OUTPUTPROC_CMDLINE\"" fi echo "[outputProc] command line = $OUTPUTPROC_CMDLINE" -- GitLab From f0734e68faf2c0ef864fa606b56336b3bb76d14f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 11 May 2016 06:29:39 +0000 Subject: [PATCH 169/933] Task #8437: Allow username to be specified on Docker command line --- Docker/lofar-base/chuser.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Docker/lofar-base/chuser.sh b/Docker/lofar-base/chuser.sh index ca90f0e5b1f..fb744213c34 100755 --- a/Docker/lofar-base/chuser.sh +++ b/Docker/lofar-base/chuser.sh @@ -1,7 +1,11 @@ #!/usr/bin/env bash # Configrue user -export USER=${UID} +if [ -z "${USER}" ]; then + export USER=${UID} +fi + +# Create home directory export HOME=/home/${USER} mkdir -p $HOME && cd $HOME -- GitLab From eb513be273f83832ba4cea4dd11793057100bdbf Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 11 May 2016 09:08:52 +0000 Subject: [PATCH 170/933] Task #9349: typo fix --- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 150d6541f8f..6cac0d6af79 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -137,7 +137,7 @@ class ResourceAssigner(): return if status != 'prescheduled': - logger.info('skipping resource assignment for CEP4 task otdb_id=%s because status=%s', (otdb_id, status)) + logger.info('skipping resource assignment for CEP4 task otdb_id=%s because status=%s' % (otdb_id, status)) return needed = self.getNeededResouces(specification_tree) -- GitLab From f65a66ec50665b553246ce1f8030acb936950a47 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 11 May 2016 09:09:38 +0000 Subject: [PATCH 171/933] Task #9349: automatically jump to selected task in list view --- .../lib/static/app/controllers/gridcontroller.js | 1 + .../ResourceAssignmentEditor/lib/templates/index.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 7d2aa29e73c..f5b0403c631 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -204,6 +204,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid if(taskIdx > -1) { $scope.gridApi.selection.selectRow($scope.gridOptions.data[taskIdx]); + $scope.gridApi.core.scrollTo($scope.gridOptions.data[taskIdx], null); } }); } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index 4c1655f2932..51894aaf7f4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -93,7 +93,7 @@ <uib-tabset margin-top='10px'> <uib-tab heading="Tasks" index='0' active='true'> <div ng-controller="GridController as gridCtrl"> - <div id="grid" ui-grid="gridOptions" ui-grid-edit ui-grid-selection ui-grid-cellnav ui-grid-resize-columns class="grid"> + <div id="grid" ui-grid="gridOptions" ui-grid-edit ui-grid-selection ui-grid-cellNav ui-grid-resize-columns class="grid"> </div> </div> </uib-tab> -- GitLab From 70324b806b3da6716560b76f5fd618c4273cd24b Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 12 May 2016 06:39:42 +0000 Subject: [PATCH 172/933] Task #8437: treeId -> otdbId, and updated copyright --- MAC/Services/src/PipelineControl.py | 39 +++++++++++++-------------- MAC/Services/src/pipelinecontrol | 3 +-- MAC/Services/test/tPipelineControl.py | 5 ++-- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 9dddc258c17..96cda4813ff 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -#coding: iso-8859-15 # -# Copyright (C) 2015 +# Copyright (C) 2016 # ASTRON (Netherlands Institute for Radio Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands # @@ -169,9 +168,9 @@ class Parset(dict): runCommand("docker-template", "${LOFAR_TAG}")) def slurmJobName(self): - return str(self.treeId()) + return str(self.otdbId()) - def treeId(self): + def otdbId(self): return int(self[PARSET_PREFIX + "Observation.otdbID"]) class Slurm(object): @@ -277,21 +276,21 @@ class PipelineControl(OTDBBusListener): def _slurmJobIds(self, parsets): return [self.slurm.jobid(p.slurmJobName()) for p in parsets] - def _getParset(self, treeId): - return Parset(self.parset_rpc( OtdbID=treeId, timeout=10 )[0]) + def _getParset(self, otdbId): + return Parset(self.parset_rpc( OtdbID=otdbId, timeout=10 )[0]) def _getPredecessorParsets(self, parset): - treeIds = parset.predecessors() + otdbIds = parset.predecessors() - logger.info("Obtaining predecessor parsets %s", treeIds) + logger.info("Obtaining predecessor parsets %s", otdbIds) - return [self._getParset(treeId) for treeId in treeIds] + return [self._getParset(otdbId) for otdbId in otdbIds] - def onObservationAborted(self, treeId, modificationTime): - logger.info("***** STOP Tree ID %s *****", treeId) + def onObservationAborted(self, otdbId, modificationTime): + logger.info("***** STOP Otdb ID %s *****", otdbId) # Request the parset - parset = self._getParset(treeId) + parset = self._getParset(otdbId) if not self._shouldHandle(parset): return @@ -323,11 +322,11 @@ class PipelineControl(OTDBBusListener): return result - def onObservationScheduled(self, treeId, modificationTime): - logger.info("***** QUEUE Tree ID %s *****", treeId) + def onObservationScheduled(self, otdbId, modificationTime): + logger.info("***** QUEUE Otdb ID %s *****", otdbId) # Request the parset - parset = self._getParset(treeId) + parset = self._getParset(otdbId) if not self._shouldHandle(parset): return @@ -363,8 +362,8 @@ class PipelineControl(OTDBBusListener): "--nodes=50", # Define better places to write the output - os.path.expandvars("--error=$LOFARROOT/var/log/docker-startPython-%s.stderr" % (treeId,)), - os.path.expandvars("--output=$LOFARROOT/var/log/docker-startPython-%s.log" % (treeId,)), + os.path.expandvars("--error=$LOFARROOT/var/log/docker-startPython-%s.stderr" % (otdbId,)), + os.path.expandvars("--output=$LOFARROOT/var/log/docker-startPython-%s.log" % (otdbId,)), ] min_starttime = self._minStartTime([x for x in preparsets if x.isObservation()]) @@ -390,7 +389,7 @@ class PipelineControl(OTDBBusListener): " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -B {status_bus}" .format( lofarenv = os.environ.get("LOFARENV", ""), - obsid = treeId, + obsid = otdbId, tag = parset.dockerTag(), cluster = parset.processingCluster(), status_bus = self.setStatus_busname, @@ -412,7 +411,7 @@ class PipelineControl(OTDBBusListener): " pipelineAborted.sh -o {obsid} -B {status_bus}" .format( lofarenv = os.environ.get("LOFARENV", ""), - obsid = treeId, + obsid = otdbId, tag = parset.dockerTag(), status_bus = self.setStatus_busname, ), @@ -436,7 +435,7 @@ class PipelineControl(OTDBBusListener): # TODO: How to avoid race condition with runPipeline.sh setting the status to STARTED # when the SLURM job starts running? logger.info("Setting status to QUEUED") - self._setStatus(treeId, "queued") + self._setStatus(otdbId, "queued") logger.info("Pipeline processed.") diff --git a/MAC/Services/src/pipelinecontrol b/MAC/Services/src/pipelinecontrol index 8962d1b4f9e..2ad9e0fde0b 100644 --- a/MAC/Services/src/pipelinecontrol +++ b/MAC/Services/src/pipelinecontrol @@ -1,7 +1,6 @@ #!/usr/bin/env python -#coding: iso-8859-15 # -# Copyright (C) 2015 +# Copyright (C) 2016 # ASTRON (Netherlands Institute for Radio Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands # diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index c3899f508ec..aecd19fc850 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -# Be able to find service python file import sys from lofar.mac.PipelineControl import * @@ -211,7 +210,7 @@ class TestPipelineControl(unittest.TestCase): self.assertTrue(self.trigger.wait()) # Verify message - self.assertEqual(self.trigger.args[0], 3) # treeId + self.assertEqual(self.trigger.args[0], 3) # otdbId # Check if job was scheduled self.assertIn("3", ps.slurm.scheduled_jobs) @@ -233,7 +232,7 @@ class TestPipelineControl(unittest.TestCase): self.assertTrue(self.trigger.wait()) # Verify message - self.assertEqual(self.trigger.args[0], 1) # treeId + self.assertEqual(self.trigger.args[0], 1) # otdbId # Check if job was scheduled self.assertIn("1", ps.slurm.scheduled_jobs) -- GitLab From 0da343fc3f073c8932c3c5636c005e0f1f299a1d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 12 May 2016 06:42:05 +0000 Subject: [PATCH 173/933] Task #8437: Also capture stderr --- MAC/Services/src/PipelineControl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 96cda4813ff..c892dc53f1a 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -82,6 +82,7 @@ def runCommand(cmdline, input=None): cmdline, stdin=subprocess.PIPE if input else file("/dev/null"), stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, shell=True, universal_newlines=True ) -- GitLab From 1403d8a5bebd6204f0f413b783d06798a4fda8a2 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 12 May 2016 06:43:49 +0000 Subject: [PATCH 174/933] Task #8437: Bugfix: Send feedback back to MAC --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 7d644173706..00215f0ac8e 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -33,7 +33,7 @@ xml_stat_file = %(runtime_directory)s/%(job_name)s/logs/%(start_time)s/statistic # Valid options: # messagebus Send feedback and status using LCS/MessageBus # none Do NOT send feedback and status -method = none +method = messagebus [remote] method = custom_cmdline -- GitLab From 08e9cf7902a370f48ce86a3e370d493acaf5d0fc Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 12 May 2016 08:43:56 +0000 Subject: [PATCH 175/933] Task #8725: handle stalled transfers and early exit of globus-url-copy. --- LTA/LTAIngest/ltacp.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index 831ad4908a8..26eff55b800 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -63,6 +63,7 @@ def convert_surl_to_turl(surl): turl = turl.replace("srm://lofar-srm.fz-juelich.de", "gsiftp://dcachepool%d.fz-juelich.de:2811" % (random.randint(9, 16),), 1) turl = turl.replace("srm://srm.target.rug.nl:8444","gsiftp://gridftp02.target.rug.nl/target/gpfs2/lofar/home/srm",1) turl = turl.replace("srm://srm.target.rug.nl","gsiftp://gridftp02.target.rug.nl/target/gpfs2/lofar/home/srm",1) + turl = turl.replace("srm://lta-head.lofar.psnc.pl:8443", "gsiftp://door0%d.lofar.psnc.pl:2811" % (random.randint(1, 2),), 1) return turl def createNetCatCmd(user, host): @@ -274,15 +275,21 @@ class LtaCp: # wait and poll for progress while all processes are runnning while len([p for p in self.started_procs.keys() if p.poll() is not None]) == 0: try: + current_progress_time = datetime.utcnow() + elapsed_secs_since_prev = timedelta_total_seconds(current_progress_time - prev_progress_time) + + if elapsed_secs_since_prev > 120: + logger.error('ltacp %s: transfer stalled. cancelling...' % self.logId) + self.cleanup() + break + # read and process md5a32bc stdout lines to create progress messages nextline = p_md5a32bc.stdout.readline().strip() if len(nextline) > 0: total_bytes_transfered = int(nextline.split()[0].strip()) percentage_done = (100.0*float(total_bytes_transfered))/float(estimated_tar_size) - current_progress_time = datetime.utcnow() elapsed_secs_since_start = timedelta_total_seconds(current_progress_time - transfer_start_time) - elapsed_secs_since_prev = timedelta_total_seconds(current_progress_time - prev_progress_time) if percentage_done > 0 and elapsed_secs_since_start > 0 and elapsed_secs_since_prev > 0: avg_speed = total_bytes_transfered / elapsed_secs_since_start current_bytes_transfered = total_bytes_transfered - prev_bytes_transfered @@ -307,6 +314,12 @@ class LtaCp: except Exception as e: logger.error('ltacp %s: %s' % (self.logId, str(e))) + logger.info('ltacp %s: waiting for transfer via globus-url-copy to LTA to finish...' % self.logId) + output_data_out = p_data_out.communicate() + if p_data_out.returncode != 0: + raise LtacpException('ltacp %s: transfer via globus-url-copy to LTA failed: %s' % (self.logId, output_data_out[1])) + logger.info('ltacp %s: data transfer via globus-url-copy to LTA complete.' % self.logId) + logger.info('ltacp %s: waiting for remote data transfer to finish...' % self.logId) output_remote_data = p_remote_data.communicate() if p_remote_data.returncode != 0: @@ -349,12 +362,6 @@ class LtaCp: raise LtacpException('md5 checksum reported by client (%s) does not match local checksum of incoming data stream (%s)' % (self.logId, md5_checksum_remote, md5_checksum_local)) logger.info('ltacp %s: remote and local md5 checksums are equal: %s' % (self.logId, md5_checksum_local,)) - logger.info('ltacp %s: waiting for transfer via globus-url-copy to LTA to finish...' % self.logId) - output_data_out = p_data_out.communicate() - if p_data_out.returncode != 0: - raise LtacpException('ltacp %s: transfer via globus-url-copy to LTA failed: %s' % (self.logId, output_data_out[1])) - logger.info('ltacp %s: data transfer via globus-url-copy to LTA complete.' % self.logId) - logger.info('ltacp %s: fetching adler32 checksum from LTA...' % self.logId) srm_ok, srm_file_size, srm_a32_checksum = get_srm_size_and_a32_checksum(self.dst_surl) -- GitLab From 32e0d614a4d62d1681e54cb689b507d3f77a3dc7 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 12 May 2016 08:51:15 +0000 Subject: [PATCH 176/933] Task #8725: handle stalled transfers via exception --- LTA/LTAIngest/ltacp.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index 26eff55b800..035c2c1fc87 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -279,9 +279,7 @@ class LtaCp: elapsed_secs_since_prev = timedelta_total_seconds(current_progress_time - prev_progress_time) if elapsed_secs_since_prev > 120: - logger.error('ltacp %s: transfer stalled. cancelling...' % self.logId) - self.cleanup() - break + raise LtacpException('ltacp %s: transfer stalled.' % (self.logId)) # read and process md5a32bc stdout lines to create progress messages nextline = p_md5a32bc.stdout.readline().strip() -- GitLab From f1f4977b8d2a10eac4d6a7cf92eb2b0e0c79baa9 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 12 May 2016 12:29:32 +0000 Subject: [PATCH 177/933] Task #9349: context menu for tasklist. improved handling of (left/right) click events outside contextmenu --- .../static/app/controllers/gridcontroller.js | 72 +++++++++++++++++++ .../angular-gantt-contextmenu-plugin.js | 16 +++-- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index f5b0403c631..2362d3486e2 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -85,6 +85,8 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid gridMenuShowHideColumns: false, columnDefs: $scope.columns, data: [], +// rowTemplate: "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.uid\" ui-grid-one-bind-id-grid=\"rowRenderIndex + '-' + col.uid + '-cell'\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" role=\"{{col.isRowHeader ? 'rowheader' : 'gridcell'}}\" ui-grid-cell></div>" + rowTemplate: "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.uid\" ui-grid-one-bind-id-grid=\"rowRenderIndex + '-' + col.uid + '-cell'\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" role=\"{{col.isRowHeader ? 'rowheader' : 'gridcell'}}\" ui-grid-cell context-menu>", onRegisterApi: function(gridApi){ $scope.gridApi = gridApi; @@ -209,3 +211,73 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid }); } ]); + +gridControllerMod.directive('contextMenu', ['$document', function($document) { + return { + restrict: 'A', + scope: { + }, + link: function($scope, $element, $attrs) { + function handleContextMenuEvent(event) { + //pragmatic 'hard-coded' way of getting the dataService and the rowEntity via scope tree. + var dataService = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.dataService; + var rowEntity = $scope.$parent.$parent.$parent.row.entity; + + if(!dataService || !rowEntity) + return true; + + var taskId = rowEntity.id; + var task = dataService.taskDict[taskId]; + dataService.selected_task_id = taskId; + + //search for already existing contextmenu element + while($document.find('#grid-context-menu').length) { + //found, remove it, so we can create a fresh one + $document.find('#grid-context-menu')[0].remove(); + + //unbind document close event handlers + angular.element($document).unbind('click', closeContextMenu); + angular.element($document).unbind('contextmenu', closeContextMenu); + } + + //create contextmenu element + //with list of menu items, + //each with it's own action + var contextmenuElement = angular.element('<div id="grid-context-menu"></div>'); + var ulElement = angular.element('<ul style="z-index:10000; position:absolute; top:initial; left:initial; display:block;" role="menu" class="dropdown-menu"></ul>'); + contextmenuElement.append(ulElement); + var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + dataService.copyTask(task); + closeContextMenu(); + }); + + var closeContextMenu = function(cme) { + contextmenuElement.remove(); + + //unbind document close event handlers + angular.element($document).unbind('click', closeContextMenu); + angular.element($document).unbind('contextmenu', closeContextMenu); + }; + + //click anywhere to remove the contextmenu + angular.element($document).bind('click', closeContextMenu); + angular.element($document).bind('contextmenu', closeContextMenu); + + //add contextmenu to clicked element + $element.append(contextmenuElement); + + //prevent bubbling event upwards + return false; + } + + $element.bind('contextmenu', handleContextMenuEvent); + + $scope.$on('$destroy', function() { + console.log('destroy'); + $element.unbind('contextmenu', handleContextMenuEvent); + }); + } + }; + }]); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index 19280914c28..daf48849c7b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -33,9 +33,13 @@ } //search for already existing contextmenu element - if(dElement.find('#gantt-context-menu').length) { + while($document.find('#gantt-context-menu').length) { //found, remove it, so we can create a fresh one - dElement.find('#gantt-context-menu')[0].remove(); + $document.find('#gantt-context-menu')[0].remove(); + + //unbind document close event handlers + angular.element($document).unbind('click', closeContextMenu); + angular.element($document).unbind('contextmenu', closeContextMenu); } //create contextmenu element @@ -54,11 +58,15 @@ var closeContextMenu = function() { contextmenuElement.remove(); - angular.element(document).unbind('click', closeContextMenu); + + //unbind document close event handlers + angular.element($document).unbind('click', closeContextMenu); + angular.element($document).unbind('contextmenu', closeContextMenu); }; //click anywhere to remove the contextmenu - angular.element(document).bind('click', closeContextMenu); + angular.element($document).bind('click', closeContextMenu); + angular.element($document).bind('contextmenu', closeContextMenu); //add contextmenu to clicked element dElement.append(contextmenuElement); -- GitLab From 5324132272bc28d809c7356a736738c137355060 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 12 May 2016 12:52:59 +0000 Subject: [PATCH 178/933] Task #9349: Remove existing task with same mom_id if already present upon insertion --- SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 75d70775208..c922b852d3c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -1170,10 +1170,17 @@ class RADatabase: ''' Insert a new specification and task in one transaction. Removes existing task with same otdb_id if present in the same transaction. + Removes existing task with same mom_id if present in the same transaction. ''' try: task = self.getTask(otdb_id=otdb_id) + if task: + # delete old specification, task, and resource claims using cascaded delete + self.deleteSpecification(task['specification_id'], False) + + task = self.getTask(mom_id=mom_id) + if task: # delete old specification, task, and resource claims using cascaded delete self.deleteSpecification(task['specification_id'], False) -- GitLab From b89ce2ee1798c20d8398ff5964b28d3b3e6d9a58 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 12 May 2016 13:16:14 +0000 Subject: [PATCH 179/933] Task #9349: made mom_id and otdb_id in task table unique --- .../ResourceAssignmentDatabase/sql/create_database.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql index 4fd10e07875..985594488bf 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql @@ -141,8 +141,8 @@ ALTER TABLE resource_allocation.specification CREATE TABLE resource_allocation.task ( id serial NOT NULL, - mom_id integer, - otdb_id integer, + mom_id integer UNIQUE, + otdb_id integer UNIQUE, status_id integer NOT NULL REFERENCES resource_allocation.task_status DEFERRABLE INITIALLY IMMEDIATE, type_id integer NOT NULL REFERENCES resource_allocation.task_type DEFERRABLE INITIALLY IMMEDIATE, specification_id integer NOT NULL REFERENCES resource_allocation.specification ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE, -- GitLab From 235355aa9364d96af55d8ffabc825a3e94f40c21 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 12 May 2016 13:43:50 +0000 Subject: [PATCH 180/933] Task #9349: minor fix --- .../ResourceAssignmentDatabase/radb.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index c922b852d3c..753cb4f1ec6 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -1050,14 +1050,15 @@ class RADatabase: # update the claims as well # updateResourceClaims also validates the updated claims claim_ids = [c['id'] for c in claimsBeforeUpdate] - updated &= self.updateResourceClaims(claim_ids, - starttime=starttime, - endtime=endtime, - status=claim_status, - session_id=session_id, - username=username, user_id=user_id, - validate=True, - commit=False) + if claim_ids: + updated &= self.updateResourceClaims(claim_ids, + starttime=starttime, + endtime=endtime, + status=claim_status, + session_id=session_id, + username=username, user_id=user_id, + validate=True, + commit=False) # because we moved or changed the status of these claims, # validate the claims 'underneath' which may have been in conflict -- GitLab From 166d40028e42eaa391d993d60a4d0dce224b2be2 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 13 May 2016 14:36:22 +0000 Subject: [PATCH 181/933] Task #9349: minor patch. exit early when start/end time are incorrect --- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index f5ee34772c4..ee98500407e 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -116,7 +116,8 @@ class ResourceAssigner(): startTime = datetime.strptime(mainParset.getString('Observation.startTime'), '%Y-%m-%d %H:%M:%S') endTime = datetime.strptime(mainParset.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') except ValueError: - logger.warning('cannot parse for start/end time from specification for otdb_id=%s', (otdb_id, )) + logger.warning('cannot parse for start/end time from specification for otdb_id=%s. skipping specification.', (otdb_id, )) + return # insert new task and specification in the radb # any existing specification and task with same otdb_id will be deleted automatically -- GitLab From 57833634c569e50c367ba616b48e38ec1bedb8bc Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 13 May 2016 14:44:01 +0000 Subject: [PATCH 182/933] Task #9349: minor patch. insert None for mom/otdb id <= 0 --- .../ResourceAssignmentDatabase/radb.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 1b282e9e5b3..669fed24139 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -221,6 +221,12 @@ class RADatabase: return task_status, task_type def insertTask(self, mom_id, otdb_id, task_status, task_type, specification_id, commit=True): + if isinstance(mom_id, int) and mom_id <= 0: + mom_id = None + + if isinstance(otdb_id, int) and otdb_id <= 0: + otdb_id = None + logger.info('insertTask mom_id=%s, otdb_id=%s, task_status=%s, task_type=%s, specification_id=%s' % (mom_id, otdb_id, task_status, task_type, specification_id)) task_status, task_type = self._convertTaskTypeAndStatusToIds(task_status, task_type) @@ -1411,6 +1417,10 @@ if __name__ == '__main__': for t in db.getTasks(): print db.getTask(t['id']) + print db.getTask(db.insertTask(12340, 56780, 600, 0, t['specification_id'])) + print db.getTask(db.insertTask(0, 567801, 600, 0, t['specification_id'])) + print db.getTask(db.insertTask(123401, 0, 600, 0, t['specification_id'])) + exit() #print db.getResourceClaims(task_id=440) -- GitLab From 08add7a533589b79c90732729dcab0d849b6b928 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 17 May 2016 11:59:52 +0000 Subject: [PATCH 183/933] Task #8437: Add support for gnu_optarch, which is an optimised build for the local architecture --- CMake/variants/GNU.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CMake/variants/GNU.cmake b/CMake/variants/GNU.cmake index 24fde524c5d..2b68de7493f 100644 --- a/CMake/variants/GNU.cmake +++ b/CMake/variants/GNU.cmake @@ -7,7 +7,7 @@ set(LOFAR_COMPILER_SUITES GNU) # Build variants -set(LOFAR_BUILD_VARIANTS DEBUG OPT OPT3) +set(LOFAR_BUILD_VARIANTS DEBUG OPT OPT3 OPTARCH) # GNU compiler suite set(GNU_COMPILERS GNU_C GNU_CXX GNU_Fortran GNU_ASM) @@ -20,21 +20,28 @@ set(GNU_C_FLAGS "-W -Wall -Wno-unknown-pragmas") set(GNU_C_FLAGS_DEBUG "-g") set(GNU_C_FLAGS_OPT "-g -O2") set(GNU_C_FLAGS_OPT3 "-g -O3") +set(GNU_C_FLAGS_OPTARCH "-g -O3 -march=native") set(GNU_CXX_FLAGS "-W -Wall -Woverloaded-virtual -Wno-unknown-pragmas") set(GNU_CXX_FLAGS_DEBUG "-g") set(GNU_CXX_FLAGS_OPT "-g -O2") set(GNU_CXX_FLAGS_OPT3 "-g -O3") +set(GNU_CXX_FLAGS_OPTARCH "-g -O3 -march=native") set(GNU_EXE_LINKER_FLAGS) set(GNU_EXE_LINKER_FLAGS_DEBUG) set(GNU_EXE_LINKER_FLAGS_OPT) set(GNU_EXE_LINKER_FLAGS_OPT3) +set(GNU_EXE_LINKER_FLAGS_OPTARCH) set(GNU_SHARED_LINKER_FLAGS) set(GNU_SHARED_LINKER_FLAGS_DEBUG) set(GNU_SHARED_LINKER_FLAGS_OPT) set(GNU_SHARED_LINKER_FLAGS_OPT3) +set(GNU_SHARED_LINKER_FLAGS_OPTARCH3) set(GNU_COMPILE_DEFINITIONS) set(GNU_COMPILE_DEFINITIONS_DEBUG "-DLOFAR_DEBUG -DENABLE_DBGASSERT -DENABLE_TRACER") set(GNU_COMPILE_DEFINITIONS_OPT) set(GNU_COMPILE_DEFINITIONS_OPT3 "-DNDEBUG -DDISABLE_DEBUG_OUTPUT") +set(GNU_COMPILE_DEFINITIONS_OPTARCH + "-DNDEBUG -DDISABLE_DEBUG_OUTPUT") + -- GitLab From 93cce30c725580675bcdf65d27b0e11130d08fff Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 17 May 2016 12:09:21 +0000 Subject: [PATCH 184/933] Task #8437: lofar-guest user is obsolete now, LOFAR source is available without login now --- Docker/lofar-base/Dockerfile.tmpl | 13 +------------ Docker/lofar-outputproc/Dockerfile.tmpl | 2 +- Docker/lofar-pipeline/Dockerfile.tmpl | 2 +- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 4c306034bee..4f99aa92438 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -116,17 +116,6 @@ RUN sudo apt-get update && sudo apt-get install -y git make g++ python-setuptool # ******************* # -# Allow auto-checkout from lofar repo. We create ~/.subversion/servers in-line to prevent Docker cache invalidation -RUN mkdir /home/${USER}/.subversion -RUN echo '\ -[groups] \n\ -astron = svn.astron.nl \n\ -\n\ -[astron] \n\ -store-passwords = yes \n\ -store-plaintext-passwords = yes \n\ -' > /home/${USER}/.subversion/servers - # Run-time dependencies # QPID daemon legacy store would require: libaio1 libdb5.1++ RUN sudo apt-get update && sudo apt-get install -y sasl2-bin libuuid1 libnss3 libnspr4 xqilla libboost-program-options${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 @@ -135,7 +124,7 @@ RUN sudo apt-get update && sudo apt-get install -y sasl2-bin libuuid1 libnss3 li # QPID daemon legacy store would require: libaio-dev libdb5.1++-dev RUN sudo apt-get update && sudo apt-get install -y subversion swig ruby ruby-dev python-dev libsasl2-dev pkg-config cmake libtool uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man fakeroot build-essential debhelper libsslcommon2-dev libxqilla-dev python-setuptools libboost-program-options${BOOST_VERSION}-dev libboost-filesystem${BOOST_VERSION}-dev && \ mkdir /opt/qpid && \ - svn --non-interactive -q --username lofar-guest --password lofar-guest co ${LOFAR_BRANCH_URL}/LCS/MessageBus/qpid/ /opt/qpid; \ + svn --non-interactive -q co ${LOFAR_BRANCH_URL}/LCS/MessageBus/qpid/ /opt/qpid; \ /opt/qpid/local/sbin/build_qpid && \ bash -c "strip /opt/qpid/{bin,lib}/* || true" && \ bash -c "rm -rf ~/sources" && \ diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index 1db1c15f8fa..6a0995005dd 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -20,7 +20,7 @@ ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/gnu_opt libcfitsio3-dev wcslib-dev && \ cd ${INSTALLDIR}/lofar && \ - svn --non-interactive -q --username lofar-guest --password lofar-guest co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ + svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && cmake -DBUILD_PACKAGES=Online_OutputProc -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DDAL_ROOT_DIR=${INSTALLDIR}/DAL -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 81948dcf203..7b61e38b118 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -45,7 +45,7 @@ ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/gnu_opt && \ cd ${INSTALLDIR}/lofar && \ - svn --non-interactive -q --username lofar-guest --password lofar-guest co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ + svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && cmake -DBUILD_PACKAGES=Offline -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASAREST_ROOT_DIR=${INSTALLDIR}/casarest/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DAOFLAGGER_ROOT_DIR=${INSTALLDIR}/aoflagger/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/gnu_opt && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ -- GitLab From 46bd03fdb4e38e790e62b6a3a770b984ee4226c2 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 17 May 2016 15:01:37 +0000 Subject: [PATCH 185/933] Task #8437: Fixed typo --- MAC/Services/test/tPipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index aecd19fc850..92fb19d2667 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -192,7 +192,7 @@ class TestPipelineControl(unittest.TestCase): with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: ps._setStatus(12345, "queued") - # Wait for the staatus to propagate + # Wait for the status to propagate self.assertTrue(self.trigger.wait()) self.assertEqual(self.trigger.args[0], 12345) -- GitLab From 1f102d397963128c9621aa568f31628ede93752e Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 17 May 2016 15:03:02 +0000 Subject: [PATCH 186/933] Task #8437: Renamed ps -> pipelineControl --- MAC/Services/test/tPipelineControl.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index 92fb19d2667..a71b43fba5a 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -189,8 +189,8 @@ class TestPipelineControl(unittest.TestCase): self.trigger = MethodTrigger(listener, "onObservationQueued") def test_setStatus(self): - with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: - ps._setStatus(12345, "queued") + with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as pipelineControl: + pipelineControl._setStatus(12345, "queued") # Wait for the status to propagate self.assertTrue(self.trigger.wait()) @@ -202,9 +202,9 @@ class TestPipelineControl(unittest.TestCase): 3 requires nothing """ - with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: + with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as pipelineControl: # Send fake status update - ps._setStatus(3, "scheduled") + pipelineControl._setStatus(3, "scheduled") # Wait for message to arrive self.assertTrue(self.trigger.wait()) @@ -213,8 +213,8 @@ class TestPipelineControl(unittest.TestCase): self.assertEqual(self.trigger.args[0], 3) # otdbId # Check if job was scheduled - self.assertIn("3", ps.slurm.scheduled_jobs) - self.assertIn("3-aborted", ps.slurm.scheduled_jobs) + self.assertIn("3", pipelineControl.slurm.scheduled_jobs) + self.assertIn("3-aborted", pipelineControl.slurm.scheduled_jobs) def testPredecessors(self): """ @@ -224,9 +224,9 @@ class TestPipelineControl(unittest.TestCase): 2 requires 3 4 is an observation """ - with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as ps: + with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as pipelineControl: # Send fake status update - ps._setStatus(1, "scheduled") + pipelineControl._setStatus(1, "scheduled") # Wait for message to arrive self.assertTrue(self.trigger.wait()) @@ -235,11 +235,11 @@ class TestPipelineControl(unittest.TestCase): self.assertEqual(self.trigger.args[0], 1) # otdbId # Check if job was scheduled - self.assertIn("1", ps.slurm.scheduled_jobs) - self.assertIn("1-aborted", ps.slurm.scheduled_jobs) + self.assertIn("1", pipelineControl.slurm.scheduled_jobs) + self.assertIn("1-aborted", pipelineControl.slurm.scheduled_jobs) # Earliest start of this job > stop time of observation - for p in ps.slurm.scheduled_jobs["1"][1]["sbatch_params"]: + for p in pipelineControl.slurm.scheduled_jobs["1"][1]["sbatch_params"]: if p.startswith("--begin="): begin = datetime.datetime.strptime(p, "--begin=%Y-%m-%dT%H:%M:%S") self.assertGreater(begin, datetime.datetime(2016, 1, 1, 1, 0, 0)) -- GitLab From 92a5890707675c643867f65d09028e0da3290937 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 17 May 2016 15:04:40 +0000 Subject: [PATCH 187/933] Task #8437: OTDB_Services group already exists in SAS/OTDB_Services/OTDB_Services.ini --- SubSystems/RAServices/RAServices.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/SubSystems/RAServices/RAServices.ini b/SubSystems/RAServices/RAServices.ini index 347a0d73e3b..8cdc451a120 100644 --- a/SubSystems/RAServices/RAServices.ini +++ b/SubSystems/RAServices/RAServices.ini @@ -1,6 +1,3 @@ -[group:OTDB] -programs=TreeService,TreeStatusEvents - [group:RA_Services] programs=raewebservice,radbservice,radbpglistener,resourceassigner,RATaskSpecified,raestimatorservice,rotspservice,ortspropagator priority=200 -- GitLab From 2b7e1e57f78d0bd852518a214857e9a3ac8e1215 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 17 May 2016 15:12:13 +0000 Subject: [PATCH 188/933] Task #8473: Use "exec" to replace bash by service to start, to allow for signal propagation --- MAC/Services/src/pipelinecontrol.ini | 2 +- SAS/OTDB_Services/TreeService.ini | 2 +- SAS/OTDB_Services/TreeStatusEvents.ini | 2 +- .../otdbtorataskstatuspropagator.ini | 2 +- .../RATaskSpecifiedService/bin/rataskspecifiedservice.ini | 2 +- .../RAtoOTDBTaskSpecificationPropagator/bin/rotspservice.ini | 2 +- .../ResourceAssigner/bin/resourceassigner.ini | 2 +- .../ResourceAssignmentDatabase/radbpglistener.ini | 2 +- .../ResourceAssignmentEditor/bin/raewebservice.ini | 2 +- .../ResourceAssignmentEstimator/raestimatorservice.ini | 2 +- .../ResourceAssignmentService/radbservice.ini | 2 +- SAS/ResourceAssignment/Services/src/rataskspecifiedservice.ini | 2 +- SAS/ResourceAssignment/SystemStatusService/ssdbservice.ini | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/MAC/Services/src/pipelinecontrol.ini b/MAC/Services/src/pipelinecontrol.ini index c80c5381d5f..51b0269945e 100644 --- a/MAC/Services/src/pipelinecontrol.ini +++ b/MAC/Services/src/pipelinecontrol.ini @@ -1,5 +1,5 @@ [program:PipelineStarter] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;pipelinecontrol' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec pipelinecontrol' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true diff --git a/SAS/OTDB_Services/TreeService.ini b/SAS/OTDB_Services/TreeService.ini index 090d256417c..2499920a0db 100644 --- a/SAS/OTDB_Services/TreeService.ini +++ b/SAS/OTDB_Services/TreeService.ini @@ -1,5 +1,5 @@ [program:TreeService] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;TreeService.py --dbcredentials=OTDB' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec TreeService.py --dbcredentials=OTDB' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals diff --git a/SAS/OTDB_Services/TreeStatusEvents.ini b/SAS/OTDB_Services/TreeStatusEvents.ini index b16300df8c7..f2458fa5437 100644 --- a/SAS/OTDB_Services/TreeStatusEvents.ini +++ b/SAS/OTDB_Services/TreeStatusEvents.ini @@ -1,5 +1,5 @@ [program:TreeStatusEvents] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;TreeStatusEvents.py --dbcredentials=OTDB' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec TreeStatusEvents.py --dbcredentials=OTDB' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/otdbtorataskstatuspropagator.ini b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/otdbtorataskstatuspropagator.ini index be97662836e..fc1b92a4cdb 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/otdbtorataskstatuspropagator.ini +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/otdbtorataskstatuspropagator.ini @@ -1,5 +1,5 @@ [program:ortspropagator] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;otdbtorataskstatuspropagator' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec otdbtorataskstatuspropagator' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals diff --git a/SAS/ResourceAssignment/RATaskSpecifiedService/bin/rataskspecifiedservice.ini b/SAS/ResourceAssignment/RATaskSpecifiedService/bin/rataskspecifiedservice.ini index 91f6b88fd51..23d43f56ee0 100644 --- a/SAS/ResourceAssignment/RATaskSpecifiedService/bin/rataskspecifiedservice.ini +++ b/SAS/ResourceAssignment/RATaskSpecifiedService/bin/rataskspecifiedservice.ini @@ -1,5 +1,5 @@ [program:RATaskSpecified] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;rataskspecifiedservice' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec rataskspecifiedservice' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/bin/rotspservice.ini b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/bin/rotspservice.ini index ff98cf9f208..15653437901 100644 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/bin/rotspservice.ini +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/bin/rotspservice.ini @@ -1,5 +1,5 @@ [program:rotspservice] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;rotspservice' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec rotspservice' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals diff --git a/SAS/ResourceAssignment/ResourceAssigner/bin/resourceassigner.ini b/SAS/ResourceAssignment/ResourceAssigner/bin/resourceassigner.ini index a144ded3d43..bba8fe5d931 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/bin/resourceassigner.ini +++ b/SAS/ResourceAssignment/ResourceAssigner/bin/resourceassigner.ini @@ -1,5 +1,5 @@ [program:resourceassigner] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;resourceassigner' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec resourceassigner' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.ini b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.ini index 24abe5422b1..b2a3326e259 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.ini +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.ini @@ -1,5 +1,5 @@ [program:radbpglistener] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;radbpglistener' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec radbpglistener' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/bin/raewebservice.ini b/SAS/ResourceAssignment/ResourceAssignmentEditor/bin/raewebservice.ini index 80069bd27de..a4bb85e8ea9 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/bin/raewebservice.ini +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/bin/raewebservice.ini @@ -1,5 +1,5 @@ [program:raewebservice] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;raewebservice -p 7412' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec raewebservice -p 7412' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/raestimatorservice.ini b/SAS/ResourceAssignment/ResourceAssignmentEstimator/raestimatorservice.ini index 49a714aedeb..b80c27c80b5 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/raestimatorservice.ini +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/raestimatorservice.ini @@ -1,5 +1,5 @@ [program:raestimatorservice] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;raestimatorservice' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec raestimatorservice' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/radbservice.ini b/SAS/ResourceAssignment/ResourceAssignmentService/radbservice.ini index 676186bcb9d..95b1a0b7521 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/radbservice.ini +++ b/SAS/ResourceAssignment/ResourceAssignmentService/radbservice.ini @@ -1,5 +1,5 @@ [program:radbservice] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;radbservice --log-queries' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec radbservice --log-queries' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals diff --git a/SAS/ResourceAssignment/Services/src/rataskspecifiedservice.ini b/SAS/ResourceAssignment/Services/src/rataskspecifiedservice.ini index fa18d43856d..1aaf8b5a77e 100644 --- a/SAS/ResourceAssignment/Services/src/rataskspecifiedservice.ini +++ b/SAS/ResourceAssignment/Services/src/rataskspecifiedservice.ini @@ -1,5 +1,5 @@ [program:RATaskSpecified] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;rataskspecifiedservice' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec rataskspecifiedservice' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true diff --git a/SAS/ResourceAssignment/SystemStatusService/ssdbservice.ini b/SAS/ResourceAssignment/SystemStatusService/ssdbservice.ini index bb5d45837f4..26043a8ca85 100644 --- a/SAS/ResourceAssignment/SystemStatusService/ssdbservice.ini +++ b/SAS/ResourceAssignment/SystemStatusService/ssdbservice.ini @@ -1,5 +1,5 @@ [program:ssdbservice] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;ssdbservice' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec ssdbservice' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals -- GitLab From 1538aefced6650825813f6bcdbf4428d870cf9e5 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 18 May 2016 08:20:34 +0000 Subject: [PATCH 189/933] Task #8437: Do not send status twice when using runPipeline.sh --- CEP/Pipeline/framework/lofarpipe/support/control.py | 7 ++++++- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 3 +++ MAC/Services/src/PipelineControl.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/control.py b/CEP/Pipeline/framework/lofarpipe/support/control.py index fb5cf895006..b1a8c04462d 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/control.py +++ b/CEP/Pipeline/framework/lofarpipe/support/control.py @@ -93,7 +93,7 @@ class control(StatefulRecipe): indicates failure. """ - if self.feedback_method == "messagebus": + if self.feedback_method == "messagebus" and self.feedback_send_status: bus = messagebus.ToBus("lofar.task.feedback.state") msg = TaskFeedbackState( "lofarpipe.support.control", @@ -134,6 +134,11 @@ class control(StatefulRecipe): except: self.feedback_method = "messagebus" + try: + self.feedbacK_send_status = self.config.getboolean('feedback', 'send_status') + except: + self.feedback_send_status = True + if self.feedback_method == "messagebus" and not messagebus.MESSAGING_ENABLED: self.logger.error("Feedback over messagebus requested, but messagebus support is not enabled or functional") return 1 diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 00215f0ac8e..e8d8adae319 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -35,6 +35,9 @@ xml_stat_file = %(runtime_directory)s/%(job_name)s/logs/%(start_time)s/statistic # none Do NOT send feedback and status method = messagebus +# Report final state on the message bus? (yes for CEP2 using "startPython.sh", no for CEP4 using "runPipeline.sh") +send_status = no + [remote] method = custom_cmdline globalfs = yes diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index c892dc53f1a..4405ab7e9f7 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -44,7 +44,7 @@ The execution chains are as follows: (runPipeline.sh) -> Calls - state <- [ACTIVE] - getParset - - (run pipeline) (which, for CEP2 compatibility, still calls state <- [FINISHED/ABORTED]) + - (run pipeline) - state <- [COMPLETING] - (wrap up) - state <- [FINISHED] -- GitLab From 5b47e890176144d589dc91545e80cae528ca7c64 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 18 May 2016 08:33:08 +0000 Subject: [PATCH 190/933] Task #8887: compute task duration in task_view in database --- SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py | 4 ---- .../ResourceAssignmentDatabase/sql/create_database.sql | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index e1d0196bafa..644e75bdc2d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -179,7 +179,6 @@ class RADatabase: for task in tasks: task['predecessor_ids'] = predIds.get(task['id'], []) task['successor_ids'] = succIds.get(task['id'], []) - task['duration'] = totalSeconds(task['endtime'] - task['starttime']) return tasks @@ -203,9 +202,6 @@ class RADatabase: task = dict(result) if result else None - if task: - task['duration'] = totalSeconds(task['endtime'] - task['starttime']) - return task def _convertTaskTypeAndStatusToIds(self, task_status, task_type): diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql index 4fd10e07875..df864051e51 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql @@ -293,7 +293,7 @@ ALTER TABLE resource_allocation.config CREATE OR REPLACE VIEW resource_allocation.task_view AS SELECT t.id, t.mom_id, t.otdb_id, t.status_id, t.type_id, t.specification_id, - ts.name AS status, tt.name AS type, s.starttime, s.endtime, + ts.name AS status, tt.name AS type, s.starttime, s.endtime, extract(epoch from age(s.endtime, s.starttime)) as duration, (SELECT array_agg(tp.predecessor_id) FROM resource_allocation.task_predecessor tp where tp.task_id=t.id) as predecessor_ids, (SELECT array_agg(tp.task_id) FROM resource_allocation.task_predecessor tp where tp.predecessor_id=t.id) as successor_ids FROM resource_allocation.task t @@ -303,7 +303,7 @@ CREATE OR REPLACE VIEW resource_allocation.task_view AS ALTER VIEW resource_allocation.task_view OWNER TO resourceassignment; COMMENT ON VIEW resource_allocation.task_view - IS 'plain view on task table including task_status.name task_type.name specification.starttime and specification.endtime and the task predecessor- and successor ids'; + IS 'plain view on task table including task_status.name task_type.name specification.starttime and specification.endtime, duration in seconds, and the task predecessor- and successor ids'; CREATE OR REPLACE VIEW resource_allocation.resource_claim_view AS -- GitLab From 794c88662ba495989e031a0b6698cdff9be7cdef Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 18 May 2016 08:48:50 +0000 Subject: [PATCH 191/933] Task #8437: Updated service and busnames of OTDB in PipelineControl --- MAC/Services/src/PipelineControl.py | 15 ++++++++------- MAC/Services/src/pipelinecontrol | 19 ++++++++----------- MAC/Services/test/tPipelineControl.py | 21 +++++++++++---------- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 4405ab7e9f7..021eb442f2d 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -63,6 +63,7 @@ The execution chains are as follows: from lofar.messaging import FromBus, ToBus, RPC, EventMessage from lofar.parameterset import PyParameterValue from lofar.sas.otdb.OTDBBusListener import OTDBBusListener +from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTDB_SERVICE_BUSNAME from lofar.common.util import waitForInterrupt from lofar.messaging.RPC import RPC, RPCTimeoutException @@ -236,17 +237,17 @@ class Slurm(object): return lines[-1] class PipelineControl(OTDBBusListener): - def __init__(self, otdb_busname=None, setStatus_busname=None, **kwargs): - super(PipelineControl, self).__init__(busname=otdb_busname, **kwargs) + def __init__(self, otdb_notification_busname=DEFAULT_OTDB_NOTIFICATION_BUSNAME, otdb_service_busname=DEFAULT_OTDB_SERVICE_BUSNAME, **kwargs): + super(PipelineControl, self).__init__(busname=otdb_notification_busname, **kwargs) - self.parset_rpc = RPC(service="TaskSpecification", busname=otdb_busname, ForwardExceptions=True) - self.setStatus_busname = setStatus_busname if setStatus_busname else otdb_busname + self.parset_rpc = RPC(service="TaskGetSpecification", busname=otdb_service_busname, ForwardExceptions=True) + self.otdb_service_busname = otdb_service_busname self.slurm = Slurm() def _setStatus(self, obsid, status): try: - with RPC("StatusUpdateCmd", busname=self.setStatus_busname, timeout=10, ForwardExceptions=True) as status_rpc: + with RPC("TaskSetStatus", busname=self.otdb_service_busname, timeout=10, ForwardExceptions=True) as status_rpc: result, _ = status_rpc(OtdbID=obsid, NewStatus=status) except RPCTimeoutException, e: # We use a queue, so delivery is guaranteed. We don't care about the answer. @@ -393,7 +394,7 @@ class PipelineControl(OTDBBusListener): obsid = otdbId, tag = parset.dockerTag(), cluster = parset.processingCluster(), - status_bus = self.setStatus_busname, + status_bus = self.otdb_service_busname, ), sbatch_params=sbatch_params @@ -414,7 +415,7 @@ class PipelineControl(OTDBBusListener): lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, tag = parset.dockerTag(), - status_bus = self.setStatus_busname, + status_bus = self.otdb_service_busname, ), sbatch_params=[ diff --git a/MAC/Services/src/pipelinecontrol b/MAC/Services/src/pipelinecontrol index 2ad9e0fde0b..78cf6a38de7 100644 --- a/MAC/Services/src/pipelinecontrol +++ b/MAC/Services/src/pipelinecontrol @@ -19,29 +19,26 @@ # with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. # # $Id: JobsToSchedule.py 33364 2016-01-21 21:21:12Z mol $ -""" -Daemon that listens to OTDB status changes to PRESCHEDULED, requests -the parset of such jobs (+ their predecessors), and posts them on the bus. -""" -from lofar.mac.PipelineStarter import PipelineStarter +from lofar.mac.PipelineControl import PipelineControl +from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTDB_SERVICE_BUSNAME if __name__ == "__main__": import sys from optparse import OptionParser # Check the invocation arguments - parser = OptionParser("%prog -O otdb_bus -S setStatus_bus [options]") - parser.add_option("-O", "--otdb_bus", dest="otdb_busname", type="string", default="", - help="Bus or queue OTDB operates on") - parser.add_option("-S", "--setStatus_bus", dest="setStatus_busname", type="string", default="", - help="Bus or queue we publish state changes on") + parser = OptionParser("%prog [options]") + parser.add_option("-N", "--otdb_notification_bus", dest="otdb_notification_busname", type="string", default=DEFAULT_OTDB_NOTIFICATION_BUSNAME, + help="Bus or queue on which OTDB notifications are received") + parser.add_option("-S", "--otdb_service_bus", dest="otdb_service_busname", type="string", default=DEFAULT_OTDB_SERVICE_BUSNAME, + help="Bus or queue to send OTDB requests to") (options, args) = parser.parse_args() if not options.otdb_busname: parser.print_help() sys.exit(1) - with PipelineStarter(otdb_busname=options.otdb_busname, setStatus_busname=options.setStatus_busname) as ps: + with PipelineControl(otdb_busname=options.otdb_busname, setStatus_busname=options.setStatus_busname) as pipelineControl: waitForInterrupt() diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index a71b43fba5a..08aae0b9666 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -4,6 +4,7 @@ import sys from lofar.mac.PipelineControl import * from lofar.sas.otdb.OTDBBusListener import OTDBBusListener +from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_SUBJECT from lofar.messaging import ToBus, Service, EventMessage from lofar.common.methodtrigger import MethodTrigger @@ -128,8 +129,8 @@ class TestPipelineControl(unittest.TestCase): # Setup mock parset service # ================================ - def TaskSpecificationService( OtdbID ): - print "***** TaskSpecificationService(%s) *****" % (OtdbID,) + def TaskGetSpecification( OtdbID ): + print "***** TaskGetSpecification(%s) *****" % (OtdbID,) if OtdbID == 1: predecessors = "[2,3,4]" @@ -157,7 +158,7 @@ class TestPipelineControl(unittest.TestCase): PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", } - service = Service("TaskSpecification", TaskSpecificationService, busname=self.busname) + service = Service("TaskGetSpecification", TaskGetSpecification, busname=self.busname) service.start_listening() self.addCleanup(service.stop_listening) @@ -165,15 +166,15 @@ class TestPipelineControl(unittest.TestCase): # Setup mock status update service # ================================ - def StatusUpdateCmd( OtdbID, NewStatus ): - print "***** StatusUpdateCmd(%s,%s) *****" % (OtdbID, NewStatus) + def TaskSetStatus( OtdbID, NewStatus ): + print "***** TaskSetStatus(%s,%s) *****" % (OtdbID, NewStatus) # Broadcast the state change content = { "treeID" : OtdbID, "state" : NewStatus, "time_of_change" : datetime.datetime.utcnow() } - msg = EventMessage(context="otdb.treestatus", content=content) + msg = EventMessage(context=DEFAULT_OTDB_NOTIFICATION_SUBJECT, content=content) self.bus.send(msg) - service = Service("StatusUpdateCmd", StatusUpdateCmd, busname=self.busname) + service = Service("TaskSetStatus", TaskSetStatus, busname=self.busname) service.start_listening() self.addCleanup(service.stop_listening) @@ -189,7 +190,7 @@ class TestPipelineControl(unittest.TestCase): self.trigger = MethodTrigger(listener, "onObservationQueued") def test_setStatus(self): - with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as pipelineControl: + with PipelineControl(otdb_notification_busname=self.busname, otdb_service_busname=self.busname) as pipelineControl: pipelineControl._setStatus(12345, "queued") # Wait for the status to propagate @@ -202,7 +203,7 @@ class TestPipelineControl(unittest.TestCase): 3 requires nothing """ - with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as pipelineControl: + with PipelineControl(otdb_notification_busname=self.busname, otdb_service_busname=self.busname) as pipelineControl: # Send fake status update pipelineControl._setStatus(3, "scheduled") @@ -224,7 +225,7 @@ class TestPipelineControl(unittest.TestCase): 2 requires 3 4 is an observation """ - with PipelineControl(otdb_busname=self.busname, setStatus_busname=self.busname) as pipelineControl: + with PipelineControl(otdb_notification_busname=self.busname, otdb_service_busname=self.busname) as pipelineControl: # Send fake status update pipelineControl._setStatus(1, "scheduled") -- GitLab From c2c0cd3a528339bb742f39cdfb8bcadb56b51453 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 18 May 2016 09:08:35 +0000 Subject: [PATCH 192/933] Task #8437: Updated bus and service names in getParset/setStatus scripts --- SAS/OTDB_Services/getParset.py | 7 ++++--- SAS/OTDB_Services/setStatus.py | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/SAS/OTDB_Services/getParset.py b/SAS/OTDB_Services/getParset.py index d6abdae4ce7..ec26938ea77 100755 --- a/SAS/OTDB_Services/getParset.py +++ b/SAS/OTDB_Services/getParset.py @@ -25,9 +25,10 @@ """ from lofar.messaging.RPC import RPC +from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME -def getParset(obsid, status, otdb_busname="lofar.otdb.command"): - with RPC("TaskSpecification", busname=otdb_busname, timeout=10) as parset_rpc: +def getParset(obsid, status, otdb_busname=DEFAULT_OTDB_SERVICE_BUSNAME): + with RPC("TaskGetSpecification", busname=otdb_busname, timeout=10) as parset_rpc: result, _ = parset_rpc(OtdbID=obsid) return result @@ -42,7 +43,7 @@ if __name__ == "__main__": # Check the invocation arguments parser = OptionParser("%prog -o obsid [options]") - parser.add_option("-B", "--busname", dest="busname", type="string", default="lofar.otdb.command", + parser.add_option("-B", "--busname", dest="busname", type="string", default=DEFAULT_OTDB_SERVICE_BUSNAME, help="Busname on which OTDB commands are sent") parser.add_option("-o", "--obsid", dest="obsid", type="int", default=0, help="Observation/tree ID to get parset of") diff --git a/SAS/OTDB_Services/setStatus.py b/SAS/OTDB_Services/setStatus.py index c3379282a3b..2628bf8294c 100755 --- a/SAS/OTDB_Services/setStatus.py +++ b/SAS/OTDB_Services/setStatus.py @@ -25,9 +25,10 @@ """ from lofar.messaging.RPC import RPC +from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME -def setStatus(obsid, status, otdb_busname="lofar.otdb.command"): - with RPC("StatusUpdateCmd", busname=otdb_busname, timeout=10) as status_rpc: +def setStatus(obsid, status, otdb_busname=DEFAULT_OTDB_SERVICE_BUSNAME): + with RPC("TaskSetStatus", busname=otdb_busname, timeout=10) as status_rpc: result, _ = status_rpc(OtdbID=obsid, NewStatus=status) if __name__ == "__main__": @@ -40,7 +41,7 @@ if __name__ == "__main__": # Check the invocation arguments parser = OptionParser("%prog -o obsid -s status [options]") - parser.add_option("-B", "--busname", dest="busname", type="string", default="lofar.otdb.command", + parser.add_option("-B", "--busname", dest="busname", type="string", default=DEFAULT_OTDB_SERVICE_BUSNAME, help="Busname on which OTDB commands are sent") parser.add_option("-o", "--obsid", dest="obsid", type="int", default=0, help="Observation/tree ID to set status for") -- GitLab From 0946e205531aacdd6ec71ffc7fe10d3f5d9a3063 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 18 May 2016 13:37:44 +0000 Subject: [PATCH 193/933] Task #8437: Moved otdbrpc to OTDB_Services, and fixed cmake dependencies and python imports --- .gitattributes | 2 +- SAS/OTDB_Services/CMakeLists.txt | 1 + .../lib => OTDB_Services}/otdbrpc.py | 0 .../RATaskSpecifiedService/lib/RATaskSpecified.py | 2 +- .../RAtoOTDBTaskSpecificationPropagator/CMakeLists.txt | 2 +- .../RAtoOTDBTaskSpecificationPropagator/lib/CMakeLists.txt | 1 - .../RAtoOTDBTaskSpecificationPropagator/lib/propagator.py | 2 +- SAS/ResourceAssignment/ResourceAssigner/CMakeLists.txt | 2 +- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 2 +- 9 files changed, 7 insertions(+), 7 deletions(-) rename SAS/{ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib => OTDB_Services}/otdbrpc.py (100%) diff --git a/.gitattributes b/.gitattributes index 1fc3af987da..dbe22097365 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4972,6 +4972,7 @@ SAS/OTDB_Services/TreeService.py -text SAS/OTDB_Services/TreeStatusEvents.ini -text SAS/OTDB_Services/TreeStatusEvents.py -text SAS/OTDB_Services/config.py -text +SAS/OTDB_Services/otdbrpc.py -text SAS/OTDB_Services/test/CMakeLists.txt -text SAS/OTDB_Services/test/t_TreeService.py -text SAS/OTDB_Services/test/t_TreeService.run -text svneol=unset#application/x-shellscript @@ -5017,7 +5018,6 @@ SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/bin/rotspservice.ini SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/CMakeLists.txt -text SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/__init__.py -text SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/config.py -text -SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/otdbrpc.py -text SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py -text SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py -text SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py -text diff --git a/SAS/OTDB_Services/CMakeLists.txt b/SAS/OTDB_Services/CMakeLists.txt index 213dfd53683..bb1b514668a 100644 --- a/SAS/OTDB_Services/CMakeLists.txt +++ b/SAS/OTDB_Services/CMakeLists.txt @@ -14,6 +14,7 @@ lofar_add_bin_scripts( set(_py_files config.py + otdbrpc.py OTDBBusListener.py ) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/otdbrpc.py b/SAS/OTDB_Services/otdbrpc.py similarity index 100% rename from SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/otdbrpc.py rename to SAS/OTDB_Services/otdbrpc.py diff --git a/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py b/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py index 493317ec635..863d3ea40d8 100755 --- a/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py +++ b/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py @@ -29,7 +29,7 @@ from lofar.messaging import FromBus, ToBus, EventMessage # RPC, from lofar.parameterset import PyParameterValue from lofar.sas.otdb.OTDBBusListener import OTDBBusListener from lofar.common.util import waitForInterrupt -from lofar.sas.resourceassignment.ratootdbtaskspecificationpropagator.otdbrpc import OTDBRPC +from lofar.sas.otdb.otdbrpc import OTDBRPC from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTDB_NOTIFICATION_SUBJECT from lofar.sas.resourceassignment.rataskspecified.config import DEFAULT_RA_TASK_SPECIFIED_NOTIFICATION_BUSNAME from lofar.sas.resourceassignment.rataskspecified.config import DEFAULT_RA_TASK_SPECIFIED_NOTIFICATION_SUBJECT diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/CMakeLists.txt b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/CMakeLists.txt index e652fe295b5..c2f60a22d1d 100644 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/CMakeLists.txt +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/CMakeLists.txt @@ -1,6 +1,6 @@ # $Id: CMakeLists.txt 30355 2014-11-04 13:46:05Z loose $ -lofar_package(RAtoOTDBTaskSpecificationPropagator 0.1 DEPENDS PyMessaging PyCommon pyparameterset) +lofar_package(RAtoOTDBTaskSpecificationPropagator 0.1 DEPENDS PyMessaging PyCommon pyparameterset OTDB_Services) include(PythonInstall) set(USE_PYTHON_COMPILATION Off) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/CMakeLists.txt b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/CMakeLists.txt index 7713a14080b..fb4a42bf7a6 100644 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/CMakeLists.txt @@ -6,6 +6,5 @@ python_install( translator.py config.py propagator.py - otdbrpc.py DESTINATION lofar/sas/resourceassignment/ratootdbtaskspecificationpropagator) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py index 725f248142e..f004949d510 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py @@ -36,7 +36,7 @@ from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC as from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME -from lofar.sas.resourceassignment.ratootdbtaskspecificationpropagator.otdbrpc import OTDBRPC +from lofar.sas.otdb.otdbrpc import OTDBRPC from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME from lofar.sas.resourceassignment.ratootdbtaskspecificationpropagator.translator import RAtoOTDBTranslator diff --git a/SAS/ResourceAssignment/ResourceAssigner/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssigner/CMakeLists.txt index e78a2d4035f..1f76919118f 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssigner/CMakeLists.txt @@ -1,6 +1,6 @@ # $Id: CMakeLists.txt 30355 2014-11-04 13:46:05Z loose $ -lofar_package(ResourceAssigner 0.1 DEPENDS PyMessaging PyCommon pyparameterset) +lofar_package(ResourceAssigner 0.1 DEPENDS PyMessaging PyCommon pyparameterset OTDB_Services) include(PythonInstall) set(USE_PYTHON_COMPILATION Off) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 150d6541f8f..7ff527088b1 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -40,7 +40,7 @@ from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAUL from lofar.sas.resourceassignment.resourceassignmentestimator.config import DEFAULT_BUSNAME as RE_BUSNAME from lofar.sas.resourceassignment.resourceassignmentestimator.config import DEFAULT_SERVICENAME as RE_SERVICENAME -from lofar.sas.resourceassignment.ratootdbtaskspecificationpropagator.otdbrpc import OTDBRPC +from lofar.sas.otdb.otdbrpc import OTDBRPC from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME from lofar.sas.systemstatus.service.SSDBrpc import SSDBRPC -- GitLab From 5122d837e5debcad9fd07c4eb87cb66319552838 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 18 May 2016 13:41:27 +0000 Subject: [PATCH 194/933] Task #8437: classmethod -> staticmethod --- MAC/Services/src/PipelineControl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 021eb442f2d..023738618bd 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -263,8 +263,8 @@ class PipelineControl(OTDBBusListener): self.parset_rpc.close() - @classmethod - def _shouldHandle(self, parset): + @staticmethod + def _shouldHandle(parset): if not parset.isPipeline(): logger.info("Not processing tree: is not a pipeline") return False -- GitLab From f14ae89893657325f487088853b5915b057c9712 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 18 May 2016 16:46:52 +0000 Subject: [PATCH 195/933] Task #8437: Fixes for changes in services API --- MAC/Services/src/PipelineControl.py | 58 +++------------ MAC/Services/test/tPipelineControl.py | 100 ++++++++++++++------------ SAS/OTDB_Services/getParset.py | 10 +-- SAS/OTDB_Services/setStatus.py | 9 ++- 4 files changed, 71 insertions(+), 106 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 023738618bd..0356126c162 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -64,8 +64,9 @@ from lofar.messaging import FromBus, ToBus, RPC, EventMessage from lofar.parameterset import PyParameterValue from lofar.sas.otdb.OTDBBusListener import OTDBBusListener from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTDB_SERVICE_BUSNAME +from lofar.sas.otdb.otdbrpc import OTDBRPC from lofar.common.util import waitForInterrupt -from lofar.messaging.RPC import RPC, RPCTimeoutException +from lofar.messaging.RPC import RPCTimeoutException import subprocess import datetime @@ -102,39 +103,6 @@ def runCommand(cmdline, input=None): """ Prefix that is common to all parset keys, depending on the exact source. """ PARSET_PREFIX="ObsSW." -class ProcessingClusterTask(object): - def __init__(self): - self.name = "" - self.partition = "" - - self.maxDuration = 0 # seconds - - self.nrCoresPerTask = 1 - self.nrTasks = 1 - self.minMemPerTask = 1024 # MB - -class CEP2Task(ProcessingClusterTask): - def __init__(self): - super(CEP2Task).__init__(self) - - self.name = "CEP2" - -class CEP4Task(ProcessingClusterTask): - def __init__(self): - super(CEP4Task).__init__(self) - - self.name = "CEP4" - self.partition = "cpu" - - self.nrCoresPerTask = 8 - self.minMemPerTask = self.nrCoresPerTask * 10 * 1024 # ~240 GB free for 24 cores -> ~10GB/core - -clusterTaskFactory = { - "": CEP2Task, - "CEP2": CEP2Task, - "CEP4": CEP4Task, -} - class Parset(dict): def predecessors(self): """ Extract the list of predecessor obs IDs from the given parset. """ @@ -154,15 +122,7 @@ class Parset(dict): return not self.isObservation() def processingCluster(self): - clusterName = self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] or "CEP2" - task = clusterTaskFactory[clusterName]() - - # override defaults if provided by parset - task.partition = self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterPartition"] or task.partition - try: - task.nrCoresPerTask = int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfCoresPerTask"] or task.nrCoresPerTask - except ValueError: - pass + return self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] or "CEP2" def dockerTag(self): # Return the version set in the parset, and fall back to our own version. @@ -240,28 +200,26 @@ class PipelineControl(OTDBBusListener): def __init__(self, otdb_notification_busname=DEFAULT_OTDB_NOTIFICATION_BUSNAME, otdb_service_busname=DEFAULT_OTDB_SERVICE_BUSNAME, **kwargs): super(PipelineControl, self).__init__(busname=otdb_notification_busname, **kwargs) - self.parset_rpc = RPC(service="TaskGetSpecification", busname=otdb_service_busname, ForwardExceptions=True) self.otdb_service_busname = otdb_service_busname - + self.otdbrpc = OTDBRPC(busname=otdb_service_busname) self.slurm = Slurm() def _setStatus(self, obsid, status): try: - with RPC("TaskSetStatus", busname=self.otdb_service_busname, timeout=10, ForwardExceptions=True) as status_rpc: - result, _ = status_rpc(OtdbID=obsid, NewStatus=status) + self.otdbrpc.taskSetStatus(otdb_id=obsid, new_status=status) except RPCTimeoutException, e: # We use a queue, so delivery is guaranteed. We don't care about the answer. pass def start_listening(self, **kwargs): - self.parset_rpc.open() + self.otdbrpc.open() super(PipelineControl, self).start_listening(**kwargs) def stop_listening(self, **kwargs): super(PipelineControl, self).stop_listening(**kwargs) - self.parset_rpc.close() + self.otdbrpc.close() @staticmethod def _shouldHandle(parset): @@ -279,7 +237,7 @@ class PipelineControl(OTDBBusListener): return [self.slurm.jobid(p.slurmJobName()) for p in parsets] def _getParset(self, otdbId): - return Parset(self.parset_rpc( OtdbID=otdbId, timeout=10 )[0]) + return Parset(self.otdbrpc.taskGetSpecification(otdb_id=otdbId)["specification"]) def _getPredecessorParsets(self, parset): otdbIds = parset.predecessors() diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index 08aae0b9666..3fe31bdeca3 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -4,8 +4,8 @@ import sys from lofar.mac.PipelineControl import * from lofar.sas.otdb.OTDBBusListener import OTDBBusListener -from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_SUBJECT -from lofar.messaging import ToBus, Service, EventMessage +from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_SUBJECT, DEFAULT_OTDB_SERVICENAME +from lofar.messaging import ToBus, Service, EventMessage, MessageHandlerInterface from lofar.common.methodtrigger import MethodTrigger @@ -15,7 +15,7 @@ import uuid import datetime import logging -logging.basicConfig(stream=sys.stdout, level=logging.INFO) +logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) try: from mock import patch @@ -126,55 +126,67 @@ class TestPipelineControl(unittest.TestCase): self.addCleanup(patcher.stop) # ================================ - # Setup mock parset service + # Setup mock otdb service # ================================ - def TaskGetSpecification( OtdbID ): - print "***** TaskGetSpecification(%s) *****" % (OtdbID,) - - if OtdbID == 1: - predecessors = "[2,3,4]" - elif OtdbID == 2: - predecessors = "[3]" - elif OtdbID == 3: - predecessors = "[]" - elif OtdbID == 4: - return { - "Version.number": "1", - PARSET_PREFIX + "Observation.ObsID": str(OtdbID), - PARSET_PREFIX + "Observation.Scheduler.predecessors": "[]", - PARSET_PREFIX + "Observation.processType": "Observation", - PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", - PARSET_PREFIX + "Observation.stopTime": "2016-01-01 01:00:00", - } - else: - raise Exception("Invalid OtdbID: %s" % OtdbID) + class MockOTDBService(MessageHandlerInterface): + def __init__(self, notification_bus): + """ + notification_bus: bus to send state changes to + """ + super(MockOTDBService, self).__init__() - return { - "Version.number": "1", - PARSET_PREFIX + "Observation.ObsID": str(OtdbID), - PARSET_PREFIX + "Observation.Scheduler.predecessors": predecessors, - PARSET_PREFIX + "Observation.processType": "Pipeline", - PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", - } + self.service2MethodMap = { + "TaskGetSpecification": self.TaskGetSpecification, + "TaskSetStatus": self.TaskSetStatus, + } - service = Service("TaskGetSpecification", TaskGetSpecification, busname=self.busname) - service.start_listening() - self.addCleanup(service.stop_listening) + self.notification_bus = notification_bus + + def TaskGetSpecification(self, OtdbID): + print "***** TaskGetSpecification(%s) *****" % (OtdbID,) + + if OtdbID == 1: + predecessors = "[2,3,4]" + elif OtdbID == 2: + predecessors = "[3]" + elif OtdbID == 3: + predecessors = "[]" + elif OtdbID == 4: + return { "TaskSpecification": { + "Version.number": "1", + PARSET_PREFIX + "Observation.otdbID": str(OtdbID), + PARSET_PREFIX + "Observation.Scheduler.predecessors": "[]", + PARSET_PREFIX + "Observation.processType": "Observation", + PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", + PARSET_PREFIX + "Observation.stopTime": "2016-01-01 01:00:00", + } } + else: + raise Exception("Invalid OtdbID: %s" % OtdbID) + + return { "TaskSpecification": { + "Version.number": "1", + PARSET_PREFIX + "Observation.otdbID": str(OtdbID), + PARSET_PREFIX + "Observation.Scheduler.predecessors": predecessors, + PARSET_PREFIX + "Observation.processType": "Pipeline", + PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", + } } - # ================================ - # Setup mock status update service - # ================================ + def TaskSetStatus(self, OtdbID, NewStatus, UpdateTimestamps): + print "***** TaskSetStatus(%s,%s) *****" % (OtdbID, NewStatus) - def TaskSetStatus( OtdbID, NewStatus ): - print "***** TaskSetStatus(%s,%s) *****" % (OtdbID, NewStatus) + # Broadcast the state change + content = { "treeID" : OtdbID, "state" : NewStatus, "time_of_change" : datetime.datetime.utcnow() } + msg = EventMessage(context=DEFAULT_OTDB_NOTIFICATION_SUBJECT, content=content) + self.notification_bus.send(msg) - # Broadcast the state change - content = { "treeID" : OtdbID, "state" : NewStatus, "time_of_change" : datetime.datetime.utcnow() } - msg = EventMessage(context=DEFAULT_OTDB_NOTIFICATION_SUBJECT, content=content) - self.bus.send(msg) + return {'OtdbID':OtdbID, 'MomID':None, 'Success':True} - service = Service("TaskSetStatus", TaskSetStatus, busname=self.busname) + service = Service(DEFAULT_OTDB_SERVICENAME, + MockOTDBService, + busname=self.busname, + use_service_methods=True, + handler_args={ "notification_bus": self.bus }) service.start_listening() self.addCleanup(service.stop_listening) diff --git a/SAS/OTDB_Services/getParset.py b/SAS/OTDB_Services/getParset.py index ec26938ea77..138475004ee 100755 --- a/SAS/OTDB_Services/getParset.py +++ b/SAS/OTDB_Services/getParset.py @@ -26,12 +26,7 @@ from lofar.messaging.RPC import RPC from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME - -def getParset(obsid, status, otdb_busname=DEFAULT_OTDB_SERVICE_BUSNAME): - with RPC("TaskGetSpecification", busname=otdb_busname, timeout=10) as parset_rpc: - result, _ = parset_rpc(OtdbID=obsid) - - return result +from lofar.sas.otdb.otdbrpc import OTDBRPC if __name__ == "__main__": from optparse import OptionParser @@ -53,7 +48,8 @@ if __name__ == "__main__": parser.print_help() sys.exit(1) - parset = getParset(options.obsid, otdb_busname=options.busname) + with OTDBRPC(busname=options.busname) as otdbrpc: + parset = otdbrpc.taskGetSpecification(otdb_id=options.obsid)["specification"] for k,v in parset.iteritems(): print "%s = %s" % (k,v) diff --git a/SAS/OTDB_Services/setStatus.py b/SAS/OTDB_Services/setStatus.py index 2628bf8294c..06625da8365 100755 --- a/SAS/OTDB_Services/setStatus.py +++ b/SAS/OTDB_Services/setStatus.py @@ -26,10 +26,7 @@ from lofar.messaging.RPC import RPC from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME - -def setStatus(obsid, status, otdb_busname=DEFAULT_OTDB_SERVICE_BUSNAME): - with RPC("TaskSetStatus", busname=otdb_busname, timeout=10) as status_rpc: - result, _ = status_rpc(OtdbID=obsid, NewStatus=status) +from lofar.sas.otdb.otdbrpc import OTDBRPC if __name__ == "__main__": from optparse import OptionParser @@ -53,4 +50,6 @@ if __name__ == "__main__": parser.print_help() sys.exit(1) - setStatus(options.obsid, options.status, otdb_busname=options.busname) + with OTDBRPC(busname=options.busname) as otdbrpc: + otdbrpc.taskSetStatus(otdb_id=options.obsid, new_status=options.status) + -- GitLab From e03601a2e279d79d97becc701429c3cc0a132900 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 19 May 2016 07:51:15 +0000 Subject: [PATCH 196/933] Task #8437: Build LOFAR software with gnu_optarch (which triggers -march=native -O3) --- Docker/lofar-outputproc/Dockerfile.tmpl | 13 +++++++------ Docker/lofar-pipeline/Dockerfile.tmpl | 13 +++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index 6a0995005dd..96efdda0a20 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -14,18 +14,19 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y li # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ - LOFAR_REVISION=${LOFAR_REVISION} + LOFAR_REVISION=${LOFAR_REVISION} \ + LOFAR_BUILDVARIANT=gnu_optarch # Install RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev && \ - mkdir -p ${INSTALLDIR}/lofar/build/gnu_opt libcfitsio3-dev wcslib-dev && \ + mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} libcfitsio3-dev wcslib-dev && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/gnu_opt && cmake -DBUILD_PACKAGES=Online_OutputProc -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DDAL_ROOT_DIR=${INSTALLDIR}/DAL -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ - cd ${INSTALLDIR}/lofar/build/gnu_opt && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ - cd ${INSTALLDIR}/lofar/build/gnu_opt && make -j ${J} && \ - cd ${INSTALLDIR}/lofar/build/gnu_opt && make install && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Online_OutputProc -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DDAL_ROOT_DIR=${INSTALLDIR}/DAL -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ bash -c "mkdir -p /home/${USER}/lofar/var/{log,run}" && \ bash -c "ln -sfT /home/${USER}/lofar/var ${INSTALLDIR}/lofar/var" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 7b61e38b118..9cbae03ac7b 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -39,18 +39,19 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y wg # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ - LOFAR_REVISION=${LOFAR_REVISION} + LOFAR_REVISION=${LOFAR_REVISION} \ + LOFAR_BUILDVARIANT=gnu_optarch # Install RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev && \ - mkdir -p ${INSTALLDIR}/lofar/build/gnu_opt && \ + mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/gnu_opt && cmake -DBUILD_PACKAGES=Offline -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASAREST_ROOT_DIR=${INSTALLDIR}/casarest/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DAOFLAGGER_ROOT_DIR=${INSTALLDIR}/aoflagger/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ - cd ${INSTALLDIR}/lofar/build/gnu_opt && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ - cd ${INSTALLDIR}/lofar/build/gnu_opt && make -j ${J} && \ - cd ${INSTALLDIR}/lofar/build/gnu_opt && make install && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Offline -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASAREST_ROOT_DIR=${INSTALLDIR}/casarest/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DAOFLAGGER_ROOT_DIR=${INSTALLDIR}/aoflagger/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ bash -c "mv ${INSTALLDIR}/lofar/var /home/${USER}/" && \ bash -c "ln -sfT /home/${USER}/lofar/var ${INSTALLDIR}/lofar/var" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ -- GitLab From 0948ca5bba8947bc4475e3d7c3d5718d0bc0219f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 19 May 2016 09:01:43 +0000 Subject: [PATCH 197/933] Task #9337: Backported OutputProc fixes to release branch --- .../OutputProc/src/MeasurementSetFormat.cc | 6 ++++++ RTCP/Cobalt/OutputProc/src/OutputThread.cc | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/RTCP/Cobalt/OutputProc/src/MeasurementSetFormat.cc b/RTCP/Cobalt/OutputProc/src/MeasurementSetFormat.cc index dac5383e4f9..6439e976674 100644 --- a/RTCP/Cobalt/OutputProc/src/MeasurementSetFormat.cc +++ b/RTCP/Cobalt/OutputProc/src/MeasurementSetFormat.cc @@ -119,6 +119,9 @@ namespace LOFAR MeasurementSetFormat::~MeasurementSetFormat() { + ScopedLock scopedLock(sharedMutex); + + itsMS = 0; } @@ -132,6 +135,9 @@ namespace LOFAR /// Next make a metafile which describes the raw datafile we're /// going to write createMSMetaFile(MSname, subband); + + // Release itsMS, we don't need it anymore + itsMS = 0; } diff --git a/RTCP/Cobalt/OutputProc/src/OutputThread.cc b/RTCP/Cobalt/OutputProc/src/OutputThread.cc index e972dfdc5ac..689d8396545 100644 --- a/RTCP/Cobalt/OutputProc/src/OutputThread.cc +++ b/RTCP/Cobalt/OutputProc/src/OutputThread.cc @@ -172,6 +172,15 @@ namespace LOFAR if (!itsParset.settings.realTime) THROW(StorageException, ex); +#if defined HAVE_AIPSPP + } + catch (casa::AipsError &ex) + { + LOG_ERROR_STR(itsLogPrefix << "Could not create meta data (AipsError): " << ex.what()); + + if (!itsParset.settings.realTime) + THROW(StorageException, ex.what()); +#endif } } @@ -188,6 +197,15 @@ namespace LOFAR if (!itsParset.settings.realTime) THROW(StorageException, ex); +#if defined HAVE_AIPSPP + } + catch (casa::AipsError &ex) + { + LOG_ERROR_STR(itsLogPrefix << "Could not add final meta data (AipsError): " << ex.what()); + + if (!itsParset.settings.realTime) + THROW(StorageException, ex.what()); +#endif } } -- GitLab From 8615149d713e23cee8c82b4517783e79042cba6c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 19 May 2016 09:46:38 +0000 Subject: [PATCH 198/933] Task #8617: Backported BBS even channel fix to 2.16 --- CEP/Calibration/BBSKernel/src/MeasurementAIPS.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CEP/Calibration/BBSKernel/src/MeasurementAIPS.cc b/CEP/Calibration/BBSKernel/src/MeasurementAIPS.cc index 75615f92f09..3863d0f2aef 100644 --- a/CEP/Calibration/BBSKernel/src/MeasurementAIPS.cc +++ b/CEP/Calibration/BBSKernel/src/MeasurementAIPS.cc @@ -726,11 +726,13 @@ void MeasurementAIPS::initDimensions() Vector<Double> upFreq = frequency(Slice(1,frequency.nelements()-1)); Vector<Double> lowFreq = frequency(Slice(0,frequency.nelements()-1)); Double freqstep0=upFreq(0)-lowFreq(0); - ASSERTSTR(allEQ(upFreq-lowFreq,freqstep0), + // Check to 1 kHz accuracy + ASSERTSTR(allNearAbs(upFreq-lowFreq,freqstep0,1.e3), "Channels are not evenly spaced. This is not supported."); } - ASSERTSTR(allEQ(width, width(0)), + // Check to 1 kHz accuracy + ASSERTSTR(allNearAbs(width, width(0),1.e3), "Channels width is not the same for all channels. This is not supported" " yet."); -- GitLab From aa212237f6e1c04b17ab92aefd5424ad6fa3f4d0 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 19 May 2016 10:13:29 +0000 Subject: [PATCH 199/933] Task #8887: Backported DPPP/BBS fixes to 2.16 --- .../BBSControl/scripts/parmdbplot.py | 2 +- CEP/DP3/DPPP/src/ApplyCal.cc | 14 ++++-- CEP/DP3/DPPP/src/GainCal.cc | 45 +++++++++++++++---- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/CEP/Calibration/BBSControl/scripts/parmdbplot.py b/CEP/Calibration/BBSControl/scripts/parmdbplot.py index 25495c22951..141a79f4193 100755 --- a/CEP/Calibration/BBSControl/scripts/parmdbplot.py +++ b/CEP/Calibration/BBSControl/scripts/parmdbplot.py @@ -771,7 +771,7 @@ class PlotWindow(QFrame): else: xlabel = ["Time (sample)", "Freq (sample)"][self.axis] - if self.calType == "CommonRotationAngle" or self.calType == "RotationAngle": + if self.calType == "CommonRotationAngle" or self.calType == "RotationAngle" or self.calType == "RotationMeasure": phaselabel = "Rotation angle (rad)" else: phaselabel = "Phase (rad)" diff --git a/CEP/DP3/DPPP/src/ApplyCal.cc b/CEP/DP3/DPPP/src/ApplyCal.cc index 0841398434e..b0394aafe9e 100644 --- a/CEP/DP3/DPPP/src/ApplyCal.cc +++ b/CEP/DP3/DPPP/src/ApplyCal.cc @@ -282,11 +282,18 @@ namespace LOFAR { double maxFreq (info().chanFreqs()[numFreqs-1]+0.5*freqInterval); itsLastTime = bufStartTime + itsTimeSlotsPerParmUpdate * itsTimeInterval; + uint numTimes = itsTimeSlotsPerParmUpdate; + + double lastMSTime = info().startTime() + info().ntime() * itsTimeInterval; + if (itsLastTime > lastMSTime) { + itsLastTime = lastMSTime; + numTimes = info().ntime() % itsTimeSlotsPerParmUpdate; + } map<string, vector<double> > parmMap; map<string, vector<double> >::iterator parmIt; - uint tfDomainSize=itsTimeSlotsPerParmUpdate*numFreqs; + uint tfDomainSize=numTimes*numFreqs; for (uint parmExprNum = 0; parmExprNum<itsParmExprs.size();++parmExprNum) { // parmMap contains parameter values for all antennas @@ -300,6 +307,7 @@ namespace LOFAR { if (parmIt != parmMap.end()) { parmvalues[parmExprNum][ant].swap(parmIt->second); + ASSERT(parmvalues[parmExprNum][ant].size()==tfDomainSize); } else {// No value found, try default Array<double> defValues; double defValue; @@ -419,8 +427,8 @@ namespace LOFAR { if (itsInvert) { chi = -chi; } - double sinv = sin(parmvalues[0][ant][tf] * chi); - double cosv = cos(parmvalues[0][ant][tf] * chi); + double sinv = sin(chi); + double cosv = cos(chi); itsParms[0][ant][tf] = cosv; itsParms[1][ant][tf] = -sinv; itsParms[2][ant][tf] = sinv; diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index 5bbdf3a21ee..f4ae1ad6a59 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -29,6 +29,7 @@ #include <DPPP/DPInfo.h> #include <DPPP/SourceDBUtil.h> #include <DPPP/MSReader.h> +#include <DPPP/DPLogger.h> #include <ParmDB/ParmDB.h> #include <ParmDB/ParmValue.h> #include <ParmDB/SourceDB.h> @@ -153,7 +154,13 @@ namespace LOFAR { void GainCal::show (std::ostream& os) const { os << "GainCal " << itsName << endl; - os << " parmdb: " << itsParmDBName << endl; + os << " parmdb: " << itsParmDBName; + if (Table::isReadable(itsParmDBName)) { + os << " (existing)"; + } else { + os << " (will be created)"; + } + os << endl; os << " solint: " << itsSolInt <<endl; os << " max iter: " << itsMaxIter << endl; os << " tolerance: " << itsTolerance << endl; @@ -806,27 +813,46 @@ namespace LOFAR { if (! itsParmDB) { itsParmDB = boost::shared_ptr<BBS::ParmDB> (new BBS::ParmDB(BBS::ParmDBMeta("casa", itsParmDBName), - true)); + false)); itsParmDB->lock(); // Store the (freq, time) resolution of the solutions. vector<double> resolution(2); resolution[0] = freqWidth[0]; resolution[1] = info().timeInterval() * itsSolInt; itsParmDB->setDefaultSteps(resolution); + string name=(itsMode=="commonscalarphase"?"CommonScalarPhase:*":"Gain:*"); + if (!itsParmDB->getNames(name).empty()) { + DPLOG_WARN_STR ("Solutions for "<<name<<" already in "<<itsParmDBName + <<", these are removed"); + itsParmDB->deleteValues(name, BBS::Box( + freqAxis->start(), tdomAxis->start(), + freqAxis->end(), tdomAxis->end(), true + ) + ); + } } + // Write out default values, if they don't exist yet + ParmMap parmset; + // Write out default amplitudes if (itsMode=="phaseonly" || itsMode=="scalarphase") { - ParmValueSet pvset(ParmValue(1.0)); - itsParmDB->putDefValue("Gain:0:0:Ampl",pvset); - itsParmDB->putDefValue("Gain:1:1:Ampl",pvset); + itsParmDB->getDefValues(parmset, "Gain:0:0:Ampl"); + if (parmset.empty()) { + ParmValueSet pvset(ParmValue(1.0)); + itsParmDB->putDefValue("Gain:0:0:Ampl",pvset); + itsParmDB->putDefValue("Gain:1:1:Ampl",pvset); + } } // Write out default gains if (itsMode=="diagonal" || itsMode=="fulljones") { - ParmValueSet pvset(ParmValue(1.0)); - itsParmDB->putDefValue("Gain:0:0:Real",pvset); - itsParmDB->putDefValue("Gain:1:1:Real",pvset); + itsParmDB->getDefValues(parmset, "Gain:0:0:Real"); + if (parmset.empty()) { + ParmValueSet pvset(ParmValue(1.0)); + itsParmDB->putDefValue("Gain:0:0:Real",pvset); + itsParmDB->putDefValue("Gain:1:1:Real",pvset); + } } // Write the solutions per parameter. @@ -899,7 +925,8 @@ namespace LOFAR { map<string,int>::const_iterator pit = itsParmIdMap.find(name); if (pit == itsParmIdMap.end()) { // First time, so a new nameId will be set. - int nameId = -1; + // Check if the name was defined in the parmdb previously + int nameId = itsParmDB->getNameId(name); itsParmDB->putValues (name, nameId, pvs); itsParmIdMap[name] = nameId; } else { -- GitLab From e39e62a13ba4ab6f33521037d191d58ca0bfd0c9 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 19 May 2016 10:14:47 +0000 Subject: [PATCH 200/933] Task #9344: Backported nicer AOFlaggerStep fix to 2.16 --- .../include/DPPP_AOFlag/AOFlaggerStep.h | 3 +++ CEP/DP3/DPPP_AOFlag/src/AOFlaggerStep.cc | 23 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CEP/DP3/DPPP_AOFlag/include/DPPP_AOFlag/AOFlaggerStep.h b/CEP/DP3/DPPP_AOFlag/include/DPPP_AOFlag/AOFlaggerStep.h index 16b718507c8..082065d7de0 100644 --- a/CEP/DP3/DPPP_AOFlag/include/DPPP_AOFlag/AOFlaggerStep.h +++ b/CEP/DP3/DPPP_AOFlag/include/DPPP_AOFlag/AOFlaggerStep.h @@ -119,6 +119,9 @@ namespace LOFAR { const aoflagger::FlagMask& rfiMask, const aoflagger::FlagMask& origMask, int bl); + // Format a number as kB, MB, etc. + static void formatBytes(std::ostream&, double); + // Fill the rfi strategy. void fillStrategy(); diff --git a/CEP/DP3/DPPP_AOFlag/src/AOFlaggerStep.cc b/CEP/DP3/DPPP_AOFlag/src/AOFlaggerStep.cc index 1ae8210abca..68f6561db7b 100644 --- a/CEP/DP3/DPPP_AOFlag/src/AOFlaggerStep.cc +++ b/CEP/DP3/DPPP_AOFlag/src/AOFlaggerStep.cc @@ -93,7 +93,28 @@ namespace LOFAR { os << " keepstatistics: " << itsDoRfiStats << std::endl; os << " autocorr: " << itsDoAutoCorr << std::endl; os << " nthreads (omp) " << OpenMP::maxThreads() << std::endl; - os << " max memory used " << itsMemoryNeeded << std::endl; + os << " max memory used "; + formatBytes(os, itsMemoryNeeded); + os << std::endl; + } + + void AOFlaggerStep::formatBytes(std::ostream& os, double bytes) { + int exp=0; + while (bytes >= 1024 && exp<5) { + bytes/=1024; + exp++; + } + + uint origPrec=os.precision(); + os.precision(1); + + if (exp==0) { + os<<fixed<<bytes<<" "<<"B"; + } else { + os<<fixed<<bytes<<" "<<"KMGTPE"[exp-1]<<"B"; + } + + os.precision(origPrec); } void AOFlaggerStep::updateInfo (const DPInfo& infoIn) -- GitLab From dd628ae5b2b49d5869f45fb24d71054a6ef2f1eb Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Fri, 20 May 2016 09:23:40 +0000 Subject: [PATCH 201/933] Task #9189: integrated bugfixes 34294, 34305, 34492 from the trunk to the release branch --- SAS/Scheduler/src/Controller.cpp | 18 +++++++++++++----- SAS/Scheduler/src/DataHandler.cpp | 10 ++++++---- SAS/Scheduler/src/GraphicResourceScene.cpp | 6 ++++-- SAS/Scheduler/src/SASConnection.cpp | 19 ++++++++++--------- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/SAS/Scheduler/src/Controller.cpp b/SAS/Scheduler/src/Controller.cpp index 5993c92f810..c93933922fd 100644 --- a/SAS/Scheduler/src/Controller.cpp +++ b/SAS/Scheduler/src/Controller.cpp @@ -1191,11 +1191,19 @@ bool Controller::updateProjects(void) { void Controller::saveSettings(void) { QString filename = gui->fileDialog(tr("Save Settings"), "set", tr("Settings files (*.set)"),1); if (!filename.isEmpty()) { - itsDataHandler->saveSettings(filename); - //set status string - std::string statStr = "Settings saved to file "; - statStr += filename.toStdString(); - gui->setStatusText(statStr.c_str()); + if (itsDataHandler->saveSettings(filename)) + { + //set status string + std::string statStr = "Settings saved to file "; + statStr += filename.toStdString(); + gui->setStatusText(statStr.c_str()); + } + else { + //set status string + std::string statStr = "Failed to save settings to file "; + statStr += filename.toStdString(); + gui->setStatusText(statStr.c_str()); + } } } diff --git a/SAS/Scheduler/src/DataHandler.cpp b/SAS/Scheduler/src/DataHandler.cpp index bbd0d5908b9..195d4bfe844 100644 --- a/SAS/Scheduler/src/DataHandler.cpp +++ b/SAS/Scheduler/src/DataHandler.cpp @@ -521,18 +521,20 @@ bool DataHandler::saveProgramPreferences(void) { bool DataHandler::saveSettings(const QString &filename) const { - QFile file(QDir::currentPath() + filename); + QFile file(filename); if (file.open(QIODevice::WriteOnly)) { QDataStream out(&file); out << (unsigned)FILE_WRITE_VERSION; out << Controller::theSchedulerSettings; file.close(); - debugInfo("ss","Wrote settings to file: ", filename.toStdString().c_str()); + debugInfo("ss","Wrote settings to file: ", (filename.toStdString().c_str())); return true; } - else - return false; + else { + debugInfo("ss","Failed to write to file: ", (filename.toStdString().c_str())); + return false; + } } bool DataHandler::loadSettings(const QString &filename) { diff --git a/SAS/Scheduler/src/GraphicResourceScene.cpp b/SAS/Scheduler/src/GraphicResourceScene.cpp index bdbb1c34557..be65553f2c9 100644 --- a/SAS/Scheduler/src/GraphicResourceScene.cpp +++ b/SAS/Scheduler/src/GraphicResourceScene.cpp @@ -225,13 +225,15 @@ void GraphicResourceScene::updateStationTimeLines() { GraphicStationTaskLine *stationTimeLine = new GraphicStationTaskLine(this, it->second, StationLineYPos); addItem(stationTimeLine); // create station label - QGraphicsSimpleTextItem *stationName = new QGraphicsSimpleTextItem(it->first.c_str(), 0); + QGraphicsSimpleTextItem *stationName = new QGraphicsSimpleTextItem(it->first.c_str(), NULL); + this->addItem(stationName); stationName->setPos(labelXpos, StationLineYPos-2); stationName->setFont(QFont("Liberation Sans", 9, QFont::Bold)); stationName->setZValue(10); const QPointF &sp(stationName->pos()); QRectF r(sp.x()-2, sp.y()-2, 9*(it->first.length()-1)+4, 13); - QGraphicsRectItem * rect = new QGraphicsRectItem(r, 0); + QGraphicsRectItem * rect = new QGraphicsRectItem(r, NULL); + this->addItem(rect); rect->setZValue(9); rect->setPen(QPen(Qt::NoPen)); rect->setBrush(QColor(255,255,255,160)); diff --git a/SAS/Scheduler/src/SASConnection.cpp b/SAS/Scheduler/src/SASConnection.cpp index a1a2a39dbd7..c33f4dd2729 100644 --- a/SAS/Scheduler/src/SASConnection.cpp +++ b/SAS/Scheduler/src/SASConnection.cpp @@ -2556,16 +2556,17 @@ bool SASConnection::saveStationSettings(int treeID, const StationTask &task, con } // nr of dataslots per RSP board (called 'nrSlotsInFrame' in SAS) // bResult &= setNodeValue(treeID, "LOFAR.ObsSW.Observation.nrSlotsInFrame", QString::number(task.getNrOfDataslotsPerRSPboard())); + //This is nonsense code as diff = NULL here, it somehow works under certain compilers because the dynamic_cast fails (obs=NULL) // TBB piggyback allowed? - const Observation *obs = dynamic_cast<const Observation *>(&task); - if (obs) { - if (diff->TBBPiggybackAllowed) - bResult &= setNodeValue(treeID, "LOFAR.ObsSW.Observation.ObservationControl.StationControl.tbbPiggybackAllowed", - (obs->getTBBPiggybackAllowed() ? "true" : "false")); - if (diff->AartfaacPiggybackAllowed) - bResult &= setNodeValue(treeID, "LOFAR.ObsSW.Observation.ObservationControl.StationControl.aartfaacPiggybackAllowed", - (obs->getAartfaacPiggybackAllowed() ? "true" : "false")); - } + //const Observation *obs = dynamic_cast<const Observation *>(&task); + //if (obs) { + // if (diff->TBBPiggybackAllowed) + // bResult &= setNodeValue(treeID, "LOFAR.ObsSW.Observation.ObservationControl.StationControl.tbbPiggybackAllowed", + // (obs->getTBBPiggybackAllowed() ? "true" : "false")); + // if (diff->AartfaacPiggybackAllowed) + // bResult &= setNodeValue(treeID, "LOFAR.ObsSW.Observation.ObservationControl.StationControl.aartfaacPiggybackAllowed", + // (obs->getAartfaacPiggybackAllowed() ? "true" : "false")); + //} } return bResult; -- GitLab From 374fc8c087ce4dc7161d50f732e978a53bfd482a Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Fri, 20 May 2016 09:43:10 +0000 Subject: [PATCH 202/933] Task #9189: Merged release 2.16.1 and 2.16.3 of XMLgen with release branch. This fixes a problem in the multiple pipelines. --- SAS/XML_generator/src/xmlgen.py | 73 ++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/SAS/XML_generator/src/xmlgen.py b/SAS/XML_generator/src/xmlgen.py index 04884462d4f..208236639f4 100755 --- a/SAS/XML_generator/src/xmlgen.py +++ b/SAS/XML_generator/src/xmlgen.py @@ -1,8 +1,35 @@ #! /usr/bin/env python - -# XML generator prototype - -VERSION = "2.15.0" +# XML generator +# xmlgen.py +# +# Copyright (C) 2016 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# Author : Alwin de Jong, Adriaan Renting +# e-mail : softwaresupport@astron.nl +# Revision : $Revision: 34492 $ +# Last change by : $Author: renting $ +# Change date : $Date: 2016-05-18 11:47:57 +0200 (wo, 18 mei 2016) $ +# First creation : unknown +# URL : $URL: https://svn.astron.nl/ROD/trunk/LOFAR_Scheduler/DataHandler.cpp $ + + +VERSION = "2.16.3" import sys, getopt, time from xml.sax.saxutils import escape as XMLescape @@ -259,7 +286,7 @@ def processingCluster(cluster, number_of_tasks=244): CEP4 = r""" <processingCluster> <name>CEP4</name> - <partition>/data</partition> + <partition>/data/projects/</partition> <numberOfTasks>%i</numberOfTasks> <minRAMPerTask unit="byte">1000000000</minRAMPerTask> <minScratchPerTask unit="byte">100000000</minScratchPerTask> @@ -281,7 +308,7 @@ def dataProductCluster(cluster): </storageCluster>""" CEP4 = r"""<storageCluster> <name>CEP4</name> - <partition>/data</partition> + <partition>/data/projects/</partition> </storageCluster>""" if cluster == "CEP4": @@ -1060,16 +1087,25 @@ def readImagingBBS(value): imagingBBS[2] = toBool(imagingBBS[2]) return imagingBBS +def checkDemixMultiples(avg_freq_step, avg_time_step, demix_freq_step, demix_time_step, name): + try: + if avg_freq_step and demix_freq_step: + if int(demix_freq_step) % int(avg_freq_step) <> 0: + raise GenException("demixFreqStep (%s) should be integer multiple of averagingFreqStep (%s) for %s" % (demix_freq_step, avg_freq_step, name)) + if avg_time_step and demix_time_step: + if int(demix_time_step) % int(avg_time_step) <> 0: + raise GenException("demixTimeStep (%s) should be integer multiple of averagingTimeStep (%s) for %s" % (demix_time_step, avg_time_step, name)) + except: + raise GenException("I can't read the Demix values for %s" % name) + def readGlobalDemix(value): globalDemix = ['','','','','','',''] if value: valList = value.split(';') for i in range(0,len(valList)): globalDemix[i] = valList[i] - if (globalDemix[0] != '') and (globalDemix[2] != ''): - if int(globalDemix[2]) % int(globalDemix[0]) <> 0: #TODO try ? - raise GenException("demixFreqStep (" + globalDemix[2] + ") should be integer multiple of averagingFreqStep (" + globalDemix[0] + ") for globalDemix") - globalDemix[6] = toBool(globalDemix[6]) # convert ignoreTarget to bool + checkDemixMultiples(globalDemix[0], globalDemix[1], globalDemix[2], globalDemix[3], "globalDemix") + globalDemix[6] = toBool(globalDemix[6]) # convert ignoreTarget to bool return globalDemix def readGlobalPulsar(value): @@ -1199,7 +1235,7 @@ def readCalibratorBeam(startLine, lines, globalSubbands, globalTABrings, globalB calibratorDemix = [] for pipeline in pipelines: if pipeline.startswith("BBS"): - calibratorBBS.append(BBSDefault) + calibratorBBS.append(BBSDefault[:]) # [:] is needed to make a deep copy calBBS = readExtraParms("BBS", [pipeline]) if len(calBBS) > 0: for i in range(0,len(calBBS)): @@ -1211,14 +1247,12 @@ def readCalibratorBeam(startLine, lines, globalSubbands, globalTABrings, globalB calibratorBBS[-1][i] = globalBBS[i] if pipeline.startswith("Demix"): - calibratorDemix.append(DemixDefault) + calibratorDemix.append(DemixDefault[:]) # [:] is needed to make a deep copy calDemix = readExtraParms("Demix", [pipeline]) if len(calDemix) > 0: for i in range(0,len(calDemix)): calibratorDemix[-1][i] = calDemix[i] - if (calibratorDemix[-1][0] != '') and (calibratorDemix[-1][2] != ''): - if int(calibratorDemix[-1][2]) % int(calibratorDemix[-1][0]) <> 0: - raise GenException("demixFreqStep (" + calibratorDemix[-1][2] + ") should be integer multiple of averagingFreqStep (" + calibratorDemix[-1][0] + ") for calibrator beam pipeline") + checkDemixMultiples(calibratorDemix[-1][0], calibratorDemix[-1][1], calibratorDemix[-1][2], calibratorDemix[-1][3], "calibratorDemix") calibratorDemix[-1][6] = toBool(calibratorDemix[-1][6]) elif globalDemix != []: printInfo('Using global demix settings for Calibrator beam pipeline') @@ -1325,26 +1359,25 @@ def readTargetBeams(startLine, lines, globalSubbands, globalBBS, globalDemix, gl if targetBeams[nr_beams][7]: # pipeline created? for pipeline in pipelines: if pipeline.startswith("BBS"): - targetBBS[nr_beams].append(BBSDefault) + targetBBS[nr_beams].append(BBSDefault[:]) # [:] is needed to make a deep copy tarBBS = readExtraParms("BBS", [pipeline]) for i in range(0, len(tarBBS)): targetBBS[nr_beams][-1][i] = tarBBS[i] targetBBS[nr_beams][-1][3] = toBool(targetBBS[nr_beams][-1][3]) if pipeline.startswith("Demix"): - targetDemix[nr_beams].append(DemixDefault) + targetDemix[nr_beams].append(DemixDefault[:]) # [:] is needed to make a deep copy tarDemix = readExtraParms("Demix", [pipeline]) if len(tarDemix) >= 4: for i in range(0,len(tarDemix)): targetDemix[nr_beams][-1][i] = tarDemix[i] - if int(targetDemix[nr_beams][-1][2]) % int(targetDemix[nr_beams][-1][0]) <> 0: - raise GenException("demixFreqStep (" + targetDemix[nr_beams][-1][2] + ") should be integer multiple of averagingFreqStep (" + targetDemix[nr_beams][-1][0] + "), target beam pipeline:" + str(nr_beams)) + checkDemixMultiples(targetDemix[nr_beams][-1][0], targetDemix[nr_beams][-1][1], targetDemix[nr_beams][-1][2], targetDemix[nr_beams][-1][3], "targetDemix[%i]" % nr_beams) targetDemix[nr_beams][-1][6] = toBool(targetDemix[nr_beams][-1][6]) # convert ignoreTarget to bool elif len(tarDemix) > 0: raise GenException("Demixing parameters should at least have the first four averaging/demixing steps (block %s, targetBeam %s)" % (blockNr, nr_beams)) if pipeline.startswith("Pulsar"): - targetPulsar[nr_beams].append(PulsarDefault) + targetPulsar[nr_beams].append(PulsarDefault[:]) # [:] is needed to make a deep copy tarPulsar = readExtraParms("Pulsar", [pipeline]) if len(tarPulsar) > 0: for i in range(0,len(tarPulsar)): -- GitLab From 4d988bdc82d1481280885568d372c16141f671a8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 20 May 2016 10:28:12 +0000 Subject: [PATCH 203/933] Task #9349: patch to get rpc from python to java (JMS) working --- LCS/Messaging/python/messaging/RPC.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LCS/Messaging/python/messaging/RPC.py b/LCS/Messaging/python/messaging/RPC.py index 4262ce36e0c..c02143c4d98 100644 --- a/LCS/Messaging/python/messaging/RPC.py +++ b/LCS/Messaging/python/messaging/RPC.py @@ -151,6 +151,9 @@ class RPC(): Reply = FromBus("%s ; %s" %(ReplyAddress,str(options)), broker=self.broker) else: Reply = FromBus("%s/%s" % (self.BusName, ReplyAddress), broker=self.broker) + # supply fully specified reply address including '{node:{type:topic}}' specification so handlers like JMS can handle reply address + ReplyAddress = "%s/%s ;{node:{type:topic}}" % (self.BusName, ReplyAddress) + with Reply: MyMsg = RequestMessage(content=Content, reply_to=ReplyAddress, has_args=HasArgs, has_kwargs=HasKwArgs) MyMsg.ttl = timeout -- GitLab From 21fd54505fb23dfdaf4a8dad88f98f56c505d922 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 20 May 2016 10:29:06 +0000 Subject: [PATCH 204/933] Task #9349: added little cmdline script to copy a task in mom (for testing and convenience) --- SAS/MoM/MoMQueryService/CMakeLists.txt | 2 +- SAS/MoM/MoMQueryService/config.py | 2 +- SAS/MoM/MoMQueryService/momrpc.py | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/SAS/MoM/MoMQueryService/CMakeLists.txt b/SAS/MoM/MoMQueryService/CMakeLists.txt index 7e6a3118ab4..b16906069d3 100644 --- a/SAS/MoM/MoMQueryService/CMakeLists.txt +++ b/SAS/MoM/MoMQueryService/CMakeLists.txt @@ -14,7 +14,7 @@ set(_py_files python_install(${_py_files} DESTINATION lofar/mom/momqueryservice) -lofar_add_bin_scripts(momqueryservice momquery) +lofar_add_bin_scripts(momqueryservice momquery momcopytask) # supervisord config files install(FILES diff --git a/SAS/MoM/MoMQueryService/config.py b/SAS/MoM/MoMQueryService/config.py index 00986504667..7067fe98593 100644 --- a/SAS/MoM/MoMQueryService/config.py +++ b/SAS/MoM/MoMQueryService/config.py @@ -6,5 +6,5 @@ from lofar.messaging import adaptNameToEnvironment DEFAULT_MOMQUERY_BUSNAME = adaptNameToEnvironment('lofar.ra.command') DEFAULT_MOMQUERY_SERVICENAME = 'momqueryservice' -DEFAULT_MOM_BUSNAME = adaptNameToEnvironment('lofar.mom.bus') +DEFAULT_MOM_BUSNAME = adaptNameToEnvironment('lofar.mom.command') DEFAULT_MOM_SERVICENAME = '' diff --git a/SAS/MoM/MoMQueryService/momrpc.py b/SAS/MoM/MoMQueryService/momrpc.py index 94d01c9aeaa..30b858f55a5 100644 --- a/SAS/MoM/MoMQueryService/momrpc.py +++ b/SAS/MoM/MoMQueryService/momrpc.py @@ -4,11 +4,15 @@ import sys import logging from optparse import OptionParser from lofar.messaging.RPC import RPC, RPCException, RPCWrapper +from lofar.messaging import setQpidLogLevel from lofar.mom.momqueryservice.config import DEFAULT_MOM_BUSNAME, DEFAULT_MOM_SERVICENAME logger = logging.getLogger(__file__) class MoMRPC(RPCWrapper): + def __init__(self, busname=DEFAULT_MOM_BUSNAME, broker=None, timeout=10, verbose=False): + super(MoMRPC, self).__init__(busname=busname, servicename='', broker=broker, timeout=timeout, verbose=verbose) + def copyTask(self, mom2id, newTaskName=None, newTaskDescription=None): logger.info("calling copyTask rpc for mom2id %s" % (mom2id)) new_task_mom2id = self.rpc('TaskCopy', mom2Id=mom2id) #, newTaskName=newTaskName, newTaskDescription=newTaskDescription) @@ -21,7 +25,6 @@ def main(): description='do rpc calls to the momservice from the commandline') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') parser.add_option('-b', '--busname', dest='busname', type='string', default=DEFAULT_MOM_BUSNAME, help='Name of the bus exchange on the qpid broker [default: %default]') - parser.add_option('-s', '--servicename', dest='servicename', type='string', default=DEFAULT_MOM_SERVICENAME, help='Name for this service [default: %default]') parser.add_option('--mom2id', dest='mom2id_to_copy', type='int', help='[REQUIRED] mom2id of the task to copy.') parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() @@ -34,8 +37,9 @@ def main(): logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.DEBUG if verbose else logging.INFO) + setQpidLogLevel(logging.WARN) - with MoMRPC(busname=options.busname, servicename=options.servicename, broker=options.broker, verbose=verbose) as rpc: + with MoMRPC(busname=options.busname, broker=options.broker, verbose=verbose) as rpc: print rpc.copyTask(options.mom2id_to_copy) if __name__ == '__main__': -- GitLab From be7f9a10f30a3678340ff5584b5167459ac589a1 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 20 May 2016 10:41:28 +0000 Subject: [PATCH 205/933] Task #9349: added little cmdline script to copy a task in mom (for testing and convenience) --- .gitattributes | 1 + SAS/MoM/MoMQueryService/momcopytask | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 SAS/MoM/MoMQueryService/momcopytask diff --git a/.gitattributes b/.gitattributes index 72c85ecef7d..e21519f0472 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4724,6 +4724,7 @@ SAS/MoM/CMakeLists.txt -text SAS/MoM/MoMQueryService/CMakeLists.txt -text SAS/MoM/MoMQueryService/__init__.py -text SAS/MoM/MoMQueryService/config.py -text +SAS/MoM/MoMQueryService/momcopytask -text SAS/MoM/MoMQueryService/momquery -text SAS/MoM/MoMQueryService/momqueryrpc.py -text SAS/MoM/MoMQueryService/momqueryservice -text diff --git a/SAS/MoM/MoMQueryService/momcopytask b/SAS/MoM/MoMQueryService/momcopytask new file mode 100644 index 00000000000..74208d5e5b9 --- /dev/null +++ b/SAS/MoM/MoMQueryService/momcopytask @@ -0,0 +1,11 @@ +#!/usr/bin/python +# $Id: $ + +''' +performs an rpc call to the mom copy task service +''' + +from lofar.mom.momqueryservice.momrpc import main + +if __name__ == '__main__': + main() -- GitLab From 13af045c137516311bae01f8e8a4e2cd375eec59 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 20 May 2016 10:47:52 +0000 Subject: [PATCH 206/933] Task #9349: fixed instantiating momrpc --- .../ResourceAssignmentEditor/lib/webservice.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 8d242a136cb..b79a7c507ad 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -50,7 +50,7 @@ from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAUL from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME from lofar.mom.momqueryservice.momrpc import MoMRPC -from lofar.mom.momqueryservice.config import DEFAULT_MOM_BUSNAME, DEFAULT_MOM_SERVICENAME +from lofar.mom.momqueryservice.config import DEFAULT_MOM_BUSNAME from lofar.sas.resourceassignment.resourceassignmenteditor.mom import updateTaskMomDetails from lofar.common import isProductionEnvironment, isTestEnvironment #from lofar.sas.resourceassignment.resourceassigner. import updateTaskMomDetails @@ -360,7 +360,6 @@ def main(): parser.add_option('--radb_notification_busname', dest='radb_notification_busname', type='string', default=DEFAULT_RADB_CHANGES_BUSNAME, help='Name of the notification bus exchange on the qpid broker on which the radb notifications are published, default: %default') parser.add_option('--radb_notification_subjects', dest='radb_notification_subjects', type='string', default=DEFAULT_RADB_CHANGES_SUBJECTS, help='Subject(s) to listen for on the radb notification bus exchange on the qpid broker, default: %default') parser.add_option('--mom_busname', dest='mom_busname', type='string', default=DEFAULT_MOM_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momservice listens, default: %default') - parser.add_option('--mom_servicename', dest='mom_servicename', type='string', default=DEFAULT_MOM_SERVICENAME, help='Name of the momservice, default: %default') parser.add_option('--mom_broker', dest='mom_broker', type='string', default=None, help='Address of the qpid broker for the mom service, default: localhost') parser.add_option('--mom_query_busname', dest='mom_query_busname', type='string', default=DEFAULT_MOMQUERY_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momqueryservice listens, default: %default') parser.add_option('--mom_query_servicename', dest='mom_query_servicename', type='string', default=DEFAULT_MOMQUERY_SERVICENAME, help='Name of the momqueryservice, default: %default') @@ -373,7 +372,7 @@ def main(): global rarpc rarpc = RARPC(busname=options.radb_busname, servicename=options.radb_servicename, broker=options.broker) global momrpc - momrpc = MoMRPC(busname=options.mom_busname, servicename=options.mom_servicename, broker=options.mom_broker) + momrpc = MoMRPC(busname=options.mom_busname, broker=options.mom_broker) global momqueryrpc momqueryrpc = MoMQueryRPC(busname=options.mom_query_busname, servicename=options.mom_query_servicename, timeout=2.5, broker=options.broker) global radbchangeshandler -- GitLab From f70e85b080a8364d2dd9a5fdf478a0dab4d6fba1 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 20 May 2016 14:33:58 +0000 Subject: [PATCH 207/933] Task #9349: make context menu direct child of body, and set position based on mouse click event. This way all styles and overflows are handled better --- .../angular-gantt-contextmenu-plugin.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index f4fc168cedc..18c7a7e29b6 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -27,6 +27,7 @@ dElement.bind('contextmenu', function(event) { //TODO: remove link to dataService in this generic plugin var dataService = dScope.scope.dataService; + var docElement = angular.element($document); if(dScope.task.model.raTask) { dataService.selected_task_id = dScope.task.model.raTask.id; @@ -38,15 +39,15 @@ $document.find('#gantt-context-menu')[0].remove(); //unbind document close event handlers - angular.element($document).unbind('click', closeContextMenu); - angular.element($document).unbind('contextmenu', closeContextMenu); + docElement.unbind('click', closeContextMenu); + docElement.unbind('contextmenu', closeContextMenu); } //create contextmenu element //with list of menu items, //each with it's own action var contextmenuElement = angular.element('<div id="gantt-context-menu"></div>'); - var ulElement = angular.element('<ul style="z-index:10000; position:absolute; top:initial; left:initial; display:block;" role="menu" class="dropdown-menu"></ul>'); + var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>'); contextmenuElement.append(ulElement); var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); ulElement.append(liElement); @@ -60,16 +61,17 @@ contextmenuElement.remove(); //unbind document close event handlers - angular.element($document).unbind('click', closeContextMenu); - angular.element($document).unbind('contextmenu', closeContextMenu); + docElement.unbind('click', closeContextMenu); + docElement.unbind('contextmenu', closeContextMenu); }; //click anywhere to remove the contextmenu - angular.element($document).bind('click', closeContextMenu); - angular.element($document).bind('contextmenu', closeContextMenu); + docElement.bind('click', closeContextMenu); + docElement.bind('contextmenu', closeContextMenu); - //add contextmenu to clicked element - dElement.append(contextmenuElement); + //add contextmenu to body + var body = $document.find('body'); + body.append(contextmenuElement); //prevent bubbling event upwards return false; -- GitLab From da643b0d70c11dc101c1c0d18ddd18e3cce9890c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 20 May 2016 14:38:35 +0000 Subject: [PATCH 208/933] Task #9349: make context menu direct child of body, and set position based on mouse click event. This way all styles and overflows are handled better --- .../static/app/controllers/gridcontroller.js | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 405a9fd439b..081ea2bd50c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -252,21 +252,23 @@ gridControllerMod.directive('contextMenu', ['$document', function($document) { var task = dataService.taskDict[taskId]; dataService.selected_task_id = taskId; + var docElement = angular.element($document); + //search for already existing contextmenu element while($document.find('#grid-context-menu').length) { //found, remove it, so we can create a fresh one $document.find('#grid-context-menu')[0].remove(); //unbind document close event handlers - angular.element($document).unbind('click', closeContextMenu); - angular.element($document).unbind('contextmenu', closeContextMenu); + docElement.unbind('click', closeContextMenu); + docElement.unbind('contextmenu', closeContextMenu); } //create contextmenu element //with list of menu items, //each with it's own action var contextmenuElement = angular.element('<div id="grid-context-menu"></div>'); - var ulElement = angular.element('<ul style="z-index:10000; position:absolute; top:initial; left:initial; display:block;" role="menu" class="dropdown-menu"></ul>'); + var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>'); contextmenuElement.append(ulElement); var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); ulElement.append(liElement); @@ -279,16 +281,17 @@ gridControllerMod.directive('contextMenu', ['$document', function($document) { contextmenuElement.remove(); //unbind document close event handlers - angular.element($document).unbind('click', closeContextMenu); - angular.element($document).unbind('contextmenu', closeContextMenu); + docElement.unbind('click', closeContextMenu); + docElement.unbind('contextmenu', closeContextMenu); }; //click anywhere to remove the contextmenu - angular.element($document).bind('click', closeContextMenu); - angular.element($document).bind('contextmenu', closeContextMenu); + docElement.bind('click', closeContextMenu); + docElement.bind('contextmenu', closeContextMenu); - //add contextmenu to clicked element - $element.append(contextmenuElement); + //add contextmenu to body + var body = $document.find('body'); + body.append(contextmenuElement); //prevent bubbling event upwards return false; -- GitLab From 5f9850ac83976cd309a3f6fe1ebce61c78053ac1 Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Sat, 21 May 2016 12:04:27 +0000 Subject: [PATCH 209/933] Task #8887: Fixed bug in CMakeLists.txt. The add_subdirectory() command MUST be placed AFTER the python_install() command. Otherwise __init__.py will not be properly symlinked, which will result in failing tests, because 'import lofar.common' will import nothing. --- LCS/PyCommon/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LCS/PyCommon/CMakeLists.txt b/LCS/PyCommon/CMakeLists.txt index c3803fdaec5..f67fdc9b785 100644 --- a/LCS/PyCommon/CMakeLists.txt +++ b/LCS/PyCommon/CMakeLists.txt @@ -5,8 +5,6 @@ lofar_package(PyCommon 1.0) lofar_find_package(Python 2.6 REQUIRED) include(PythonInstall) -add_subdirectory(test) - set(_py_files __init__.py dbcredentials.py @@ -16,3 +14,5 @@ set(_py_files datetimeutils.py) python_install(${_py_files} DESTINATION lofar/common) + +add_subdirectory(test) -- GitLab From 0ff4841f808e4a5d1ce30dd31c0591a58b120825 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 23 May 2016 10:22:56 +0000 Subject: [PATCH 210/933] Task #8392: feature branch for ingest@cep4 -- GitLab From 5241b859bf8ea822794afb8d0398fd077d7117cc Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Tue, 24 May 2016 11:29:33 +0000 Subject: [PATCH 211/933] Task #9462: create task branch -- GitLab From f2ab691cf62a7c76dfcba80074e330406639f404 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Tue, 24 May 2016 11:30:54 +0000 Subject: [PATCH 212/933] Task #9462: gaincal and applycal improvements --- CEP/DP3/DPPP/include/DPPP/ApplyCal.h | 16 +- CEP/DP3/DPPP/include/DPPP/GainCal.h | 22 ++- CEP/DP3/DPPP/src/ApplyCal.cc | 214 ++++++++++++--------------- CEP/DP3/DPPP/src/GainCal.cc | 195 +++++++++++++++--------- CEP/DP3/DPPP/test/tGainCal.run | 9 ++ 5 files changed, 250 insertions(+), 206 deletions(-) diff --git a/CEP/DP3/DPPP/include/DPPP/ApplyCal.h b/CEP/DP3/DPPP/include/DPPP/ApplyCal.h index 19508cef2a2..cc11330a97b 100644 --- a/CEP/DP3/DPPP/include/DPPP/ApplyCal.h +++ b/CEP/DP3/DPPP/include/DPPP/ApplyCal.h @@ -78,15 +78,19 @@ namespace LOFAR { // Invert a 2x2 matrix in place static void invert (casa::DComplex* v, double sigmaMMSE=0); - private: // Apply a diagonal Jones matrix to the 2x2 visibilities matrix: A.V.B^H - void applyDiag (casa::Complex* vis, float* weight, bool* flag, - uint bl, int chan, int time); + static void applyDiag (casa::DComplex* gainA, casa::DComplex* gainB, + casa::Complex* vis, float* weight, bool* flag, + uint bl, uint chan, bool updateWeights, + FlagCounter& flagCounter); // Apply a full Jones matrix to the 2x2 visibilities matrix: A.V.B^H - void applyFull (casa::Complex* vis, float* weight, bool* flag, - uint bl, int chan, int time); + static void applyFull (casa::DComplex* gainA, casa::DComplex* gainB, + casa::Complex* vis, float* weight, bool* flag, + uint bl, uint chan, bool updateWeights, + FlagCounter& flagCounter); + private: // Read parameters from the associated parmdb and store them in itsParms void updateParms (const double bufStartTime); @@ -113,7 +117,7 @@ namespace LOFAR { vector<casa::String> itsParmExprs; // parameters, numparms, antennas, time x frequency - vector<vector<vector<casa::DComplex> > > itsParms; + casa::Cube<casa::DComplex> itsParms; uint itsTimeStep; // time step within current chunk uint itsNCorr; double itsTimeInterval; diff --git a/CEP/DP3/DPPP/include/DPPP/GainCal.h b/CEP/DP3/DPPP/include/DPPP/GainCal.h index c2e2344c9b0..1d83b99da49 100644 --- a/CEP/DP3/DPPP/include/DPPP/GainCal.h +++ b/CEP/DP3/DPPP/include/DPPP/GainCal.h @@ -96,8 +96,14 @@ namespace LOFAR { void removeDeadAntennas (); // Fills the matrices itsVis and itsMVis - void fillMatrices (casa::Complex* model, casa::Complex* data, float* weight, - const casa::Bool* flag); + void fillMatrices (casa::Complex* model, casa::Complex* data, + float* weight, const casa::Bool* flag); + + // Initialize the parmdb + void initParmDB(); + + // Write out the solutions of the current parameter chunk (timeslotsperparmupdate) + void writeSolutions (double startTime); //# Data members. DPInput* itsInput; @@ -109,7 +115,6 @@ namespace LOFAR { shared_ptr<BBS::ParmDB> itsParmDB; string itsMode; - uint itsTStep; uint itsDebugLevel; bool itsDetectStalling; @@ -130,16 +135,19 @@ namespace LOFAR { uint itsMaxIter; double itsTolerance; - bool itsPropagateSolutions; - uint itsSolInt; - uint itsNChan; + bool itsPropagateSolutions; // Not used currently, TODO: use this + uint itsSolInt; // Time cell size + uint itsNChan; // Frequency cell size uint itsNFreqCells; uint itsMinBLperAnt; + uint itsTimeSlotsPerParmUpdate; uint itsConverged; uint itsNonconverged; uint itsStalled; - uint itsNTimes; + uint itsTimeStep; // Timestep within parameter update + double itsChunkStartTime; // First time value of chunk to be stored + uint itsNTimes; // Timestep within solint NSTimer itsTimer; NSTimer itsTimerPredict; NSTimer itsTimerSolve; diff --git a/CEP/DP3/DPPP/src/ApplyCal.cc b/CEP/DP3/DPPP/src/ApplyCal.cc index e161f96e9f1..337c6476cd0 100644 --- a/CEP/DP3/DPPP/src/ApplyCal.cc +++ b/CEP/DP3/DPPP/src/ApplyCal.cc @@ -255,17 +255,24 @@ namespace LOFAR { #pragma omp parallel for for (size_t bl=0; bl<nbl; ++bl) { for (size_t chan=0;chan<nchan;chan++) { - if (itsParms.size()>2) { - applyFull( &data[bl * itsNCorr * nchan + chan * itsNCorr ], - &weight[bl * itsNCorr * nchan + chan * itsNCorr ], - &flag[ bl * itsNCorr * nchan + chan * itsNCorr ], - bl, chan, itsTimeStep); + uint timeFreqOffset=(itsTimeStep*info().nchan())+chan; + uint antA = info().getAnt1()[bl]; + uint antB = info().getAnt2()[bl]; + if (itsParms.shape()[0]>2) { + applyFull( &itsParms(0, antA, timeFreqOffset), + &itsParms(0, antB, timeFreqOffset), + &data[bl * itsNCorr * nchan + chan * itsNCorr ], + &weight[bl * itsNCorr * nchan + chan * itsNCorr ], + &flag[ bl * itsNCorr * nchan + chan * itsNCorr ], + bl, chan, itsUpdateWeights, itsFlagCounter); } else { - applyDiag( &data[bl * itsNCorr * nchan + chan * itsNCorr ], - &weight[bl * itsNCorr * nchan + chan * itsNCorr ], - &flag[ bl * itsNCorr * nchan + chan * itsNCorr ], - bl, chan, itsTimeStep); + applyDiag( &itsParms(0, antA, timeFreqOffset), + &itsParms(0, antB, timeFreqOffset), + &data[bl * itsNCorr * nchan + chan * itsNCorr ], + &weight[bl * itsNCorr * nchan + chan * itsNCorr ], + &flag[ bl * itsNCorr * nchan + chan * itsNCorr ], + bl, chan, itsUpdateWeights, itsFlagCounter); } } } @@ -289,7 +296,7 @@ namespace LOFAR { int numAnts = info().antennaNames().size(); // itsParms contains the parameters to a grid, first for all parameters - // (e.g. Gain:0:0 and Gain:1:1), next all antennas, next over frec * time + // (e.g. Gain:0:0 and Gain:1:1), next all antennas, next over freq * time // as returned by ParmDB vector<vector<vector<double> > > parmvalues; parmvalues.resize(itsParmExprs.size()); @@ -375,59 +382,59 @@ namespace LOFAR { if (itsCorrectType=="gain") { if (itsUseAP) { // Data as Amplitude / Phase - itsParms[0][ant][tf] = polar(parmvalues[0][ant][tf], - parmvalues[1][ant][tf]); - itsParms[1][ant][tf] = polar(parmvalues[2][ant][tf], - parmvalues[3][ant][tf]); + itsParms(0, ant, tf) = polar(parmvalues[0][ant][tf], + parmvalues[1][ant][tf]); + itsParms(1, ant, tf) = polar(parmvalues[2][ant][tf], + parmvalues[3][ant][tf]); } else { // Data as Real / Imaginary - itsParms[0][ant][tf] = DComplex(parmvalues[0][ant][tf], - parmvalues[1][ant][tf]); - itsParms[1][ant][tf] = DComplex(parmvalues[2][ant][tf], - parmvalues[3][ant][tf]); + itsParms(0, ant, tf) = DComplex(parmvalues[0][ant][tf], + parmvalues[1][ant][tf]); + itsParms(1, ant, tf) = DComplex(parmvalues[2][ant][tf], + parmvalues[3][ant][tf]); } } else if (itsCorrectType=="fulljones") { if (itsUseAP) { // Data as Amplitude / Phase - itsParms[0][ant][tf] = polar(parmvalues[0][ant][tf], - parmvalues[1][ant][tf]); - itsParms[1][ant][tf] = polar(parmvalues[2][ant][tf], - parmvalues[3][ant][tf]); - itsParms[2][ant][tf] = polar(parmvalues[4][ant][tf], - parmvalues[5][ant][tf]); - itsParms[3][ant][tf] = polar(parmvalues[6][ant][tf], - parmvalues[7][ant][tf]); + itsParms(0, ant, tf) = polar(parmvalues[0][ant][tf], + parmvalues[1][ant][tf]); + itsParms(1, ant, tf) = polar(parmvalues[2][ant][tf], + parmvalues[3][ant][tf]); + itsParms(2, ant, tf) = polar(parmvalues[4][ant][tf], + parmvalues[5][ant][tf]); + itsParms(3, ant, tf) = polar(parmvalues[6][ant][tf], + parmvalues[7][ant][tf]); } else { // Data as Real / Imaginary - itsParms[0][ant][tf] = DComplex(parmvalues[0][ant][tf], - parmvalues[1][ant][tf]); - itsParms[1][ant][tf] = DComplex(parmvalues[2][ant][tf], - parmvalues[3][ant][tf]); - itsParms[2][ant][tf] = DComplex(parmvalues[4][ant][tf], - parmvalues[5][ant][tf]); - itsParms[3][ant][tf] = DComplex(parmvalues[6][ant][tf], - parmvalues[7][ant][tf]); + itsParms(0, ant, tf) = DComplex(parmvalues[0][ant][tf], + parmvalues[1][ant][tf]); + itsParms(1, ant, tf) = DComplex(parmvalues[2][ant][tf], + parmvalues[3][ant][tf]); + itsParms(2, ant, tf) = DComplex(parmvalues[4][ant][tf], + parmvalues[5][ant][tf]); + itsParms(3, ant, tf) = DComplex(parmvalues[6][ant][tf], + parmvalues[7][ant][tf]); } } else if (itsCorrectType=="tec") { - itsParms[0][ant][tf]=polar(1., + itsParms(0, ant, tf)=polar(1., parmvalues[0][ant][tf] * -8.44797245e9 / freq); if (itsParmExprs.size() == 1) { // No TEC:0, only TEC: - itsParms[1][ant][tf]=polar(1., + itsParms(1, ant, tf)=polar(1., parmvalues[0][ant][tf] * -8.44797245e9 / freq); } else { // TEC:0 and TEC:1 - itsParms[1][ant][tf]=polar(1., + itsParms(1, ant, tf)=polar(1., parmvalues[1][ant][tf] * -8.44797245e9 / freq); } } else if (itsCorrectType=="clock") { - itsParms[0][ant][tf]=polar(1., + itsParms(0, ant, tf)=polar(1., parmvalues[0][ant][tf] * freq * casa::C::_2pi); if (itsParmExprs.size() == 1) { // No Clock:0, only Clock: - itsParms[1][ant][tf]=polar(1., + itsParms(1, ant, tf)=polar(1., parmvalues[0][ant][tf] * freq * casa::C::_2pi); } else { // Clock:0 and Clock:1 - itsParms[1][ant][tf]=polar(1., + itsParms(1, ant, tf)=polar(1., parmvalues[1][ant][tf] * freq * casa::C::_2pi); } } @@ -438,10 +445,10 @@ namespace LOFAR { } double sinv=sin(phi); double cosv=cos(phi); - itsParms[0][ant][tf] = cosv; - itsParms[1][ant][tf] = -sinv; - itsParms[2][ant][tf] = sinv; - itsParms[3][ant][tf] = cosv; + itsParms(0, ant, tf) = cosv; + itsParms(1, ant, tf) = -sinv; + itsParms(2, ant, tf) = sinv; + itsParms(3, ant, tf) = cosv; } else if (itsCorrectType=="rotationmeasure") { double lambda2 = casa::C::c / freq; @@ -452,21 +459,23 @@ namespace LOFAR { } double sinv = sin(chi); double cosv = cos(chi); - itsParms[0][ant][tf] = cosv; - itsParms[1][ant][tf] = -sinv; - itsParms[2][ant][tf] = sinv; - itsParms[3][ant][tf] = cosv; + itsParms(0, ant, tf) = cosv; + itsParms(1, ant, tf) = -sinv; + itsParms(2, ant, tf) = sinv; + itsParms(3, ant, tf) = cosv; } else if (itsCorrectType=="commonscalarphase") { - itsParms[0][ant][tf] = polar(1., parmvalues[0][ant][tf]); - itsParms[1][ant][tf] = polar(1., parmvalues[0][ant][tf]); + itsParms(0, ant, tf) = polar(1., parmvalues[0][ant][tf]); + itsParms(1, ant, tf) = polar(1., parmvalues[0][ant][tf]); } - // Invert diagonal corrections (not fulljones and commonrotationangle) - // For fulljones it will be handled in applyFull - if (itsInvert && itsParms.size()==2) { - itsParms[0][ant][tf] = 1./itsParms[0][ant][tf]; - itsParms[1][ant][tf] = 1./itsParms[1][ant][tf]; + // Invert (rotationmeasure and commonrotationangle are already inverted) + if (itsInvert && itsParms.shape()[0]==2) { + itsParms(0, ant, tf) = 1./itsParms(0, ant, tf); + itsParms(1, ant, tf) = 1./itsParms(1, ant, tf); + } + else if (itsInvert && itsCorrectType=="fulljones") { + invert(&itsParms(0, ant, tf),itsSigmaMMSE); } } } @@ -486,54 +495,39 @@ namespace LOFAR { numParms = 2; } - itsParms.resize(numParms); - - for (uint parmNum=0;parmNum<numParms;parmNum++) { - itsParms[parmNum].resize(numAnts); - for (uint ant=0;ant<numAnts;++ant) { - itsParms[parmNum][ant].resize(tfDomainSize); - } - } + itsParms.resize(numParms, numAnts, tfDomainSize); } - void ApplyCal::applyDiag (Complex* vis, float* weight, bool* flag, - uint bl, int chan, int time) { - int timeFreqOffset=(time*info().nchan())+chan; - - uint antA = info().getAnt1()[bl]; - uint antB = info().getAnt2()[bl]; - - DComplex diag0A = itsParms[0][antA][timeFreqOffset]; - DComplex diag1A = itsParms[1][antA][timeFreqOffset]; - DComplex diag0B = itsParms[0][antB][timeFreqOffset]; - DComplex diag1B = itsParms[1][antB][timeFreqOffset]; - + void ApplyCal::applyDiag (DComplex* gainA, DComplex* gainB, + Complex* vis, float* weight, bool* flag, + uint bl, uint chan, bool updateWeights, + FlagCounter& flagCounter) { // If parameter is NaN or inf, do not apply anything and flag the data - if (! (isFinite(diag0A.real()) && isFinite(diag0A.imag()) && - isFinite(diag0B.real()) && isFinite(diag0B.imag()) && - isFinite(diag1A.real()) && isFinite(diag1A.imag()) && - isFinite(diag1B.real()) && isFinite(diag1B.imag())) ) { + if (! (isFinite(gainA[0].real()) && isFinite(gainA[0].imag()) && + isFinite(gainB[0].real()) && isFinite(gainB[0].imag()) && + isFinite(gainA[1].real()) && isFinite(gainA[1].imag()) && + isFinite(gainB[1].real()) && isFinite(gainB[1].imag())) ) { // Only update flagcounter for first correlation if (!flag[0]) { - itsFlagCounter.incrChannel(chan); - itsFlagCounter.incrBaseline(bl); + flagCounter.incrChannel(chan); + flagCounter.incrBaseline(bl); } - for (uint corr=0; corr<itsNCorr; ++corr) { + for (uint corr=0; corr<4; ++corr) { flag[corr]=true; } return; } - vis[0] *= diag0A * conj(diag0B); - vis[1] *= diag0A * conj(diag1B); - vis[2] *= diag1A * conj(diag0B); - vis[3] *= diag1A * conj(diag1B); + vis[0] *= gainA[0] * conj(gainB[0]); + vis[1] *= gainA[0] * conj(gainB[1]); + vis[2] *= gainA[1] * conj(gainB[0]); + vis[3] *= gainA[1] * conj(gainB[1]); - if (itsUpdateWeights) { - weight[0] /= norm(diag0A) * norm(diag0B); - weight[1] /= norm(diag0A) * norm(diag1B); - weight[2] /= norm(diag1A) * norm(diag0B); - weight[3] /= norm(diag1A) * norm(diag1B); + if (updateWeights) { + weight[0] /= norm(gainA[0]) * norm(gainB[0]); + weight[1] /= norm(gainA[0]) * norm(gainB[1]); + weight[2] /= norm(gainA[1]) * norm(gainB[0]); + weight[3] /= norm(gainA[1]) * norm(gainB[1]); } } @@ -552,34 +546,15 @@ namespace LOFAR { v[3] = v0 * invDet; } - void ApplyCal::applyFull (Complex* vis, float* weight, bool* flag, - uint bl, int chan, int time) { - int timeFreqOffset=(time*info().nchan())+chan; - DComplex gainA[4]; - DComplex gainB[4]; - - uint antA = info().getAnt1()[bl]; - uint antB = info().getAnt2()[bl]; - - gainA[0] = itsParms[0][antA][timeFreqOffset]; - gainA[1] = itsParms[1][antA][timeFreqOffset]; - gainA[2] = itsParms[2][antA][timeFreqOffset]; - gainA[3] = itsParms[3][antA][timeFreqOffset]; - - gainB[0] = itsParms[0][antB][timeFreqOffset]; - gainB[1] = itsParms[1][antB][timeFreqOffset]; - gainB[2] = itsParms[2][antB][timeFreqOffset]; - gainB[3] = itsParms[3][antB][timeFreqOffset]; - + void ApplyCal::applyFull (DComplex* gainA, DComplex* gainB, + Complex* vis, float* weight, bool* flag, + uint bl, uint chan, bool updateWeights, + FlagCounter& flagCounter) { DComplex gainAxvis[4]; - if (itsInvert && itsCorrectType=="fulljones") { - invert(gainA,itsSigmaMMSE); - invert(gainB,itsSigmaMMSE); - } // If parameter is NaN or inf, do not apply anything and flag the data bool anyinfnan = false; - for (uint corr=0; corr<itsNCorr; ++corr) { + for (uint corr=0; corr<4; ++corr) { if (! (isFinite(gainA[corr].real()) && isFinite(gainA[corr].imag()) && isFinite(gainB[corr].real()) && isFinite(gainB[corr].imag())) ) { anyinfnan = true; @@ -589,10 +564,10 @@ namespace LOFAR { if (anyinfnan) { // Only update flag counter for first correlation if (!flag[0]) { - itsFlagCounter.incrChannel(chan); - itsFlagCounter.incrBaseline(bl); + flagCounter.incrChannel(chan); + flagCounter.incrBaseline(bl); } - for (uint corr=0; corr<itsNCorr; ++corr) { + for (uint corr=0; corr<4; ++corr) { flag[corr]=true; } return; @@ -622,8 +597,7 @@ namespace LOFAR { // The input covariance matrix C is assumed to be diagonal with elements // w_i (the weights), the result the diagonal of // (gainA kronecker gainB^H).C.(gainA kronecker gainB^H)^H - if (itsUpdateWeights) { - ASSERTSTR (itsInvert, "Updating weights has not been implemented for invert=false"); + if (updateWeights) { float cov[4], normGainA[4], normGainB[4]; for (uint i=0;i<4;++i) { cov[i]=1./weight[i]; diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index a3863457e68..af63fc3612f 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -69,20 +69,24 @@ namespace LOFAR { itsUseModelColumn(parset.getBool (prefix + "usemodelcolumn", false)), itsParmDBName (parset.getString (prefix + "parmdb", "")), itsMode (parset.getString (prefix + "caltype")), - itsTStep (0), itsDebugLevel (parset.getInt (prefix + "debuglevel", 0)), itsDetectStalling (parset.getBool (prefix + "detectstalling", true)), itsBaselines (), itsMaxIter (parset.getInt (prefix + "maxiter", 50)), itsTolerance (parset.getDouble (prefix + "tolerance", 1.e-5)), - itsPropagateSolutions (parset.getBool(prefix + "propagatesolutions", false)), + itsPropagateSolutions + (parset.getBool(prefix + "propagatesolutions", false)), itsSolInt (parset.getInt(prefix + "solint", 1)), itsNChan (parset.getInt(prefix + "nchan", 0)), itsNFreqCells (0), itsMinBLperAnt (parset.getInt(prefix + "minblperant", 4)), + itsTimeSlotsPerParmUpdate + (parset.getInt(prefix + "timeslotsperparmupdate", 500)), itsConverged (0), itsNonconverged (0), itsStalled (0), + itsTimeStep (0), + itsChunkStartTime(0), itsNTimes (0) { if (itsParmDBName=="") { @@ -136,7 +140,7 @@ namespace LOFAR { itsSolInt=info().ntime(); } - itsSols.reserve(info().ntime()); + itsSols.reserve(itsTimeSlotsPerParmUpdate); if (itsNChan==0) { itsNChan = info().nchan(); @@ -164,6 +168,8 @@ namespace LOFAR { info().antennaNames().size(), itsDetectStalling, itsDebugLevel)); } + + itsChunkStartTime = info().startTime(); } void GainCal::show (std::ostream& os) const @@ -276,13 +282,20 @@ namespace LOFAR { if (itsNTimes==itsSolInt-1) { // Solve past solution interval stefcal(); + itsTimeStep++; itsNTimes=0; } else { itsNTimes++; } itsTimer.stop(); - itsTStep++; + + if (itsTimeStep == itsTimeSlotsPerParmUpdate) { + writeSolutions(itsChunkStartTime); + itsChunkStartTime += itsSolInt * itsTimeSlotsPerParmUpdate * info().timeInterval(); + itsSols.clear(); + itsTimeStep = 0; + } getNextStep()->process(itsBuf); return false; } @@ -399,13 +412,16 @@ namespace LOFAR { // Stefcal terminated (either by maxiter or by converging) // Let's save G... - Cube<DComplex> allg(info().antennaNames().size(), iS[0].numCorrelations(), itsNFreqCells); + Cube<DComplex> allg(iS[0].numCorrelations(), info().antennaNames().size(), itsNFreqCells); + + uint transpose[2][4] = { { 0, 1, 0, 0 }, { 0, 2, 1, 3 } }; for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { //cout<<endl<<"freqCell="<<freqCell<<", timeCell="<<itsTStep/itsSolInt<<", tstep="<<itsTStep<<endl; casa::Matrix<casa::DComplex> sol = iS[freqCell].getSolution(); for (uint st=0; st<info().antennaNames().size(); st++) { for (uint cr=0; cr<iS[0].numCorrelations(); ++cr) { - allg(st,cr,freqCell)=sol(st,cr); + uint crt=transpose[iS[0].numCorrelations()/4][cr]; // Conjugate transpose ! (only for numCorrelations = 4) + allg(crt, st, freqCell) = conj(sol(st, cr)); } } } @@ -414,69 +430,41 @@ namespace LOFAR { itsTimerSolve.stop(); } - void GainCal::finish() - { - itsTimer.start(); + void GainCal::initParmDB() { + itsParmDB = boost::shared_ptr<BBS::ParmDB> + (new BBS::ParmDB(BBS::ParmDBMeta("casa", itsParmDBName), + false)); + itsParmDB->lock(); + // Store the (freq, time) resolution of the solutions. - //Solve remaining time slots if any - if (itsNTimes!=0) { - stefcal(); + double freqWidth = getInfo().chanWidths()[0]; + if (getInfo().chanFreqs().size()>1) { // Handle data with evenly spaced gaps between channels + freqWidth = info().chanFreqs()[1]-info().chanFreqs()[0]; } - itsTimerWrite.start(); - - uint nSt=info().antennaNames().size(); - - uint ntime=itsSols.size(); - - // Construct solution grid. - const Vector<double>& freq = getInfo().chanFreqs(); - const Vector<double>& freqWidth = getInfo().chanWidths(); - BBS::Axis::ShPtr freqAxis( - new BBS::RegularAxis( - freq[0] - freqWidth[0] * 0.5, - freqWidth[0]*itsNChan, - itsNFreqCells)); - BBS::Axis::ShPtr timeAxis( - new BBS::RegularAxis( - info().startTime(), - info().timeInterval() * itsSolInt, - ntime)); - BBS::Grid solGrid(freqAxis, timeAxis); - // Create domain grid. - BBS::Axis::ShPtr tdomAxis( - new BBS::RegularAxis( - info().startTime(), - info().timeInterval() * itsSolInt * ntime, - 1)); - BBS::Axis::ShPtr fdomAxis( - new BBS::RegularAxis( - freq[0] - freqWidth[0] * 0.5, - getInfo().totalBW(), 1)); - BBS::Grid domainGrid(fdomAxis, tdomAxis); - - // Open the ParmDB at the first write. - // In that way the instrumentmodel ParmDB can be in the MS directory. - if (! itsParmDB) { - itsParmDB = boost::shared_ptr<BBS::ParmDB> - (new BBS::ParmDB(BBS::ParmDBMeta("casa", itsParmDBName), - false)); - itsParmDB->lock(); - // Store the (freq, time) resolution of the solutions. - vector<double> resolution(2); - resolution[0] = freqWidth[0]; - resolution[1] = info().timeInterval() * itsSolInt; - itsParmDB->setDefaultSteps(resolution); - string name=(itsMode=="commonscalarphase"?"CommonScalarPhase:*":"Gain:*"); - if (!itsParmDB->getNames(name).empty()) { - DPLOG_WARN_STR ("Solutions for "<<name<<" already in "<<itsParmDBName - <<", these are removed"); - itsParmDB->deleteValues(name, BBS::Box( - freqAxis->start(), tdomAxis->start(), - freqAxis->end(), tdomAxis->end(), true - ) - ); - } + vector<double> resolution(2); + resolution[0] = freqWidth * itsNChan; + resolution[1] = info().timeInterval() * itsSolInt; + itsParmDB->setDefaultSteps(resolution); + string name=(itsMode=="commonscalarphase"?"CommonScalarPhase:*":"Gain:*"); + if (!itsParmDB->getNames(name).empty()) { + DPLOG_WARN_STR ("Solutions for "<<name<<" already in "<<itsParmDBName + <<", these are removed"); + // Specify entire domain of this MS; only to specify that the existing + // values should be deleted for this domain + BBS::Axis::ShPtr tdomAxis( + new BBS::RegularAxis( + info().startTime(), + info().ntime() * info().timeInterval(), + 1)); + BBS::Axis::ShPtr fdomAxis( + new BBS::RegularAxis( + info().chanFreqs()[0] - freqWidth * 0.5, + freqWidth * getInfo().chanFreqs().size(), 1)); + + itsParmDB->deleteValues(name, BBS::Box( + fdomAxis->start(), tdomAxis->start(), + fdomAxis->end(), tdomAxis->end(), true)); } // Write out default values, if they don't exist yet @@ -501,14 +489,57 @@ namespace LOFAR { itsParmDB->putDefValue("Gain:1:1:Real",pvset); } } + } + + void GainCal::writeSolutions(double startTime) { + itsTimer.start(); + itsTimerWrite.start(); + + // Open the ParmDB at the first write. + // In that way the instrumentmodel ParmDB can be in the MS directory. + if (! itsParmDB) { + initParmDB(); + } // End initialization of parmdb + + uint ntime=itsSols.size(); + + // Construct solution grid for the current chunk + double freqWidth = getInfo().chanWidths()[0]; + if (getInfo().chanFreqs().size()>1) { // Handle data with evenly spaced gaps between channels + freqWidth = info().chanFreqs()[1]-info().chanFreqs()[0]; + } + BBS::Axis::ShPtr freqAxis( + new BBS::RegularAxis( + getInfo().chanFreqs()[0] - freqWidth * 0.5, + freqWidth*itsNChan, + itsNFreqCells)); + BBS::Axis::ShPtr timeAxis( + new BBS::RegularAxis( + startTime, + info().timeInterval() * itsSolInt, + ntime)); + BBS::Grid solGrid(freqAxis, timeAxis); + + // Construct domain grid for the current chunk + BBS::Axis::ShPtr tdomAxis( + new BBS::RegularAxis( + startTime, + ntime * info().timeInterval() * itsSolInt, + 1)); + BBS::Axis::ShPtr fdomAxis( + new BBS::RegularAxis( + info().chanFreqs()[0] - freqWidth * 0.5, + freqWidth * getInfo().chanFreqs().size(), 1)); + BBS::Grid domainGrid(fdomAxis, tdomAxis); // Write the solutions per parameter. - const char* str0101[] = {"0:0:","1:0:","0:1:","1:1:"}; // Conjugate transpose! + const char* str0101[] = {"0:0:","0:1:","1:0:","1:1:"}; const char* strri[] = {"Real:","Imag:"}; Matrix<double> values(itsNFreqCells, ntime); DComplex sol; + uint nSt=info().antennaNames().size(); for (size_t st=0; st<nSt; ++st) { uint seqnr = 0; // To take care of real and imaginary part string suffix(info().antennaNames()[st]); @@ -537,28 +568,29 @@ namespace LOFAR { for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { if (itsMode=="fulljones") { if (seqnr%2==0) { - values(freqCell, ts) = real(itsSols[ts](st,seqnr/2,freqCell)); + values(freqCell, ts) = real(itsSols[ts](seqnr/2,st,freqCell)); } else { - values(freqCell, ts) = -imag(itsSols[ts](st,seqnr/2,freqCell)); // Conjugate transpose! + values(freqCell, ts) = imag(itsSols[ts](seqnr/2,st,freqCell)); } } else if (itsMode=="diagonal") { if (seqnr%2==0) { - values(freqCell, ts) = real(itsSols[ts](st,pol/3,freqCell)); + values(freqCell, ts) = real(itsSols[ts](pol/3,st,freqCell)); } else { - values(freqCell, ts) = -imag(itsSols[ts](st,pol/3,freqCell)); // Conjugate transpose! + values(freqCell, ts) = imag(itsSols[ts](pol/3,st,freqCell)); } } else if (itsMode=="scalarphase" || itsMode=="phaseonly") { - values(freqCell, ts) = -arg(itsSols[ts](st,pol/3,freqCell)); + values(freqCell, ts) = arg(itsSols[ts](pol/3,st,freqCell)); } } } - //cout.flush(); seqnr++; BBS::ParmValue::ShPtr pv(new BBS::ParmValue()); pv->setScalars (solGrid, values); + BBS::ParmValueSet pvs(domainGrid, vector<BBS::ParmValue::ShPtr>(1, pv)); map<string,int>::const_iterator pit = itsParmIdMap.find(name); + if (pit == itsParmIdMap.end()) { // First time, so a new nameId will be set. // Check if the name was defined in the parmdb previously @@ -576,6 +608,23 @@ namespace LOFAR { itsTimerWrite.stop(); itsTimer.stop(); + } + + void GainCal::finish() + { + itsTimer.start(); + + //Solve remaining time slots if any + if (itsNTimes!=0) { + stefcal(); + } + + itsTimer.stop(); + + if (!itsSols.empty()) { + writeSolutions(itsChunkStartTime); + } + // Let the next steps finish. getNextStep()->finish(); } diff --git a/CEP/DP3/DPPP/test/tGainCal.run b/CEP/DP3/DPPP/test/tGainCal.run index a92ab4621b1..8192e1c2d8e 100755 --- a/CEP/DP3/DPPP/test/tGainCal.run +++ b/CEP/DP3/DPPP/test/tGainCal.run @@ -35,6 +35,15 @@ echo; echo "Test caltype=diagonal"; echo $taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where not near(dpppres,bbsres,1.e-2)' > taql.out diff taql.out taql.ref || exit 1 +echo; echo "Test caltype=diagonal with timeslotsperparmupdate=4"; echo +../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal-tpp gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 gaincal.timeslotsperparmupdate=1 +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_TPP steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal-tpp +$taqlexe 'select from tNDPP-generic.MS where not all(near(DPPP_DIAGONAL, DPPP_DIAGONAL_TPP))' + +# Compare the difference between applying with timeslotsperparmupdate = default and timeslotsperparmupdate=1 +$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where not near(dpppres,bbsres,1.e-2)' > taql.out +diff taql.out taql.ref || exit 1 + echo; echo "Test caltype=fulljones"; echo ../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-fulljones gaincal.usebeammodel=false gaincal.caltype=fulljones gaincal.solint=1 ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_FULLJONES steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-fulljones -- GitLab From f7e8724d38067e221d85e7d0cd38dec7ff2e4068 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Tue, 24 May 2016 11:55:53 +0000 Subject: [PATCH 213/933] Task #9462: add amplitude only solve to gaincal --- CEP/DP3/DPPP/include/DPPP/StefCal.h | 2 +- CEP/DP3/DPPP/src/GainCal.cc | 39 +++++++++++++++++++++++------ CEP/DP3/DPPP/src/StefCal.cc | 10 +++++--- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/CEP/DP3/DPPP/include/DPPP/StefCal.h b/CEP/DP3/DPPP/include/DPPP/StefCal.h index f9fba37a388..9373e551ef1 100644 --- a/CEP/DP3/DPPP/include/DPPP/StefCal.h +++ b/CEP/DP3/DPPP/include/DPPP/StefCal.h @@ -88,7 +88,7 @@ namespace LOFAR { Status relax(uint iter); void doStep_polarized(); - void doStep_unpolarized(bool phaseOnly); + void doStep_unpolarized(); uint _savedNCr; std::vector<int> _antMap; // Length antennaNames, contains size(antennaNames)-nSt times the value -1 diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index af63fc3612f..d2222ad2236 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -108,7 +108,8 @@ namespace LOFAR { } } ASSERT(itsMode=="diagonal" || itsMode=="phaseonly" || - itsMode=="fulljones" || itsMode=="scalarphase"); + itsMode=="fulljones" || itsMode=="scalarphase" || + itsMode=="amplitudeonly" || itsMode=="scalaramplitude"); } GainCal::~GainCal() @@ -480,6 +481,16 @@ namespace LOFAR { } } + // Write out default phases + if (itsMode=="amplitudeonly" || itsMode=="scalaramplitude") { + itsParmDB->getDefValues(parmset, "Gain:0:0:Phase"); + if (parmset.empty()) { + ParmValueSet pvset(ParmValue(0.0)); + itsParmDB->putDefValue("Gain:0:0:Phase",pvset); + itsParmDB->putDefValue("Gain:1:1:Phase",pvset); + } + } + // Write out default gains if (itsMode=="diagonal" || itsMode=="fulljones") { itsParmDB->getDefValues(parmset, "Gain:0:0:Real"); @@ -545,23 +556,37 @@ namespace LOFAR { string suffix(info().antennaNames()[st]); for (int pol=0; pol<4; ++pol) { // For 0101 - if ((itsMode=="diagonal" || itsMode=="phaseonly") && (pol==1||pol==2)) { + if ((itsMode=="diagonal" || itsMode=="phaseonly" || + itsMode=="amplitudeonly") && (pol==1||pol==2)) { continue; } - if (itsMode=="scalarphase" && pol>0) { + if ((itsMode=="scalarphase" || itsMode=="scalaramplitude" ) && pol>0) { continue; } int realimmax; - if (itsMode=="phaseonly" || itsMode=="scalarphase") { + if (itsMode=="phaseonly" || itsMode=="scalarphase" || + itsMode=="amplitudeonly" || itsMode=="scalaramplitude") { realimmax=1; } else { realimmax=2; } for (int realim=0; realim<realimmax; ++realim) { // For real and imaginary - string name(string("Gain:") + - str0101[pol] + (itsMode=="phaseonly"?"Phase:":strri[realim]) + suffix); + string name; + if (itsMode=="scalarphase") { - name="CommonScalarPhase:"+suffix; + name=string("CommonScalarPhase:")+suffix; + } else if (itsMode=="scalaramplitude") { + name=string("CommonScalarAmplitude:")+suffix; + } else { + name=string("Gain:") + str0101[pol]; + if (itsMode=="phaseonly") { + name=name+"Phase:"; + } else if (itsMode=="amplitudeonly") { + name=name+"Amplitude:"; + } else { + name=name+strri[realim]; + } + name+=suffix; } // Collect its solutions for all times and frequency cells in a single array. for (uint ts=0; ts<ntime; ++ts) { diff --git a/CEP/DP3/DPPP/src/StefCal.cc b/CEP/DP3/DPPP/src/StefCal.cc index d23a73f3bf2..3a529b20da0 100644 --- a/CEP/DP3/DPPP/src/StefCal.cc +++ b/CEP/DP3/DPPP/src/StefCal.cc @@ -134,8 +134,8 @@ namespace LOFAR { doStep_polarized(); return relax(2*iter); } else { - doStep_unpolarized(_mode=="phaseonly" || _mode=="scalarphase"); - doStep_unpolarized(_mode=="phaseonly" || _mode=="scalarphase"); + doStep_unpolarized(); + doStep_unpolarized(); return relax(2*iter); } } @@ -206,7 +206,7 @@ namespace LOFAR { } } - void StefCal::doStep_unpolarized(bool phaseOnly) { + void StefCal::doStep_unpolarized() { _gold=_g; for (uint st=0;st<_nUn;++st) { @@ -242,8 +242,10 @@ namespace LOFAR { //cout<<", w="<<ww<<" "; _g(st1,0)=tt/ww; //cout<<", g="<<iS.g(st1,0)<<endl; - if (phaseOnly) { + if (_mode=="phaseonly" || _mode=="scalarphase") { _g(st1,0)/=abs(_g(st1,0)); + } else if (_mode=="amplitudeonly" || _mode=="scalaramplitude") { + _g(st1,0) = abs(_g(st1,0)); } } } -- GitLab From e3561d0ad30e2e66a2cb48b9e540e231457b41ca Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 24 May 2016 17:57:09 +0000 Subject: [PATCH 214/933] Task #8437: Use UID instead of USER, as USER is not set yet --- Docker/lofar-base/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 4f99aa92438..9db1ced7023 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -51,7 +51,7 @@ RUN echo 'ALL ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \ # # setup install dir # -RUN mkdir -p ${INSTALLDIR} && chown ${USER}.${USER} ${INSTALLDIR} +RUN mkdir -p ${INSTALLDIR} && chown ${UID}.${UID} ${INSTALLDIR} USER ${USER} -- GitLab From dffea397e782223eb28a7ecca81be7366a79cf88 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 24 May 2016 18:06:32 +0000 Subject: [PATCH 215/933] Task #8437: Do not set user id during build, to avoid having to add one. Also, do not run apt-get upgrade as that is unnecessary. --- Docker/lofar-base/Dockerfile.tmpl | 38 ++++++++++++------------- Docker/lofar-base/chuser.sh | 6 ++-- Docker/lofar-outputproc/Dockerfile.tmpl | 10 +++---- Docker/lofar-pipeline/Dockerfile.tmpl | 22 +++++++------- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 9db1ced7023..bcef46cbee7 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -38,7 +38,7 @@ ENV J=6 # #RUN sed -i 's/archive.ubuntu.com/osmirror.rug.nl/' /etc/apt/sources.list RUN apt-get update && \ - apt-get install -y sudo python2.7 libpython2.7 && \ + apt-get install -y python2.7 libpython2.7 && \ apt-get install -y libblas3 liblapacke python-numpy libcfitsio3 libwcs4 libfftw3-bin libhdf5-7 libboost-python${BOOST_VERSION}.0 && \ apt-get install -y nano @@ -51,16 +51,14 @@ RUN echo 'ALL ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \ # # setup install dir # -RUN mkdir -p ${INSTALLDIR} && chown ${UID}.${UID} ${INSTALLDIR} - -USER ${USER} +RUN mkdir -p ${INSTALLDIR} # # ******************* # Casacore # ******************* # -RUN sudo apt-get update && sudo apt-get install -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/casacore/build && \ mkdir -p ${INSTALLDIR}/casacore/data && \ cd ${INSTALLDIR}/casacore && git clone https://github.com/casacore/casacore.git src && \ @@ -72,15 +70,15 @@ RUN sudo apt-get update && sudo apt-get install -y wget git cmake g++ gfortran f cd ${INSTALLDIR}/casacore/build && make install && \ bash -c "strip ${INSTALLDIR}/casacore/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casacore/{build,src}" && \ - sudo apt-get purge -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ - sudo apt-get autoremove -y + apt-get purge -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ + apt-get autoremove -y # # ******************* # Casarest # ******************* # -RUN sudo apt-get update && sudo apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/casarest/build && \ cd ${INSTALLDIR}/casarest && git clone https://github.com/casacore/casarest.git src && \ if [ "${CASAREST_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casarest/src && git checkout tags/v${CASAREST_VERSION}; fi && \ @@ -89,15 +87,15 @@ RUN sudo apt-get update && sudo apt-get install -y git cmake g++ gfortran libboo cd ${INSTALLDIR}/casarest/build && make install && \ bash -c "strip ${INSTALLDIR}/casarest/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casarest/{build,src}" && \ - sudo apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ - sudo apt-get autoremove -y + apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ + apt-get autoremove -y # # ******************* # Pyrap # ******************* # -RUN sudo apt-get update && sudo apt-get install -y git make g++ python-setuptools libboost-python-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y git make g++ python-setuptools libboost-python-dev libcfitsio3-dev wcslib-dev && \ mkdir ${INSTALLDIR}/python-casacore && \ cd ${INSTALLDIR}/python-casacore && git clone https://github.com/casacore/python-casacore && \ if [ "$PYTHON_CASACORE_VERSION" != "latest" ]; then cd ${INSTALLDIR}/python-casacore/python-casacore && git checkout tags/v${PYTHON_CASACORE_VERSION}; fi && \ @@ -107,8 +105,8 @@ RUN sudo apt-get update && sudo apt-get install -y git make g++ python-setuptool export PYTHONPATH=${INSTALLDIR}/python-casacore/lib/python${PYTHON_VERSION}/site-packages:${INSTALLDIR}/python-casacore/lib64/python${PYTHON_VERSION}/site-packages:$PYTHONPATH && cd ${INSTALLDIR}/python-casacore/python-casacore && ./setup.py install --prefix=${INSTALLDIR}/python-casacore/ && \ bash -c "find ${INSTALLDIR}/python-casacore/lib -name '*.so' | xargs strip || true" && \ bash -c "rm -rf ${INSTALLDIR}/python-casacore/python-casacore" && \ - sudo apt-get purge -y git make g++ python-setuptools libboost-python-dev libcfitsio3-dev wcslib-dev && \ - sudo apt-get autoremove -y + apt-get purge -y git make g++ python-setuptools libboost-python-dev libcfitsio3-dev wcslib-dev && \ + apt-get autoremove -y # # ******************* @@ -118,24 +116,24 @@ RUN sudo apt-get update && sudo apt-get install -y git make g++ python-setuptool # Run-time dependencies # QPID daemon legacy store would require: libaio1 libdb5.1++ -RUN sudo apt-get update && sudo apt-get install -y sasl2-bin libuuid1 libnss3 libnspr4 xqilla libboost-program-options${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 +RUN apt-get update && apt-get install -y sasl2-bin libuuid1 libnss3 libnspr4 xqilla libboost-program-options${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 # Install # QPID daemon legacy store would require: libaio-dev libdb5.1++-dev -RUN sudo apt-get update && sudo apt-get install -y subversion swig ruby ruby-dev python-dev libsasl2-dev pkg-config cmake libtool uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man fakeroot build-essential debhelper libsslcommon2-dev libxqilla-dev python-setuptools libboost-program-options${BOOST_VERSION}-dev libboost-filesystem${BOOST_VERSION}-dev && \ +RUN apt-get update && apt-get install -y subversion swig ruby ruby-dev python-dev libsasl2-dev pkg-config cmake libtool uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man fakeroot build-essential debhelper libsslcommon2-dev libxqilla-dev python-setuptools libboost-program-options${BOOST_VERSION}-dev libboost-filesystem${BOOST_VERSION}-dev && \ mkdir /opt/qpid && \ svn --non-interactive -q co ${LOFAR_BRANCH_URL}/LCS/MessageBus/qpid/ /opt/qpid; \ /opt/qpid/local/sbin/build_qpid && \ bash -c "strip /opt/qpid/{bin,lib}/* || true" && \ bash -c "rm -rf ~/sources" && \ - sudo apt-get purge -y subversion swig ruby ruby-dev python-dev libsasl2-dev pkg-config cmake libtool uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man fakeroot build-essential debhelper libsslcommon2-dev libxqilla-dev python-setuptools libboost-program-options${BOOST_VERSION}-dev libboost-filesystem${BOOST_VERSION}-dev && \ - sudo apt-get autoremove -y + apt-get purge -y subversion swig ruby ruby-dev python-dev libsasl2-dev pkg-config cmake libtool uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man fakeroot build-essential debhelper libsslcommon2-dev libxqilla-dev python-setuptools libboost-program-options${BOOST_VERSION}-dev libboost-filesystem${BOOST_VERSION}-dev && \ + apt-get autoremove -y # # ******************* # DAL # ******************* # -RUN sudo apt-get update && sudo apt-get install -y git cmake g++ swig python-dev libhdf5-dev && \ +RUN apt-get update && apt-get install -y git cmake g++ swig python-dev libhdf5-dev && \ mkdir ${INSTALLDIR}/DAL && \ cd ${INSTALLDIR}/DAL && git clone https://github.com/nextgen-astrodata/DAL.git src && \ mkdir ${INSTALLDIR}/DAL/build && cd ${INSTALLDIR}/DAL/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/DAL ../src && \ @@ -143,8 +141,8 @@ RUN sudo apt-get update && sudo apt-get install -y git cmake g++ swig python-dev make install && \ bash -c "find ${INSTALLDIR}/DAL/lib -name '*.so' | xargs strip || true" && \ bash -c "rm -rf ${INSTALLDIR}/DAL/{src,build}" && \ - sudo apt-get purge -y git cmake g++ swig python-dev libhdf5-dev && \ - sudo apt-get autoremove -y + apt-get purge -y git cmake g++ swig python-dev libhdf5-dev && \ + apt-get autoremove -y # # entry diff --git a/Docker/lofar-base/chuser.sh b/Docker/lofar-base/chuser.sh index fb744213c34..bfe0f0db2cd 100755 --- a/Docker/lofar-base/chuser.sh +++ b/Docker/lofar-base/chuser.sh @@ -6,8 +6,10 @@ if [ -z "${USER}" ]; then fi # Create home directory -export HOME=/home/${USER} -mkdir -p $HOME && cd $HOME +if [ -z "${HOME}" ]; then + export HOME=/home/${USER} + mkdir -p $HOME && cd $HOME +fi # Add user to system fgrep -q ":x:${UID}:" /etc/passwd || echo "${USER}:x:${UID}:${UID}::${HOME}:/bin/bash" >> /etc/passwd diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index 96efdda0a20..eb5ef0c866b 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -10,7 +10,7 @@ FROM lofar-base:${LOFAR_TAG} # # Run-time dependencies -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libboost-regex${BOOST_VERSION}.0 +RUN apt-get update && apt-get install -y liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libboost-regex${BOOST_VERSION}.0 # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ @@ -18,7 +18,7 @@ ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ LOFAR_BUILDVARIANT=gnu_optarch # Install -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} libcfitsio3-dev wcslib-dev && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ @@ -31,7 +31,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y su bash -c "ln -sfT /home/${USER}/lofar/var ${INSTALLDIR}/lofar/var" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ - sudo setcap cap_sys_nice,cap_ipc_lock=ep ${INSTALLDIR}/lofar/bin/outputProc && \ - sudo apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev && \ - sudo apt-get autoremove -y + setcap cap_sys_nice,cap_ipc_lock=ep ${INSTALLDIR}/lofar/bin/outputProc && \ + apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev && \ + apt-get autoremove -y diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 9cbae03ac7b..a3f38f86b66 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -6,11 +6,11 @@ FROM lofar-base:${LOFAR_TAG} ENV AOFLAGGER_VERSION=2.7.1 # Run-time dependencies -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ - sudo apt-get -y install python-pip python-dev && \ - sudo pip install pyfits pywcs python-monetdb && \ - sudo apt-get -y purge python-pip python-dev && \ - sudo apt-get -y autoremove +RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ + apt-get -y install python-pip python-dev && \ + pip install pyfits pywcs python-monetdb && \ + apt-get -y purge python-pip python-dev && \ + apt-get -y autoremove # # ******************* @@ -18,7 +18,7 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y py # ******************* # -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ +RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ mkdir -p ${INSTALLDIR}/aoflagger/build && \ bash -c "cd ${INSTALLDIR}/aoflagger && wget --retry-connrefused http://downloads.sourceforge.net/project/aoflagger/aoflagger-${AOFLAGGER_VERSION%%.?}.0/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ cd ${INSTALLDIR}/aoflagger && tar xf aoflagger-${AOFLAGGER_VERSION}.tar.bz2 && \ @@ -28,8 +28,8 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y wg bash -c "strip ${INSTALLDIR}/aoflagger/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/{build,aoflagger-${AOFLAGGER_VERSION}}" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ - sudo apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ - sudo apt-get -y autoremove + apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ + apt-get -y autoremove # # ******************* @@ -43,7 +43,7 @@ ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ LOFAR_BUILDVARIANT=gnu_optarch # Install -RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ @@ -56,6 +56,6 @@ RUN sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install -y su bash -c "ln -sfT /home/${USER}/lofar/var ${INSTALLDIR}/lofar/var" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ - sudo apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ - sudo apt-get autoremove -y + apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ + apt-get autoremove -y -- GitLab From d1496965c187fb5b2743747784c31431ff8c59f3 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 24 May 2016 19:20:39 +0000 Subject: [PATCH 216/933] Task #8437: Added missing chuser.sh to image --- Docker/lofar-base/Dockerfile.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index bcef46cbee7..5287f520532 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -148,5 +148,6 @@ RUN apt-get update && apt-get install -y git cmake g++ swig python-dev libhdf5-d # entry # COPY ["bashrc", "bashrc.d", "${INSTALLDIR}/"] +COPY ["chuser.sh", "/usr/local/bin"] ENTRYPOINT ["/usr/local/bin/chuser.sh"] -- GitLab From ef61583df285d576c6f992150d773a970d6859a0 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 24 May 2016 19:24:45 +0000 Subject: [PATCH 217/933] Task #8437: Allow editing accounts by running user --- Docker/lofar-base/Dockerfile.tmpl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 5287f520532..7562a541e25 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -43,10 +43,11 @@ RUN apt-get update && \ apt-get install -y nano # -# setup-account +# open security holes (allow smooth user switching, allow sudo) # RUN echo 'ALL ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \ - sed -i 's/requiretty/!requiretty/g' /etc/sudoers + sed -i 's/requiretty/!requiretty/g' /etc/sudoers && \ + chmod a+rw /etc/group /etc/passwd # # setup install dir -- GitLab From 0123beee8076586a5eea465e7bc4c970a9fdef2e Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Wed, 25 May 2016 07:32:25 +0000 Subject: [PATCH 218/933] Task #9462: fix Ampl name in amplitude solve --- CEP/DP3/DPPP/src/GainCal.cc | 2 +- CEP/DP3/DPPP/src/StefCal.cc | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index d2222ad2236..a284d9bcbf6 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -582,7 +582,7 @@ namespace LOFAR { if (itsMode=="phaseonly") { name=name+"Phase:"; } else if (itsMode=="amplitudeonly") { - name=name+"Amplitude:"; + name=name+"Ampl:"; } else { name=name+strri[realim]; } diff --git a/CEP/DP3/DPPP/src/StefCal.cc b/CEP/DP3/DPPP/src/StefCal.cc index 3a529b20da0..fb71b7c52fa 100644 --- a/CEP/DP3/DPPP/src/StefCal.cc +++ b/CEP/DP3/DPPP/src/StefCal.cc @@ -110,8 +110,10 @@ namespace LOFAR { fronormvis=sqrt(fronormvis); fronormmod=sqrt(fronormmod); + // Initialize solution with sensible values (or 1.) double ginit=1; - if (_nSt>0 && abs(fronormmod)>1.e-15) { + if ((_mode != "phaseonly" && _mode != "scalarphase" ) && + _nSt>0 && abs(fronormmod)>1.e-15) { ginit=sqrt(fronormvis/fronormmod); } if (_nCr==4) { -- GitLab From 00365e41113f591924ee17d32b43446123fc582c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 26 May 2016 10:37:54 +0000 Subject: [PATCH 219/933] Task #8437: Correct UID to actual user id --- Docker/lofar-base/chuser.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Docker/lofar-base/chuser.sh b/Docker/lofar-base/chuser.sh index bfe0f0db2cd..9ac3603c1a0 100755 --- a/Docker/lofar-base/chuser.sh +++ b/Docker/lofar-base/chuser.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash -# Configrue user +# Correct UID +export UID=`id -u` + +# Configure user if [ -z "${USER}" ]; then export USER=${UID} fi -- GitLab From a41fe69a4a85c2427004ebbeb8407d388f9e9544 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 26 May 2016 13:59:40 +0000 Subject: [PATCH 220/933] Task #8392: merged changes from ^/branches/LtaCp-Task8725 via trunk into this feature branch --- .gitattributes | 2 + LTA/LTAIngest/ingestpipeline.py | 175 +++++++++++--- LTA/LTAIngest/ltacp.py | 308 ++++++++++++------------ LTA/LTAIngest/md5adler/makefile | 8 +- LTA/LTAIngest/md5adler/md5.h | 5 +- LTA/LTAIngest/md5adler/md5a32bc | Bin 0 -> 17808 bytes LTA/LTAIngest/md5adler/md5a32bc.c | 376 ++++++++++++++++++++++++++++++ 7 files changed, 689 insertions(+), 185 deletions(-) create mode 100755 LTA/LTAIngest/md5adler/md5a32bc create mode 100644 LTA/LTAIngest/md5adler/md5a32bc.c diff --git a/.gitattributes b/.gitattributes index 83ff5ec0109..b4037d10d77 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2941,6 +2941,8 @@ LTA/LTAIngest/ltaingest_build.sh eol=lf LTA/LTAIngest/md5adler/a32 -text LTA/LTAIngest/md5adler/foo -text LTA/LTAIngest/md5adler/md5a32 -text +LTA/LTAIngest/md5adler/md5a32bc -text +LTA/LTAIngest/md5adler/md5a32bc.c -text LTA/LTAIngest/mechanize-0.2.5/PKG-INFO -text LTA/LTAIngest/mechanize-0.2.5/examples/forms/data.dat -text LTA/LTAIngest/mechanize-0.2.5/examples/forms/echo.cgi -text diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index 8dede787e44..612b6032ce0 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -40,7 +40,7 @@ class PipelineError(Exception): #---------------------- IngestPipeline ------------------------------------------ class IngestPipeline(): - def __init__(self, logdir, job, momClient, ltaClient, ltacphost, ltacpport, mailCommand, momRetry, ltaRetry, srmRetry, srmInit): + def __init__(self, logdir, job, momClient, ltaClient, ltacphost, ltacpport, mailCommand, momRetry, ltaRetry, srmRetry, srmInit, user=getpass.getuser()): self.logdir = logdir self.job = job self.momClient = momClient @@ -48,6 +48,7 @@ class IngestPipeline(): self.ltacphost = ltacphost self.ltacpport = ltacpport self.mailCommand = mailCommand + self.user = user self.Project = job['Project'] self.DataProduct = job['DataProduct'] @@ -69,7 +70,7 @@ class IngestPipeline(): if 'summary' in self.DataProduct: self.FileType = pulp_type self.JobId = job['JobId'] - self.ArchiveId = int(job['ArchiveId']) + self.ArchiveId = int(job['ArchiveId']) self.ObsId = int(job['ObservationId']) self.HostLocation = job['Location'].split(':')[0] self.Location = job['Location'].split(':')[1] @@ -101,6 +102,7 @@ class IngestPipeline(): self.ltaRetry = ltaRetry self.srmRetry = srmRetry self.status = IngestStarted + self.finishedSuccessfully = True ## Set logger self.logger =logging.getLogger('Slave') @@ -227,7 +229,8 @@ class IngestPipeline(): cp = ltacp.LtaCp(host, os.path.join(self.LocationDir, self.Source), - self.PrimaryUri) + self.PrimaryUri, + self.user) self.MD5Checksum, self.Adler32Checksum, self.FileSize = cp.transfer() @@ -381,11 +384,9 @@ class IngestPipeline(): self.logger.debug('SIP received for %s from MoM with size %d (%s): %s' % (self.JobId, len(self.SIP), humanreadablesize(len(self.SIP)), self.SIP[0:256])) elif self.Type.lower() == "eor": try: - sip_host = job['SIPLocation'].split(':')[0] - for i in range(1, 43): - sip_host = sip_host.replace('node%d.intra.dawn.rug.nl' % (i+100,), '10.196.232.%d' % (i+10,)) - sip_path = job['SIPLocation'].split(':')[1] - cmd = ['ssh', '-tt', '-n', '-x', '-q', '%s@%s' % (getpass.getuser(), sip_host), 'cat %s' % sip_path] + sip_host = self.job['SIPLocation'].split(':')[0] + sip_path = self.job['SIPLocation'].split(':')[1] + cmd = ['ssh', '-tt', '-n', '-x', '-q', '%s@%s' % (self.user, sip_host), 'cat %s' % sip_path] self.logger.debug("GetSIP for %s with mom2DPId %s - StorageTicket %s - FileName %s - Uri %s - cmd %s" % (self.JobId, self.ArchiveId, self.ticket, self.FileName, self.PrimaryUri, ' ' .join(cmd))) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() @@ -441,6 +442,7 @@ class IngestPipeline(): self.SIP = sip_dom.toxml("utf-8") self.SIP = self.SIP.replace('<stationType>Europe</stationType>','<stationType>International</stationType>') + self.SIP = self.SIP.replace('<source>EOR</source>','<source>EoR</source>') #EoR sips sometime contain EOR instead of EoR as source except: self.logger.exception('Getting SIP from EoR failed') @@ -530,11 +532,10 @@ class IngestPipeline(): start = time.time() self.RetryRun(self.GetStorageTicket, self.ltaRetry, 'Getting storage ticket') - self.RetryRun(self.TransferFileNew, self.srmRetry , 'Transfering file') - #if self.Type.lower() == "eor": - #self.RetryRun(self.TransferFileNew, self.srmRetry , 'Transfering file') - #else: - #self.RetryRun(self.TransferFile, self.srmRetry , 'Transfering file') + if self.Type.lower() == "eor": + self.RetryRun(self.TransferFileNew, self.srmRetry , 'Transfering file') + else: + self.RetryRun(self.TransferFile, self.srmRetry , 'Transfering file') self.RetryRun(self.SendChecksums, self.ltaRetry, 'Sending Checksums') # self.RenameFile() @@ -548,7 +549,8 @@ class IngestPipeline(): self.logger.debug("Ingest Pipeline finished for %s in %d sec with average speed of %s for %s including all overhead" % (self.JobId, elapsed, humanreadablesize(avgSpeed, 'Bps'), humanreadablesize(float(self.FileSize), 'B'))) except Exception: self.logger.debug("Ingest Pipeline finished for %s in %d sec" % (self.JobId, elapsed)) - + self.finishedSuccessfully = True + except PipelineError as pe: self.logger.debug('Encountered PipelineError for %s : %s' % (self.JobId, str(pe))) ## roll back transfer if necessary @@ -605,30 +607,141 @@ class IngestPipeline(): #----------------------------------------------------------------- selfstarter - if __name__ == '__main__': import sys - if len(sys.argv) != 2: + import threading + import os + import os.path + import job_parser + from optparse import OptionParser + + # Check the invocation arguments + parser = OptionParser("%prog [options] <jobfile/dir_with_job_files>", + description='Run the ingestpipeline on a single jobfile, or a directory containing many jobfiles.') + parser.add_option("-u", "--user", dest="user", type="string", default=getpass.getuser(), help="username for to login on <host>, [default: %default]") + parser.add_option("-p", "--parallel", dest="maxParallelJobs", type="int", default=4, help="number of parellel pipelines to run when processing a directory of jobfiles [default: %default]") + (options, args) = parser.parse_args() + + if len(args) != 1: + parser.print_help() + sys.exit(1) + print 'usage: ingestpipeline.py <path_to_jobfile.xml>' + print 'or' + print 'usage: ingestpipeline.py <path_to_dir_containing_set_of_jobfiles>' logging.basicConfig(format="%(asctime)-15s %(levelname)s %(message)s", level=logging.DEBUG) logger = logging.getLogger('Slave') - jobfile = sys.argv[1] - import job_parser - parser = job_parser.parser(logger) - job = parser.parse(jobfile) - job['filename'] = jobfile - - logger.info(str(job)) - - if getpass.getuser() == 'ingest': + if options.user == 'ingest': import ingest_config as config else: import ingest_config_test as config - #create misc mock args - ltacphost = 'mock-ltacphost' - ltacpport = -1 - mailCommand = '' - srmInit = '' + path = args[0] + + if os.path.isfile(path): + parser = job_parser.parser(logger) + job = parser.parse(path) + job['filename'] = path + logger.info("Parsed jobfile %s: %s" % (path, str(job))) + jobPipeline = IngestPipeline(None, jobToRun, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit) + jobPipeline.run() + exit(0) + + if os.path.isdir(path): + dirEntries = [os.path.join(path, x) for x in os.listdir(path)] + jobfilepaths = [p for p in dirEntries if os.path.isfile(p)] - standalone = IngestPipeline(None, job, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit) - standalone.run() + try: + if not os.path.exists(os.path.join(path, 'done')): + os.mkdir(os.path.join(path, 'done')) + except Exception as e: + logger.error(str(e)) + exit(-1) + + # scan all jobfilepaths + # put each job in hostJobQueues dict, in a list per host + # this gives us a job queue per host, so we can parallelize. + hostJobQueues = {} + for jobfilepath in jobfilepaths: + try: + parser = job_parser.parser(logger) + job = parser.parse(jobfilepath) + job['filename'] = jobfilepath + logger.info("Parsed jobfile %s: %s" % (jobfilepath, str(job))) + + if 'host' in job: + host = job['host'] + if not host in hostJobQueues: + hostJobQueues[host] = [] + hostJobQueues[host].append(job) + except Exception as e: + logger.error("Could not parse %s: %s" % (jobfilepath, str(e))) + + runningPipelinesPerHost = {} + + while hostJobQueues: + busyHosts = set(runningPipelinesPerHost.keys()) + freeHosts = set(hostJobQueues.keys()) - busyHosts + + startedNewJobs = False + + # start new pipeline for one job per free host + for host in freeHosts: + jobQueue = hostJobQueues[host] + + if not jobQueue: + logging.info('no more jobs for host: %s' % host) + del hostJobQueues[host] + elif len(runningPipelinesPerHost) < options.maxParallelJobs: + jobToRun = jobQueue.pop(0) + + logging.info('starting job %s on host %s' % (jobToRun['JobId'], host)) + jobPipeline = IngestPipeline(None, jobToRun, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit, options.user) + + def runPipeline(pl): + try: + pl.run() + except Exception as e: + logger.error(str(e)) + + pipelineThread = threading.Thread(target=runPipeline, kwargs={'pl':jobPipeline}) + pipelineThread.daemon = True + pipelineThread.start() + + runningPipelinesPerHost[host] = (jobPipeline, pipelineThread) + logging.info('started job %s on host %s' % (jobToRun['JobId'], host)) + startedNewJobs = True + + if startedNewJobs: + for h, q in hostJobQueues.items(): + logger.info("%s: %s jobs in queue" % (h, len(q))) + + # check which jobs are done + busyHosts = set(runningPipelinesPerHost.keys()) + + for host in busyHosts: + try: + jobPipeline, pipelineThread = runningPipelinesPerHost[host] + + if pipelineThread.isAlive(): + logging.info('job %s on host %s is running' % (jobPipeline.job['JobId'], host)) + else: + logging.info('job %s on host %s is done' % (jobPipeline.job['JobId'], host)) + pipelineThread.join(10) + del runningPipelinesPerHost[host] + + if jobPipeline.finishedSuccessfully: + try: + if os.path.isdir(path): + path, filename = os.split(jobPipeline.job['filename']) + os.rename(jobPipeline.job['filename'], os.path.join(path, 'done', filename)) + except Exception as e: + logger.error(str(e)) + else: + #retry, put back in jobqueue + logging.info('job %s on host %s was put back in the queue' % (jobPipeline.job['JobId'], host)) + hostJobQueues[host].append(jobPipeline.job) + except Exception as e: + logger.error(str(e)) + + time.sleep(30) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index 6f6b0554257..035c2c1fc87 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -8,6 +8,7 @@ # adler32 is used between localhost and the SRM. import logging +from optparse import OptionParser from subprocess import Popen, PIPE import socket import os, sys, getpass @@ -33,15 +34,6 @@ logger = logging.getLogger('Slave') _ingest_init_script = '/globalhome/ingest/service/bin/init.sh' -if __name__ == '__main__': - log_handler = logging.StreamHandler() - formatter = logging.Formatter('%(asctime)-15s %(levelname)s %(message)s') - formatter.converter = time.gmtime - log_handler.setFormatter(formatter) - logger.addHandler(log_handler) - logger.setLevel(logging.INFO) - - class LtacpException(Exception): def __init__(self, value): self.value = value @@ -57,7 +49,6 @@ def getLocalIPAddress(): def convert_surl_to_turl(surl): #list of sara doors is based on recent actual transfers using srmcp, which translates the surl to a 'random' turl sara_nodes = ['fly%d' % i for i in range(1, 11)] + \ - ['wasp%d' % i for i in range(1, 10)] + \ ['by27-%d' % i for i in range(1, 10)] + \ ['bw27-%d' % i for i in range(1, 10)] + \ ['by32-%d' % i for i in range(1, 10)] + \ @@ -72,6 +63,7 @@ def convert_surl_to_turl(surl): turl = turl.replace("srm://lofar-srm.fz-juelich.de", "gsiftp://dcachepool%d.fz-juelich.de:2811" % (random.randint(9, 16),), 1) turl = turl.replace("srm://srm.target.rug.nl:8444","gsiftp://gridftp02.target.rug.nl/target/gpfs2/lofar/home/srm",1) turl = turl.replace("srm://srm.target.rug.nl","gsiftp://gridftp02.target.rug.nl/target/gpfs2/lofar/home/srm",1) + turl = turl.replace("srm://lta-head.lofar.psnc.pl:8443", "gsiftp://door0%d.lofar.psnc.pl:2811" % (random.randint(1, 2),), 1) return turl def createNetCatCmd(user, host): @@ -79,7 +71,7 @@ def createNetCatCmd(user, host): # nc has no version option or other ways to check it's version # so, just try the variants and pick the first one that does not fail - nc_variants = ['nc --send-only', 'nc -q 0'] + nc_variants = ['nc --send-only', 'nc -q 0', 'nc'] for nc_variant in nc_variants: cmd = ['ssh', '-n', '-x', '%s@%s' % (user, host), nc_variant] @@ -124,6 +116,26 @@ class LtaCp: dst_turl = convert_surl_to_turl(self.dst_surl) logger.info('ltacp %s: initiating transfer of %s:%s to %s' % (self.logId, self.src_host, self.src_path_data, self.dst_surl)) + # get input datasize + cmd_remote_du = self.ssh_cmd + ['du -b --max-depth=0 %s' % (self.src_path_data,)] + logger.info('ltacp %s: remote getting datasize. executing: %s' % (self.logId, ' '.join(cmd_remote_du))) + p_remote_du = Popen(cmd_remote_du, stdout=PIPE, stderr=PIPE) + self.started_procs[p_remote_du] = cmd_remote_du + + # block until du is finished + output_remote_du = p_remote_du.communicate() + del self.started_procs[p_remote_du] + if p_remote_du.returncode != 0: + raise LtacpException('ltacp %s: remote du failed: \nstdout: %s\nstderr: %s' % (self.logId, + output_remote_du[0], + output_remote_du[1])) + # compute various parameters for progress logging + input_datasize = int(output_remote_du[0].split()[0]) + logger.info('ltacp %s: input datasize: %d bytes, %s' % (self.logId, input_datasize, humanreadablesize(input_datasize))) + estimated_tar_size = 512*(input_datasize / 512) + 3*512 #512byte header, 2*512byte ending, 512byte modulo data + logger.info('ltacp %s: estimated_tar_size: %d bytes, %s' % (self.logId, estimated_tar_size, humanreadablesize(estimated_tar_size))) + + #--- # Server part #--- @@ -133,58 +145,28 @@ class LtaCp: random.seed(hash(self.src_path_data) ^ hash(time.time())) p_data_in, port_data = self._ncListen('data') - p_md5_receive, port_md5 = self._ncListen('md5 checksums') - - # create fifo paths - self.local_fifo_basename = '/tmp/ltacp_datapipe_%s_%s' % (self.src_host, self.logId) - - def createLocalFifo(fifo_postfix): - fifo_path = '%s_%s' % (self.local_fifo_basename, fifo_postfix) - logger.info('ltacp %s: creating data fifo: %s' % (self.logId, fifo_path)) - if os.path.exists(fifo_path): - os.remove(fifo_path) - os.mkfifo(fifo_path) - if not os.path.exists(fifo_path): - raise LtacpException("ltacp %s: Could not create fifo: %s" % (self.logId, fifo_path)) - self.fifos.append(fifo_path) - return fifo_path - - self.local_data_fifo = createLocalFifo('globus_url_copy') - self.local_byte_count_fifo = createLocalFifo('local_byte_count') - self.local_adler32_fifo = createLocalFifo('local_adler32') - - # tee incoming data stream to fifo (and pipe stream in tee_proc.stdout) - def teeDataStreams(pipe_in, fifo_out): - cmd_tee = ['tee', fifo_out] - logger.info('ltacp %s: splitting datastream. executing: %s' % (self.logId, ' '.join(cmd_tee),)) - tee_proc = Popen(cmd_tee, stdin=pipe_in, stdout=PIPE, stderr=PIPE) - self.started_procs[tee_proc] = cmd_tee - return tee_proc - - p_tee_data = teeDataStreams(p_data_in.stdout, self.local_data_fifo) - p_tee_byte_count = teeDataStreams(p_tee_data.stdout, self.local_byte_count_fifo) - p_tee_checksums = teeDataStreams(p_tee_byte_count.stdout, self.local_adler32_fifo) - - # start counting number of bytes in incoming data stream - cmd_byte_count = ['wc', '-c', self.local_byte_count_fifo] - logger.info('ltacp %s: computing byte count. executing: %s' % (self.logId, ' '.join(cmd_byte_count))) - p_byte_count = Popen(cmd_byte_count, stdout=PIPE, stderr=PIPE, env=dict(os.environ, LC_ALL="C")) - self.started_procs[p_byte_count] = cmd_byte_count - - # start computing md5 checksum of incoming data stream - cmd_md5_local = ['md5sum'] - logger.info('ltacp %s: computing local md5 checksum. executing on data pipe: %s' % (self.logId, ' '.join(cmd_md5_local))) - p_md5_local = Popen(cmd_md5_local, stdin=p_tee_checksums.stdout, stdout=PIPE, stderr=PIPE) - self.started_procs[p_md5_local] = cmd_md5_local - - # start computing adler checksum of incoming data stream - cmd_a32_local = ['./md5adler/a32', self.local_adler32_fifo] - #cmd_a32_local = ['md5sum', self.local_adler32_fifo] - logger.info('ltacp %s: computing local adler32 checksum. executing: %s' % (self.logId, ' '.join(cmd_a32_local))) - p_a32_local = Popen(cmd_a32_local, stdout=PIPE, stderr=PIPE) - self.started_procs[p_a32_local] = cmd_a32_local - - # start copy fifo stream to SRM + + + self.local_data_fifo = '/tmp/ltacp_datapipe_%s_%s' % (self.src_host, self.logId) + + logger.info('ltacp %s: creating data fifo: %s' % (self.logId, self.local_data_fifo)) + if os.path.exists(self.local_data_fifo): + os.remove(self.local_data_fifo) + os.mkfifo(self.local_data_fifo) + if not os.path.exists(self.local_data_fifo): + raise LtacpException("ltacp %s: Could not create fifo: %s" % (self.logId, self.local_data_fifo)) + + # transfer incomming data stream via md5a32bc to compute md5, adler32 and byte_count + # data is written to fifo, which is then later fed into globus-url-copy + # on stdout we can monitor progress + # set progress message step 0f 0.5% of estimated_tar_size + currdir = os.path.dirname(os.path.realpath(__file__)) + cmd_md5a32bc = [currdir + '/md5adler/md5a32bc', '-p', str(estimated_tar_size/200), self.local_data_fifo] + logger.info('ltacp %s: processing data stream for md5, adler32 and byte_count. executing: %s' % (self.logId, ' '.join(cmd_md5a32bc),)) + p_md5a32bc = Popen(cmd_md5a32bc, stdin=p_data_in.stdout, stdout=PIPE, stderr=PIPE) + self.started_procs[p_md5a32bc] = cmd_md5a32bc + + # start copy fifo stream to globus-url-copy guc_options = ['-cd', #create remote directories if missing '-p 4', #number of parallel ftp connections '-bs 131072', #buffer size @@ -221,7 +203,9 @@ class LtaCp: # 3) simultaneously to 2), calculate checksum of fifo stream # 4) break fifo - self.remote_data_fifo = '/tmp/ltacp_md5_pipe_%s_%s' % (self.logId, port_md5) + self.remote_data_fifo = '/tmp/ltacp_md5_pipe_%s' % (self.logId, ) + #make sure there is no old remote fifo + self._removeRemoteFifo() cmd_remote_mkfifo = self.ssh_cmd + ['mkfifo %s' % (self.remote_data_fifo,)] logger.info('ltacp %s: remote creating fifo. executing: %s' % (self.logId, ' '.join(cmd_remote_mkfifo))) p_remote_mkfifo = Popen(cmd_remote_mkfifo, stdout=PIPE, stderr=PIPE) @@ -233,41 +217,47 @@ class LtaCp: if p_remote_mkfifo.returncode != 0: raise LtacpException('ltacp %s: remote fifo creation failed: \nstdout: %s\nstderr: %s' % (self.logId, output_remote_mkfifo[0],output_remote_mkfifo[1])) - # get input datasize - cmd_remote_du = self.ssh_cmd + ['du -b --max-depth=0 %s' % (self.src_path_data,)] - logger.info('ltacp %s: remote getting datasize. executing: %s' % (self.logId, ' '.join(cmd_remote_du))) - p_remote_du = Popen(cmd_remote_du, stdout=PIPE, stderr=PIPE) - self.started_procs[p_remote_du] = cmd_remote_du - # block until du is finished - output_remote_du = p_remote_du.communicate() - del self.started_procs[p_remote_du] - if p_remote_du.returncode != 0: - raise LtacpException('ltacp %s: remote fifo creation failed: \nstdout: %s\nstderr: %s' % (self.logId, - output_remote_du[0], - output_remote_du[1])) - # compute various parameters for progress logging - input_datasize = int(output_remote_du[0].split()[0]) - logger.info('ltacp %s: input datasize: %d bytes, %s' % (self.logId, input_datasize, humanreadablesize(input_datasize))) - estimated_tar_size = 512*(input_datasize / 512) + 3*512 #512byte header, 2*512byte ending, 512byte modulo data - logger.info('ltacp %s: estimated_tar_size: %d bytes, %s' % (self.logId, estimated_tar_size, humanreadablesize(estimated_tar_size))) - tar_record_size = 10240 # 20 * 512 byte blocks + # get input filetype + cmd_remote_filetype = self.ssh_cmd + ['ls -l %s' % (self.src_path_data,)] + logger.info('ltacp %s: remote getting file info. executing: %s' % (self.logId, ' '.join(cmd_remote_filetype))) + p_remote_filetype = Popen(cmd_remote_filetype, stdout=PIPE, stderr=PIPE) + self.started_procs[p_remote_filetype] = cmd_remote_filetype + + # block until ls is finished + output_remote_filetype = p_remote_filetype.communicate() + del self.started_procs[p_remote_filetype] + if p_remote_filetype.returncode != 0: + raise LtacpException('ltacp %s: remote file listing failed: \nstdout: %s\nstderr: %s' % (self.logId, + output_remote_filetype[0], + output_remote_filetype[1])) + + # determine if input is file + input_is_file = (output_remote_filetype[0][0] == '-') + logger.info('ltacp %s: remote path is a %s' % (self.logId, 'file' if input_is_file else 'directory')) with open(os.devnull, 'r') as devnull: # start sending remote data, tee to fifo - src_path_parent, src_path_child = os.path.split(self.src_path_data) - cmd_remote_data = self.ssh_cmd + ['cd %s && tar c --blocking-factor=20 --checkpoint=1000 --checkpoint-action="ttyout=checkpoint %%u\\n" -O %s | tee %s | %s %s %s' % (src_path_parent, - src_path_child, - self.remote_data_fifo, - self.remoteNetCatCmd, - self.localIPAddress, - port_data)] + if input_is_file: + cmd_remote_data = self.ssh_cmd + ['cat %s | tee %s | %s %s %s' % (self.src_path_data, + self.remote_data_fifo, + self.remoteNetCatCmd, + self.localIPAddress, + port_data)] + else: + src_path_parent, src_path_child = os.path.split(self.src_path_data) + cmd_remote_data = self.ssh_cmd + ['cd %s && tar c -O %s | tee %s | %s %s %s' % (src_path_parent, + src_path_child, + self.remote_data_fifo, + self.remoteNetCatCmd, + self.localIPAddress, + port_data)] logger.info('ltacp %s: remote starting transfer. executing: %s' % (self.logId, ' '.join(cmd_remote_data))) p_remote_data = Popen(cmd_remote_data, stdin=devnull, stdout=PIPE, stderr=PIPE) self.started_procs[p_remote_data] = cmd_remote_data # start computation of checksum on remote fifo stream - cmd_remote_checksum = self.ssh_cmd + ['md5sum %s | %s %s %s' % (self.remote_data_fifo, self.remoteNetCatCmd, self.localIPAddress, port_md5)] + cmd_remote_checksum = self.ssh_cmd + ['md5sum %s' % (self.remote_data_fifo,)] logger.info('ltacp %s: remote starting computation of md5 checksum. executing: %s' % (self.logId, ' '.join(cmd_remote_checksum))) p_remote_checksum = Popen(cmd_remote_checksum, stdin=devnull, stdout=PIPE, stderr=PIPE) self.started_procs[p_remote_checksum] = cmd_remote_checksum @@ -277,7 +267,7 @@ class LtaCp: return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / float(10**6) # waiting for output, comparing checksums, etc. - logger.info('ltacp %s: transfering...' % self.logId) + logger.info('ltacp %s: transfering... waiting for progress...' % self.logId) transfer_start_time = datetime.utcnow() prev_progress_time = datetime.utcnow() prev_bytes_transfered = 0 @@ -285,15 +275,19 @@ class LtaCp: # wait and poll for progress while all processes are runnning while len([p for p in self.started_procs.keys() if p.poll() is not None]) == 0: try: - # read and process tar stdout lines to create progress messages - nextline = p_remote_data.stdout.readline().strip() + current_progress_time = datetime.utcnow() + elapsed_secs_since_prev = timedelta_total_seconds(current_progress_time - prev_progress_time) + + if elapsed_secs_since_prev > 120: + raise LtacpException('ltacp %s: transfer stalled.' % (self.logId)) + + # read and process md5a32bc stdout lines to create progress messages + nextline = p_md5a32bc.stdout.readline().strip() + if len(nextline) > 0: - record_nr = int(nextline.split()[-1].strip()) - total_bytes_transfered = record_nr * tar_record_size + total_bytes_transfered = int(nextline.split()[0].strip()) percentage_done = (100.0*float(total_bytes_transfered))/float(estimated_tar_size) - current_progress_time = datetime.utcnow() elapsed_secs_since_start = timedelta_total_seconds(current_progress_time - transfer_start_time) - elapsed_secs_since_prev = timedelta_total_seconds(current_progress_time - prev_progress_time) if percentage_done > 0 and elapsed_secs_since_start > 0 and elapsed_secs_since_prev > 0: avg_speed = total_bytes_transfered / elapsed_secs_since_start current_bytes_transfered = total_bytes_transfered - prev_bytes_transfered @@ -303,16 +297,20 @@ class LtaCp: prev_bytes_transfered = total_bytes_transfered percentage_to_go = 100.0 - percentage_done time_to_go = elapsed_secs_since_start * percentage_to_go / percentage_done - logger.info('ltacp %s: transfered %d bytes, %s, %.1f%% at avgSpeed=%s curSpeed=%s to_go=%s' % (self.logId, - total_bytes_transfered, + logger.info('ltacp %s: transfered %s %.1f%% at avgSpeed=%s (%s) curSpeed=%s (%s) to_go=%s to %s' % (self.logId, humanreadablesize(total_bytes_transfered), percentage_done, humanreadablesize(avg_speed, 'Bps'), + humanreadablesize(avg_speed*8, 'bps'), humanreadablesize(current_speed, 'Bps'), - timedelta(seconds=int(round(time_to_go))))) + humanreadablesize(current_speed*8, 'bps'), + timedelta(seconds=int(round(time_to_go))), + dst_turl)) time.sleep(0.05) except KeyboardInterrupt: self.cleanup() + except Exception as e: + logger.error('ltacp %s: %s' % (self.logId, str(e))) logger.info('ltacp %s: waiting for transfer via globus-url-copy to LTA to finish...' % self.logId) output_data_out = p_data_out.communicate() @@ -332,42 +330,35 @@ class LtaCp: raise LtacpException('ltacp %s: Error in remote md5 checksum computation: %s' % (self.logId, output_remote_checksum[1])) logger.debug('ltacp %s: remote md5 checksum computation finished.' % self.logId) - logger.debug('ltacp %s: waiting to receive remote md5 checksum...' % self.logId) - output_md5_receive = p_md5_receive.communicate() - if p_md5_receive.returncode != 0: - raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_md5_receive[1])) - logger.debug('ltacp %s: received md5 checksum.' % self.logId) - - logger.debug('ltacp %s: waiting for local computation of md5 checksum...' % self.logId) - output_md5_local = p_md5_local.communicate() - if p_md5_local.returncode != 0: - raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_md5_local[1])) - logger.debug('ltacp %s: computed local md5 checksum.' % self.logId) - - # compare remote and local md5 checksums - try: - md5_checksum_remote = output_md5_receive[0].split(' ')[0] - md5_checksum_local = output_md5_local[0].split(' ')[0] + logger.info('ltacp %s: waiting for local computation of md5 adler32 and byte_count...' % self.logId) + output_md5a32bc_local = p_md5a32bc.communicate() + if p_md5a32bc.returncode != 0: + raise LtacpException('ltacp %s: Error while computing md5 adler32 and byte_count: %s' % (self.logId, output_md5a32bc_local[1])) + logger.debug('ltacp %s: computed local md5 adler32 and byte_count.' % self.logId) + + # process remote md5 checksums + try: + md5_checksum_remote = output_remote_checksum[0].split(' ')[0] + except Exception as e: + logger.error('ltacp %s: error while parsing remote md5: %s\n%s' % (self.logId, output_remote_checksum[0], output_remote_checksum[1])) + raise + + # process local md5 adler32 and byte_count + try: + items = output_md5a32bc_local[1].splitlines()[-1].split(' ') + md5_checksum_local = items[0].strip() + a32_checksum_local = items[1].strip().zfill(8) + byte_count = int(items[2].strip()) + except Exception as e: + logger.error('ltacp %s: error while parsing md5 adler32 and byte_count outputs: %s' % (self.logId, output_md5a32bc_local[0])) + raise + + logger.info('ltacp %s: byte count of datastream is %d %s' % (self.logId, byte_count, humanreadablesize(byte_count))) + + # compare local and remote md5 if(md5_checksum_remote != md5_checksum_local): raise LtacpException('md5 checksum reported by client (%s) does not match local checksum of incoming data stream (%s)' % (self.logId, md5_checksum_remote, md5_checksum_local)) logger.info('ltacp %s: remote and local md5 checksums are equal: %s' % (self.logId, md5_checksum_local,)) - except Exception as e: - logger.error('ltacp %s: error while parsing md5 checksum outputs: local=%s received=%s' % (self.logId, output_md5_local[0], output_md5_receive[0])) - raise - - logger.debug('ltacp %s: waiting for local byte count on datastream...' % self.logId) - output_byte_count = p_byte_count.communicate() - if p_byte_count.returncode != 0: - raise LtacpException('ltacp %s: Error while receiving remote md5 checksum: %s' % (self.logId, output_byte_count[1])) - byte_count = int(output_byte_count[0].split()[0].strip()) - logger.info('ltacp %s: byte count of datastream is %d %s' % (self.logId, byte_count, humanreadablesize(byte_count))) - - logger.debug('ltacp %s: waiting for local adler32 checksum to complete...' % self.logId) - output_a32_local = p_a32_local.communicate() - if p_a32_local.returncode != 0: - raise LtacpException('ltacp %s: local adler32 checksum computation failed: %s' (self.logId, str(output_a32_local))) - logger.debug('ltacp %s: finished computation of local adler32 checksum' % self.logId) - a32_checksum_local = output_a32_local[0].split()[1] logger.info('ltacp %s: fetching adler32 checksum from LTA...' % self.logId) srm_ok, srm_file_size, srm_a32_checksum = get_srm_size_and_a32_checksum(self.dst_surl) @@ -375,16 +366,17 @@ class LtaCp: if not srm_ok: raise LtacpException('ltacp %s: Could not get srm adler32 checksum for: %s' % (self.logId, self.dst_surl)) - if(srm_a32_checksum != a32_checksum_local): + if srm_a32_checksum != a32_checksum_local: raise LtacpException('ltacp %s: adler32 checksum reported by srm (%s) does not match original data checksum (%s)' % (self.logId, srm_a32_checksum, a32_checksum_local)) - logger.info('ltacp %s: adler32 checksums are equal: %s' % (self.logId, a32_checksum_local)) + logger.info('ltacp %s: adler32 checksums are equal: %s' % (self.logId, a32_checksum_local,)) - if(srm_file_size != byte_count): + if int(srm_file_size) != int(byte_count): raise LtacpException('ltacp %s: file size reported by srm (%s) does not match datastream byte count (%s)' % (self.logId, - srm_file_size, byte_count)) + srm_file_size, + byte_count)) logger.info('ltacp %s: srm file size and datastream byte count are equal: %s bytes (%s)' % (self.logId, srm_file_size, @@ -424,18 +416,26 @@ class LtaCp: return (p_listen, port) + def _removeRemoteFifo(self): + if hasattr(self, 'remote_data_fifo') and self.remote_data_fifo: + '''remove a file (or fifo) on a remote host. Test if file exists before deleting.''' + cmd_remote_ls = self.ssh_cmd + ['ls %s' % (self.remote_data_fifo,)] + p_remote_ls = Popen(cmd_remote_ls, stdout=PIPE, stderr=PIPE) + p_remote_ls.communicate() + + if p_remote_ls.returncode == 0: + cmd_remote_rm = self.ssh_cmd + ['rm %s' % (self.remote_data_fifo,)] + logger.info('ltacp %s: removing remote fifo. executing: %s' % (self.logId, ' '.join(cmd_remote_rm))) + p_remote_rm = Popen(cmd_remote_rm, stdout=PIPE, stderr=PIPE) + p_remote_rm.communicate() + if p_remote_rm.returncode != 0: + logger.error("Could not remove remote fifo %s@%s:%s\n%s" % (self.src_user, self.src_host, self.remote_data_fifo, p_remote_rm.stderr)) + self.remote_data_fifo = None + def cleanup(self): logger.debug('ltacp %s: cleaning up' % (self.logId)) - if hasattr(self, 'remote_data_fifo') and self.remote_data_fifo: - '''remove a file (or fifo) on a remote host. Test if file exists before deleting.''' - cmd_remote_rm = self.ssh_cmd + ['if [ -e "%s" ] ; then rm %s ; fi ;' % (self.remote_data_fifo, self.remote_data_fifo)] - logger.info('ltacp %s: removing remote fifo. executing: %s' % (self.logId, ' '.join(cmd_remote_rm))) - p_remote_rm = Popen(cmd_remote_rm, stdout=PIPE, stderr=PIPE) - p_remote_rm.communicate() - if p_remote_rm.returncode != 0: - logger.error("Could not remove remote fifo %s@%s:%s\n%s" % (self.src_user, self.src_host, self.remote_data_fifo, p_remote_rm.stderr)) - self.remote_data_fifo = None + self._removeRemoteFifo() # remove local fifos for fifo in self.fifos: @@ -555,12 +555,18 @@ def create_missing_directories(surl): # limited standalone mode for testing: # usage: ltacp.py <remote-host> <remote-path> <surl> if __name__ == '__main__': + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO) + + # Check the invocation arguments + parser = OptionParser("%prog [options] <source_host> <source_path> <lta-detination-srm-url>", + description='copy a file/directory from <source_host>:<source_path> to the LTA <lta-detination-srm-url>') + parser.add_option("-u", "--user", dest="user", type="string", default=getpass.getuser(), help="username for to login on <host>, default: %s" % getpass.getuser()) + (options, args) = parser.parse_args() - if len(sys.argv) < 4: - print 'example: ./ltacp.py 10.196.232.11 /home/users/ingest/1M.txt srm://lofar-srm.fz-juelich.de:8443/pnfs/fz-juelich.de/data/lofar/ops/test/eor/1M.txt' - sys.exit() + if len(args) != 3: + parser.print_help() + sys.exit(1) - # transfer test: - cp = LtaCp(sys.argv[1], sys.argv[2], sys.argv[3], 'ingest') + cp = LtaCp(args[0], args[1], args[2], options.user) cp.transfer() diff --git a/LTA/LTAIngest/md5adler/makefile b/LTA/LTAIngest/md5adler/makefile index 1881dac9029..6768b346366 100644 --- a/LTA/LTAIngest/md5adler/makefile +++ b/LTA/LTAIngest/md5adler/makefile @@ -2,17 +2,21 @@ CFLAGS=-O3 -all: md5a32 a32 +all: md5a32 a32 md5a32bc md5a32: md5a32.o adler32.o md5a32.o: md5.h +md5a32bc: md5a32bc.o adler32.o + +md5a32bc.o: md5.h + adler32: adler32.c adler32.h gcc -c adler32.c -o adler32.o a32: a32.o adler32.o clean: - rm -f adler32.o md5a32.o a32.o md5a32 a32 + rm -f adler32.o md5a32.o md5a32bc.o a32.o md5a32 md5a32bc a32 diff --git a/LTA/LTAIngest/md5adler/md5.h b/LTA/LTAIngest/md5adler/md5.h index a45ba7ae955..d8f4dab91cc 100644 --- a/LTA/LTAIngest/md5adler/md5.h +++ b/LTA/LTAIngest/md5adler/md5.h @@ -42,9 +42,12 @@ /* typedef unsigned long int UINT4; */ typedef unsigned int UINT4; -/* Data structure for MD5 (Message Digest) computation */ +/* Data structure for MD5 (Message Digest) computation + * plus additional adler32 and byte_count variables + */ typedef struct { unsigned int adler32; /* adler 32 crc */ + unsigned long byte_count; /* total number of bytes processed */ UINT4 i[2]; /* number of _bits_ handled mod 2^64 */ UINT4 buf[4]; /* scratch buffer */ unsigned char in[64]; /* input buffer */ diff --git a/LTA/LTAIngest/md5adler/md5a32bc b/LTA/LTAIngest/md5adler/md5a32bc new file mode 100755 index 0000000000000000000000000000000000000000..64d6aa9eb8597fa9042461796da9082f6835d73d GIT binary patch literal 17808 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 Nfu$K%p-H0K4*)Py{$KzA literal 0 HcmV?d00001 diff --git a/LTA/LTAIngest/md5adler/md5a32bc.c b/LTA/LTAIngest/md5adler/md5a32bc.c new file mode 100644 index 00000000000..fc4573012c3 --- /dev/null +++ b/LTA/LTAIngest/md5adler/md5a32bc.c @@ -0,0 +1,376 @@ +/* + ********************************************************************** + ** md5a32bc.c ** + ** Compute md5, adler32 and byte count while transfering data from ** + ** stdin to stdout. Results are printed on stderr. ** + ** ** + ** md5 methods are copied from ** + ** RSA Data Security, Inc. MD5 Message Digest Algorithm ** + ********************************************************************** + */ + +/* + ********************************************************************** + ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. ** + ** ** + ** License to copy and use this software is granted provided that ** + ** it is identified as the "RSA Data Security, Inc. MD5 Message ** + ** Digest Algorithm" in all material mentioning or referencing this ** + ** software or this function. ** + ** ** + ** License is also granted to make and use derivative works ** + ** provided that such works are identified as "derived from the RSA ** + ** Data Security, Inc. MD5 Message Digest Algorithm" in all ** + ** material mentioning or referencing the derived work. ** + ** ** + ** RSA Data Security, Inc. makes no representations concerning ** + ** either the merchantability of this software or the suitability ** + ** of this software for any particular purpose. It is provided "as ** + ** is" without express or implied warranty of any kind. ** + ** ** + ** These notices must be retained in any copies of any part of this ** + ** documentation and/or software. ** + ********************************************************************** + */ + +#include <stdio.h> +#include "md5.h" + +/* forward declaration */ +static void Transform (); + +static unsigned char PADDING[64] = { + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* F, G and H are basic MD5 functions: selection, majority, parity */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s, ac) \ + {(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) \ + {(a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) \ + {(a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) \ + {(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +void MD5Init (mdContext) +MD5_CTX *mdContext; +{ + mdContext->i[0] = mdContext->i[1] = (UINT4)0; + + /* Load magic initialization constants. + */ + mdContext->buf[0] = (UINT4)0x67452301; + mdContext->buf[1] = (UINT4)0xefcdab89; + mdContext->buf[2] = (UINT4)0x98badcfe; + mdContext->buf[3] = (UINT4)0x10325476; + + /* Set adler32 init value to one + */ + mdContext->adler32 = 1; + + mdContext->byte_count = 0; +} + +void MD5Update (mdContext, inBuf, inLen) +MD5_CTX *mdContext; +unsigned char *inBuf; +unsigned int inLen; +{ + UINT4 in[16]; + int mdi; + unsigned int i, ii; + + + /* compute number of bytes mod 64 */ + mdi = (int)((mdContext->i[0] >> 3) & 0x3F); + + /* update number of bits */ + if ((mdContext->i[0] + ((UINT4)inLen << 3)) < mdContext->i[0]) + mdContext->i[1]++; + mdContext->i[0] += ((UINT4)inLen << 3); + mdContext->i[1] += ((UINT4)inLen >> 29); + + while (inLen--) { + /* add new character to buffer, increment mdi */ + mdContext->in[mdi++] = *inBuf++; + + /* transform if necessary */ + if (mdi == 0x40) { + for (i = 0, ii = 0; i < 16; i++, ii += 4) + in[i] = (((UINT4)mdContext->in[ii+3]) << 24) | + (((UINT4)mdContext->in[ii+2]) << 16) | + (((UINT4)mdContext->in[ii+1]) << 8) | + ((UINT4)mdContext->in[ii]); + Transform (mdContext->buf, in); + mdi = 0; + } + } + +} + +void MD5Final (mdContext) +MD5_CTX *mdContext; +{ + UINT4 in[16]; + int mdi; + unsigned int i, ii; + unsigned int padLen; + + /* save number of bits */ + in[14] = mdContext->i[0]; + in[15] = mdContext->i[1]; + + /* compute number of bytes mod 64 */ + mdi = (int)((mdContext->i[0] >> 3) & 0x3F); + + /* pad out to 56 mod 64 */ + padLen = (mdi < 56) ? (56 - mdi) : (120 - mdi); + MD5Update (mdContext, PADDING, padLen); + + /* append length in bits and transform */ + for (i = 0, ii = 0; i < 14; i++, ii += 4) + in[i] = (((UINT4)mdContext->in[ii+3]) << 24) | + (((UINT4)mdContext->in[ii+2]) << 16) | + (((UINT4)mdContext->in[ii+1]) << 8) | + ((UINT4)mdContext->in[ii]); + Transform (mdContext->buf, in); + + /* store buffer in digest */ + for (i = 0, ii = 0; i < 4; i++, ii += 4) { + mdContext->digest[ii] = (unsigned char)(mdContext->buf[i] & 0xFF); + mdContext->digest[ii+1] = + (unsigned char)((mdContext->buf[i] >> 8) & 0xFF); + mdContext->digest[ii+2] = + (unsigned char)((mdContext->buf[i] >> 16) & 0xFF); + mdContext->digest[ii+3] = + (unsigned char)((mdContext->buf[i] >> 24) & 0xFF); + } +} + +/* Basic MD5 step. Transform buf based on in. + */ +static void Transform (buf, in) +UINT4 *buf; +UINT4 *in; +{ + UINT4 a = buf[0], b = buf[1], c = buf[2], d = buf[3]; + + /* Round 1 */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 + FF ( a, b, c, d, in[ 0], S11, 3614090360); /* 1 */ + FF ( d, a, b, c, in[ 1], S12, 3905402710); /* 2 */ + FF ( c, d, a, b, in[ 2], S13, 606105819); /* 3 */ + FF ( b, c, d, a, in[ 3], S14, 3250441966); /* 4 */ + FF ( a, b, c, d, in[ 4], S11, 4118548399); /* 5 */ + FF ( d, a, b, c, in[ 5], S12, 1200080426); /* 6 */ + FF ( c, d, a, b, in[ 6], S13, 2821735955); /* 7 */ + FF ( b, c, d, a, in[ 7], S14, 4249261313); /* 8 */ + FF ( a, b, c, d, in[ 8], S11, 1770035416); /* 9 */ + FF ( d, a, b, c, in[ 9], S12, 2336552879); /* 10 */ + FF ( c, d, a, b, in[10], S13, 4294925233); /* 11 */ + FF ( b, c, d, a, in[11], S14, 2304563134); /* 12 */ + FF ( a, b, c, d, in[12], S11, 1804603682); /* 13 */ + FF ( d, a, b, c, in[13], S12, 4254626195); /* 14 */ + FF ( c, d, a, b, in[14], S13, 2792965006); /* 15 */ + FF ( b, c, d, a, in[15], S14, 1236535329); /* 16 */ + + /* Round 2 */ +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 + GG ( a, b, c, d, in[ 1], S21, 4129170786); /* 17 */ + GG ( d, a, b, c, in[ 6], S22, 3225465664); /* 18 */ + GG ( c, d, a, b, in[11], S23, 643717713); /* 19 */ + GG ( b, c, d, a, in[ 0], S24, 3921069994); /* 20 */ + GG ( a, b, c, d, in[ 5], S21, 3593408605); /* 21 */ + GG ( d, a, b, c, in[10], S22, 38016083); /* 22 */ + GG ( c, d, a, b, in[15], S23, 3634488961); /* 23 */ + GG ( b, c, d, a, in[ 4], S24, 3889429448); /* 24 */ + GG ( a, b, c, d, in[ 9], S21, 568446438); /* 25 */ + GG ( d, a, b, c, in[14], S22, 3275163606); /* 26 */ + GG ( c, d, a, b, in[ 3], S23, 4107603335); /* 27 */ + GG ( b, c, d, a, in[ 8], S24, 1163531501); /* 28 */ + GG ( a, b, c, d, in[13], S21, 2850285829); /* 29 */ + GG ( d, a, b, c, in[ 2], S22, 4243563512); /* 30 */ + GG ( c, d, a, b, in[ 7], S23, 1735328473); /* 31 */ + GG ( b, c, d, a, in[12], S24, 2368359562); /* 32 */ + + /* Round 3 */ +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 + HH ( a, b, c, d, in[ 5], S31, 4294588738); /* 33 */ + HH ( d, a, b, c, in[ 8], S32, 2272392833); /* 34 */ + HH ( c, d, a, b, in[11], S33, 1839030562); /* 35 */ + HH ( b, c, d, a, in[14], S34, 4259657740); /* 36 */ + HH ( a, b, c, d, in[ 1], S31, 2763975236); /* 37 */ + HH ( d, a, b, c, in[ 4], S32, 1272893353); /* 38 */ + HH ( c, d, a, b, in[ 7], S33, 4139469664); /* 39 */ + HH ( b, c, d, a, in[10], S34, 3200236656); /* 40 */ + HH ( a, b, c, d, in[13], S31, 681279174); /* 41 */ + HH ( d, a, b, c, in[ 0], S32, 3936430074); /* 42 */ + HH ( c, d, a, b, in[ 3], S33, 3572445317); /* 43 */ + HH ( b, c, d, a, in[ 6], S34, 76029189); /* 44 */ + HH ( a, b, c, d, in[ 9], S31, 3654602809); /* 45 */ + HH ( d, a, b, c, in[12], S32, 3873151461); /* 46 */ + HH ( c, d, a, b, in[15], S33, 530742520); /* 47 */ + HH ( b, c, d, a, in[ 2], S34, 3299628645); /* 48 */ + + /* Round 4 */ +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + II ( a, b, c, d, in[ 0], S41, 4096336452); /* 49 */ + II ( d, a, b, c, in[ 7], S42, 1126891415); /* 50 */ + II ( c, d, a, b, in[14], S43, 2878612391); /* 51 */ + II ( b, c, d, a, in[ 5], S44, 4237533241); /* 52 */ + II ( a, b, c, d, in[12], S41, 1700485571); /* 53 */ + II ( d, a, b, c, in[ 3], S42, 2399980690); /* 54 */ + II ( c, d, a, b, in[10], S43, 4293915773); /* 55 */ + II ( b, c, d, a, in[ 1], S44, 2240044497); /* 56 */ + II ( a, b, c, d, in[ 8], S41, 1873313359); /* 57 */ + II ( d, a, b, c, in[15], S42, 4264355552); /* 58 */ + II ( c, d, a, b, in[ 6], S43, 2734768916); /* 59 */ + II ( b, c, d, a, in[13], S44, 1309151649); /* 60 */ + II ( a, b, c, d, in[ 4], S41, 4149444226); /* 61 */ + II ( d, a, b, c, in[11], S42, 3174756917); /* 62 */ + II ( c, d, a, b, in[ 2], S43, 718787259); /* 63 */ + II ( b, c, d, a, in[ 9], S44, 3951481745); /* 64 */ + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#include <stdio.h> + +int main (int argc, char *argv[]) +{ + if (argc == 2 && strcmp (argv[1], "-h") == 0) + { + printf("md5a32bc is a tool which computes the md5 and adler32 checksum and counts the number of bytes on the stdin datastream.\n"); + printf("this input datastream is copied and written to stdout by default, or to the output file given as last argument.\n"); + printf("progress messages can be written every <n> bytes with flag -p <n>.\n"); + printf("\n"); + printf("Usage:\n"); + printf("<some_prog> | md5a32bc\n"); + printf("or:\n"); + printf("<some_prog> | md5a32bc <my_output_file>\n"); + printf("or:\n"); + printf("<some_prog> | md5a32bc -p <n> <my_output_file>\n"); + printf("or:\n"); + printf("<some_prog> | md5a32bc -p <n>\n"); + exit(0); + } + + MD5_CTX mdContext; + int num_bytes_read, num_bytes_written; + const int BUFFSIZE = 4096; + unsigned char data[BUFFSIZE]; + int num_progress_bytes = -1; + int progress_step_cntr = 0; + int progress_step = 0; + + if (argc >= 3 && strcmp (argv[1], "-p") == 0) + { + int num = atoi(argv[2]); + if(num > 0) + num_progress_bytes = num; + } + + FILE *outfile = stdout; + FILE *logstream = stderr; + FILE *resultstream = stderr; + + if (argc == 2 || argc == 4) + { + outfile = fopen(argv[argc-1], "wb"); + logstream = stdout; + } + + MD5Init (&mdContext); + + while (1) + { + num_bytes_read = fread (data, 1, BUFFSIZE, stdin); + + if(num_bytes_read == 0) + break; + + mdContext.adler32=adler32(mdContext.adler32, data, num_bytes_read); + MD5Update (&mdContext, data, num_bytes_read); + mdContext.byte_count += num_bytes_read; + + num_bytes_written = fwrite (data, 1, num_bytes_read, outfile); + + if(num_bytes_written != num_bytes_read) + { + fprintf (logstream, "error while writing\n"); + if(outfile != stdout) + fclose(outfile); + return -1; + } + + progress_step = mdContext.byte_count / num_progress_bytes; + + if(progress_step > progress_step_cntr) + { + fprintf (logstream, "%lu bytes processed\n", mdContext.byte_count); + fflush(logstream); + progress_step_cntr = progress_step; + } + } + + fflush(outfile); + fflush(logstream); + + if(outfile != stdout) + fclose(outfile); + + MD5Final (&mdContext); + + for (int i = 0; i < 16; i++) + fprintf (resultstream, "%02x", mdContext.digest[i]); + + fprintf (resultstream, " %x %lu\n", mdContext.adler32, mdContext.byte_count); + fflush(resultstream); + + return 0; +} -- GitLab From 8cd42d0d78f52b2964528b78a6738ce14c7f286b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 26 May 2016 14:02:43 +0000 Subject: [PATCH 221/933] Task #8392: cep4 modifications --- LTA/LTAIngest/ingestpipeline.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index 612b6032ce0..ef5ece42d4f 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -74,6 +74,11 @@ class IngestPipeline(): self.ObsId = int(job['ObservationId']) self.HostLocation = job['Location'].split(':')[0] self.Location = job['Location'].split(':')[1] + if 'cep4' in self.HostLocation.lower(): + # lexar003/lexar004 have cep4 lustrefs /data dir mounted on /data, + # so we can read data with ltacp client from localhost + self.HostLocation = 'localhost' + pos = self.Location.find(self.DataProduct) if pos > 0: ## trick to support tar files with different names self.LocationDir = self.Location[:pos] @@ -169,15 +174,22 @@ class IngestPipeline(): self.logger.debug('Starting file transfer') hostname = socket.getfqdn() javacmd = "java" - if "lexar" in hostname: + if "lexar001" in hostname or "lexar002" in hostname: javacmd = "/data/java7/jdk1.7.0_55/bin/java" ltacppath = "/globalhome/%s/ltacp" % ("ingesttest" if self.ltacpport == 8801 else "ingest") - if self.PrimaryUri: - cmd = ["ssh", "-T", "ingest@" +self.HostLocation, "cd %s;%s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.PrimaryUri, self.Source)] + if self.HostLocation == 'localhost': + # copy data with ltacp client from HostLocation localhost, to ltacpserver at localhost + # so no need for ssh + cmd = ["cd %s;%s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.PrimaryUri, self.Source)] else: - cmd = ["ssh", "-T", "ingest@" + self.HostLocation, "cd %s;%s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s/%s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.tempPrimary, self.FileName, self.Source)] + # copy data with ltacp from a remote host, so use ssh + if self.PrimaryUri: + cmd = ["ssh", "-T", "ingest@" +self.HostLocation, "cd %s;%s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.PrimaryUri, self.Source)] + else: + cmd = ["ssh", "-T", "ingest@" + self.HostLocation, "cd %s;%s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s/%s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.tempPrimary, self.FileName, self.Source)] + ## SecondaryUri handling not implemented self.logger.debug(cmd) start = time.time() @@ -643,7 +655,7 @@ if __name__ == '__main__': job = parser.parse(path) job['filename'] = path logger.info("Parsed jobfile %s: %s" % (path, str(job))) - jobPipeline = IngestPipeline(None, jobToRun, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit) + jobPipeline = IngestPipeline(None, job, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit) jobPipeline.run() exit(0) -- GitLab From c49f7f2707b1c002043e842fb7b880e8a18dcefd Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 26 May 2016 19:37:14 +0000 Subject: [PATCH 222/933] Task #8437: Allow docker image:tag to be specified in parset, instead of just the tag --- MAC/Services/src/PipelineControl.py | 12 ++++++------ MAC/Services/test/tPipelineControl.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 0356126c162..083ee3250db 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -124,10 +124,10 @@ class Parset(dict): def processingCluster(self): return self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] or "CEP2" - def dockerTag(self): + def dockerImage(self): # Return the version set in the parset, and fall back to our own version. return (self[PARSET_PREFIX + "Observation.ObservationControl.PythonControl.softwareVersion"] or - runCommand("docker-template", "${LOFAR_TAG}")) + runCommand("docker-template", "lofar-pipeline:${LOFAR_TAG}")) def slurmJobName(self): return str(self.otdbId()) @@ -345,12 +345,12 @@ class PipelineControl(OTDBBusListener): " -e LOFARENV={lofarenv}" " -v $HOME/.ssh:/home/lofar/.ssh:ro" " -e SLURM_JOB_ID=$SLURM_JOB_ID" - " lofar-pipeline:{tag}" + " {image}" " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -B {status_bus}" .format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, - tag = parset.dockerTag(), + image = parset.dockerImage(), cluster = parset.processingCluster(), status_bus = self.otdb_service_busname, ), @@ -367,12 +367,12 @@ class PipelineControl(OTDBBusListener): " --net=host" " -u $UID" " -e LOFARENV={lofarenv}" - " lofar-pipeline:{tag}" + " {image}" " pipelineAborted.sh -o {obsid} -B {status_bus}" .format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, - tag = parset.dockerTag(), + image = parset.dockerImage(), status_bus = self.otdb_service_busname, ), diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index 3fe31bdeca3..31d281b80d7 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -121,8 +121,8 @@ class TestPipelineControl(unittest.TestCase): self.addCleanup(patcher.stop) # Catch functions to prevent running executables - patcher = patch('lofar.mac.PipelineControl.Parset.dockerTag') - patcher.start().return_value = "trunk" + patcher = patch('lofar.mac.PipelineControl.Parset.dockerImage') + patcher.start().return_value = "lofar-pipeline:trunk" self.addCleanup(patcher.stop) # ================================ -- GitLab From 19c166765f7199ccddbc19942012c17895b5a139 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 27 May 2016 09:39:15 +0000 Subject: [PATCH 223/933] Task #8392: startup of ltacp for cep4 on localhost --- LTA/LTAIngest/ingestpipeline.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index ef5ece42d4f..af6a44ab461 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -182,7 +182,8 @@ class IngestPipeline(): if self.HostLocation == 'localhost': # copy data with ltacp client from HostLocation localhost, to ltacpserver at localhost # so no need for ssh - cmd = ["cd %s;%s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.PrimaryUri, self.Source)] + hostname = 'localhost' + cmd = ["cd %s && %s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.PrimaryUri, self.Source)] else: # copy data with ltacp from a remote host, so use ssh if self.PrimaryUri: @@ -193,7 +194,7 @@ class IngestPipeline(): ## SecondaryUri handling not implemented self.logger.debug(cmd) start = time.time() - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) logs = p.communicate() elapsed = time.time() - start self.logger.debug("File transfer for %s took %d sec" % (self.JobId, elapsed)) @@ -532,7 +533,7 @@ class IngestPipeline(): break retry += 1 if retry < times: - wait_time = random.randint(30, 60) * retry + wait_time = 1 #random.randint(30, 60) * retry self.logger.debug('waiting %d seconds before trying %s again' % (wait_time, self.JobId)) time.sleep(wait_time) if error: -- GitLab From 2f2545a2d2ec9b64c5aec0da0844bdbb6cc6e146 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 27 May 2016 10:33:17 +0000 Subject: [PATCH 224/933] Task #9487: Do NOT abort if stations didnt quit properly. --- .../src/ObservationControl/ObservationControl.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc b/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc index 0e3407d780e..36a3c3d01fb 100644 --- a/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc +++ b/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc @@ -535,7 +535,18 @@ GCFEvent::TResult ObservationControl::active_state(GCFEvent& event, GCFPortInter } else if (timerEvent.id == itsForcedQuitTimer) { LOG_WARN("QUITING BEFORE ALL CHILDREN DIED."); - itsQuitReason = CT_RESULT_EMERGENCY_TIMEOUT; + + bool centralControllerRunning = + (itsChildControl->countChilds(0, itsProcessType == "Observation" ? CNTLRTYPE_ONLINECTRL : CNTLRTYPE_OFFLINECTRL)); + + if (centralControllerRunning) { + LOG_FATAL("Central controller did not die."); + itsQuitReason = CT_RESULT_EMERGENCY_TIMEOUT; + } else { + // JDM #9487: For stations, a loss of connection is NOT fatal + itsQuitReason = CT_RESULT_NO_ERROR; + } + TRAN(ObservationControl::finishing_state); } // some other timer? -- GitLab From 950ceea78bf39385b6f7d188a83e049a60e35e63 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 27 May 2016 12:41:00 +0000 Subject: [PATCH 225/933] Task #8437: Fix copying of bashrc.d --- Docker/lofar-base/Dockerfile.tmpl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 7562a541e25..5eff1ea9549 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -148,7 +148,8 @@ RUN apt-get update && apt-get install -y git cmake g++ swig python-dev libhdf5-d # # entry # -COPY ["bashrc", "bashrc.d", "${INSTALLDIR}/"] +COPY ["bashrc", "${INSTALLDIR}/"] +COPY ["bashrc.d", "${INSTALLDIR}/bashrc.d/"] COPY ["chuser.sh", "/usr/local/bin"] ENTRYPOINT ["/usr/local/bin/chuser.sh"] -- GitLab From 2e34f53a329a9d0196c05594e9729440d6281422 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 27 May 2016 13:18:17 +0000 Subject: [PATCH 226/933] Task #8392: modifications to get master and slave running on lexar003/004 --- LTA/LTAIngest/ingest_config.py.in | 8 +++--- LTA/LTAIngest/master.py | 44 +++++++++++++++++++------------ LTA/LTAIngest/slave.py | 12 ++++++--- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/LTA/LTAIngest/ingest_config.py.in b/LTA/LTAIngest/ingest_config.py.in index e39b43b8a6e..87d9043bb41 100644 --- a/LTA/LTAIngest/ingest_config.py.in +++ b/LTA/LTAIngest/ingest_config.py.in @@ -21,7 +21,7 @@ ltaClient = None host = socket.gethostname() ## Using netifaces module or something similar would be nicer, but it doesn't want to install in a custom dir ## So we use this hack -if 'lexar' in host: +if 'lexar001' in host or 'lexar002' in host: host = host + '.offline.lofar' if 'gridftp01.target.rug.nl' in host: host = 'lotar2.staging.lofar' @@ -120,9 +120,11 @@ momURLlogout = 'https://lcs029.control.lofar:8443/useradministration/user/logout momRetry = 3 srmRetry = 2 -if 'lexar' in host: +if 'lexar001' in host or 'lexar002' in host: srmInit = '/globalhome/ingest/service/bin/init.sh' -if 'lotar' in host: +elif 'lexar003' in host or 'lexar004' in host: + srmInit = '/globalhome/ingest/.grid/.ingest_profile' +elif 'lotar' in host: srmInit = '/home/lofarlocal/ltacp/bin/init.sh' try: diff --git a/LTA/LTAIngest/master.py b/LTA/LTAIngest/master.py index 773d5fe66f2..ed52c927b3a 100755 --- a/LTA/LTAIngest/master.py +++ b/LTA/LTAIngest/master.py @@ -174,6 +174,7 @@ class jobHandler(Process): class manager(SyncManager): pass manager.register('number') manager.register('get') + manager.register('slave_names') self.manager = manager(address=(self.masterAddress, self.masterPort), authkey=self.masterAuth) self.manager.connect() nr_of_slaves = int(str(self.manager.number())) @@ -182,7 +183,7 @@ class jobHandler(Process): nr_of_slaves = int(str(self.manager.number())) time.sleep(10) #Let's wait a few seconds for any more slaves. Currently all slaves need to connect in 10 seconds. nr_of_slaves = int(str(self.manager.number())) - self.logger.info('Slaves found: %d' % nr_of_slaves) + self.logger.info('Slaves found: %d %s' % (nr_of_slaves, self.manager.slave_names())) os.system('echo "The LTA Ingest has been restarted."|mailx -s "LTA Ingest restarted" ' + self.mailCommand) ## ======= Main loop ====== @@ -204,7 +205,7 @@ class jobHandler(Process): self.update_job_msg.put((job, job['Status'], JobProducing, None)) job['Status'] = JobProducing self.active[job['ExportID']] = job - self.manager.get(None, job['destination']).put(job) ## sends it to the slave with the shortest queue of the possible destinations + self.manager.get(job.get('slave_hosts')).put(job) ## sends it to the slave with the shortest queue of the possible slave_hosts self.logger.debug("Job's started: %s (%i)" % (job['ExportID'], len(self.active))) first = True except Empty: pass @@ -260,6 +261,11 @@ class queueHandler(Process): if job['Status'] == JobScheduled: self.update_job(job, None, JobScheduled, None) job['destination'] = self.job_groups[job['job_group']].get_destination() + + sourcehost = job['Location'].split(':')[0] + if 'cep4' in sourcehost.lower(): + job['slave_hosts'] = ['lexar003.lexar.control.lofar', 'lexar004.lexar.control.lofar'] + self.scheduled.put(job) # self.talker.put(job) ## Tell MoM we've done something else: @@ -353,27 +359,30 @@ class ltaMaster(): raise Exception('No MoM to listen to!') def add_slave(self, slave): + self.logger.info('Slave \'%s\' added to master' % (slave, )) self.slaves[slave] = Queue() return self.slaves[slave] def slave_size(self): return len(self.slaves) - ##Gives you the shortest slave queue unless you ask for a specific one. - def get_slave(self, source, destination): - if source: ## this code was developed for use on lse nodes/staging area, not really used. - return self.slaves[source] - else: - result = None - length = sys.maxint - for k in self.slaves.keys(): - if destination in k:# subselection of slaves based on destination, bit of a hack right now: choice between: lexar,lotar - size = self.slaves[k].qsize() - if length > size: - result = self.slaves[k] - length = size - self.logger.debug('found slave %s' % k) - return result + def slave_names(self): + return self.slaves.keys() + + ##Gives you the shortest slave queue out of the requested slave_hosts + def get_slave(self, slave_hosts): + if slave_hosts == None: + slave_hosts = self.slaves.keys() + + result = None + length = sys.maxint + for k in self.slaves.keys(): + size = self.slaves[k].qsize() + if length > size: + result = self.slaves[k] + length = size + self.logger.debug('found slave %s' % k) + return result def remove_slave(self, slave): q = self.slaves.pop(slave, None) @@ -391,6 +400,7 @@ class ltaMaster(): class manager(SyncManager): pass manager.register('add_slave', self.add_slave) manager.register('number', self.slave_size) + manager.register('slave_names', self.slave_names) manager.register('get', self.get_slave) manager.register('remove_slave', self.remove_slave) manager.register('slave_done', self.slave_done) diff --git a/LTA/LTAIngest/slave.py b/LTA/LTAIngest/slave.py index 680a77afadb..0b2ae2921a4 100755 --- a/LTA/LTAIngest/slave.py +++ b/LTA/LTAIngest/slave.py @@ -135,8 +135,10 @@ class executer(Process): if (self.job['Status'] == JobProduced) or (self.job['Status'] == JobError): self.talker.put(self.job) self.manager.slave_done(self.job, self.result, pipeline.FileType) - with self.jobs.get_lock(): - self.jobs.value -= 1 + #python 'with' does not work for some reason, so just use acquire/release + self.jobs.get_lock().acquire() + self.jobs.value -= 1 + self.jobs.get_lock().release() self.logger.debug("Slave Pipeline executer finished for %s in %d sec" % (self.job['ExportID'], time.time() - start)) ## ---------------- LTA Slave -------------------------------------------- @@ -198,8 +200,10 @@ class ltaSlave(): except QueueEmpty: job = None if job: - with self.jobs.get_lock(): - self.jobs.value += 1 + #python 'with' does not work for some reason, so just use acquire/release + self.jobs.get_lock().acquire() + self.jobs.value += 1 + self.jobs.get_lock().release() runner = executer(self.logger, self.logdir, job, talker, self.jobs, self.momClient, self.ltaClient, self.host, self.ltacpport, self.mailSlCommand, self.manager, self.pipelineRetry, self.momRetry, self.ltaRetry, self.srmRetry, self.srmInit) runner.start() else: -- GitLab From c001baf2c06ad35a7fe113c7ebc55cf9fdc0b94d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 27 May 2016 13:39:38 +0000 Subject: [PATCH 227/933] Task #8437: Fix compile warning --- CEP/Imager/LofarFT/src/LofarConvolutionFunction.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Imager/LofarFT/src/LofarConvolutionFunction.cc b/CEP/Imager/LofarFT/src/LofarConvolutionFunction.cc index 4b04e7d1fc3..2c78fcf6584 100644 --- a/CEP/Imager/LofarFT/src/LofarConvolutionFunction.cc +++ b/CEP/Imager/LofarFT/src/LofarConvolutionFunction.cc @@ -1165,7 +1165,7 @@ namespace LOFAR IPosition posOut(4,1,1,1,1); IPosition posEl(2,1,1); Complex pix; - uInt jjpix,ch; + uInt jjpix,ch = 0; /* gcc somehow complains ch can be uninitialised */ Complex ElementValue; for(ch=0; ch<input_grid.shape()[3]; ++ch){ #pragma omp parallel -- GitLab From 1f83b7518cb1d45e78b2a72448680ccb950765d8 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 27 May 2016 13:39:59 +0000 Subject: [PATCH 228/933] Task #8437: Boost components are actually mandatory --- LCS/ApplCommon/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LCS/ApplCommon/CMakeLists.txt b/LCS/ApplCommon/CMakeLists.txt index 2541f284bfb..6f563d1af0b 100644 --- a/LCS/ApplCommon/CMakeLists.txt +++ b/LCS/ApplCommon/CMakeLists.txt @@ -3,7 +3,7 @@ lofar_package(ApplCommon 3.1 DEPENDS Common) include(LofarFindPackage) -lofar_find_package(Boost COMPONENTS date_time regex) +lofar_find_package(Boost COMPONENTS date_time regex REQUIRED) add_subdirectory(include/ApplCommon) add_subdirectory(src) -- GitLab From 1fcbafcb3f4b7e5cab6967c075a63ce94dc4fd9d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 27 May 2016 13:41:30 +0000 Subject: [PATCH 229/933] Task #8392: random small waittime --- LTA/LTAIngest/ingestpipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index af6a44ab461..f26ba5b52d5 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -533,7 +533,7 @@ class IngestPipeline(): break retry += 1 if retry < times: - wait_time = 1 #random.randint(30, 60) * retry + wait_time = random.randint(30, 60) * retry self.logger.debug('waiting %d seconds before trying %s again' % (wait_time, self.JobId)) time.sleep(wait_time) if error: -- GitLab From ebaaad32a53d0532b8220c29d6d4665b38566f00 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 27 May 2016 14:00:32 +0000 Subject: [PATCH 230/933] Task #8437: Remove 'verbosity=2' from unittest.main() call to support older clients (CEP2) --- LCS/PyCommon/test/t_dbcredentials.py | 2 +- LCS/PyCommon/test/t_methodtrigger.py | 2 +- LTA/ltastorageoverview/test/test_lso_webservice.py | 2 +- LTA/ltastorageoverview/test/test_store.py | 2 +- MAC/Services/test/tPipelineControl.py | 2 +- RTCP/Cobalt/GPUProc/test/t_generate_globalfs_locations.py | 2 +- SAS/MoM/MoMQueryService/test/test_momqueryservice.py | 2 +- .../RATaskSpecifiedService/test/tRATaskSpecified.py | 2 +- .../RAtoOTDBTaskSpecificationPropagator/test/t_rotspservice.py | 2 +- .../ResourceAssigner/test/t_resourceassigner.py | 2 +- .../ResourceAssignmentEditor/test/test_webservice.py | 2 +- .../ResourceAssignmentEstimator/test/t_resource_estimator.py | 2 +- .../ResourceAssignmentService/test/test_ra_service_and_rpc.py | 2 +- SAS/ResourceAssignment/Services/test/tRATaskSpecified.py | 2 +- .../test/test_datamonitorqueueservice_and_rpc.py | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/LCS/PyCommon/test/t_dbcredentials.py b/LCS/PyCommon/test/t_dbcredentials.py index b5e8db69c45..8c2d131d127 100644 --- a/LCS/PyCommon/test/t_dbcredentials.py +++ b/LCS/PyCommon/test/t_dbcredentials.py @@ -95,7 +95,7 @@ database = mydb def main(argv): - unittest.main(verbosity=2) + unittest.main() if __name__ == "__main__": # run all tests diff --git a/LCS/PyCommon/test/t_methodtrigger.py b/LCS/PyCommon/test/t_methodtrigger.py index 5b96ebcf6de..a7562ad5ac5 100644 --- a/LCS/PyCommon/test/t_methodtrigger.py +++ b/LCS/PyCommon/test/t_methodtrigger.py @@ -115,7 +115,7 @@ class TestArgs(unittest.TestCase): self.assertEqual(self.trigger.kwargs, {"c": 3, "d": 4}) def main(argv): - unittest.main(verbosity=2) + unittest.main() if __name__ == "__main__": # run all tests diff --git a/LTA/ltastorageoverview/test/test_lso_webservice.py b/LTA/ltastorageoverview/test/test_lso_webservice.py index 6cc2e2e2527..4831f3bec65 100755 --- a/LTA/ltastorageoverview/test/test_lso_webservice.py +++ b/LTA/ltastorageoverview/test/test_lso_webservice.py @@ -117,7 +117,7 @@ class TestLTAStorageWebService(FlaskLiveTestCase): def main(argv): - unittest.main(verbosity=2) + unittest.main() # run tests if main if __name__ == '__main__': diff --git a/LTA/ltastorageoverview/test/test_store.py b/LTA/ltastorageoverview/test/test_store.py index d95c318f37c..30b08fa0325 100755 --- a/LTA/ltastorageoverview/test/test_store.py +++ b/LTA/ltastorageoverview/test/test_store.py @@ -128,4 +128,4 @@ class TestLTAStorageDb(unittest.TestCase): # run tests if main if __name__ == '__main__': - unittest.main(verbosity=2) + unittest.main() diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index 31d281b80d7..03283a0b385 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -261,7 +261,7 @@ class TestPipelineControl(unittest.TestCase): self.assertTrue(False, "--begin parameter not given to SLURM job") def main(argv): - unittest.main(verbosity=2) + unittest.main() if __name__ == "__main__": # run all tests diff --git a/RTCP/Cobalt/GPUProc/test/t_generate_globalfs_locations.py b/RTCP/Cobalt/GPUProc/test/t_generate_globalfs_locations.py index cef56e81940..f21c5e083e8 100644 --- a/RTCP/Cobalt/GPUProc/test/t_generate_globalfs_locations.py +++ b/RTCP/Cobalt/GPUProc/test/t_generate_globalfs_locations.py @@ -45,7 +45,7 @@ class ProcessParset(unittest.TestCase): self.assertTrue(l.startswith("foo:") or l.startswith("bar:")) def main(argv): - unittest.main(verbosity=2) + unittest.main() if __name__ == "__main__": # run all tests diff --git a/SAS/MoM/MoMQueryService/test/test_momqueryservice.py b/SAS/MoM/MoMQueryService/test/test_momqueryservice.py index d9a918f8db6..77a4ec251ec 100755 --- a/SAS/MoM/MoMQueryService/test/test_momqueryservice.py +++ b/SAS/MoM/MoMQueryService/test/test_momqueryservice.py @@ -73,7 +73,7 @@ try: with self.assertRaises(ValueError) as error: result = momrpc.getProjectDetails(inj_testid) - unittest.main(verbosity=2) + unittest.main() finally: # cleanup test bus and exit diff --git a/SAS/ResourceAssignment/RATaskSpecifiedService/test/tRATaskSpecified.py b/SAS/ResourceAssignment/RATaskSpecifiedService/test/tRATaskSpecified.py index ccffef18377..b6d589d8c58 100644 --- a/SAS/ResourceAssignment/RATaskSpecifiedService/test/tRATaskSpecified.py +++ b/SAS/ResourceAssignment/RATaskSpecifiedService/test/tRATaskSpecified.py @@ -183,7 +183,7 @@ class TestService(unittest.TestCase): self.assertEqual(self.requested_parsets, 3) def main(argv): - unittest.main(verbosity=2) + unittest.main() if __name__ == "__main__": # run all tests diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/test/t_rotspservice.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/test/t_rotspservice.py index 19e4f19a2cd..66bfa974c64 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/test/t_rotspservice.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/test/t_rotspservice.py @@ -69,4 +69,4 @@ with patch('lofar.sas.resourceassignment.ratootdbtaskspecificationpropagator.rpc ##TODO: added test asserts etc - #unittest.main(verbosity=2) + #unittest.main() diff --git a/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py b/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py index 76848599e54..90a9cb2828a 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py +++ b/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py @@ -85,4 +85,4 @@ with patch('lofar.sas.resourceassignment.resourceassignmentservice.rpc.RARPC', a #TODO: added test asserts etc - unittest.main(verbosity=2) + unittest.main() diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/test/test_webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/test/test_webservice.py index 17568953a73..9209f02fe51 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/test/test_webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/test/test_webservice.py @@ -114,7 +114,7 @@ class TestLiveResourceAssignmentEditor(FlaskLiveTestCase): def main(argv): - unittest.main(verbosity=2) + unittest.main() # run all tests if main if __name__ == '__main__': diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/test/t_resource_estimator.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/test/t_resource_estimator.py index 0ffcb51efee..27a1ad8871f 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/test/t_resource_estimator.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/test/t_resource_estimator.py @@ -96,7 +96,7 @@ try: # create and run the service with createService(busname=busname): # and run all tests - unittest.main(verbosity=2) + unittest.main() finally: # cleanup test bus and exit diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/test/test_ra_service_and_rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/test/test_ra_service_and_rpc.py index 6557d8d76ff..a5d727a0a96 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentService/test/test_ra_service_and_rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/test/test_ra_service_and_rpc.py @@ -94,7 +94,7 @@ try: with createService(busname=busname): # and run all tests - unittest.main(verbosity=2) + unittest.main() except ConnectError as ce: logger.error(ce) diff --git a/SAS/ResourceAssignment/Services/test/tRATaskSpecified.py b/SAS/ResourceAssignment/Services/test/tRATaskSpecified.py index 457028e2565..f3b29ef117d 100644 --- a/SAS/ResourceAssignment/Services/test/tRATaskSpecified.py +++ b/SAS/ResourceAssignment/Services/test/tRATaskSpecified.py @@ -180,7 +180,7 @@ class TestService(unittest.TestCase): self.assertEqual(self.requested_parsets, 3) def main(argv): - unittest.main(verbosity=2) + unittest.main() if __name__ == "__main__": # run all tests diff --git a/SAS/ResourceAssignment/SystemStatusService/test/test_datamonitorqueueservice_and_rpc.py b/SAS/ResourceAssignment/SystemStatusService/test/test_datamonitorqueueservice_and_rpc.py index 5adf460bdc0..c2c81b0dd59 100755 --- a/SAS/ResourceAssignment/SystemStatusService/test/test_datamonitorqueueservice_and_rpc.py +++ b/SAS/ResourceAssignment/SystemStatusService/test/test_datamonitorqueueservice_and_rpc.py @@ -91,7 +91,7 @@ try: # create and run the service with createService(busname=busname, servicename=servicename): # and run all tests - unittest.main(verbosity=2) + unittest.main() print "done testing" finally: -- GitLab From 2cc28330e873cdb76d5fbdaee07f2bbf9be14c5d Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 27 May 2016 14:04:35 +0000 Subject: [PATCH 231/933] Task #9462: propagatesolutions now works --- CEP/DP3/DPPP/include/DPPP/CMakeLists.txt | 2 +- CEP/DP3/DPPP/include/DPPP/GainCal.h | 5 +- CEP/DP3/DPPP/include/DPPP/StefCal.h | 28 ++-- CEP/DP3/DPPP/src/CMakeLists.txt | 1 + CEP/DP3/DPPP/src/GainCal.cc | 108 +++++++++----- CEP/DP3/DPPP/src/StefCal.cc | 177 ++++++++++++++--------- CEP/DP3/DPPP/test/tGainCal.run | 27 +++- 7 files changed, 223 insertions(+), 125 deletions(-) diff --git a/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt b/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt index c92e64372df..07b6c83e8f2 100644 --- a/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt +++ b/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt @@ -16,7 +16,7 @@ set(inst_HEADERS DemixerNew.h DemixInfo.h DemixWorker.h ApplyBeam.h ApplyBeam.tcc Predict.h - GainCal.h StefCal.h + GainCal.h StefCal.h phasefitter.h ) # Create symbolic link to include directory. diff --git a/CEP/DP3/DPPP/include/DPPP/GainCal.h b/CEP/DP3/DPPP/include/DPPP/GainCal.h index 1d83b99da49..c3c51a1d742 100644 --- a/CEP/DP3/DPPP/include/DPPP/GainCal.h +++ b/CEP/DP3/DPPP/include/DPPP/GainCal.h @@ -29,6 +29,7 @@ #include <DPPP/DPInput.h> #include <DPPP/DPBuffer.h> +#include <DPPP/phasefitter.h> #include <DPPP/StefCal.h> #include <DPPP/Patch.h> #include <DPPP/Predict.h> @@ -89,7 +90,7 @@ namespace LOFAR { // Counts the number of antennas with non-flagged data, // Set a map for the used antennas in iS, returns the number of antennas - uint setAntennaMaps (const casa::Bool* flag, uint freqCell); + void setAntennaMaps (const casa::Bool* flag, uint freqCell); // Remove rows and colums corresponding to antennas with too much // flagged data from vis and mvis @@ -121,6 +122,7 @@ namespace LOFAR { vector<Baseline> itsBaselines; + vector<casa::Matrix<casa::DComplex> > itsPrevSol; // previous solution, for propagating solutions, for each freq vector<casa::Cube<casa::DComplex> > itsSols; // for every timeslot, nSt x nCr x nFreqCells std::vector<StefCal> iS; @@ -145,6 +147,7 @@ namespace LOFAR { uint itsConverged; uint itsNonconverged; uint itsStalled; + vector<uint> itsNIter; // Total iterations made (for converged, stalled and nonconverged) uint itsTimeStep; // Timestep within parameter update double itsChunkStartTime; // First time value of chunk to be stored uint itsNTimes; // Timestep within solint diff --git a/CEP/DP3/DPPP/include/DPPP/StefCal.h b/CEP/DP3/DPPP/include/DPPP/StefCal.h index 9373e551ef1..354469d0cc6 100644 --- a/CEP/DP3/DPPP/include/DPPP/StefCal.h +++ b/CEP/DP3/DPPP/include/DPPP/StefCal.h @@ -43,11 +43,14 @@ namespace LOFAR { StefCal(uint solInt, uint nChan, const string& mode, double tolerance, uint maxAntennas, bool detectStalling, uint debugLevel); - // Sets visibility matrices to zero, resizes them to nSt stations - void resetVis(uint nSt); + // Sets visibility matrices to zero + void resetVis(); // Initializes a new run of stefcal, resizes all internal vectors - void init(); + // If initSolutions is false, you are responsible for setting them + // before running the solver. You could set the solutions to those + // of the previous time step. + void init(bool initSolutions); // Perform sone iteration of stefcal. Returns CONVERGED, NOTCONVERGED // or STALLED @@ -68,11 +71,8 @@ namespace LOFAR { return _mvis; } - // Returns a reference to the antenna map. This map has length - // maxAntennas, its values are the indices in the stefcal internal - // numbering, or zero when an antenna was not solved for - std::vector<int>& getAntMap() { - return _antMap; + std::vector<bool>& getStationFlagged() { + return _stationFlagged; } // Number of correlations in the solution (1,2 or 4) @@ -80,6 +80,14 @@ namespace LOFAR { return _savedNCr; } + // Number of correlations (1 or 4) + uint nCr() { + return _nCr; + } + + // Clear antFlagged + void clearStationFlagged(); + private: // Number of unknowns (nSt or 2*nSt, depending on _mode) uint nUn(); @@ -90,8 +98,10 @@ namespace LOFAR { void doStep_polarized(); void doStep_unpolarized(); + double getAverageUnflaggedSolution(); + uint _savedNCr; - std::vector<int> _antMap; // Length antennaNames, contains size(antennaNames)-nSt times the value -1 + std::vector<bool> _stationFlagged ; // Contains true for totally flagged stations casa::Array<casa::DComplex> _vis; // Visibility matrix casa::Array<casa::DComplex> _mvis; // Model visibility matrix casa::Matrix<casa::DComplex> _g; // Solution, indexed by station, correlation diff --git a/CEP/DP3/DPPP/src/CMakeLists.txt b/CEP/DP3/DPPP/src/CMakeLists.txt index 51598d1c8b1..0c4077b777b 100644 --- a/CEP/DP3/DPPP/src/CMakeLists.txt +++ b/CEP/DP3/DPPP/src/CMakeLists.txt @@ -20,6 +20,7 @@ lofar_add_library(dppp DemixerNew.cc DemixInfo.cc DemixWorker.cc Predict.cc ApplyBeam.cc + phasefitter.cc ) lofar_add_bin_program(NDPPP NDPPP.cc) diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index a284d9bcbf6..f4ac44ecf91 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -24,6 +24,7 @@ #include <lofar_config.h> #include <DPPP/GainCal.h> #include <DPPP/Simulate.h> +#include <DPPP/phasefitter.h> #include <DPPP/CursorUtilCasa.h> #include <DPPP/DPBuffer.h> #include <DPPP/DPInfo.h> @@ -75,7 +76,7 @@ namespace LOFAR { itsMaxIter (parset.getInt (prefix + "maxiter", 50)), itsTolerance (parset.getDouble (prefix + "tolerance", 1.e-5)), itsPropagateSolutions - (parset.getBool(prefix + "propagatesolutions", false)), + (parset.getBool(prefix + "propagatesolutions", true)), itsSolInt (parset.getInt(prefix + "solint", 1)), itsNChan (parset.getInt(prefix + "nchan", 0)), itsNFreqCells (0), @@ -107,6 +108,9 @@ namespace LOFAR { itsApplyBeamStep.setNextStep(DPStep::ShPtr(itsResultStep)); } } + + itsNIter.resize(3,0); + ASSERT(itsMode=="diagonal" || itsMode=="phaseonly" || itsMode=="fulljones" || itsMode=="scalarphase" || itsMode=="amplitudeonly" || itsMode=="scalaramplitude"); @@ -183,13 +187,14 @@ namespace LOFAR { os << " (will be created)"; } os << endl; - os << " solint: " << itsSolInt <<endl; - os << " nchan: " << itsNChan <<endl; - os << " max iter: " << itsMaxIter << endl; - os << " tolerance: " << itsTolerance << endl; - os << " mode: " << itsMode << endl; - os << " detect stalling: " << boolalpha << itsDetectStalling << endl; - os << " use model column: " << boolalpha << itsUseModelColumn << endl; + os << " solint: " << itsSolInt <<endl; + os << " nchan: " << itsNChan <<endl; + os << " max iter: " << itsMaxIter << endl; + os << " tolerance: " << itsTolerance << endl; + os << " mode: " << itsMode << endl; + os << " propagate solutions: " << boolalpha << itsPropagateSolutions << endl; + os << " detect stalling: " << boolalpha << itsDetectStalling << endl; + os << " use model column: " << boolalpha << itsUseModelColumn << endl; if (!itsUseModelColumn) { itsPredictStep.show(os); } else if (itsApplyBeamToModelColumn) { @@ -222,6 +227,9 @@ namespace LOFAR { os << " "; os <<"Converged: "<<itsConverged<<", stalled: "<<itsStalled<<", non converged: "<<itsNonconverged<<endl; + os <<"Iters converged: " << (itsConverged==0?0:itsNIter[0]/itsConverged); + os << ", stalled: "<< (itsStalled ==0?0:itsNIter[1]/itsStalled); + os << ", non converged: "<<(itsNonconverged==0?0:itsNIter[2]/itsNonconverged)<<endl; } bool GainCal::process (const DPBuffer& bufin) @@ -266,9 +274,8 @@ namespace LOFAR { // Start new solution interval for (uint freqCell=0; freqCell<itsNFreqCells; freqCell++) { - uint nSt=setAntennaMaps(flag, freqCell); - //cout<<"nSt="<<nSt<<endl; - iS[freqCell].resetVis(nSt); + setAntennaMaps(flag, freqCell); + iS[freqCell].resetVis(); } } @@ -311,9 +318,12 @@ namespace LOFAR { for (uint ch=0;ch<nCh;++ch) { for (uint bl=0;bl<nBl;++bl) { - int ant1=iS[ch/itsNChan].getAntMap()[info().getAnt1()[bl]]; - int ant2=iS[ch/itsNChan].getAntMap()[info().getAnt2()[bl]]; - if (ant1==ant2 || ant1==-1 || ant2 == -1 || flag[bl*nCr*nCh+ch*nCr]) { // Only check flag of cr==0 + int ant1=info().getAnt1()[bl]; + int ant2=info().getAnt2()[bl]; + if (ant1==ant2 || + iS[ch/itsNChan].getStationFlagged()[ant1] || + iS[ch/itsNChan].getStationFlagged()[ant2] || + flag[bl*nCr*nCh+ch*nCr]) { // Only check flag of cr==0 continue; } @@ -337,11 +347,13 @@ namespace LOFAR { } } - uint GainCal::setAntennaMaps (const Bool* flag, uint freqCell) { + void GainCal::setAntennaMaps (const Bool* flag, uint freqCell) { uint nCr=info().ncorr(); uint nCh=info().nchan(); uint nBl=info().nbaselines(); + iS[freqCell].clearStationFlagged(); + casa::Vector<casa::uInt> dataPerAntenna; // nAnt dataPerAntenna.resize(info().antennaNames().size()); dataPerAntenna=0; @@ -363,28 +375,43 @@ namespace LOFAR { } } - uint nSt=0; - for (uint ant=0; ant<info().antennaNames().size(); ++ant) { if (dataPerAntenna(ant)>nCr*itsMinBLperAnt) { - iS[freqCell].getAntMap()[ant]=nSt++; // Index in stefcal numbering + iS[freqCell].getStationFlagged()[ant]=false; // Index in stefcal numbering } else { - iS[freqCell].getAntMap()[ant]=-1; // Not enough data + iS[freqCell].getStationFlagged()[ant]=true; // Not enough data } } - - return nSt; } void GainCal::stefcal () { itsTimerSolve.start(); for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { - iS[freqCell].init(); + if (itsPropagateSolutions) { + iS[freqCell].init(false); + } else { + iS[freqCell].init(true); + } } uint iter=0; + PhaseFitter fitter(itsNFreqCells); + // Set frequency data for TEC fitter + double* nu = fitter.FrequencyData(); + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + double meanfreq=0; + uint chmin=itsNChan*freqCell; + uint chmax=min(info().nchan(), chmin+itsNChan); + + meanfreq = std::accumulate(info().chanFreqs().data()+chmin, + info().chanFreqs().data()+chmax, 0.0); + + nu[freqCell] = meanfreq / (chmax-chmin); + //cout<<"freqCell:" <<freqCell<<", meanfreq: "<<meanfreq/(chmax-chmin)<<endl; + } + std::vector<StefCal::Status> converged(itsNFreqCells,StefCal::NOTCONVERGED); for (;iter<itsMaxIter;++iter) { bool allConverged=true; @@ -395,34 +422,43 @@ namespace LOFAR { converged[freqCell] = iS[freqCell].doStep(iter); if (converged[freqCell]==StefCal::NOTCONVERGED) { allConverged = false; + } else if (converged[freqCell]==StefCal::CONVERGED) { + itsNIter[0] += iter; } - if (allConverged) { - break; - } - } } // End niter + } + if (allConverged) { + break; + } + } // End niter for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { switch (converged[freqCell]) { case StefCal::CONVERGED: {itsConverged++; break;} - case StefCal::STALLED: {itsStalled++; break;} - case StefCal::NOTCONVERGED: {itsStalled++; break;} + case StefCal::STALLED: {itsStalled++; itsNIter[1]+=iter; break;} + case StefCal::NOTCONVERGED: {itsNonconverged++; itsNIter[2]+=iter; break;} default: THROW(Exception, "Unknown converged status"); } } // Stefcal terminated (either by maxiter or by converging) - // Let's save G... + Cube<DComplex> allg(iS[0].numCorrelations(), info().antennaNames().size(), itsNFreqCells); uint transpose[2][4] = { { 0, 1, 0, 0 }, { 0, 2, 1, 3 } }; + + uint nSt = info().antennaNames().size(); + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { - //cout<<endl<<"freqCell="<<freqCell<<", timeCell="<<itsTStep/itsSolInt<<", tstep="<<itsTStep<<endl; casa::Matrix<casa::DComplex> sol = iS[freqCell].getSolution(); - for (uint st=0; st<info().antennaNames().size(); st++) { - for (uint cr=0; cr<iS[0].numCorrelations(); ++cr) { - uint crt=transpose[iS[0].numCorrelations()/4][cr]; // Conjugate transpose ! (only for numCorrelations = 4) - allg(crt, st, freqCell) = conj(sol(st, cr)); + + for (uint st=0; st<nSt; st++) { + for (uint cr=0; cr<iS[0].nCr(); ++cr) { + uint crt=transpose[iS[0].numCorrelations()/4][cr]; // Conjugate transpose ! (only for numCorrelations = 4) + allg(crt, st, freqCell) = conj(sol(st, cr)); // Conjugate transpose + if (itsMode=="diagonal" || itsMode=="phaseonly" || itsMode=="amplitudeonly") { + allg(crt+1, st, freqCell) = conj(sol(st+nSt,cr)); // Conjugate transpose + } } } } @@ -508,7 +544,7 @@ namespace LOFAR { // Open the ParmDB at the first write. // In that way the instrumentmodel ParmDB can be in the MS directory. - if (! itsParmDB) { + if (!itsParmDB) { initParmDB(); } // End initialization of parmdb @@ -551,6 +587,7 @@ namespace LOFAR { DComplex sol; uint nSt=info().antennaNames().size(); + for (size_t st=0; st<nSt; ++st) { uint seqnr = 0; // To take care of real and imaginary part string suffix(info().antennaNames()[st]); @@ -654,6 +691,5 @@ namespace LOFAR { getNextStep()->finish(); } - } //# end namespace } diff --git a/CEP/DP3/DPPP/src/StefCal.cc b/CEP/DP3/DPPP/src/StefCal.cc index fb71b7c52fa..539524e6184 100644 --- a/CEP/DP3/DPPP/src/StefCal.cc +++ b/CEP/DP3/DPPP/src/StefCal.cc @@ -47,46 +47,32 @@ namespace LOFAR { _detectStalling (detectStalling), _debugLevel (debugLevel) { - _antMap.resize(maxAntennas, -1); - resetVis(maxAntennas); + resetVis(); + _nSt = maxAntennas; if (_mode=="fulljones") { _nCr=4; _nSp=1; _savedNCr=4; - } else if (_mode=="scalarphase") { + } else if (_mode=="scalarphase" || _mode=="scalaramplitude") { _nCr=1; _nSp=2; _savedNCr=1; - } else { // mode=="phaseonly", mode=="diagonal" + } else { // mode=="phaseonly", mode=="diagonal", mode=="amplitudeonly" _nCr=1; _nSp=1; _savedNCr=2; } - init(); - } - - void StefCal::resetVis(uint nSt) { - _nSt = nSt; - _vis.resize(IPosition(6,nSt,2,_solInt,_nChan,2,nSt)); - _mvis.resize(IPosition(6,nSt,2,_solInt,_nChan,2,nSt)); - _vis=0; - _mvis=0; + _vis.resize(IPosition(6,_nSt,2,_solInt,_nChan,2,_nSt)); + _mvis.resize(IPosition(6,_nSt,2,_solInt,_nChan,2,_nSt)); - if (_mode=="fulljones" || _mode=="scalarphase") { + if (_mode=="fulljones" || _mode=="scalarphase" || _mode=="scalaramplitude") { _nUn = _nSt; - } else { + } else { // mode=="phaseonly", mode=="diagonal", mode=="amplitudeonly" _nUn = 2*_nSt; } - } - - void StefCal::init() { - _dg=1.0e29; - _dgx=1.0e30; - _dgs.clear(); - _g.resize(_nUn,_nCr); _gold.resize(_nUn,_nCr); _gx.resize(_nUn,_nCr); @@ -94,40 +80,98 @@ namespace LOFAR { _h.resize(_nUn,_nCr); _z.resize(_nUn*_nChan*_solInt*_nSp,_nCr); - // Initialize all vectors - double fronormvis=0; - double fronormmod=0; + _stationFlagged.resize(_nSt, false); - DComplex* t_vis_p=_vis.data(); - DComplex* t_mvis_p=_mvis.data(); + init(true); + } - uint vissize=_vis.size(); - for (uint i=0;i<vissize;++i) { - fronormvis+=norm(t_vis_p[i]); - fronormmod+=norm(t_mvis_p[i]); - } + void StefCal::resetVis() { + _vis=0; + _mvis=0; + } - fronormvis=sqrt(fronormvis); - fronormmod=sqrt(fronormmod); + void StefCal::clearStationFlagged() { + fill(_stationFlagged.begin(), _stationFlagged.end(), false); + } - // Initialize solution with sensible values (or 1.) - double ginit=1; - if ((_mode != "phaseonly" && _mode != "scalarphase" ) && - _nSt>0 && abs(fronormmod)>1.e-15) { - ginit=sqrt(fronormvis/fronormmod); - } - if (_nCr==4) { - for (uint st=0;st<_nUn;++st) { - _g(st,0)=ginit; - _g(st,1)=0.; - _g(st,2)=0.; - _g(st,3)=ginit; + void StefCal::init(bool initSolutions) { + _dg=1.0e29; + _dgx=1.0e30; + _dgs.clear(); + + if (initSolutions) { + double ginit=1.0; + if (_mode != "phaseonly" && _mode != "scalarphase" ) { + // Initialize solution with sensible amplitudes + double fronormvis=0; + double fronormmod=0; + + DComplex* t_vis_p=_vis.data(); + DComplex* t_mvis_p=_mvis.data(); + + uint vissize=_vis.size(); + for (uint i=0;i<vissize;++i) { + fronormvis+=norm(t_vis_p[i]); + fronormmod+=norm(t_mvis_p[i]); + } + + fronormvis=sqrt(fronormvis); + fronormmod=sqrt(fronormmod); + if (abs(fronormmod)>1.e-15) { + ginit=sqrt(fronormvis/fronormmod); + } else { + ginit=1.0; + } + } + + if (_nCr==4) { + for (uint ant=0;ant<_nUn;++ant) { + _g(ant,0)=ginit; + _g(ant,1)=0.; + _g(ant,2)=0.; + _g(ant,3)=ginit; + } + } else { + _g=ginit; + } + + _gx = _g; + } else { // Take care of NaNs in solution + for (uint ant=0; ant<_nUn; ++ant) { + double ginit=0; + if (!isFinite(_g(ant,0).real()) ) { + if (ginit==0 && !_stationFlagged[ant%_nSt]) { + // Avoid calling getAverageUnflaggedSolution for stations that are always flagged + ginit = getAverageUnflaggedSolution(); + } + if (_nCr==4) { + _g(ant,0)=ginit; + _g(ant,1)=0.; + _g(ant,2)=0.; + _g(ant,3)=ginit; + } else { + _g(ant,0)=ginit; + } + } } - } else { - _g=ginit; } + } - _gx = _g; + double StefCal::getAverageUnflaggedSolution() { + // Get average solution of unflagged antennas only once + double total=0.; + uint unflaggedstations=0; + for (uint ant2=0; ant2<_nUn; ++ant2) { + if (isFinite(_g(ant2,0).real())) { + total += abs(_g(ant2,0)); + unflaggedstations++; + if (_nCr==4) { + total += abs(_g(ant2,3)); + unflaggedstations++; + } + } + } + return total/unflaggedstations; } StefCal::Status StefCal::doStep(uint iter) { @@ -153,6 +197,10 @@ namespace LOFAR { } for (uint st1=0;st1<_nSt;++st1) { + if (_stationFlagged[st1]) { + continue; + } + DComplex* vis_p; DComplex* mvis_p; Vector<DComplex> w(_nCr); @@ -211,11 +259,14 @@ namespace LOFAR { void StefCal::doStep_unpolarized() { _gold=_g; - for (uint st=0;st<_nUn;++st) { - _h(st,0)=conj(_g(st,0)); + for (uint ant=0;ant<_nUn;++ant) { + _h(ant,0)=conj(_g(ant,0)); } for (uint st1=0;st1<_nUn;++st1) { + if (_stationFlagged[st1%_nSt]) { + continue; + } DComplex* vis_p; DComplex* mvis_p; double ww=0; // Same as w, but specifically for pol==false @@ -253,31 +304,15 @@ namespace LOFAR { } casa::Matrix<casa::DComplex> StefCal::getSolution() { - casa::Matrix<casa::DComplex> sol; - sol.resize(_antMap.size(), _savedNCr); - - uint sSt=0; // Index in stefcal numbering - for (uint st=0; st<_antMap.size(); ++st) { - if (_antMap[st]==-1) { + for (uint ant=0; ant<_nUn; ++ant) { + if (_stationFlagged[ant%_nSt]) { for (uint cr=0; cr<_nCr; ++cr) { - sol(st,cr)=std::numeric_limits<double>::quiet_NaN(); - } - } else { - for (uint cr=0; cr<_nCr; ++cr) { - sol(st,cr)=_g(sSt,cr); - if (_mode=="diagonal" || _mode=="phaseonly") { - sol(st,cr+1)=_g(sSt+_nSt,cr); - } - if (cr==_nCr-1) { - sSt++; - } + _g(ant,cr)=std::numeric_limits<double>::quiet_NaN(); } } } - ASSERT(sSt==_nSt); - - return sol; + return _g; } StefCal::Status StefCal::relax(uint iter) { diff --git a/CEP/DP3/DPPP/test/tGainCal.run b/CEP/DP3/DPPP/test/tGainCal.run index 8192e1c2d8e..ce64da56f69 100755 --- a/CEP/DP3/DPPP/test/tGainCal.run +++ b/CEP/DP3/DPPP/test/tGainCal.run @@ -26,22 +26,29 @@ echo " select result of 0 rows" > taql.ref # Create MODEL_DATA so that residual can be computed ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=MODEL_DATA steps=[predict] predict.sourcedb=tNDPPP-generic.MS/sky predict.usebeammodel=false - +exit 3 echo; echo "Test caltype=diagonal"; echo -../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 +../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.propagatesolutions=true gaincal.solint=1 + ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal # Compare the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima. -$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where not near(dpppres,bbsres,1.e-2)' > taql.out +$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out +diff taql.out taql.ref || exit 1 +# Check that not everything was flagged +$taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out diff taql.out taql.ref || exit 1 echo; echo "Test caltype=diagonal with timeslotsperparmupdate=4"; echo -../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal-tpp gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 gaincal.timeslotsperparmupdate=1 +../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal-tpp gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 gaincal.timeslotsperparmupdate=1 gaincal.propagatesolutions=false ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_TPP steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal-tpp $taqlexe 'select from tNDPP-generic.MS where not all(near(DPPP_DIAGONAL, DPPP_DIAGONAL_TPP))' # Compare the difference between applying with timeslotsperparmupdate = default and timeslotsperparmupdate=1 -$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where not near(dpppres,bbsres,1.e-2)' > taql.out +$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out +diff taql.out taql.ref || exit 1 +# Check that not everything was flagged +$taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out diff taql.out taql.ref || exit 1 echo; echo "Test caltype=fulljones"; echo @@ -49,7 +56,10 @@ echo; echo "Test caltype=fulljones"; echo ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_FULLJONES steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-fulljones # Compare the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima. -$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_FULLJONES-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_FULLJONES-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where not near(dpppres,bbsres,1.e-2)' > taql.out +$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_FULLJONES-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_FULLJONES-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out +diff taql.out taql.ref || exit 1 +# Check that not everything was flagged +$taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out diff taql.out taql.ref || exit 1 echo; echo "Test caltype=diagonal, nchan=2"; echo @@ -57,5 +67,8 @@ echo; echo "Test caltype=diagonal, nchan=2"; echo ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_NCHAN steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal-nchan # Compare the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima. -$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL_NCHAN-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL_NCHAN-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where not near(dpppres,bbsres,1.e-2)' > taql.out +$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL_NCHAN-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL_NCHAN-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out +diff taql.out taql.ref || exit 1 +# Check that not everything was flagged +$taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out diff taql.out taql.ref || exit 1 -- GitLab From 050ff73aa6e6c13135d1f28f5882e2ad8c5236a2 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 27 May 2016 14:18:15 +0000 Subject: [PATCH 232/933] Task #9462: add phasefitter files --- .gitattributes | 2 + CEP/DP3/DPPP/include/DPPP/phasefitter.h | 217 ++++++++++++++++++++++++ CEP/DP3/DPPP/src/phasefitter.cc | 109 ++++++++++++ 3 files changed, 328 insertions(+) create mode 100644 CEP/DP3/DPPP/include/DPPP/phasefitter.h create mode 100644 CEP/DP3/DPPP/src/phasefitter.cc diff --git a/.gitattributes b/.gitattributes index 83ff5ec0109..7e6fc5a17fb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -854,6 +854,7 @@ CEP/DP3/DPPP/include/DPPP/Stokes.h -text CEP/DP3/DPPP/include/DPPP/SubtractMixed.h -text CEP/DP3/DPPP/include/DPPP/SubtractNew.h -text CEP/DP3/DPPP/include/DPPP/UVWCalculator.h -text +CEP/DP3/DPPP/include/DPPP/phasefitter.h -text CEP/DP3/DPPP/package.dox -text CEP/DP3/DPPP/share/HBAdefault -text CEP/DP3/DPPP/share/LBAdefault -text @@ -883,6 +884,7 @@ CEP/DP3/DPPP/src/Stokes.cc -text CEP/DP3/DPPP/src/SubtractMixed.cc -text CEP/DP3/DPPP/src/SubtractNew.cc -text CEP/DP3/DPPP/src/__init__.py -text +CEP/DP3/DPPP/src/phasefitter.cc -text CEP/DP3/DPPP/src/taqlflagger -text CEP/DP3/DPPP/test/findenv.run_tmpl -text CEP/DP3/DPPP/test/tApplyBeam.run -text diff --git a/CEP/DP3/DPPP/include/DPPP/phasefitter.h b/CEP/DP3/DPPP/include/DPPP/phasefitter.h new file mode 100644 index 00000000000..878cf7ce6e7 --- /dev/null +++ b/CEP/DP3/DPPP/include/DPPP/phasefitter.h @@ -0,0 +1,217 @@ +/** + * @file phasefitter.h Implements TEC model phase filter @ref PhaseFitter. + * @author André Offringa + * @date 2016-04-06 + */ + +#ifndef PHASE_FITTER_H +#define PHASE_FITTER_H + +#include <vector> +#include <cmath> +#include <cstring> + +/** + * Phase fitter that can force phase solutions over frequency onto a TEC model. + * To use: + * - Construct and set the frequencies (with @ref FrequencyData() ) and if already possible + * the weights (@ref WeightData()). + * - Perform a calibration iteration. + * - Set the phase values (@ref PhaseData()) and, if not yet done, the weights (@ref WeightData()) + * to the current phase solutions / weights of a single antenna. + * - Call @ref FitDataToTECModel(). + * - Read the new phase values. + * - When fitting multiple polarizations, the above steps should be done twice for each + * diagonal value of the Jones matrix. Also repeat for all antennae. + * - Continue the iteration with the new phase values. + */ +class PhaseFitter +{ + public: + /** + * Construct a phase fitter for the given number of channels. + * Weights are initialized to unity. The fitting accuracy is + * initialized to 1e-6. + * @param channelCount number of channels. + */ + PhaseFitter(size_t channelCount) : + _phases(channelCount, 0.0), + _frequencies(channelCount, 0.0), + _weights(channelCount, 1.0), + _fittingAccuracy(1e-6) + { } + + ~PhaseFitter() = default; + PhaseFitter(const PhaseFitter&) = delete; + PhaseFitter& operator=(const PhaseFitter&) = delete; + + /** + * Fits the given phase values to a TEC model. This function is + * robust even when phase wrapping occurs. + * After this call, the @ref PhaseData() satisfy the TEC model. + * The TEC model has two parameters and are fitted as described in + * @ref FitTECModelParameters(). + * @param alpha Found value for the alpha parameter. + * @param beta Found value for the beta parameter. + */ + void FitDataToTECModel(double& alpha, double& beta); + + /** + * Fits the given phase values to a TEC model using prior estimates of the + * model parameters. This method is similar to @ref FitDataToTECModel(), + * except that it will use the provided initial values of alpha and + * beta to speed up the solution. When the provided initial values + * are not accurate, the fit might not be accurate. + * + * @todo No fast method has been implemented -- instead it will perform a full parameter search. + * @param alpha Estimate of alpha parameter on input, found value on output. + * @param beta Estimate of beta parameter on input, found value on output. + */ + void FitDataToTECModelWithInitialValues(double& alpha, double& beta) + { + return FitDataToTECModel(alpha, beta); + } + + /** + * Fit the data and get the best fitting parameters. The model + * used is f(nu) = alpha/nu + beta, with possible 2pi wrappings in the + * data. + * The phase data is not changed. + * The alpha parameter is linearly related to the TEC. The beta parameter + * is a constant phase offset, given in radians. + * The fitting algorithm uses a combination of brute force searching and + * binary-like searching (ternary search). + * @param alpha Will be set to the fitted value for the alpha parameter + * (value on input is not used). + * @param beta Will be set to the fitted value for the beta parameter + * (value on input is not used). + */ + void FitTECModelParameters(double& alpha, double& beta) const; + + /** + * Get a pointer to the array of phase values. This array should be filled with the + * phases of solutions before calling one of the fit methods. @ref FitDataToTECModel() + * sets this array to the fitted phases. + * Normally, these values should be set to std::arg(z) or atan2(z.imag(), z.real()), where + * z is a complex solution for one polarizations. All phases should correspond to the same + * polarizations, i.e., different polarizations (xx/yy/ll/rr, etc.) should be independently + * fitted. + * @returns Array of @ref Size() doubles with the phases. + */ + double* PhaseData() { return _phases.data(); } + + /** + * Get a constant pointer to the array of values. + * @returns Constant array of @ref Size() doubles with the phases. + */ + const double* PhaseData() const { return _phases.data(); } + + /** + * Get a pointer to the array of frequency values. This array should be set to the + * frequency values of the channels, such that FrequencyData()[ch] is the frequency + * corresponding to the phase value PhaseData()[ch]. The fitter will not change this + * array. + * @returns Array of @ref Size() doubles with the frequencies in Hz. + */ + double* FrequencyData() { return _frequencies.data(); } + + /** + * Constant frequency data. + * @returns Constant array of @ref Size() doubles with the frequencies in Hz. + */ + const double* FrequencyData() const { return _frequencies.data(); } + + /** + * This array should be filled with the weights + * of the channel phase solutions. If the solver supports weights during solving, this value should + * be set to the sum of weights of all visibilities that are used for the solution of + * this channel. If the solver does not support weights, it should be set to the number + * of unflagged visibilities used by the solver to generate the corresponding phase. Another + * way of saying this, is that the weights should be set to the esimated inverse variance of the phase + * solutions. + * + * The use of weights will make sure that noisy channels do not bias the result. Weighting is + * for example helpful to avoid that the few remaining samples in a badly RFI contaminated + * channels cause the fit to be inaccurate. + * + * While the weights could be different for each antenna solution, generally the weight of a channel + * is constant over the antennas. The latter implies that the weights can be set once before + * the solution starts, and only the @ref PhaseData() need to be changed within solution + * iterations. + * + * The weights are initially set to one. + * + * @returns Array of @ref Size() doubles with the weights. + */ + double* WeightData() { return _weights.data(); } + + /** + * Constant array of weights, as described above. + * @returns Constant array of @ref Size() doubles with weights. + */ + const double* WeightData() const { return _weights.data(); } + + /** + * Number of channels used for the fitter. + * @returns Number of channels. + */ + size_t Size() const { return _phases.size(); } + + /** + * Get the fitting accuracy. The fitter will stop once this accuracy is approximately reached. + * The default value is 1e-6. + * @returns Fitting accuracy. + */ + double FittingAccuracy() const { return _fittingAccuracy; } + + /** + * Change the fitting accuracy. See @ref FittingAccuracy(). + * @param newAccuracy New accuracy. + */ + void SetFittingAccuracy(double newAccuracy) { _fittingAccuracy = newAccuracy; } + + /** + * Evaluate the cost function for given TEC model parameters. The higher the + * cost, the worser the data fit the given parameters. + * @param alpha TEC parameter + * @param beta Phase offset parameter + * @returns sum of | nu_i / alpha + beta - theta_i |, and each sum term is + * phase unwrapped. + */ + double TECModelCost(double alpha, double beta) const; + + /** + * Like @ref TECModelFunc(), but 2-pi wrapped. + * @param nu Frequency in Hz + * @param alpha TEC parameter (in undefined units) + * @param beta Phase offset parameter (in radians) + * @returns | nu_i / alpha + beta | % 2pi + */ + static double TECModelFunc(double nu, double alpha, double beta) + { + return alpha / nu + beta; + } + + /** + * Like @ref TECModelFunc(), but 2-pi wrapped. + * @param nu Frequency in Hz + * @param alpha TEC parameter (in undefined units) + * @param beta Phase offset parameter (in radians) + * @returns | nu_i / alpha + beta | % 2pi + */ + static double TECModelFuncWrapped(double nu, double alpha, double beta) + { + return fmod(alpha / nu + beta, 2*M_PI); + } + private: + std::vector<double> _phases, _frequencies, _weights; + double _fittingAccuracy; + + double fitTECModelBeta(double alpha, double betaEstimate) const; + void bruteForceSearchTECModel(double& lowerAlpha, double& upperAlpha, double& beta) const; + double ternarySearchTECModelAlpha(double startAlpha, double endAlpha, double& beta) const; + void fillDataWithTECModel(double alpha, double beta); + +}; + +#endif diff --git a/CEP/DP3/DPPP/src/phasefitter.cc b/CEP/DP3/DPPP/src/phasefitter.cc new file mode 100644 index 00000000000..91b987d81ab --- /dev/null +++ b/CEP/DP3/DPPP/src/phasefitter.cc @@ -0,0 +1,109 @@ +#include <lofar_config.h> +#include "DPPP/phasefitter.h" + +#include <limits> + +double PhaseFitter::TECModelCost(double alpha, double beta) const +{ + double costVal = 0.0; + for(int i=0; i!=Size(); ++i) { + double estphase = TECModelFuncWrapped(_frequencies[i], alpha, beta); + double dCost = fmod(std::fabs(estphase - _phases[i]), 2.0*M_PI); + if(dCost > M_PI) dCost = 2.0*M_PI - dCost; + dCost *= _weights[i]; + costVal += dCost; + } + return costVal; +} + +double PhaseFitter::fitTECModelBeta(double alpha, double betaEstimate) const { + double weightSum = 0.0; + for(size_t iter=0; iter!=3; ++iter) { + double sum = 0.0; + for(size_t i=0; i!=Size(); ++i) { + double p = _phases[i], e = TECModelFunc(_frequencies[i], alpha, betaEstimate); + double dist = fmod(p - e, 2.0*M_PI); + if(dist < -M_PI) + dist += 2.0*M_PI; + else if(dist > M_PI) + dist -= 2.0*M_PI; + sum += dist * _weights[i]; + weightSum += _weights[i]; + } + betaEstimate = betaEstimate + sum / weightSum; + } + return fmod(betaEstimate, 2.0*M_PI); +} + +void PhaseFitter::bruteForceSearchTECModel(double& lowerAlpha, double& upperAlpha, double& beta) const +{ + double minCost = std::numeric_limits<double>::max(); + double alphaOversampling = 128; + //size_t betaOversampling = 16; + double dAlpha = upperAlpha - lowerAlpha; + const double bandw = _frequencies.back() - _frequencies.front(); + int alphaIndex = 0; + for(int i=0; i!=alphaOversampling; ++i) { + // make r between [0, 1] + double r = double(i)/alphaOversampling; + double alpha = lowerAlpha + r*dAlpha; + double curBeta = fitTECModelBeta(alpha, beta); + double costVal = TECModelCost(alpha, curBeta); + if(costVal < minCost) { + beta = curBeta; + minCost = costVal; + alphaIndex = i; + } + } + double newLowerAlpha = double(alphaIndex-1)/alphaOversampling*dAlpha + lowerAlpha; + upperAlpha = double(alphaIndex+1)/alphaOversampling*dAlpha + lowerAlpha; + lowerAlpha = newLowerAlpha; +} + +double PhaseFitter::ternarySearchTECModelAlpha(double startAlpha, double endAlpha, double& beta) const +{ + size_t iter = 0; + double dCost, lAlpha, rAlpha; + do { + lAlpha = startAlpha + (endAlpha - startAlpha) * (1.0/3.0); + rAlpha = startAlpha + (endAlpha - startAlpha) * (2.0/3.0); + double lBeta = fitTECModelBeta(lAlpha, beta); + double rBeta = fitTECModelBeta(rAlpha, beta); + double lCost = TECModelCost(lAlpha, lBeta); + double rCost = TECModelCost(rAlpha, rBeta); + if(lCost < rCost) { + endAlpha = rAlpha; + beta = lBeta; + } else { + startAlpha = lAlpha; + beta = rBeta; + } + dCost = std::fabs(lCost - rCost); + ++iter; + } while(dCost > _fittingAccuracy && iter < 100); + double finalAlpha = (lAlpha + rAlpha) * 0.5; + beta = fitTECModelBeta(finalAlpha, beta); + return finalAlpha; +} + +void PhaseFitter::fillDataWithTECModel(double alpha, double beta) +{ + for(size_t ch=0; ch!=Size(); ++ch) + _phases[ch] = TECModelFunc(_frequencies[ch], alpha, beta); +} + +void PhaseFitter::FitTECModelParameters(double& alpha, double& beta) const +{ + double lowerAlpha = 0.0, upperAlpha = 40000.0e6; + bruteForceSearchTECModel(lowerAlpha, upperAlpha, beta); + alpha = (lowerAlpha + upperAlpha) * 0.5; + //beta = fitBeta(alpha, beta); + alpha = ternarySearchTECModelAlpha(lowerAlpha, upperAlpha, beta); +} + +void PhaseFitter::FitDataToTECModel(double& alpha, double& beta) +{ + FitTECModelParameters(alpha, beta); + fillDataWithTECModel(alpha, beta); +} + -- GitLab From 2e2a771fc1801928299d1a95f02cc8ea76f46757 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 27 May 2016 14:35:33 +0000 Subject: [PATCH 233/933] Task #8437: Abort pipelines on conflict/hold/drescribed/prepared/approved/prescheduled --- MAC/Services/src/PipelineControl.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 083ee3250db..c87aef02285 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -264,8 +264,12 @@ class PipelineControl(OTDBBusListener): """ More statusses we want to abort on. """ - onObservationConflict = onObservationAborted - onObservationHold = onObservationAborted + onObservationDescribed = onObservationAborted + onObservationPrepared = onObservationAborted + onObservationApproved = onObservationAborted + onObservationPrescheduled = onObservationAborted + onObservationConflict = onObservationAborted + onObservationHold = onObservationAborted @classmethod def _minStartTime(self, preparsets, margin=datetime.timedelta(0, 60, 0)): -- GitLab From 6248a700e1c74fd7487ebf15fa51dad4013d0ea2 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 27 May 2016 14:37:09 +0000 Subject: [PATCH 234/933] Task #8437: Rollout MACServices as part of RAServices --- SubSystems/RAServices/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SubSystems/RAServices/CMakeLists.txt b/SubSystems/RAServices/CMakeLists.txt index 19156d9b5f0..50dda7052a1 100644 --- a/SubSystems/RAServices/CMakeLists.txt +++ b/SubSystems/RAServices/CMakeLists.txt @@ -1,7 +1,8 @@ # $Id: CMakeLists.txt 20934 2012-05-15 09:26:48Z schoenmakers $ lofar_package(RAServices - DEPENDS MoMQueryService + DEPENDS MAC_Services + MoMQueryService OTDBtoRATaskStatusPropagator RATaskSpecifiedService RAtoOTDBTaskSpecificationPropagator -- GitLab From b1746228eed489cb86ccb65d9d425d97ad199e50 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Fri, 27 May 2016 15:59:13 +0000 Subject: [PATCH 235/933] Task #9192: Small fixes from testing the RA on CEP4 --- .../lib/translator.py | 27 +++++++++------ .../calibration_pipeline.py | 34 +++++++++---------- .../resource_estimators/pulsar_pipeline.py | 17 +++++----- .../ResourceAssignmentEstimator/service.py | 19 ++++++----- 4 files changed, 52 insertions(+), 45 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 3a70d827f69..49e2fc33c60 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -71,6 +71,11 @@ class RAtoOTDBTranslator(): locations.append(self.locationPath(project_name, otdb_id)) filenames.append("L%d_SAP%03d_SB%03d_uv.MS" % (otdb_id, sap['sap_nr'], sb_nr)) sb_nr += 1 + if not 'saps' in storage_properties: ## It's a pipeline (no SAPs) + for _ in xrange(storage_properties['nr_of_uv_files']): + locations.append(self.locationPath(project_name, otdb_id)) + filenames.append("L%d_SB%03d_uv.MS" % (otdb_id, sb_nr)) + sb_nr += 1 result[PREFIX + 'DataProducts.Output_Correlated.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_Correlated.filenames'] = '[' + to_csv_string(filenames) + ']' return result @@ -115,7 +120,7 @@ class RAtoOTDBTranslator(): result[PREFIX + 'DataProducts.Output_IncoherentStokes.filenames'] = '[' + to_csv_string(filenames) + ']' return result - def CreateCreateInstrumentModel(self, otdb_id, storage_properties, project_name): + def CreateInstrumentModel(self, otdb_id, storage_properties, project_name): SB_nr = 0 locations = [] filenames = [] @@ -144,15 +149,14 @@ class RAtoOTDBTranslator(): return result def CreatePulsarPipeline(self, otdb_id, storage_properties, project_name): - SB_nr = 0 + p_nr = 0 locations = [] filenames = [] result = {} - for sap in storage_properties["saps"]: ##We might need to sort saps? - if "nr_of_uv_files" in sap['properties']: - for _ in range(sap['properties']['nr_of_pulp_files']): - locations.append(self.locationPath(project_name, otdb_id)) - filenames.append("L%d_SAP%03d_SB%03d_bf.h5" % (otdb_id, sap['sap_nr'], sb_nr)) + for _ in range(storage_properties['nr_of_pulp_files']): + locations.append(self.locationPath(project_name, otdb_id)) + filenames.append("L%d_P%03d_pulp.tgz" % (otdb_id, p_nr)) + p_nr += 1 result[PREFIX + 'DataProducts.Output_Pulsar.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_Pulsar.filenames'] = '[' + to_csv_string(filenames) + ']' return result @@ -167,8 +171,8 @@ class RAtoOTDBTranslator(): result.update(self.CreateCoherentStokes(otdb_id, storage_properties, project_name)) if 'nr_of_is_files' in storage_properties: result.update(self.CreateIncoherentStokes(otdb_id, storage_properties, project_name)) - if 'nr_of_im_files' in storage_properties: - result.update(self.CreateInstrumentModel(otdb_id, storage_properties, project_name)) + #if 'nr_of_im_files' in storage_properties: + # result.update(self.CreateInstrumentModel(otdb_id, storage_properties, project_name)) if 'nr_of_img_files' in storage_properties: result.update(self.CreateSkyImage(otdb_id, storage_properties, project_name)) if 'nr_of_pulp_files' in storage_properties: @@ -184,8 +188,9 @@ class RAtoOTDBTranslator(): for p in s['properties']: properties[p['type_name']] = p['value'] result['saps'].append({'sap_nr' : s['sap_nr'], 'properties': properties}) - for p in storage_claim['properties']: - result[p['type_name']] = p['value'] + if 'properties' in storage_claim: + for p in storage_claim['properties']: + result[p['type_name']] = p['value'] return result def CreateParset(self, otdb_id, ra_info, project_name): diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py index 0d4167e8b10..a33526962f4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py @@ -36,14 +36,14 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): """ def __init__(self): logger.info("init CalibrationPipelineResourceEstimator") - BaseResourceEstimator.__init__(self, name='calibration_pipeline') + BaseResourceEstimator.__init__(self, name='pipeline') self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_Correlated.enabled', DATAPRODUCTS + 'Output_InstrumentModel.enabled', DATAPRODUCTS + 'Output_Correlated.enabled', - PIPELINE + 'DPPP.demixer.freqstep', - PIPELINE + 'DPPP.demixer.timestep') + PIPELINE + 'DPPP.demixer.demixfreqstep', + PIPELINE + 'DPPP.demixer.demixtimestep') def _calculate(self, parset, input_files): """ Estimate for CalibrationPipeline. Also gets used for AveragingPipeline @@ -62,10 +62,10 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): """ logger.debug("start estimate '{}'".format(self.name)) logger.info('parset: %s ' % parset) - result = {'errors': []} + result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} duration = self._getDuration(parset.getString('Observation.startTime'), parset.getString('Observation.stopTime')) - freq_step = parset.getInt(PIPELINE + 'DPPP.demixer.freqstep', 1) #TODO, should these have defaults? - time_step = parset.getInt(PIPELINE + 'DPPP.demixer.timestep', 1) + freq_step = parset.getInt(PIPELINE + 'DPPP.demixer.demixfreqstep', 1) #TODO, should these have defaults? + time_step = parset.getInt(PIPELINE + 'DPPP.demixer.demixtimestep', 1) reduction_factor = freq_step * time_step if not parset.getBool(DATAPRODUCTS + 'Output_Correlated.enabled'): @@ -80,25 +80,25 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): if result['errors']: return result - logger.debug("calculate correlated data size") - result['output_files'] = {} + logger.info("calculate correlated data size") + result['storage']['output_files'] = {} input_file_size = input_files['uv']['uv_file_size'] output_file_size = 0.0 new_size = input_file_size / float(reduction_factor) output_file_size = new_size + new_size / 64.0 * (1.0 + reduction_factor) + new_size / 2.0 - result['output_files']['uv'] = {'nr_of_uv_files': input_files['uv']['nr_of_uv_files'], 'uv_file_size': int(output_file_size)} - logger.debug("correlated_uv: {} files {} bytes each".format(result['output_files']['uv']['nr_of_uv_files'], result['output_files']['uv']['uv_file_size'])) + result['storage']['output_files']['uv'] = {'nr_of_uv_files': input_files['uv']['nr_of_uv_files'], 'uv_file_size': int(output_file_size)} + logger.info("correlated_uv: {} files {} bytes each".format(result['storage']['output_files']['uv']['nr_of_uv_files'], result['storage']['output_files']['uv']['uv_file_size'])) if parset.getBool(DATAPRODUCTS + 'Output_InstrumentModel.enabled'): - logger.debug("calculate instrument-model data size") - result['output_files']['im'] = {'nr_of_im_files': input_files['uv']['nr_of_uv_files'], 'im_file_size': 1000} # 1 kB was hardcoded in the Scheduler - logger.debug("correlated_uv: {} files {} bytes each".format(result['output_files']['im']['nr_of_im_files'], result['output_files']['im']['im_file_size'])) + logger.info("calculate instrument-model data size") + result['storage']['output_files']['im'] = {'nr_of_im_files': input_files['uv']['nr_of_uv_files'], 'im_file_size': 1000} # 1 kB was hardcoded in the Scheduler + logger.info("correlated_im: {} files {} bytes each".format(result['storage']['output_files']['im']['nr_of_im_files'], result['storage']['output_files']['im']['im_file_size'])) # count total data size - total_data_size = result['output_files']['uv']['nr_of_uv_files'] * result['output_files']['uv']['uv_file_size'] + \ - result['output_files']['im']['nr_of_im_files'] * result['output_files']['im']['im_file_size'] # bytes + total_data_size = result['storage']['output_files']['uv']['nr_of_uv_files'] * result['storage']['output_files']['uv']['uv_file_size'] + \ + result['storage']['output_files']['im']['nr_of_im_files'] * result['storage']['output_files']['im']['im_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - result['storage'] = {'total_size': total_data_size} - result['bandwidth'] = {'total_size': total_bandwidth} + result['storage']['total_size'] = total_data_size + result['bandwidth']['total_size'] = total_bandwidth return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py index c3b80967d1c..575d46c1fe5 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py @@ -36,7 +36,7 @@ class PulsarPipelineResourceEstimator(BaseResourceEstimator): """ def __init__(self): logger.info("init PulsarPipelineResourceEstimator") - BaseResourceEstimator.__init__(self, name='pulsar_pipeline') + BaseResourceEstimator.__init__(self, name='pipeline') self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_CoherentStokes.enabled', @@ -59,7 +59,7 @@ class PulsarPipelineResourceEstimator(BaseResourceEstimator): """ logger.debug("start estimate '{}'".format(self.name)) logger.info('parset: %s ' % parset) - result = {'errors': []} + result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} duration = self._getDuration(parset.getString('Observation.startTime'), parset.getString('Observation.stopTime')) if not parset.getBool(DATAPRODUCTS + 'Output_Pulsar.enabled'): @@ -72,7 +72,7 @@ class PulsarPipelineResourceEstimator(BaseResourceEstimator): return result logger.debug("calculate pulp data size") - result['output_files'] = {} + result['storage']['output_files'] = {} nr_output_files = 0 if 'cs' in input_files: nr_input_files = input_files['cs']['nr_of_cs_files'] @@ -85,12 +85,13 @@ class PulsarPipelineResourceEstimator(BaseResourceEstimator): nr_input_files = input_files['is']['nr_of_is_files'] nr_output_files += nr_input_files - result['output_files']['pulp'] = {'nr_of_pulp_files': nr_output_files, 'pulp_file_size': 1000} # 1 kB was hardcoded in the Scheduler - logger.debug("correlated_uv: {} files {} bytes each".format(result['output_files']['pulp']['nr_of_pulp_files'], result['output_files']['pulp']['pulp_file_size'])) + result['storage']['output_files']['pulp'] = {'nr_of_pulp_files': nr_output_files, 'pulp_file_size': 1000} # 1 kB was hardcoded in the Scheduler + logger.info(result) + logger.info("pulsar_pipeline pulp: {} files {} bytes each".format(result['storage']['output_files']['pulp']['nr_of_pulp_files'], result['storage']['output_files']['pulp']['pulp_file_size'])) # count total data size - total_data_size = result['output_files']['pulp']['nr_of_pulp_files'] * result['output_files']['pulp']['pulp_file_size'] # bytes + total_data_size = result['storage']['output_files']['pulp']['nr_of_pulp_files'] * result['storage']['output_files']['pulp']['pulp_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - result['storage'] = {'total_size': total_data_size} - result['bandwidth'] = {'total_size': total_bandwidth} + result['storage']['total_size'] = total_data_size + result['bandwidth']['total_size'] = total_bandwidth return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index a6b9d4cbccc..c6fbc46d6d1 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -27,6 +27,7 @@ class ResourceEstimatorHandler(MessageHandlerInterface): specification_tree = content["specification_tree"] return self._get_estimated_resources(specification_tree) ##TODO also handle MoM tasks in RA 1.2 + #TODO use something else than .values()[0]['storage'] ?? def get_subtree_estimate(self, specification_tree): otdb_id = specification_tree['otdb_id'] parset = specification_tree['specification'] @@ -35,31 +36,31 @@ class ResourceEstimatorHandler(MessageHandlerInterface): elif specification_tree['task_type'] == 'pipeline': branch_estimates = {} for branch in specification_tree['predecessors']: - branch_estimates.update(get_subtree_estimate(branch)) - + branch_estimates.update(self.get_subtree_estimate(branch)) + logger.info(str(branch_estimates)) if specification_tree['task_subtype'] in ['averaging pipeline', 'calibration pipeline']: - for id, estimate in branch_estimates: - if not 'im' in estimate['output_files'] and 'uv' in estimate['output_files']: # Not a calibrator pipeline - logger.info('found %d as the target of pipeline %d' % (id, otdb_id)) - input_files = estimate['output_files'] # Need sap here as well + for id, estimate in branch_estimates.iteritems(): + if not 'im' in estimate.values()[0]['storage']['output_files'] and 'uv' in estimate.values()[0]['storage']['output_files']: # Not a calibrator pipeline + logger.info('found %s as the target of pipeline %s' % (id, otdb_id)) + input_files = estimate.values()[0]['storage']['output_files'] # Need sap here as well return {str(otdb_id): self.calibration_pipeline.verify_and_estimate(parset, input_files)} if specification_tree['task_subtype'] in ['imaging pipeline', 'imaging pipeline msss']: if len(branch_estimates) > 1: logger.error('Imaging pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) - input_files = branch_estimates.items()[0][1]['ouput_files'] + input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] return {str(otdb_id): self.calibration_pipeline.verify_and_estimate(parset, input_files)} if specification_tree['task_subtype'] in ['long baseline pipeline']: if len(branch_estimates) > 1: logger.error('Long baseline pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) - input_files = branch_estimates.items()[0][1]['ouput_files'] + input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] return {str(otdb_id): self.longbaseline_pipeline.verify_and_estimate(parset, input_files)} if specification_tree['task_subtype'] in ['pulsar pipeline']: if len(branch_estimates) > 1: logger.error('Pulsar pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) - input_files = branch_estimates.items()[0][1]['ouput_files'] + input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] return {str(otdb_id): self.pulsar_pipeline.verify_and_estimate(parset, input_files)} else: # reservation, maintenance, system tasks? -- GitLab From 8afc818f1f96e1b84691de45892147d2006a4e53 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Fri, 27 May 2016 16:11:42 +0000 Subject: [PATCH 236/933] Task #9192: Small fix for CEP4 support pipelnes --- SAS/Scheduler/src/Controller.cpp | 290 ++++++++++++++++--------------- 1 file changed, 148 insertions(+), 142 deletions(-) diff --git a/SAS/Scheduler/src/Controller.cpp b/SAS/Scheduler/src/Controller.cpp index c93933922fd..504b8b04a40 100644 --- a/SAS/Scheduler/src/Controller.cpp +++ b/SAS/Scheduler/src/Controller.cpp @@ -3926,110 +3926,116 @@ void Controller::copyTask(unsigned int taskID) { // then gets the data product information and checks and sets the correct info for these dependencies in the task // These dependencies have to be checked and set before a task is set to SCHEDULED std::pair<unscheduled_reasons, QString> Controller::setInputFilesForPipeline(Task *pPipe) { - std::pair<unscheduled_reasons, QString> error; - std::vector<const Task *> predecessors; - const IDvector &predecessorIDs(pPipe->getPredecessors()); - if (!predecessorIDs.empty()) { - // collect all predecessor tasks in a vector for faster searching - const Task *predTask; - for (IDvector::const_iterator predit = predecessorIDs.begin(); predit != predecessorIDs.end(); ++ predit) { - predTask = data.getTask(predit->second, predit->first); - if (predTask) { - predecessors.push_back(predTask); - } - else { - error.first = PREDECESSOR_NOT_FOUND; - error.second = QString("The predecessor task ") + QString::number(predit->second) + " could not be found."; - return error; // one of the predecessors could not be found - } - } + std::pair<unscheduled_reasons, QString> error; + std::vector<const Task *> predecessors; + const IDvector &predecessorIDs(pPipe->getPredecessors()); + if (!predecessorIDs.empty()) { + // collect all predecessor tasks in a vector for faster searching + const Task *predTask; + for (IDvector::const_iterator predit = predecessorIDs.begin(); predit != predecessorIDs.end(); ++ predit) { + predTask = data.getTask(predit->second, predit->first); + if (predTask) { + predecessors.push_back(predTask); + } + else { + error.first = PREDECESSOR_NOT_FOUND; + error.second = QString("The predecessor task ") + QString::number(predit->second) + " could not be found."; + return error; // one of the predecessors could not be found + } + } - // task has predecessors, check all input data products identifications of this task and search the - // output data products of the predecessors for matching identifications - // if a match is found then copy the corresponding filenames and location arrays into the inputDataProduct of this task - // if no match then error condition data product not found in predecessor(s) tasks, task cannot be scheduled - std::map<dataProductTypes, TaskStorage::inputDataProduct> &inputDataProducts(pPipe->storage()->getInputDataProductsForChange()); - - int idxIt; - bool foundit; - std::map<dataProductTypes, TaskStorage::outputDataProduct>::const_iterator pit; - QString sapstr; - QStringList locations, filenames; - std::vector<bool> skipVec; - bool resetSkipVector, SyncSkipWithPredecessor; - - for (dataProductTypes dpType = _BEGIN_DATA_PRODUCTS_ENUM_; dpType < _END_DATA_PRODUCTS_ENUM_-1; dpType = dataProductTypes(dpType + 1)) { - if (pPipe->storage()->isInputDataProduktEnabled(dpType)) { // is this input data product type enabled? - TaskStorage::inputDataProduct &dp = inputDataProducts[dpType]; // also creates the record in the inputDataProducts map if it doesn't exist yet - resetSkipVector = (dp.skip.empty() /*|| (dp.skip.size() != (unsigned)dp.filenames.size())*/); // the skip vector should only be synchronized with the predecessor skip vector the first time (i.e. when it is not yet set) - storageVector storageVec; - - for (QStringList::const_iterator identit = dp.identifications.begin(); identit != dp.identifications.end(); ++identit) { - foundit = false; - for (std::vector<const Task *>::const_iterator predit = predecessors.begin(); predit != predecessors.end(); ++predit) { - if ((*predit)->hasStorage()) { - const TaskStorage *predStorage((*predit)->storage()); + // task has predecessors, check all input data products identifications of this task and search the + // output data products of the predecessors for matching identifications + // if a match is found then copy the corresponding filenames and location arrays into the inputDataProduct of this task + // if no match then error condition data product not found in predecessor(s) tasks, task cannot be scheduled + std::map<dataProductTypes, TaskStorage::inputDataProduct> &inputDataProducts(pPipe->storage()->getInputDataProductsForChange()); + + int idxIt; + bool foundit; + std::map<dataProductTypes, TaskStorage::outputDataProduct>::const_iterator pit; + QString sapstr; + QStringList locations, filenames; + std::vector<bool> skipVec; + bool resetSkipVector, SyncSkipWithPredecessor; + + for (dataProductTypes dpType = _BEGIN_DATA_PRODUCTS_ENUM_; dpType < _END_DATA_PRODUCTS_ENUM_-1; dpType = dataProductTypes(dpType + 1)) { + if (pPipe->storage()->isInputDataProduktEnabled(dpType)) { // is this input data product type enabled? + TaskStorage::inputDataProduct &dp = inputDataProducts[dpType]; // also creates the record in the inputDataProducts map if it doesn't exist yet + resetSkipVector = (dp.skip.empty() /*|| (dp.skip.size() != (unsigned)dp.filenames.size())*/); // the skip vector should only be synchronized with the predecessor skip vector the first time (i.e. when it is not yet set) + storageVector storageVec; + + for (QStringList::const_iterator identit = dp.identifications.begin(); identit != dp.identifications.end(); ++identit) { + foundit = false; + for (std::vector<const Task *>::const_iterator predit = predecessors.begin(); predit != predecessors.end(); ++predit) { + if ((*predit)->hasStorage()) { + const TaskStorage *predStorage((*predit)->storage()); const std::map<dataProductTypes, TaskStorage::outputDataProduct> &pred_output(predStorage->getOutputDataProducts()); - pit = pred_output.find(dpType); - if (pit != pred_output.end()) { - idxIt = pit->second.identifications.indexOf(*identit); - if (idxIt != -1) { // found? + pit = pred_output.find(dpType); + if (pit != pred_output.end()) { + idxIt = pit->second.identifications.indexOf(*identit); + if (idxIt != -1) { // found? storageVector predecessorStorageVec(predStorage->getStorageLocations(dpType)); - unsigned psz(predecessorStorageVec.size()); - if (psz != 0) { - // copy the filenames and locations pointed to by this identification to the input data product list of this task - if (pit->second.filenames.size() == pit->second.locations.size()) { + unsigned psz(predecessorStorageVec.size()); + if (psz != 0 || (*predit)->getOutputDataproductCluster() == "CEP4") { + // copy the filenames and locations pointed to by this identification to the input data product list of this task + if (pit->second.filenames.size() == pit->second.locations.size()) { if ((dpType == DP_CORRELATED_UV) || (dpType == DP_COHERENT_STOKES) || (dpType == DP_INCOHERENT_STOKES)) { // for these data product types copy only the files that have the corresponding SAP - const QString &identification(pit->second.identifications.at(idxIt)); - int i(identification.indexOf(".SAP")); - if (i != -1) { // does it contain a reference to a specific SAP? - sapstr = identification.mid(i+1, identification.indexOf('.',i+1) - i - 1); - SyncSkipWithPredecessor = (pit->second.skip.size() == (unsigned)pit->second.filenames.size()); - for (int i = 0; i < pit->second.filenames.size(); ++ i) { - const QString &filename(pit->second.filenames.at(i)); - if (filename.contains(sapstr)) { - filenames.push_back(filename); - locations.push_back(pit->second.locations.at(i)); - storageVec.push_back(predecessorStorageVec.at(i % psz)); - if (resetSkipVector) { - if (SyncSkipWithPredecessor) { - skipVec.push_back(pit->second.skip.at(i)); - } - else { - skipVec.push_back(false); - } - } - } - } - } - else { // no specific SAP specified in identification, just copy all files - filenames += pit->second.filenames; - locations += pit->second.locations; - storageVec.insert(storageVec.end(), predecessorStorageVec.begin(), predecessorStorageVec.end()); - if (resetSkipVector) { - if (pit->second.skip.size() == (unsigned)pit->second.filenames.size()) { - skipVec.insert(skipVec.end(), pit->second.skip.begin(), pit->second.skip.end()); - } - else { - skipVec.assign(filenames.size(), false); - } - } - } - } - else { // for all other data product types copy all files - filenames += pit->second.filenames; - locations += pit->second.locations; - storageVec.insert(storageVec.end(), predecessorStorageVec.begin(), predecessorStorageVec.end()); - if (resetSkipVector) { - if (pit->second.skip.size() == (unsigned)pit->second.filenames.size()) { - skipVec.insert(skipVec.end(), pit->second.skip.begin(), pit->second.skip.end()); - } - else { - skipVec.assign(filenames.size(), false); - } - } - } - // also set the input file size + const QString &identification(pit->second.identifications.at(idxIt)); + int i(identification.indexOf(".SAP")); + if (i != -1) { // does it contain a reference to a specific SAP? + sapstr = identification.mid(i+1, identification.indexOf('.',i+1) - i - 1); + SyncSkipWithPredecessor = (pit->second.skip.size() == (unsigned)pit->second.filenames.size()); + for (int i = 0; i < pit->second.filenames.size(); ++ i) { + const QString &filename(pit->second.filenames.at(i)); + if (filename.contains(sapstr)) { + filenames.push_back(filename); + locations.push_back(pit->second.locations.at(i)); + if ((*predit)->getOutputDataproductCluster() != "CEP4") { + storageVec.push_back(predecessorStorageVec.at(i % psz)); + } + if (resetSkipVector) { + if (SyncSkipWithPredecessor) { + skipVec.push_back(pit->second.skip.at(i)); + } + else { + skipVec.push_back(false); + } + } + } + } + } + else { // no specific SAP specified in identification, just copy all files + filenames += pit->second.filenames; + locations += pit->second.locations; + if ((*predit)->getOutputDataproductCluster() != "CEP4") { + storageVec.insert(storageVec.end(), predecessorStorageVec.begin(), predecessorStorageVec.end()); + } + if (resetSkipVector) { + if (pit->second.skip.size() == (unsigned)pit->second.filenames.size()) { + skipVec.insert(skipVec.end(), pit->second.skip.begin(), pit->second.skip.end()); + } + else { + skipVec.assign(filenames.size(), false); + } + } + } + } + else { // for all other data product types copy all files + filenames += pit->second.filenames; + locations += pit->second.locations; + if ((*predit)->getOutputDataproductCluster() != "CEP4") { + storageVec.insert(storageVec.end(), predecessorStorageVec.begin(), predecessorStorageVec.end()); + } + if (resetSkipVector) { + if (pit->second.skip.size() == (unsigned)pit->second.filenames.size()) { + skipVec.insert(skipVec.end(), pit->second.skip.begin(), pit->second.skip.end()); + } + else { + skipVec.assign(filenames.size(), false); + } + } + } + // also set the input file size const std::pair<double, unsigned> &outputSizes(predStorage->getOutputFileSizes(dpType)); pPipe->storage()->setInputFileSizes(dpType, std::pair<double, unsigned>(outputSizes.first, filenames.size())); @@ -4041,50 +4047,50 @@ std::pair<unscheduled_reasons, QString> Controller::setInputFilesForPipeline(Tas pPulsarPipe->setCoherentType(predObs->getRTCPsettings().coherentType); pPulsarPipe->setIncoherentType(predObs->getRTCPsettings().incoherentType); } - } - else { - std::cerr << "filenames and locations array of task: " << (*predit)->getID() << " for data product: " << DATA_PRODUCTS[dpType] << " are not equal in length!" << std::endl; - } - foundit = true; - break; // breaks out of predecessor search loop for the current identification - } - } - } + } + else { + std::cerr << "filenames and locations array of task: " << (*predit)->getID() << " for data product: " << DATA_PRODUCTS[dpType] << " are not equal in length!" << std::endl; + } + foundit = true; + break; // breaks out of predecessor search loop for the current identification + } + } + } } - } - if (!foundit) { - // one of the identifications was not found in any of the predecessor tasks - // this is an error, scheduling of this task cannot continue - error.first = INPUT_DATA_PRODUCT_NOT_FOUND; - error.second = QString("The task cannot be scheduled because the ") + DATA_PRODUCTS[dpType] + " input data product with identification:" + *identit + " could not be found in its predecessor tasks.\nAre all predecessor tasks correctly defined for this task?"; - return error; // identification not found - } - } - // set storage location IDs equal to the accumulation of the predecessor output storage vec's - pPipe->storage()->addInputStorageLocations(dpType, storageVec); - dp.filenames = filenames; - dp.locations = locations; - if (!resetSkipVector) { - if (dp.skip.size() != (unsigned)dp.filenames.size()) { - dp.skip.assign(dp.filenames.size(), false); - } - } - else { - dp.skip = skipVec; - } - filenames.clear(); - locations.clear(); - skipVec.clear(); - } - } - } - else { - error.first = INPUT_DATA_PRODUCT_NOT_FOUND; - error.second = QString("The task cannot be scheduled because it doesn't have a predecessor defined"); - return error; - } + } + if (!foundit) { + // one of the identifications was not found in any of the predecessor tasks + // this is an error, scheduling of this task cannot continue + error.first = INPUT_DATA_PRODUCT_NOT_FOUND; + error.second = QString("The task cannot be scheduled because the ") + DATA_PRODUCTS[dpType] + " input data product with identification:" + *identit + " could not be found in its predecessor tasks.\nAre all predecessor tasks correctly defined for this task?"; + return error; // identification not found + } + } + // set storage location IDs equal to the accumulation of the predecessor output storage vec's + pPipe->storage()->addInputStorageLocations(dpType, storageVec); + dp.filenames = filenames; + dp.locations = locations; + if (!resetSkipVector) { + if (dp.skip.size() != (unsigned)dp.filenames.size()) { + dp.skip.assign(dp.filenames.size(), false); + } + } + else { + dp.skip = skipVec; + } + filenames.clear(); + locations.clear(); + skipVec.clear(); + } + } + } + else { + error.first = INPUT_DATA_PRODUCT_NOT_FOUND; + error.second = QString("The task cannot be scheduled because it doesn't have a predecessor defined"); + return error; + } - return error; + return error; } bool Controller::doScheduleChecks(Task *pTask) { -- GitLab From 7eb3a742ac454d13a55c9a2010c2dbfde93be079 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 30 May 2016 11:10:54 +0000 Subject: [PATCH 237/933] Task #8887: Fix naming of PipelineControl service in supervisord --- MAC/Services/src/pipelinecontrol.ini | 2 +- SubSystems/RAServices/RAServices.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/pipelinecontrol.ini b/MAC/Services/src/pipelinecontrol.ini index 51b0269945e..c5a07a437f9 100644 --- a/MAC/Services/src/pipelinecontrol.ini +++ b/MAC/Services/src/pipelinecontrol.ini @@ -1,4 +1,4 @@ -[program:PipelineStarter] +[program:PipelineControl] command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec pipelinecontrol' user=lofarsys stopsignal=INT ; KeyboardInterrupt diff --git a/SubSystems/RAServices/RAServices.ini b/SubSystems/RAServices/RAServices.ini index 8cdc451a120..1d296900b09 100644 --- a/SubSystems/RAServices/RAServices.ini +++ b/SubSystems/RAServices/RAServices.ini @@ -3,4 +3,4 @@ programs=raewebservice,radbservice,radbpglistener,resourceassigner,RATaskSpecifi priority=200 [group:MAC] -programs=pipelinecontrol +programs=PipelineControl -- GitLab From ba9cd6a0eaa0d26b7724baebfd5dbb7a5b4895ce Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 30 May 2016 11:17:08 +0000 Subject: [PATCH 238/933] Task #8887: Fix pipelinecontrol startup script to use correct busname variable names --- MAC/Services/src/pipelinecontrol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/pipelinecontrol b/MAC/Services/src/pipelinecontrol index 78cf6a38de7..325a953a534 100644 --- a/MAC/Services/src/pipelinecontrol +++ b/MAC/Services/src/pipelinecontrol @@ -35,10 +35,10 @@ if __name__ == "__main__": help="Bus or queue to send OTDB requests to") (options, args) = parser.parse_args() - if not options.otdb_busname: + if not options.otdb_notification_busname or not options.otdb_service_busname: parser.print_help() sys.exit(1) - with PipelineControl(otdb_busname=options.otdb_busname, setStatus_busname=options.setStatus_busname) as pipelineControl: + with PipelineControl(otdb_notification_busname=options.otdb_notification_busname, otdb_service_busname=options.otdb_service_busname) as pipelineControl: waitForInterrupt() -- GitLab From 690c482bcd40c46d4add6a96aa5718497ee21402 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 30 May 2016 11:34:15 +0000 Subject: [PATCH 239/933] Task #8887: Add missing import --- MAC/Services/src/pipelinecontrol | 1 + 1 file changed, 1 insertion(+) diff --git a/MAC/Services/src/pipelinecontrol b/MAC/Services/src/pipelinecontrol index 325a953a534..8361f2c0838 100644 --- a/MAC/Services/src/pipelinecontrol +++ b/MAC/Services/src/pipelinecontrol @@ -22,6 +22,7 @@ from lofar.mac.PipelineControl import PipelineControl from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTDB_SERVICE_BUSNAME +from lofar.common.util import waitForInterrupt if __name__ == "__main__": import sys -- GitLab From 2408322aae52dad361d3c480d6b517952436fdb4 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 30 May 2016 12:42:52 +0000 Subject: [PATCH 240/933] Task #9487: Do not override already set itsQuitReason, and explain EMERGENCY_TIMEOUT when reporting the error --- MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc b/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc index 36a3c3d01fb..f8669511d32 100644 --- a/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc +++ b/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc @@ -253,6 +253,10 @@ void ObservationControl::setState(CTState::CTstateNr newState) message = "Lost connection(s)"; reportState = RTDB_OBJ_STATE_BROKEN; break; + case CT_RESULT_EMERGENCY_TIMEOUT: + message = "Central controller did not die"; + reportState = RTDB_OBJ_STATE_BROKEN; + break; default: message = formatString("Unknown reason(%d)", itsQuitReason); reportState = RTDB_OBJ_STATE_BROKEN; @@ -544,7 +548,6 @@ GCFEvent::TResult ObservationControl::active_state(GCFEvent& event, GCFPortInter itsQuitReason = CT_RESULT_EMERGENCY_TIMEOUT; } else { // JDM #9487: For stations, a loss of connection is NOT fatal - itsQuitReason = CT_RESULT_NO_ERROR; } TRAN(ObservationControl::finishing_state); -- GitLab From 86c55b3a0840b93a43687adf1cba364dd5024767 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 31 May 2016 10:29:55 +0000 Subject: [PATCH 241/933] Task #8887: color grid status cells by value --- .../static/app/controllers/gridcontroller.js | 6 +- .../lib/static/css/main.css | 65 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index ae7f5cf8513..b70aacf2b59 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -59,7 +59,11 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid selectOptions: [] }, editableCellTemplate: 'ui-grid/dropdownEditor', - editDropdownOptionsArray: [] + editDropdownOptionsArray: [], + cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) { + var value = grid.getCellValue(row,col); + return "grid-status-" + value; + } }, { field: 'type', enableCellEdit: false, diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index 2abc793dd10..ba081e0e0ae 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -140,3 +140,68 @@ div.gantt-task.claim-task-status-error span { background: #990033; } +.grid-status-prepared { + background-color: #cccccc !important; +} + +.grid-status-approved { + background-color: #8cb3d9 !important; +} + +.grid-status-on_hold { + background-color: #b34700 !important; + color: #ffffff; +} + +.grid-status-conflict { + background-color: #ff0000 !important; +} + +.grid-status-prescheduled { + background-color: #6666ff !important; + color: #ffffff; +} + +.grid-status-scheduled { + background-color: #0000ff !important; + color: #ffffff; +} + +.grid-status-queued { + background-color: #ccffff !important; + color: #ffffff; +} + +.grid-status-active { + background-color: #ffff00 !important; +} + +.grid-status-completing { + background-color: #ffdd99 !important; +} + +.grid-status-finished { + background-color: #00ff00 !important; +} + +.grid-status-aborted { + background-color: #cc0000 !important; + color: #ffffff; +} + +.grid-status-error { + background-color: #990033 !important; + color: #ffffff; +} + + + + + + + + + + + + -- GitLab From 3317f5409f677308cc6e7b86d0be62e87872b87c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 31 May 2016 14:15:39 +0000 Subject: [PATCH 242/933] Task #8887: Add dynamic routing support to QPIDInfra tools, and use dynamic routing for lofar.otdb.command --- SAS/QPIDInfrastructure/bin/addtoQPIDDB.py | 3 ++- SAS/QPIDInfrastructure/bin/configQPIDfromDB.py | 5 ++++- SAS/QPIDInfrastructure/bin/populateDB.sh | 10 ++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/SAS/QPIDInfrastructure/bin/addtoQPIDDB.py b/SAS/QPIDInfrastructure/bin/addtoQPIDDB.py index 0d12ef72902..de5793a3404 100755 --- a/SAS/QPIDInfrastructure/bin/addtoQPIDDB.py +++ b/SAS/QPIDInfrastructure/bin/addtoQPIDDB.py @@ -14,6 +14,7 @@ if __name__ == '__main__': parser.add_option('-q', '--queue', dest='queue', type='string', default=None, help='Name of the queue on the broker') parser.add_option('-e', '--exchange', dest='exchange', type='string', default=None, help='Name of the exchange on the broker') parser.add_option('-k', '--routingkey', dest='routingkey', type='string', default='#', help='Federation routing key') + parser.add_option('-d', '--dynamic', dest='dynamic', action="store_true", default=False, help='Create a dynamic route') parser.add_option_group(dbcredentials.options_group(parser, "qpidinfra")) (options, args) = parser.parse_args() @@ -55,7 +56,7 @@ if __name__ == '__main__': if (options.exchange): QPIDinfra.addexchange(options.exchange) # should be superfluous QPIDinfra.bindexchangetohost(options.exchange,options.federation) - QPIDinfra.setexchangeroute(options.exchange,options.routingkey,options.broker,options.federation) + QPIDinfra.setexchangeroute(options.exchange,options.routingkey,options.broker,options.federation,dynamic=options.dynamic) else: raise Exception("federation can only be setup with a queue or an exchange") diff --git a/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py b/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py index 38d1ffbc8d9..4d4631b7a8a 100755 --- a/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py +++ b/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py @@ -10,8 +10,11 @@ def qpidconfig_add_topic(settings): print ("qpid-config -b %s add exchange topic %s --durable" %(settings['hostname'],settings['exchangename'])) def qpidroute_add(settings): + cmd = "dynamic" if settings['dynamic'] else "route" + print ("qpid-route -d route del %s %s %s \'%s\' " %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) - print ("qpid-route -d route add %s %s %s \'%s\' " %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) + print ("qpid-route -d dynamic del %s %s %s \'%s\' " %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) + print ("qpid-route -d %s add %s %s %s \'%s\' " %(cmd,settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) def qpidQroute_add(settings): print ("qpid-route -d queue del %s %s '%s' '%s'" %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['queuename'])) diff --git a/SAS/QPIDInfrastructure/bin/populateDB.sh b/SAS/QPIDInfrastructure/bin/populateDB.sh index e24f0454a77..c78f147dcbb 100755 --- a/SAS/QPIDInfrastructure/bin/populateDB.sh +++ b/SAS/QPIDInfrastructure/bin/populateDB.sh @@ -148,14 +148,16 @@ addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.otdb.notification addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.ssdb.command addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.ssdb.notification -# TODO: messages will end up at $SCU twice? -for tnode in head{01..02}.cep4 +# TODO: HA +for tnode in head01.cep4 do for fnode in cpu{01..50}.cep4 do - addtoQPIDDB.py --broker $fnode --exchange ${PREFIX}lofar.otdb.command --federation $tnode + addtoQPIDDB.py --dynamic --broker $fnode --exchange ${PREFIX}lofar.otdb.command --federation $tnode + addtoQPIDDB.py --dynamic --broker $tnode --exchange ${PREFIX}lofar.otdb.command --federation $fnode done - addtoQPIDDB.py --broker $tnode --exchange ${PREFIX}lofar.otdb.command --federation $SCU + addtoQPIDDB.py --dynamic --broker $tnode --exchange ${PREFIX}lofar.otdb.command --federation $SCU + addtoQPIDDB.py --dynamic --broker $SCU --exchange ${PREFIX}lofar.otdb.command --federation $tnode done -- GitLab From de27a51d03c7f79792170c4d1c85070f136f1528 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 31 May 2016 14:22:31 +0000 Subject: [PATCH 243/933] Task #9078: Merged QPIDInfra of #8887 to get dynamic routing changes --- SAS/QPIDInfrastructure/bin/addtoQPIDDB.py | 3 ++- SAS/QPIDInfrastructure/bin/configQPIDfromDB.py | 5 ++++- SAS/QPIDInfrastructure/bin/populateDB.sh | 12 ++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/SAS/QPIDInfrastructure/bin/addtoQPIDDB.py b/SAS/QPIDInfrastructure/bin/addtoQPIDDB.py index 0d12ef72902..de5793a3404 100755 --- a/SAS/QPIDInfrastructure/bin/addtoQPIDDB.py +++ b/SAS/QPIDInfrastructure/bin/addtoQPIDDB.py @@ -14,6 +14,7 @@ if __name__ == '__main__': parser.add_option('-q', '--queue', dest='queue', type='string', default=None, help='Name of the queue on the broker') parser.add_option('-e', '--exchange', dest='exchange', type='string', default=None, help='Name of the exchange on the broker') parser.add_option('-k', '--routingkey', dest='routingkey', type='string', default='#', help='Federation routing key') + parser.add_option('-d', '--dynamic', dest='dynamic', action="store_true", default=False, help='Create a dynamic route') parser.add_option_group(dbcredentials.options_group(parser, "qpidinfra")) (options, args) = parser.parse_args() @@ -55,7 +56,7 @@ if __name__ == '__main__': if (options.exchange): QPIDinfra.addexchange(options.exchange) # should be superfluous QPIDinfra.bindexchangetohost(options.exchange,options.federation) - QPIDinfra.setexchangeroute(options.exchange,options.routingkey,options.broker,options.federation) + QPIDinfra.setexchangeroute(options.exchange,options.routingkey,options.broker,options.federation,dynamic=options.dynamic) else: raise Exception("federation can only be setup with a queue or an exchange") diff --git a/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py b/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py index 38d1ffbc8d9..4d4631b7a8a 100755 --- a/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py +++ b/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py @@ -10,8 +10,11 @@ def qpidconfig_add_topic(settings): print ("qpid-config -b %s add exchange topic %s --durable" %(settings['hostname'],settings['exchangename'])) def qpidroute_add(settings): + cmd = "dynamic" if settings['dynamic'] else "route" + print ("qpid-route -d route del %s %s %s \'%s\' " %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) - print ("qpid-route -d route add %s %s %s \'%s\' " %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) + print ("qpid-route -d dynamic del %s %s %s \'%s\' " %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) + print ("qpid-route -d %s add %s %s %s \'%s\' " %(cmd,settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) def qpidQroute_add(settings): print ("qpid-route -d queue del %s %s '%s' '%s'" %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['queuename'])) diff --git a/SAS/QPIDInfrastructure/bin/populateDB.sh b/SAS/QPIDInfrastructure/bin/populateDB.sh index 6b972899f59..9f3fa08d042 100755 --- a/SAS/QPIDInfrastructure/bin/populateDB.sh +++ b/SAS/QPIDInfrastructure/bin/populateDB.sh @@ -170,16 +170,16 @@ addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.otdb.notification addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.ssdb.command addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.ssdb.notification -# TODO: messages will be duplicated? -for head in head{01..02}.cep4 +# TODO: HA +for head in head01.cep4 do for cpu in cpu{01..50}.cep4 do - addtoQPIDDB.py --broker $cpu --exchange ${PREFIX}lofar.otdb.command --federation $head - addtoQPIDDB.py --broker $head --exchange ${PREFIX}lofar.otdb.notification --federation $cpu + addtoQPIDDB.py --dynamic --broker $cpu --exchange ${PREFIX}lofar.otdb.command --federation $head + addtoQPIDDB.py --dynamic --broker $head --exchange ${PREFIX}lofar.otdb.command --federation $cpu done - addtoQPIDDB.py --broker $head --exchange ${PREFIX}lofar.otdb.command --federation $SCU - addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.otdb.notification --federation $head + addtoQPIDDB.py --dynamic --broker $head --exchange ${PREFIX}lofar.otdb.command --federation $SCU + addtoQPIDDB.py --dynamic --broker $SCU --exchange ${PREFIX}lofar.otdb.command --federation $head done -- GitLab From a3b93baba61f0e57c1cf81497c33560155e5b9c7 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 31 May 2016 14:27:45 +0000 Subject: [PATCH 244/933] Task #9078: Use FQDN for all nodes --- SAS/QPIDInfrastructure/bin/populateDB.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/QPIDInfrastructure/bin/populateDB.sh b/SAS/QPIDInfrastructure/bin/populateDB.sh index 9f3fa08d042..fb5cb8699ee 100755 --- a/SAS/QPIDInfrastructure/bin/populateDB.sh +++ b/SAS/QPIDInfrastructure/bin/populateDB.sh @@ -171,9 +171,9 @@ addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.ssdb.command addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.ssdb.notification # TODO: HA -for head in head01.cep4 +for head in head01.cep4.control.lofar do - for cpu in cpu{01..50}.cep4 + for cpu in $CEP4 do addtoQPIDDB.py --dynamic --broker $cpu --exchange ${PREFIX}lofar.otdb.command --federation $head addtoQPIDDB.py --dynamic --broker $head --exchange ${PREFIX}lofar.otdb.command --federation $cpu -- GitLab From 493559b579bec037cc7b3ce5eee10902cb32ff0a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 31 May 2016 14:29:27 +0000 Subject: [PATCH 245/933] Task #9078: Add db wipe instruction --- SAS/QPIDInfrastructure/bin/populateDB.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SAS/QPIDInfrastructure/bin/populateDB.sh b/SAS/QPIDInfrastructure/bin/populateDB.sh index fb5cb8699ee..bb54d9a0242 100755 --- a/SAS/QPIDInfrastructure/bin/populateDB.sh +++ b/SAS/QPIDInfrastructure/bin/populateDB.sh @@ -1,4 +1,10 @@ #!/bin/bash +# +# To DB first, wipe, use +# +# psql -U postgres -h sas099 --dbname=qpidinfra --file=$LOFARROOT/share/qpidinfrastructure/sql/qpidinfradb.sql +# + # ----------------------------------------- # Configuration -- GitLab From 405c5a4af0edc08abd0dc008432331605d5ed1d4 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 31 May 2016 14:38:39 +0000 Subject: [PATCH 246/933] Task #9078: Fix command line of dynamic routes --- SAS/QPIDInfrastructure/bin/configQPIDfromDB.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py b/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py index 4d4631b7a8a..6faa807dd8a 100755 --- a/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py +++ b/SAS/QPIDInfrastructure/bin/configQPIDfromDB.py @@ -13,8 +13,12 @@ def qpidroute_add(settings): cmd = "dynamic" if settings['dynamic'] else "route" print ("qpid-route -d route del %s %s %s \'%s\' " %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) - print ("qpid-route -d dynamic del %s %s %s \'%s\' " %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) - print ("qpid-route -d %s add %s %s %s \'%s\' " %(cmd,settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) + print ("qpid-route -d dynamic del %s %s %s" %(settings['tohost'],settings['fromhost'],settings['exchangename'])) + + if settings['dynamic']: + print ("qpid-route -d dynamic add %s %s %s" %(settings['tohost'],settings['fromhost'],settings['exchangename'])) + else: + print ("qpid-route -d route add %s %s %s \'%s\' " %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['routingkey'])) def qpidQroute_add(settings): print ("qpid-route -d queue del %s %s '%s' '%s'" %(settings['tohost'],settings['fromhost'],settings['exchangename'],settings['queuename'])) -- GitLab From 35280fdbf8860a2c3044f1b27d175006bc5f49c5 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 1 Jun 2016 09:26:06 +0000 Subject: [PATCH 247/933] Task #8887: Add Docker dependency to obtain docker-template executable --- MAC/Services/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/CMakeLists.txt b/MAC/Services/CMakeLists.txt index 25f37c622c3..d721a170318 100644 --- a/MAC/Services/CMakeLists.txt +++ b/MAC/Services/CMakeLists.txt @@ -1,6 +1,6 @@ # $Id$ -lofar_package(MAC_Services 1.0 DEPENDS PyMessaging OTDB_Services pyparameterset) +lofar_package(MAC_Services 1.0 DEPENDS PyMessaging OTDB_Services pyparameterset Docker) add_subdirectory(src) add_subdirectory(test) -- GitLab From 20348fc9f4a21d92bdbd1f75fdc622e6231233b5 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 1 Jun 2016 09:27:52 +0000 Subject: [PATCH 248/933] Task #8887: Use default docker image to run pipelineAborted.sh, since its a stock executable --- MAC/Services/src/PipelineControl.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index c87aef02285..5c67f31930f 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -124,10 +124,14 @@ class Parset(dict): def processingCluster(self): return self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] or "CEP2" + @staticmethod + def defaultDockerImage(): + return runCommand("docker-template", "lofar-pipeline:${LOFAR_TAG}")) + def dockerImage(self): # Return the version set in the parset, and fall back to our own version. return (self[PARSET_PREFIX + "Observation.ObservationControl.PythonControl.softwareVersion"] or - runCommand("docker-template", "lofar-pipeline:${LOFAR_TAG}")) + self.defaultDockerImage()) def slurmJobName(self): return str(self.otdbId()) @@ -376,7 +380,7 @@ class PipelineControl(OTDBBusListener): .format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, - image = parset.dockerImage(), + image = parset.defaultDockerImage(), status_bus = self.otdb_service_busname, ), -- GitLab From 8a378d113e9c4ddf288a98d77036134abde1afc4 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 1 Jun 2016 09:38:10 +0000 Subject: [PATCH 249/933] Task #8887: Fixed typo --- MAC/Services/src/PipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 5c67f31930f..e270c58e938 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -126,7 +126,7 @@ class Parset(dict): @staticmethod def defaultDockerImage(): - return runCommand("docker-template", "lofar-pipeline:${LOFAR_TAG}")) + return runCommand("docker-template", "lofar-pipeline:${LOFAR_TAG}") def dockerImage(self): # Return the version set in the parset, and fall back to our own version. -- GitLab From 299b6ef0c279d32b3951f104e71e0744f3095e73 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Wed, 1 Jun 2016 09:50:26 +0000 Subject: [PATCH 250/933] Task #9192: small fix to translator for supporting pipelines --- .../lib/translator.py | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 49e2fc33c60..53b4cbdff60 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -64,18 +64,19 @@ class RAtoOTDBTranslator(): locations = [] filenames = [] result = {} - for sap in storage_properties["saps"]: ##We might need to sort saps? - logging.debug('processing sap: %s' % sap) - if "nr_of_uv_files" in sap['properties']: - for _ in xrange(sap['properties']['nr_of_uv_files']): - locations.append(self.locationPath(project_name, otdb_id)) - filenames.append("L%d_SAP%03d_SB%03d_uv.MS" % (otdb_id, sap['sap_nr'], sb_nr)) - sb_nr += 1 if not 'saps' in storage_properties: ## It's a pipeline (no SAPs) for _ in xrange(storage_properties['nr_of_uv_files']): locations.append(self.locationPath(project_name, otdb_id)) filenames.append("L%d_SB%03d_uv.MS" % (otdb_id, sb_nr)) sb_nr += 1 + else: + for sap in storage_properties["saps"]: ##We might need to sort saps? + logging.debug('processing sap: %s' % sap) + if "nr_of_uv_files" in sap['properties']: + for _ in xrange(sap['properties']['nr_of_uv_files']): + locations.append(self.locationPath(project_name, otdb_id)) + filenames.append("L%d_SAP%03d_SB%03d_uv.MS" % (otdb_id, sap['sap_nr'], sb_nr)) + sb_nr += 1 result[PREFIX + 'DataProducts.Output_Correlated.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_Correlated.filenames'] = '[' + to_csv_string(filenames) + ']' return result @@ -180,9 +181,21 @@ class RAtoOTDBTranslator(): return result def parseStorageProperties(self, storage_claim): + """input something like: + {u'username': u'anonymous', u'status': u'allocated', u'resource_name': u'cep4storage', u'user_id': -1, + u'resource_type_id': 5, u'task_id': 6030, u'status_id': 1, u'resource_id': 117, u'session_id': 1, + u'id': 118, u'claim_size': 15860763720, u'starttime': datetime.datetime(2016, 6, 4, 12, 38, 6), + u'resource_type_name': u'storage', u'endtime': datetime.datetime(2016, 7, 5, 12, 53, 6), + u'properties': [{u'type_name': u'nr_of_uv_files', u'id': 347, u'value': 488, u'type_id': 2}, + {u'type_name': u'uv_file_size', u'id': 348, u'value': 32500565, u'type_id': 10}, + {u'type_name': u'im_file_size', u'id': 349, u'value': 1000, u'type_id': 11}, + {u'type_name': u'nr_of_im_files', u'id': 350, u'value': 488, u'type_id': 3}]} + output something like: + {u'nr_of_im_files': 488, u'nr_of_uv_files': 488, u'im_file_size': 1000, u'uv_file_size': 32500565} + """ result = {} - result['saps'] = [] if 'saps' in storage_claim: + result['saps'] = [] for s in storage_claim['saps']: properties = {} for p in s['properties']: @@ -191,6 +204,7 @@ class RAtoOTDBTranslator(): if 'properties' in storage_claim: for p in storage_claim['properties']: result[p['type_name']] = p['value'] + logging.info(result) return result def CreateParset(self, otdb_id, ra_info, project_name): @@ -202,10 +216,12 @@ class RAtoOTDBTranslator(): parset[PREFIX+'stopTime'] = ra_info['endtime'].strftime('%Y-%m-%d %H:%M:%S') if 'storage' in ra_info: - logging.debug(ra_info['storage']) + logging.info(ra_info['storage']) parset.update(self.CreateStorageKeys(otdb_id, self.parseStorageProperties(ra_info['storage']), project_name)) if 'stations' in ra_info: parset[PREFIX+'VirtualInstrument.stationList'] = ra_info["stations"] + parset[PREFIX+'ObservationControl.OnlineControl.inspectionHost'] = 'head01.cep4.control.lofar' + parset[PREFIX+'ObservationControl.OnlineControl.inspectionProgram'] = 'inspection-plots-observation.sh' return parset -- GitLab From 8c9d8e50274b36661a04cdc98eb82b73125efeae Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 1 Jun 2016 10:13:07 +0000 Subject: [PATCH 251/933] Task #8887: Configure logger, add verbose flag --- MAC/Services/src/pipelinecontrol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAC/Services/src/pipelinecontrol b/MAC/Services/src/pipelinecontrol index 8361f2c0838..9d03027965f 100644 --- a/MAC/Services/src/pipelinecontrol +++ b/MAC/Services/src/pipelinecontrol @@ -20,16 +20,20 @@ # # $Id: JobsToSchedule.py 33364 2016-01-21 21:21:12Z mol $ +import logging from lofar.mac.PipelineControl import PipelineControl from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTDB_SERVICE_BUSNAME from lofar.common.util import waitForInterrupt +logger = logging.getLogger(__name__) + if __name__ == "__main__": import sys from optparse import OptionParser # Check the invocation arguments parser = OptionParser("%prog [options]") + parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') parser.add_option("-N", "--otdb_notification_bus", dest="otdb_notification_busname", type="string", default=DEFAULT_OTDB_NOTIFICATION_BUSNAME, help="Bus or queue on which OTDB notifications are received") parser.add_option("-S", "--otdb_service_bus", dest="otdb_service_busname", type="string", default=DEFAULT_OTDB_SERVICE_BUSNAME, @@ -40,6 +44,9 @@ if __name__ == "__main__": parser.print_help() sys.exit(1) + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.INFO if options.verbose else logging.WARN) + with PipelineControl(otdb_notification_busname=options.otdb_notification_busname, otdb_service_busname=options.otdb_service_busname) as pipelineControl: waitForInterrupt() -- GitLab From 8e780b973772c82cfe40a949ceec276729c6dffc Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 1 Jun 2016 11:50:26 +0000 Subject: [PATCH 252/933] Task #8887: Put logs in /data/log on CEP4 --- MAC/Services/src/PipelineControl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index e270c58e938..6452fe0f5bc 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -330,8 +330,8 @@ class PipelineControl(OTDBBusListener): "--nodes=50", # Define better places to write the output - os.path.expandvars("--error=$LOFARROOT/var/log/docker-startPython-%s.stderr" % (otdbId,)), - os.path.expandvars("--output=$LOFARROOT/var/log/docker-startPython-%s.log" % (otdbId,)), + os.path.expandvars("--error=/data/log/runPipeline-%s.stderr" % (otdbId,)), + os.path.expandvars("--output=/data/log/runPipeline-%s.log" % (otdbId,)), ] min_starttime = self._minStartTime([x for x in preparsets if x.isObservation()]) -- GitLab From 5ad4c286e661a5650b30c59ee7fdea084c2573fa Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 1 Jun 2016 12:37:08 +0000 Subject: [PATCH 253/933] Task #8887: added task status colors for mom statuses opened and suspended --- .../lib/static/app/controllers/datacontroller.js | 4 +++- .../lib/static/css/main.css | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index e524f91b4e2..622cb56b710 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -72,7 +72,9 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, 'finished': '#00ff00', 'aborted': '#cc0000', 'error': '#990033', - 'obsolete': '#555555'}; + 'obsolete': '#555555', + 'opened': '#d9e5f2', + 'suspended': '#996666'}; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index ba081e0e0ae..46a6dac3cce 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -140,10 +140,26 @@ div.gantt-task.claim-task-status-error span { background: #990033; } +div.gantt-task.claim-task-status-opened span { + background: #d9e5f2; +} + +div.gantt-task.claim-task-status-suspended span { + background: #996666; +} + .grid-status-prepared { background-color: #cccccc !important; } +.grid-status-opened { + background-color: #aaaaaa !important; +} + +.grid-status-suspended { + background-color: #776666 !important; +} + .grid-status-approved { background-color: #8cb3d9 !important; } -- GitLab From eac55f17960f5f36281da4c252a40c82d70a16be Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Wed, 1 Jun 2016 15:27:59 +0000 Subject: [PATCH 254/933] Task #9192: first more or less working pipelines --- SAS/OTDB_Services/TreeService.py | 2 +- .../lib/translator.py | 44 +++++++++---------- .../calibration_pipeline.py | 11 +++-- .../resource_estimators/image_pipeline.py | 16 +++---- .../longbaseline_pipeline.py | 18 ++++---- .../resource_estimators/pulsar_pipeline.py | 2 +- .../ResourceAssignmentEstimator/service.py | 2 +- 7 files changed, 48 insertions(+), 47 deletions(-) diff --git a/SAS/OTDB_Services/TreeService.py b/SAS/OTDB_Services/TreeService.py index 12e6793bf81..3a5b824220f 100755 --- a/SAS/OTDB_Services/TreeService.py +++ b/SAS/OTDB_Services/TreeService.py @@ -108,7 +108,7 @@ def TaskGetIDs(input_dict, db_connection, return_tuple=True): # Task not found in any way return input values to the user return (None, otdb_id, mom_id) if return_tuple else [None, otdb_id, mom_id] - + # TODO have this return a Dict # Task Get Specification def TaskGetSpecification(input_dict, db_connection): diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 53b4cbdff60..2553dd80787 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -64,12 +64,7 @@ class RAtoOTDBTranslator(): locations = [] filenames = [] result = {} - if not 'saps' in storage_properties: ## It's a pipeline (no SAPs) - for _ in xrange(storage_properties['nr_of_uv_files']): - locations.append(self.locationPath(project_name, otdb_id)) - filenames.append("L%d_SB%03d_uv.MS" % (otdb_id, sb_nr)) - sb_nr += 1 - else: + if 'saps' in storage_properties: ## It's a pipeline (no SAPs) for sap in storage_properties["saps"]: ##We might need to sort saps? logging.debug('processing sap: %s' % sap) if "nr_of_uv_files" in sap['properties']: @@ -77,12 +72,16 @@ class RAtoOTDBTranslator(): locations.append(self.locationPath(project_name, otdb_id)) filenames.append("L%d_SAP%03d_SB%03d_uv.MS" % (otdb_id, sap['sap_nr'], sb_nr)) sb_nr += 1 + else: + for _ in xrange(storage_properties['nr_of_uv_files']): + locations.append(self.locationPath(project_name, otdb_id)) + filenames.append("L%d_SB%03d_uv.MS" % (otdb_id, sb_nr)) + sb_nr += 1 result[PREFIX + 'DataProducts.Output_Correlated.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_Correlated.filenames'] = '[' + to_csv_string(filenames) + ']' return result def CreateCoherentStokes(self, otdb_id, storage_properties, project_name): - SB_nr = 0 locations = [] filenames = [] result = {} @@ -102,7 +101,6 @@ class RAtoOTDBTranslator(): return result def CreateIncoherentStokes(self, otdb_id, storage_properties, project_name): - SB_nr = 0 locations = [] filenames = [] result = {} @@ -122,29 +120,27 @@ class RAtoOTDBTranslator(): return result def CreateInstrumentModel(self, otdb_id, storage_properties, project_name): - SB_nr = 0 + sb_nr = 0 locations = [] filenames = [] result = {} - for sap in storage_properties["saps"]: ##We might need to sort saps? - if "nr_of_im_files" in sap['properties']: - for _ in range(sap['properties']['nr_of_im_files']): - locations.append(self.locationPath(project_name, otdb_id)) - filenames.append("L%d_SAP%03d_SB%03d_inst.INST" % (otdb_id, sap['sap_nr'], sb_nr)) + for _ in xrange(storage_properties['nr_of_im_files']): + locations.append(self.locationPath(project_name, otdb_id)) + filenames.append("L%d_SB%03d_inst.INST" % (otdb_id, sb_nr)) + sb_nr += 1 result[PREFIX + 'DataProducts.Output_InstrumentModel.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_InstrumentModel.filenames'] = '[' + to_csv_string(filenames) + ']' return result def CreateSkyImage(self, otdb_id, storage_properties, project_name): - SB_nr = 0 + sbg_nr = 0 locations = [] filenames = [] result = {} - for sap in storage_properties["saps"]: ##We might need to sort saps? - if "nr_of_img_files" in sap['properties']: - for _ in range(sap['properties']['nr_of_img_files']): - locations.append(self.locationPath(project_name, otdb_id)) - filenames.append("L%d_SAP%03d_SB%03d_sky.IM" % (otdb_id, sap['sap_nr'], sb_nr)) + for _ in xrange(storage_properties['nr_of_img_files']): + locations.append(self.locationPath(project_name, otdb_id)) + filenames.append("L%d_SBG%03d_sky.IM" % (otdb_id, sbg_nr)) + sbg_nr += 1 result[PREFIX + 'DataProducts.Output_SkyImage.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_SkyImage.filenames'] = '[' + to_csv_string(filenames) + ']' return result @@ -172,8 +168,8 @@ class RAtoOTDBTranslator(): result.update(self.CreateCoherentStokes(otdb_id, storage_properties, project_name)) if 'nr_of_is_files' in storage_properties: result.update(self.CreateIncoherentStokes(otdb_id, storage_properties, project_name)) - #if 'nr_of_im_files' in storage_properties: - # result.update(self.CreateInstrumentModel(otdb_id, storage_properties, project_name)) + if 'nr_of_im_files' in storage_properties: + result.update(self.CreateInstrumentModel(otdb_id, storage_properties, project_name)) if 'nr_of_img_files' in storage_properties: result.update(self.CreateSkyImage(otdb_id, storage_properties, project_name)) if 'nr_of_pulp_files' in storage_properties: @@ -220,8 +216,8 @@ class RAtoOTDBTranslator(): parset.update(self.CreateStorageKeys(otdb_id, self.parseStorageProperties(ra_info['storage']), project_name)) if 'stations' in ra_info: parset[PREFIX+'VirtualInstrument.stationList'] = ra_info["stations"] - parset[PREFIX+'ObservationControl.OnlineControl.inspectionHost'] = 'head01.cep4.control.lofar' - parset[PREFIX+'ObservationControl.OnlineControl.inspectionProgram'] = 'inspection-plots-observation.sh' +# parset[PREFIX+'ObservationControl.OnlineControl.inspectionHost'] = 'head01.cep4.control.lofar' +# parset[PREFIX+'ObservationControl.OnlineControl.inspectionProgram'] = 'inspection-plots-observation.sh' return parset diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py index a33526962f4..bd3b060c5c1 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py @@ -36,7 +36,7 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): """ def __init__(self): logger.info("init CalibrationPipelineResourceEstimator") - BaseResourceEstimator.__init__(self, name='pipeline') + BaseResourceEstimator.__init__(self, name='pipeline') #FIXME name='calibration_pipeline' self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_Correlated.enabled', @@ -95,8 +95,13 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): logger.info("correlated_im: {} files {} bytes each".format(result['storage']['output_files']['im']['nr_of_im_files'], result['storage']['output_files']['im']['im_file_size'])) # count total data size - total_data_size = result['storage']['output_files']['uv']['nr_of_uv_files'] * result['storage']['output_files']['uv']['uv_file_size'] + \ - result['storage']['output_files']['im']['nr_of_im_files'] * result['storage']['output_files']['im']['im_file_size'] # bytes + total_data_size = 0 + for data_type in result['storage']['output_files']: + total_data_size += result['storage']['output_files'][data_type]['nr_of_' + data_type + '_files'] * result['storage']['output_files'][data_type][data_type + '_file_size'] # bytes + # FIXME I don't think this is technically correct, as the IM files are sometimes created and used, just not a required export? + # Need to split averaging pipeline and calibration pipeline + #total_data_size = result['storage']['output_files']['uv']['nr_of_uv_files'] * result['storage']['output_files']['uv']['uv_file_size'] + \ + # result['storage']['output_files']['im']['nr_of_im_files'] * result['storage']['output_files']['im']['im_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second result['storage']['total_size'] = total_data_size result['bandwidth']['total_size'] = total_bandwidth diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py index 2c4b60797ef..d05e28cf51d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py @@ -38,7 +38,7 @@ class ImagePipelineResourceEstimator(BaseResourceEstimator): """ def __init__(self): logger.info("init ImagePipelineResourceEstimator") - BaseResourceEstimator.__init__(self, name='imaging_pipeline') + BaseResourceEstimator.__init__(self, name='pipeline') #FIXME name='imaging_pipeline' self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_Correlated.enabled', @@ -62,7 +62,7 @@ class ImagePipelineResourceEstimator(BaseResourceEstimator): """ logger.debug("start estimate '{}'".format(self.name)) logger.info('parset: %s ' % parset) - result = {'errors': []} + result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} duration = self._getDuration(parset.getString('Observation.startTime'), parset.getString('Observation.stopTime')) slices_per_image = parset.getInt(PIPELINE + 'Imaging.slices_per_image', 0) #TODO, should these have defaults? subbands_per_image = parset.getInt(PIPELINE + 'Imaging.subbands_per_image', 0) @@ -85,15 +85,15 @@ class ImagePipelineResourceEstimator(BaseResourceEstimator): return result logger.debug("calculate sky image data size") - result['output_files'] = {} + result['storage']['output_files'] = {} nr_images = nr_input_subbands / (subbands_per_image * slices_per_image) - result['output_files']['img'] = {'nr_of_img_files': nr_images, 'img_file_size': 1000} # 1 kB was hardcoded in the Scheduler - logger.debug("sky_images: {} files {} bytes each".format(result['output_files']['img']['nr_of_img_files'], result['output_files']['img']['img_file_size'])) + result['storage']['output_files']['img'] = {'nr_of_img_files': nr_images, 'img_file_size': 1000} # 1 kB was hardcoded in the Scheduler + logger.debug("sky_images: {} files {} bytes each".format(result['storage']['output_files']['img']['nr_of_img_files'], result['storage']['output_files']['img']['img_file_size'])) # count total data size - total_data_size = result['output_files']['img']['nr_of_img_files'] * result['output_files']['img']['img_file_size'] # bytes + total_data_size = result['storage']['output_files']['img']['nr_of_img_files'] * result['storage']['output_files']['img']['img_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - result['storage'] = {'total_size': total_data_size} - result['bandwidth'] = {'total_size': total_bandwidth} + result['storage']['total_size'] = total_data_size + result['bandwidth']['total_size'] = total_bandwidth return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py index c8d82defa44..0219cf586af 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py @@ -36,7 +36,7 @@ class LongBaselinePipelineResourceEstimator(BaseResourceEstimator): """ def __init__(self): logger.info("init LongBaselinePipelineResourceEstimator") - BaseResourceEstimator.__init__(self, name='longbaseline_pipeline') + BaseResourceEstimator.__init__(self, name='pipeline') #FIXME name='longbaseline_pipeline' self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_Correlated.enabled', @@ -60,7 +60,7 @@ class LongBaselinePipelineResourceEstimator(BaseResourceEstimator): """ logger.debug("start estimate '{}'".format(self.name)) logger.info('parset: %s ' % parset) - result = {'errors': []} + result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} duration = self._getDuration(parset.getString('Observation.startTime'), parset.getString('Observation.stopTime')) subbandgroups_per_ms = parset.getInt(PIPELINE + 'LongBaseline.subbandgroups_per_ms', 0) #TODO, should these have defaults? subbands_per_subbandgroup = parset.getInt(PIPELINE + 'LongBaseline.subbands_per_subbandgroup', 0) @@ -76,21 +76,21 @@ class LongBaselinePipelineResourceEstimator(BaseResourceEstimator): if not subbandgroups_per_ms or not subbands_per_subbandgroup: logger.warning('subbandgroups_per_ms or subbands_per_subbandgroup are not valid') result['errors'].append('Missing UV Dataproducts in input_files') - if nr_input_files % (subbands_per_subband_group * subband_groups_per_ms) > 0: + if nr_input_files % (subbands_per_subbandgroup * subbandgroups_per_ms) > 0: logger.warning('subbandgroups_per_ms and subbands_per_subbandgroup not a multiple of number of inputs') result['errors'].append('subbandgroups_per_ms and subbands_per_subbandgroup not a multiple of number of inputs') if result['errors']: return result logger.debug("calculate correlated data size") - result['output_files'] = {} + result['storage']['output_files'] = {} nr_output_files = nr_input_files / (subbands_per_subbandgroup * subbandgroups_per_ms) - result['output_files']['uv'] = {'nr_of_uv_files': nr_output_files, 'uv_file_size': 1000} # 1 kB was hardcoded in the Scheduler - logger.debug("correlated_uv: {} files {} bytes each".format(result['output_files']['uv']['nr_of_uv_files'], result['output_files']['uv']['uv_file_size'])) + result['storage']['output_files']['uv'] = {'nr_of_uv_files': nr_output_files, 'uv_file_size': 1000} # 1 kB was hardcoded in the Scheduler + logger.debug("correlated_uv: {} files {} bytes each".format(result['storage']['output_files']['uv']['nr_of_uv_files'], result['storage']['output_files']['uv']['uv_file_size'])) # count total data size - total_data_size = result['output_files']['uv']['nr_of_uv_files'] * result['output_files']['uv']['uv_file_size'] # bytes + total_data_size = result['storage']['output_files']['uv']['nr_of_uv_files'] * result['storage']['output_files']['uv']['uv_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - result['storage'] = {'total_size': total_data_size} - result['bandwidth'] = {'total_size': total_bandwidth} + result['storage']['total_size'] = total_data_size + result['bandwidth']['total_size'] = total_bandwidth return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py index 575d46c1fe5..26471d794ea 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py @@ -36,7 +36,7 @@ class PulsarPipelineResourceEstimator(BaseResourceEstimator): """ def __init__(self): logger.info("init PulsarPipelineResourceEstimator") - BaseResourceEstimator.__init__(self, name='pipeline') + BaseResourceEstimator.__init__(self, name='pipeline') #FIXME name='pulsar_pipeline' self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_CoherentStokes.enabled', diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index c6fbc46d6d1..78c707874ca 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -49,7 +49,7 @@ class ResourceEstimatorHandler(MessageHandlerInterface): if len(branch_estimates) > 1: logger.error('Imaging pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] - return {str(otdb_id): self.calibration_pipeline.verify_and_estimate(parset, input_files)} + return {str(otdb_id): self.imaging_pipeline.verify_and_estimate(parset, input_files)} if specification_tree['task_subtype'] in ['long baseline pipeline']: if len(branch_estimates) > 1: -- GitLab From b2e1c38cb154b73e829cae8467ff6b9c52ba4698 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 2 Jun 2016 08:41:40 +0000 Subject: [PATCH 255/933] Task #8887: resourceassigner publishes scheduled/conflict events on bus. rotspservice reacts to these events via rabuslistener instead of radbbuslistener --- .../lib/rotspservice.py | 54 ++++++++----------- .../ResourceAssigner/lib/CMakeLists.txt | 1 + .../ResourceAssigner/lib/assignment.py | 27 +++++++++- .../ResourceAssigner/lib/config.py | 4 ++ .../ResourceAssigner/lib/raservice.py | 11 +++- 5 files changed, 62 insertions(+), 35 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py index d67c9b37e8d..1a942ead365 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py @@ -35,17 +35,16 @@ import time from lofar.messaging.RPC import RPC, RPCException import lofar.sas.resourceassignment.resourceassignmentservice.rpc as rarpc ## RA DB -from lofar.sas.resourceassignment.database.radbbuslistener import RADBBusListener -from lofar.sas.resourceassignment.database.config import DEFAULT_NOTIFICATION_BUSNAME as RA_NOTIFICATION_BUSNAME -from lofar.sas.resourceassignment.database.config import DEFAULT_NOTIFICATION_PREFIX as RA_NOTIFICATION_PREFIX +from lofar.sas.resourceassignment.resourceassigner.rabuslistener import RABusListener +from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_BUSNAME, DEFAULT_RA_NOTIFICATION_SUBJECTS, DEFAULT_RA_NOTIFICATION_PREFIX from lofar.sas.resourceassignment.ratootdbtaskspecificationpropagator.propagator import RAtoOTDBPropagator logger = logging.getLogger(__name__) -class RATaskStatusChangedListener(RADBBusListener): +class RATaskStatusChangedListener(RABusListener): def __init__(self, - busname=RA_NOTIFICATION_BUSNAME, - subject=RA_NOTIFICATION_PREFIX + 'TaskUpdated', + busname=DEFAULT_RA_NOTIFICATION_BUSNAME, + subject=DEFAULT_RA_NOTIFICATION_SUBJECTS, broker=None, propagator=None, ## TODO also give translator? **kwargs): @@ -65,32 +64,21 @@ class RATaskStatusChangedListener(RADBBusListener): if not self.propagator: self.propagator = RAtoOTDBPropagator() - def onTaskUpdated(self, old_task, new_task): - # override super onTaskUpdated - # check for status change, and call either onTaskScheduled or onTaskScheduled - if old_task['status_id'] != new_task['status_id']: - if new_task['status'] == 'scheduled': - self.onTaskScheduled(new_task['id'], new_task['otdb_id'], new_task['mom_id']) - elif new_task['status'] == 'conflict': - self.onTaskConflict(new_task['id'], new_task['otdb_id'], new_task['mom_id']) + def onTaskScheduled(self, task_ids): + radb_id = task_ids.get('radb_id') + otdb_id = task_ids.get('otdb_id') + mom_id = task_ids.get('mom_id') + logger.info('onTaskScheduled: radb_id=%s otdb_id=%s mom_id=%s', radb_id, otdb_id, mom_id) - def onTaskInserted(self, new_task): - # override super onTaskInserted - # check for status, and call either onTaskScheduled or onTaskScheduled - if new_task['status'] == 'scheduled': - self.onTaskScheduled(new_task['id'], new_task['otdb_id'], new_task['mom_id']) - elif new_task['status'] == 'conflict': - self.onTaskConflict(new_task['id'], new_task['otdb_id'], new_task['mom_id']) + self.propagator.doTaskScheduled(radb_id, otdb_id, mom_id) - def onTaskScheduled(self, ra_id, otdb_id, mom_id): - logger.info('onTaskScheduled: ra_id=%s otdb_id=%s mom_id=%s' % (ra_id, otdb_id, mom_id)) + def onTaskConflict(self, task_ids): + radb_id = task_ids.get('radb_id') + otdb_id = task_ids.get('otdb_id') + mom_id = task_ids.get('mom_id') + logger.info('onTaskConflict: radb_id=%s otdb_id=%s mom_id=%s', radb_id, otdb_id, mom_id) - self.propagator.doTaskScheduled(ra_id, otdb_id, mom_id) - - def onTaskConflict(self, ra_id, otdb_id, mom_id): - logger.info('onTaskConflict: ra_id=%s otdb_id=%s mom_id=%s' % (ra_id, otdb_id, mom_id)) - - self.propagator.doTaskConflict(ra_id, otdb_id, mom_id) + self.propagator.doTaskConflict(radb_id, otdb_id, mom_id) __all__ = ["RATaskStatusChangedListener"] @@ -111,8 +99,8 @@ def main(): parser = OptionParser("%prog [options]", description='runs the RAtoOTDBTaskSpecificationPropagator service') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') - parser.add_option("--notification_busname", dest="notification_busname", type="string", default=RA_NOTIFICATION_BUSNAME, help="Name of the notification bus on which messages are published, default: %default") - parser.add_option("--notification_subject", dest="notification_subject", type="string", default=RA_NOTIFICATION_PREFIX+'TaskUpdated', help="Subject of the published messages to listen for, default: %default") + parser.add_option("--ra_notification_busname", dest="ra_notification_busname", type="string", default=DEFAULT_RA_NOTIFICATION_BUSNAME, help="Name of the notification bus on which messages are published, default: %default") + parser.add_option("--ra_notification_prefix", dest="ra_notification_prefix", type="string", default=DEFAULT_RA_NOTIFICATION_PREFIX, help="Prefix of the subjects of the published messages to listen for, default: %default") parser.add_option("--radb_busname", dest="radb_busname", type="string", default=RADB_BUSNAME, help="Name of the bus on which the RADB service listens, default: %default") parser.add_option("--radb_servicename", dest="radb_servicename", type="string", default=RADB_SERVICENAME, help="Name of the RADB service, default: %default") parser.add_option("--otdb_busname", dest="otdb_busname", type="string", default=DEFAULT_OTDB_SERVICE_BUSNAME, help="Name of the bus on which the OTDB service listens, default: %default") @@ -134,8 +122,8 @@ def main(): mom_busname=options.mom_busname, mom_servicename=options.mom_servicename, broker=options.broker) as propagator: - with RATaskStatusChangedListener(busname=options.notification_busname, - subject=options.notification_subject, + with RATaskStatusChangedListener(busname=options.ra_notification_busname, + subject=options.ra_notification_prefix + '*', broker=options.broker, propagator=propagator) as listener: waitForInterrupt() diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssigner/lib/CMakeLists.txt index 658a1700412..9311ccd8977 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/CMakeLists.txt @@ -4,6 +4,7 @@ python_install( __init__.py raservice.py assignment.py + rabuslistener.py config.py DESTINATION lofar/sas/resourceassignment/resourceassigner) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 86e334e2f86..c5df521642a 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -30,6 +30,8 @@ import time import collections from lofar.common.util import humanreadablesize +from lofar.messaging.messages import EventMessage +from lofar.messaging.messagebus import ToBus from lofar.messaging.RPC import RPC, RPCException from lofar.parameterset import parameterset @@ -47,6 +49,9 @@ from lofar.sas.systemstatus.service.SSDBrpc import SSDBRPC from lofar.sas.systemstatus.service.config import DEFAULT_SSDB_BUSNAME from lofar.sas.systemstatus.service.config import DEFAULT_SSDB_SERVICENAME +from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_BUSNAME +from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_PREFIX + logger = logging.getLogger(__name__) class ResourceAssigner(): @@ -59,6 +64,8 @@ class ResourceAssigner(): ssdb_servicename=DEFAULT_SSDB_SERVICENAME, otdb_busname=DEFAULT_OTDB_SERVICE_BUSNAME, otdb_servicename=DEFAULT_OTDB_SERVICENAME, + ra_notification_busname=DEFAULT_RA_NOTIFICATION_BUSNAME, + ra_notification_prefix=DEFAULT_RA_NOTIFICATION_PREFIX, broker=None): """ ResourceAssigner inserts/updates tasks in the radb and assigns resources to it based on incoming parset. @@ -74,6 +81,8 @@ class ResourceAssigner(): self.rerpc = RPC(re_servicename, busname=re_busname, broker=broker, ForwardExceptions=True) self.ssdbrpc = SSDBRPC(servicename=ssdb_servicename, busname=ssdb_busname, broker=broker) self.otdbrpc = OTDBRPC(busname=otdb_busname, servicename=otdb_servicename, broker=broker) ## , ForwardExceptions=True hardcoded in RPCWrapper right now + self.ra_notification_bus = ToBus(address=ra_notification_busname, broker=broker) + self.ra_notification_prefix = ra_notification_prefix def __enter__(self): """Internal use only. (handles scope 'with')""" @@ -90,6 +99,7 @@ class ResourceAssigner(): self.rerpc.open() self.otdbrpc.open() self.ssdbrpc.open() + self.ra_notification_bus.open() def close(self): """Close rpc connections to radb service and resource estimator service""" @@ -97,6 +107,7 @@ class ResourceAssigner(): self.rerpc.close() self.otdbrpc.close() self.ssdbrpc.close() + self.ra_notification_bus.close() def doAssignment(self, specification_tree): logger.info('doAssignment: specification_tree=%s' % (specification_tree)) @@ -169,13 +180,27 @@ class ResourceAssigner(): conflictingClaims = self.radbrpc.getResourceClaims(task_ids=taskId, status='conflict') if conflictingClaims: + # radb set's task status to conflict automatically logger.warning('doAssignment: %s conflicting claims detected. Task cannot be scheduled. %s' % (len(conflictingClaims), conflictingClaims)) + self._sendNotification(task, 'conflict') else: logger.info('doAssignment: all claims for task %s were succesfully claimed. Setting task status to scheduled' % (taskId,)) self.radbrpc.updateTaskAndResourceClaims(taskId, task_status='scheduled', claim_status='allocated') + self._sendNotification(task, 'scheduled') + + self.processPredecessors(specification_tree) - self.processPredecessors(specification_tree) + def _sendNotification(self, task, status): + try: + if status == 'scheduled' or status == 'conflict': + content={'radb_id': task['id'], 'otdb_id':task['otdb_id'], 'mom_id': task['mom_id']} + subject= 'TaskScheduled' if status == 'scheduled' else 'TaskConflict' + msg = EventMessage(context=self.ra_notification_prefix + subject, content=content) + logger.info('Sending notification %s: %s' % (subject, str(content).replace('\n', ' '))) + self.ra_notification_bus.send(msg) + except Exception as e: + logger.error(str(e)) def processPredecessors(self, specification_tree): try: diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/config.py b/SAS/ResourceAssignment/ResourceAssigner/lib/config.py index 24a16cad21b..fccb37107e9 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/config.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/config.py @@ -5,3 +5,7 @@ from lofar.messaging import adaptNameToEnvironment DEFAULT_BUSNAME = adaptNameToEnvironment('lofar.ra.command') DEFAULT_SERVICENAME = 'RAService' + +DEFAULT_RA_NOTIFICATION_BUSNAME = adaptNameToEnvironment('lofar.ra.notification') +DEFAULT_RA_NOTIFICATION_PREFIX = 'ResourceAssigner.' +DEFAULT_RA_NOTIFICATION_SUBJECTS=DEFAULT_RA_NOTIFICATION_PREFIX+'*' diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py index 4d7e7c0e14f..1ad6bd5994c 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py @@ -84,6 +84,8 @@ def main(): from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME from lofar.sas.systemstatus.service.config import DEFAULT_SSDB_BUSNAME from lofar.sas.systemstatus.service.config import DEFAULT_SSDB_SERVICENAME + from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_BUSNAME + from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_PREFIX # Check the invocation arguments parser = OptionParser("%prog [options]", @@ -117,10 +119,15 @@ def main(): parser.add_option("--ssdb_servicename", dest="ssdb_servicename", type="string", default=DEFAULT_SSDB_SERVICENAME, help="Name of the ssdb service. [default: %default]") + parser.add_option("--ra_notification_busname", dest="ra_notification_busname", type="string", + default=DEFAULT_RA_NOTIFICATION_BUSNAME, + help="Name of the notification bus on which the resourceassigner publishes its notifications. [default: %default]") + parser.add_option("--ra_notification_prefix", dest="ra_notification_prefix", type="string", + default=DEFAULT_RA_NOTIFICATION_PREFIX, + help="Prefix for the subject of the by the resourceassigner published notification messages. [default: %default]") parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() - logging.getLogger('lofar.messaging.messagebus').setLevel(logging.WARNING) setQpidLogLevel(logging.INFO) logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.DEBUG if options.verbose else logging.INFO) @@ -133,6 +140,8 @@ def main(): otdb_servicename=options.otdb_servicename, ssdb_busname=options.ssdb_busname, ssdb_servicename=options.ssdb_servicename, + ra_notification_busname=options.ra_notification_busname, + ra_notification_prefix=options.ra_notification_prefix, broker=options.broker) as assigner: with SpecifiedTaskListener(busname=options.notification_busname, subject=options.notification_subject, -- GitLab From e7a9fa2dff9a355c00eb24cdd287032c8c6cb6f7 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 2 Jun 2016 08:55:01 +0000 Subject: [PATCH 256/933] Task #8887: Fixes for docker/slurm command line, and some minor tweaking --- Docker/docker-template | 3 ++- MAC/Services/src/PipelineControl.py | 27 +++++++++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Docker/docker-template b/Docker/docker-template index 7b3987abe45..53be24bc26b 100755 --- a/Docker/docker-template +++ b/Docker/docker-template @@ -44,7 +44,8 @@ while getopts "hv:" opt; do done # Make sure we obtain info about the project source! -VERSION_INFO=`$VERSION_DOCKER` +# Drop stderr to prevent logger output from contaminating our output +VERSION_INFO=`$VERSION_DOCKER 2>/dev/null` # Extract branch name w.r.t. repository root, e.g. branches/LOFAR-Task1234 export LOFAR_BRANCH_NAME=`echo "$VERSION_INFO" | perl -ne 'print "$1" if /branch += +(.+)/;'` diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 6452fe0f5bc..40517b11a80 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -77,7 +77,7 @@ import logging logger = logging.getLogger(__name__) def runCommand(cmdline, input=None): - logger.info("Running '%s'", cmdline) + logger.info("runCommand starting: %s", cmdline) # Start command proc = subprocess.Popen( @@ -90,8 +90,9 @@ def runCommand(cmdline, input=None): ) # Feed input and wait for termination + logger.info("runCommand input: %s", input) stdout, _ = proc.communicate(input) - logger.debug(stdout) + logger.info("runCommand output: %s", stdout) # Check exit status, bail on error if proc.returncode != 0: @@ -124,6 +125,10 @@ class Parset(dict): def processingCluster(self): return self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] or "CEP2" + @staticmethod + def dockerRepository(): + return "nexus.cep4.control.lofar:18080" + @staticmethod def defaultDockerImage(): return runCommand("docker-template", "lofar-pipeline:${LOFAR_TAG}") @@ -146,15 +151,19 @@ class Slurm(object): # TODO: Derive SLURM partition name self.partition = "cpu" - def _runCommand(self, cmdline): + def _runCommand(self, cmdline, input=None): cmdline = "ssh %s %s" % (self.headnode, cmdline) - runCommand(cmdline) + return runCommand(cmdline, input) def submit(self, jobName, cmdline, sbatch_params=None): if sbatch_params is None: sbatch_params = [] - stdout = self._runCommand("sbatch --partition=%s --job-name=%s %s bash -c '%s'" % (self.partition, jobName, " ".join(sbatch_params), cmdline)) + script = """#!/bin/bash +{cmdline} +""".format(cmdline = cmdline) + + stdout = self._runCommand("sbatch --partition=%s --job-name=%s %s" % (self.partition, jobName, " ".join(sbatch_params)), script) # Returns "Submitted batch job 3" -- extract ID match = re.search("Submitted batch job (\d+)", stdout) @@ -327,7 +336,7 @@ class PipelineControl(OTDBBusListener): "--time=31-0", # TODO: Compute nr nodes - "--nodes=50", + "--nodes=24", # Define better places to write the output os.path.expandvars("--error=/data/log/runPipeline-%s.stderr" % (otdbId,)), @@ -385,11 +394,13 @@ class PipelineControl(OTDBBusListener): ), sbatch_params=[ - "--cpus-per=task=1", - "--ntasks=1" + "--cpus-per-task=1", + "--ntasks=1", "--dependency=afternotok:%s" % slurm_job_id, "--kill-on-invalid-dep=yes", "--requeue", + "--error=/data/log/pipelineAborted-%s.stderr" % (otdbId,), + "--output=/data/log/pipelineAborted-%s.log" % (otdbId,), ] ) logger.info("Scheduled SLURM job %s" % (slurm_cancel_job_id,)) -- GitLab From f6ac9ef19ca6eb0c74ee48b2eba2132f5542e474 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 2 Jun 2016 09:11:48 +0000 Subject: [PATCH 257/933] Task #8887: Removed dependency on pipelineAborted.sh, allowing pipelines to go to aborted even if the docker image is not available --- .gitattributes | 1 - CEP/Pipeline/recipes/sip/CMakeLists.txt | 1 - .../recipes/sip/bin/pipelineAborted.sh | 52 ------------------- MAC/Services/src/PipelineControl.py | 25 +++++---- 4 files changed, 12 insertions(+), 67 deletions(-) delete mode 100755 CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh diff --git a/.gitattributes b/.gitattributes index 32f2277e1b6..07d87d47bd6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1522,7 +1522,6 @@ CEP/Pipeline/recipes/sip/bin/long_baseline_pipeline.py eol=lf CEP/Pipeline/recipes/sip/bin/msss_calibrator_pipeline.py eol=lf CEP/Pipeline/recipes/sip/bin/msss_imager_pipeline.py eol=lf CEP/Pipeline/recipes/sip/bin/msss_target_pipeline.py eol=lf -CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh eol=lf CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py -text CEP/Pipeline/recipes/sip/bin/runPipeline.sh eol=lf CEP/Pipeline/recipes/sip/bin/selfcal_imager_pipeline.py eol=lf diff --git a/CEP/Pipeline/recipes/sip/CMakeLists.txt b/CEP/Pipeline/recipes/sip/CMakeLists.txt index b034415be1d..297a997162b 100644 --- a/CEP/Pipeline/recipes/sip/CMakeLists.txt +++ b/CEP/Pipeline/recipes/sip/CMakeLists.txt @@ -74,7 +74,6 @@ lofar_add_bin_scripts( bin/pulsar_pipeline.py bin/long_baseline_pipeline.py bin/selfcal_imager_pipeline.py - bin/pipelineAborted.sh bin/runPipeline.sh bin/startPython.sh bin/startPythonVersion.sh diff --git a/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh b/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh deleted file mode 100755 index bf835676ca8..00000000000 --- a/CEP/Pipeline/recipes/sip/bin/pipelineAborted.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -e - -# Signals a specific obs id as ABORTED -# -# The following chain is executed: -# -# setStatus(ABORTED) -# -# Syntax: -# -# runPipeline.sh -o <obsid> || pipelineAborted.sh -o <obsid> - -# ======= Defaults - -# Obs ID -OBSID= - -# Queue on which to post status changes -SETSTATUS_BUS=lofar.otdb.command - -# ======= Parse command-line parameters - -function usage() { - echo "$0 -o OBSID [options]" - echo "" - echo " -o OBSID Task identifier" - echo " -B busname Bus name to post status changes on (default: $SETSTATUS_BUS)" - exit 1 -} - -while getopts "o:c:p:B:" opt; do - case $opt in - h) usage - ;; - o) OBSID="$OPTARG" - ;; - B) SETSTATUS_BUS="$OPTARG" - ;; - \?) error "Invalid option: -$OPTARG" - ;; - :) error "Option requires an argument: -$OPTARG" - ;; - esac -done -[ -z "$OBSID" ] && usage - -# ======= Run - -# Mark as aborted -setStatus.py -o $OBSID -s aborted -B $SETSTATUS_BUS || true - -exit 0 diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 40517b11a80..e8447d68307 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -30,7 +30,7 @@ The execution chains are as follows: [SCHEDULED] -> PipelineControl schedules - runPipeline.sh <obsid> || pipelineAborted.sh <obsid> + runPipeline.sh <obsid> || setStatus.py -o <obsid> -s aborted using two SLURM jobs, guaranteeing that pipelineAborted.sh is called in the following circumstances: @@ -49,7 +49,7 @@ The execution chains are as follows: - (wrap up) - state <- [FINISHED] -(pipelineAborted.sh) -> Calls +(setStatus.py) -> Calls - state <- [ABORTED] ----------------------------- @@ -72,6 +72,7 @@ import subprocess import datetime import os import re +from socket import getfqdn import logging logger = logging.getLogger(__name__) @@ -378,18 +379,16 @@ class PipelineControl(OTDBBusListener): # Schedule pipelineAborted.sh logger.info("Scheduling SLURM job for pipelineAborted.sh") - slurm_cancel_job_id = self.slurm.submit("%s-aborted" % parset.slurmJobName(), + slurm_cancel_job_id = self.slurm.submit("%s-abort-trigger" % parset.slurmJobName(), - "docker run --rm" - " --net=host" - " -u $UID" - " -e LOFARENV={lofarenv}" - " {image}" - " pipelineAborted.sh -o {obsid} -B {status_bus}" + "ssh {myhostname} '" + "source {lofarroot}/lofarinit.sh && " + "setStatus.py -o {obsid} -s aborted -B {status_bus}" + "'" .format( - lofarenv = os.environ.get("LOFARENV", ""), + myhostname = getfqdn(), + lofarroot = os.environ.get("LOFARROOT", ""), obsid = otdbId, - image = parset.defaultDockerImage(), status_bus = self.otdb_service_busname, ), @@ -399,8 +398,8 @@ class PipelineControl(OTDBBusListener): "--dependency=afternotok:%s" % slurm_job_id, "--kill-on-invalid-dep=yes", "--requeue", - "--error=/data/log/pipelineAborted-%s.stderr" % (otdbId,), - "--output=/data/log/pipelineAborted-%s.log" % (otdbId,), + "--error=/data/log/abort-trigger-%s.stderr" % (otdbId,), + "--output=/data/log/abort-trigger-%s.log" % (otdbId,), ] ) logger.info("Scheduled SLURM job %s" % (slurm_cancel_job_id,)) -- GitLab From 05d3f2c1f17fdc9a7ab9ffcef99bac99251e933e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 2 Jun 2016 10:12:46 +0000 Subject: [PATCH 258/933] Task #8887: added getTreeInfo method --- SAS/OTDB_Services/TreeService.py | 19 ++++++++++++++++++- SAS/OTDB_Services/otdbrpc.py | 3 +++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/SAS/OTDB_Services/TreeService.py b/SAS/OTDB_Services/TreeService.py index 3a5b824220f..042673b1a4a 100755 --- a/SAS/OTDB_Services/TreeService.py +++ b/SAS/OTDB_Services/TreeService.py @@ -157,6 +157,18 @@ def TaskGetSpecification(input_dict, db_connection): answer_dict["tree"] = answer_list return {'TaskSpecification':answer_dict} +# Task Get TreeInfo +def TaskGetTreeInfo(otdb_id, db_connection): + result = db_connection.query("""select momID, groupID, d_creation, modificationdate, state, starttime, stoptime, processType, processSubtype, description + from OTDBtree where treeID = %s""" % (otdb_id,)).getresult() + + if result: + return {'OtdbID':otdb_id, 'momID': result[0][0], 'groupID': result[0][1], 'creationDate': result[0][2], 'modificationdate': result[0][3], + 'state': result[0][4], 'starttime': result[0][5], 'stoptime': result[0][6], + 'processType': result[0][7], 'processSubtype': result[0][8], 'description': result[0][9]} + + return None + # Task Create def TaskCreate(input_dict, db_connection): @@ -569,7 +581,8 @@ class PostgressMessageHandler(MessageHandlerInterface): "TaskDelete": self._TaskDelete, "GetDefaultTemplates": self._GetDefaultTemplates, "GetStations": self._GetStations, - "SetProject": self._SetProject + "SetProject": self._SetProject, + "TaskGetTreeInfo": self._TaskGetTreeInfo } def prepare_receive(self): @@ -603,6 +616,10 @@ class PostgressMessageHandler(MessageHandlerInterface): logger.info("_TaskSetStatus({})".format(kwargs)) return TaskSetStatus(kwargs, self.connection) + def _TaskGetTreeInfo(self, **kwargs): + logger.info("_TaskGetTreeInfo({})".format(kwargs)) + return TaskGetTreeInfo(kwargs.get('otdb_id'), self.connection) + def _TaskSetSpecification(self, **kwargs): logger.info("_TaskSetSpecification({})".format(kwargs)) return TaskSetSpecification(kwargs, self.connection) diff --git a/SAS/OTDB_Services/otdbrpc.py b/SAS/OTDB_Services/otdbrpc.py index 8c71aea1cf4..e0e84e641b1 100644 --- a/SAS/OTDB_Services/otdbrpc.py +++ b/SAS/OTDB_Services/otdbrpc.py @@ -57,6 +57,9 @@ class OTDBRPC(RPCWrapper): raise OTDBPRCException("TaskCreate failed for MoM ID %i" % (mom_id,)) return {"mom_id": answer["MomID"], "otdb_id": answer["OtdbID"]} + def taskGetTreeInfo(self, otdb_id): + return self.rpc('TaskGetTreeInfo', otdb_id=otdb_id) + def taskGetStatus(self, otdb_id): return self.rpc('TaskGetStatus', otdb_id=otdb_id)['status'] -- GitLab From 9d4d0c0da304b74e2e7313d1bc2b0440912f20be Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 2 Jun 2016 12:09:25 +0000 Subject: [PATCH 259/933] Task #8887: Fix access to /opt/lofar/var/{log,run} in Docker image --- Docker/lofar-pipeline/Dockerfile.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index a3f38f86b66..eae03529529 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -52,8 +52,8 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ - bash -c "mv ${INSTALLDIR}/lofar/var /home/${USER}/" && \ - bash -c "ln -sfT /home/${USER}/lofar/var ${INSTALLDIR}/lofar/var" && \ + bash -c "mkdir -p ${INSTALLDIR}/lofar/var/{log,run}" && \ + bash -c "chmod a+rwx ${INSTALLDIR}/lofar/var/{log,run}" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ -- GitLab From e3e0b879994e53ca48d7621423cb70ba5cd80711 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 2 Jun 2016 12:09:40 +0000 Subject: [PATCH 260/933] Task #8887: Pull image from repository --- MAC/Services/src/PipelineControl.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index e8447d68307..ef0ed415fdd 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -160,7 +160,7 @@ class Slurm(object): if sbatch_params is None: sbatch_params = [] - script = """#!/bin/bash + script = """#!/bin/bash -v {cmdline} """.format(cmdline = cmdline) @@ -174,9 +174,7 @@ class Slurm(object): return match.group(1) def cancel(self, jobName): - stdout = self._runCommand("scancel --jobname %s" % (jobName,)) - - logger.debug("scancel output: %s" % (output,)) + self._runCommand("scancel --jobname %s" % (jobName,)) def jobs(self, maxage=datetime.timedelta(365)): starttime = (datetime.datetime.utcnow() - maxage).strftime("%FT%T") @@ -363,11 +361,12 @@ class PipelineControl(OTDBBusListener): " -e LOFARENV={lofarenv}" " -v $HOME/.ssh:/home/lofar/.ssh:ro" " -e SLURM_JOB_ID=$SLURM_JOB_ID" - " {image}" + " {repository}/{image}" " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -B {status_bus}" .format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, + repository = parset.dockerRepository(), image = parset.dockerImage(), cluster = parset.processingCluster(), status_bus = self.otdb_service_busname, -- GitLab From bd404ec4dc8bc67cd06c1752c7ef2b509fd9a430 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 2 Jun 2016 13:11:03 +0000 Subject: [PATCH 261/933] Task #8887: convert strings to datetime type --- SAS/OTDB_Services/TreeService.py | 19 ++++++++++++++++--- SAS/OTDB_Services/otdbrpc.py | 7 ++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/SAS/OTDB_Services/TreeService.py b/SAS/OTDB_Services/TreeService.py index 042673b1a4a..9713f724d8c 100755 --- a/SAS/OTDB_Services/TreeService.py +++ b/SAS/OTDB_Services/TreeService.py @@ -41,6 +41,7 @@ SetProject --- : Creates or updates the information of a project import sys, time, pg import logging +from datetime import datetime from lofar.messaging.Service import * from lofar.common.util import waitForInterrupt @@ -163,9 +164,16 @@ def TaskGetTreeInfo(otdb_id, db_connection): from OTDBtree where treeID = %s""" % (otdb_id,)).getresult() if result: - return {'OtdbID':otdb_id, 'momID': result[0][0], 'groupID': result[0][1], 'creationDate': result[0][2], 'modificationdate': result[0][3], - 'state': result[0][4], 'starttime': result[0][5], 'stoptime': result[0][6], - 'processType': result[0][7], 'processSubtype': result[0][8], 'description': result[0][9]} + result = list(result[0]) + for i in [2, 3, 5, 6]: + if '.' in result[i]: + result[i] = datetime.strptime(result[i], '%Y-%m-%d %H:%M:%S.%f') + else: + result[i] = datetime.strptime(result[i], '%Y-%m-%d %H:%M:%S') + + return {'OtdbID':otdb_id, 'momID': result[0], 'groupID': result[1], 'creationtime': result[2], 'modificationtime': result[3], + 'state': result[4], 'starttime': result[5], 'stoptime': result[6], + 'processType': result[7], 'processSubtype': result[8], 'description': result[9]} return None @@ -658,6 +666,9 @@ if __name__ == "__main__": # Check the invocation arguments parser = OptionParser("%prog [options]") + parser.add_option('-q', '--broker', dest='broker', type='string', + default=None, + help='Address of the qpid broker, default: localhost') parser.add_option("-B", "--busname", dest="busname", type="string", default=DEFAULT_OTDB_SERVICE_BUSNAME, help="Busname or queue-name on which RPC commands are received. [default: %default") @@ -667,6 +678,7 @@ if __name__ == "__main__": parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') # Add options of dbcredentials: --database, --host, ... parser.add_option_group(dbcredentials.options_group(parser)) + parser.set_defaults(dbcredentials="OTDB") (options, args) = parser.parse_args() setQpidLogLevel(logging.INFO) @@ -679,6 +691,7 @@ if __name__ == "__main__": use_service_methods=True, numthreads=1, handler_args={"dbcreds" : dbcreds}, + broker=options.broker, verbose=True): waitForInterrupt() diff --git a/SAS/OTDB_Services/otdbrpc.py b/SAS/OTDB_Services/otdbrpc.py index e0e84e641b1..7768335328c 100644 --- a/SAS/OTDB_Services/otdbrpc.py +++ b/SAS/OTDB_Services/otdbrpc.py @@ -58,7 +58,12 @@ class OTDBRPC(RPCWrapper): return {"mom_id": answer["MomID"], "otdb_id": answer["OtdbID"]} def taskGetTreeInfo(self, otdb_id): - return self.rpc('TaskGetTreeInfo', otdb_id=otdb_id) + info = self.rpc('TaskGetTreeInfo', otdb_id=otdb_id) + logger.info("taskGetTreeInfo(%s): %s", otdb_id, info) + for key in ['creationtime', 'modificationtime', 'starttime', 'stoptime']: + info[key] = info[key].datetime() + logger.info("taskGetTreeInfo(%s): %s", otdb_id, info) + return info def taskGetStatus(self, otdb_id): return self.rpc('TaskGetStatus', otdb_id=otdb_id)['status'] -- GitLab From cc4ae3bc64c72118e7d578c1d8460531db8a1aa5 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 2 Jun 2016 13:12:28 +0000 Subject: [PATCH 262/933] Task #8887: removed obsolete logging --- SAS/OTDB_Services/otdbrpc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/SAS/OTDB_Services/otdbrpc.py b/SAS/OTDB_Services/otdbrpc.py index 7768335328c..8b5383b1073 100644 --- a/SAS/OTDB_Services/otdbrpc.py +++ b/SAS/OTDB_Services/otdbrpc.py @@ -59,10 +59,8 @@ class OTDBRPC(RPCWrapper): def taskGetTreeInfo(self, otdb_id): info = self.rpc('TaskGetTreeInfo', otdb_id=otdb_id) - logger.info("taskGetTreeInfo(%s): %s", otdb_id, info) for key in ['creationtime', 'modificationtime', 'starttime', 'stoptime']: info[key] = info[key].datetime() - logger.info("taskGetTreeInfo(%s): %s", otdb_id, info) return info def taskGetStatus(self, otdb_id): -- GitLab From d1e2c9c95f76001e7f9e17c328d0fdc3938575ae Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 2 Jun 2016 13:13:36 +0000 Subject: [PATCH 263/933] Task #8887: added missing rabuslistener.py which should have been in r34584 --- .gitattributes | 1 + .../ResourceAssigner/lib/rabuslistener.py | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 SAS/ResourceAssignment/ResourceAssigner/lib/rabuslistener.py diff --git a/.gitattributes b/.gitattributes index 07d87d47bd6..fd72c21e29a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5036,6 +5036,7 @@ SAS/ResourceAssignment/ResourceAssigner/lib/CMakeLists.txt -text SAS/ResourceAssignment/ResourceAssigner/lib/__init__.py -text SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py -text SAS/ResourceAssignment/ResourceAssigner/lib/config.py -text +SAS/ResourceAssignment/ResourceAssigner/lib/rabuslistener.py -text SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py -text SAS/ResourceAssignment/ResourceAssigner/test/CMakeLists.txt -text SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py -text diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/rabuslistener.py b/SAS/ResourceAssignment/ResourceAssigner/lib/rabuslistener.py new file mode 100644 index 00000000000..573b791a8e0 --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/rabuslistener.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +# RABusListener.py +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: RABusListener.py 1580 2015-09-30 14:18:57Z loose $ + +""" +RABusListener listens on the lofar notification message bus and calls (empty) on<SomeMessage> methods when such a message is received. +Typical usage is to derive your own subclass from RABusListener and implement the specific on<SomeMessage> methods that you are interested in. +""" + +from lofar.messaging.messagebus import AbstractBusListener +from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_BUSNAME, DEFAULT_RA_NOTIFICATION_SUBJECTS +from lofar.common.util import waitForInterrupt + +import qpid.messaging +import logging +from datetime import datetime + +logger = logging.getLogger(__name__) + + +class RABusListener(AbstractBusListener): + def __init__(self, busname=DEFAULT_RA_NOTIFICATION_BUSNAME, subjects=DEFAULT_RA_NOTIFICATION_SUBJECTS, broker=None, **kwargs): + """ + RABusListener listens on the lofar notification message bus and calls (empty) on<SomeMessage> methods when such a message is received. + Typical usage is to derive your own subclass from RABusListener and implement the specific on<SomeMessage> methods that you are interested in. + :param busname: valid Qpid address (default: lofar.ra.notification) + :param broker: valid Qpid broker host (default: None, which means localhost) + additional parameters in kwargs: + options= <dict> Dictionary of options passed to QPID + exclusive= <bool> Create an exclusive binding so no other services can consume duplicate messages (default: False) + numthreads= <int> Number of parallel threads processing messages (default: 1) + verbose= <bool> Output extra logging over stdout (default: False) + """ + self.subject_prefix = (subjects.split('.')[0]+'.') if '.' in subjects else '' + + address = "%s/%s" % (busname, subjects) + super(RABusListener, self).__init__(address, broker, **kwargs) + + + def _handleMessage(self, msg): + logger.info("on%s: %s" % (msg.subject.replace(self.subject_prefix, ''), str(msg.content).replace('\n', ' '))) + + if msg.subject == '%sTaskScheduled' % self.subject_prefix: + self.onTaskScheduled(msg.content) + elif msg.subject == '%sTaskConflict' % self.subject_prefix: + self.onTaskConflict(msg.content) + else: + logger.error("RABusListener.handleMessage: unknown subject: %s" %str(msg.subject)) + + def onTaskScheduled(self, task_ids): + '''onTaskScheduled is called upon receiving a TaskScheduled message. + :param task_ids: a dict containing radb_id, mom_id and otdb_id''' + pass + + def onTaskConflict(self, task_ids): + '''onTaskConflict is called upon receiving a TaskConflict message. + :param task_ids: a dict containing radb_id, mom_id and otdb_id''' + pass + +if __name__ == '__main__': + with RABusListener(broker=None) as listener: + waitForInterrupt() + +__all__ = ["RABusListener"] -- GitLab From e74acc94c7bb58ded10eb5ef0eb47bc54b78777b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 2 Jun 2016 13:14:29 +0000 Subject: [PATCH 264/933] Task #8887: propagte start/end time from slurm queued/running/fininshed pipelines to radb --- .../propagator.py | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index c0367f03836..94db5f62f14 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -5,9 +5,12 @@ TODO: add doc ''' import logging +from datetime import datetime, timedelta from optparse import OptionParser -from lofar.sas.otdb.OTDBBusListener import OTDBBusListener from lofar.common.util import waitForInterrupt +from lofar.sas.otdb.OTDBBusListener import OTDBBusListener +from lofar.sas.otdb.otdbrpc import OTDBRPC +from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTDB_NOTIFICATION_SUBJECT from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as DEFAULT_RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as DEFAULT_RADB_SERVICENAME @@ -19,6 +22,8 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): def __init__(self, otdb_notification_busname=DEFAULT_OTDB_NOTIFICATION_BUSNAME, otdb_notification_subject=DEFAULT_OTDB_NOTIFICATION_SUBJECT, + otdb_service_busname=DEFAULT_OTDB_SERVICE_BUSNAME, + otdb_service_subject=DEFAULT_OTDB_SERVICENAME, radb_busname=DEFAULT_RADB_BUSNAME, radb_servicename=DEFAULT_RADB_SERVICENAME, broker=None, **kwargs): @@ -27,13 +32,16 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): broker=broker, **kwargs) + self.otdb = OTDBRPC(busname=otdb_service_busname, servicename=otdb_service_subject, broker=broker) ## , ForwardExceptions=True hardcoded in RPCWrapper right now self.radb = RARPC(busname=radb_busname, servicename=radb_servicename, broker=broker) def start_listening(self, **kwargs): + self.otdb.open() self.radb.open() super(OTDBtoRATaskStatusPropagator, self).start_listening(**kwargs) def stop_listening(self, **kwargs): + self.otdb.close() self.radb.close() super(OTDBtoRATaskStatusPropagator, self).stop_listening(**kwargs) @@ -65,15 +73,58 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): def onObservationQueued(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'queued') + # pipeline control puts tasks in queued state for pipelines, + # and gives the pipeline to slurm + # from that moment it is not known exactly the the task will run. + # we do know however that it will run after its predecessors, and after 'now' + # reflect that in radb for pipelines + task = self.radb.getTask(otdb_id=treeId) + logger.info("task=%s", task) + if task and task['type'] == 'pipeline': + predecessor_tasks = [self.radb.getTask(pid) for pid in task['predecessor_ids']] + logger.info("predecessor_tasks=%s", predecessor_tasks) + if predecessor_tasks: + pred_endtimes = [t['endtime'] for t in predecessor_tasks] + max_pred_endtime = max(pred_endtimes) + logger.info("max_pred_endtime=%s", max_pred_endtime) + min_startime = max([max_pred_endtime, datetime.utcnow()]) + logger.info("min_startime=%s", min_startime) + + self.radb.updateTaskAndResourceClaims(task['id'], + starttime=min_startime, + endtime=min_startime + timedelta(seconds=task['duration'])) + + task = self.radb.getTask(otdb_id=treeId) + logger.info("task=%s", task) + def onObservationStarted(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'active') + # otdb adjusts starttime when starting, + # reflect that in radb for pipelines + radb_task = self.radb.getTask(otdb_id=treeId) + if radb_task and radb_task['type'] == 'pipeline': + otdb_task = self.otdb.taskGetTreeInfo(otdb_id=treeId) + if otdb_task: + self.radb.updateTaskAndResourceClaims(radb_task['id'], + starttime=otdb_task['starttime'], + endtime=otdb_task['starttime'] + timedelta(seconds=radb_task['duration'])) + def onObservationCompleting(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'completing') def onObservationFinished(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'finished') + # otdb adjusts stoptime when finishing, + # reflect that in radb for pipelines + radb_task = self.radb.getTask(otdb_id=treeId) + if radb_task and radb_task['type'] == 'pipeline': + otdb_task = self.otdb.taskGetTreeInfo(otdb_id=treeId) + if otdb_task: + self.radb.updateTaskAndResourceClaims(radb_task['id'], + endtime=otdb_task['stoptime']) + def onObservationAborted(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'aborted') -- GitLab From 0e6dcb44fe8df2f278ccc8d9c850ec61fe327564 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 2 Jun 2016 13:18:41 +0000 Subject: [PATCH 265/933] Task #8887: Fixed typo --- CEP/Pipeline/framework/lofarpipe/support/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/control.py b/CEP/Pipeline/framework/lofarpipe/support/control.py index b1a8c04462d..b95ac9f0309 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/control.py +++ b/CEP/Pipeline/framework/lofarpipe/support/control.py @@ -135,7 +135,7 @@ class control(StatefulRecipe): self.feedback_method = "messagebus" try: - self.feedbacK_send_status = self.config.getboolean('feedback', 'send_status') + self.feedback_send_status = self.config.getboolean('feedback', 'send_status') except: self.feedback_send_status = True -- GitLab From ff87b6630eed871a9da980b5286112bc4f9710e2 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 2 Jun 2016 13:19:16 +0000 Subject: [PATCH 266/933] Task #8887: Pull image from repo on demand --- MAC/Services/src/PipelineControl.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index ef0ed415fdd..30125f7028b 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -353,7 +353,13 @@ class PipelineControl(OTDBBusListener): # Schedule runPipeline.sh logger.info("Scheduling SLURM job for runPipeline.sh") slurm_job_id = self.slurm.submit(parset.slurmJobName(), - + # pull docker image from repository on all nodes + "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull" + " docker pull {repository}/{image}\n" + # put a local tag on the pulled image + "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-tag" + " docker tag -f {repository}/{image} {image}\n" + # call runPipeline.sh in the image on this node "docker run --rm" " --net=host" " -v /data:/data" @@ -361,7 +367,7 @@ class PipelineControl(OTDBBusListener): " -e LOFARENV={lofarenv}" " -v $HOME/.ssh:/home/lofar/.ssh:ro" " -e SLURM_JOB_ID=$SLURM_JOB_ID" - " {repository}/{image}" + " {image}" " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -B {status_bus}" .format( lofarenv = os.environ.get("LOFARENV", ""), -- GitLab From 3215fff27546293307ddd7de354b2095d3fa2300 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 2 Jun 2016 13:22:44 +0000 Subject: [PATCH 267/933] Task #8887: propagte scheduler-modified start/end time to radb --- .../OTDBtoRATaskStatusPropagator/propagator.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index 94db5f62f14..8feba69b850 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -52,11 +52,24 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): if not result or 'updated' not in result or not result['updated']: logger.warning("could not update task with otdb_id %s to status %s" % (otdb_id, task_status)) + def _updateStartStopTimes(self, treeId): + # cep2 jobs still get scheduled via old scheduler + # so, if the start/endtime were changed in the old scheduler + # and the times are different to the radb times, then propagate these to radb + radb_task = self.radb.getTask(otdb_id=treeId) + if radb_task: + otdb_task = self.otdb.taskGetTreeInfo(otdb_id=treeId) + if otdb_task and (otdb_task['starttime'] != radb_task['starttime'] or otdb_task['stoptime'] != radb_task['endtime']): + self.radb.updateTaskAndResourceClaims(radb_task['id'], + starttime=otdb_task['starttime'], + endtime=otdb_task['stoptime']) + def onObservationPrepared(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'prepared') def onObservationApproved(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'approved') + self._updateStartStopTimes(treeId) def onObservationOnHold(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'on_hold') @@ -69,6 +82,7 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): def onObservationScheduled(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'scheduled') + self._updateStartStopTimes(treeId) def onObservationQueued(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'queued') -- GitLab From 2cc8b6fd8e0ed2d264faf227f604f2bec75a1cdf Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 2 Jun 2016 13:44:58 +0000 Subject: [PATCH 268/933] Task #8887: Renamed setStatus.py -> setOTDBTreeStatus, and getParset.py -> getOTDBParset --- CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 8 ++++---- MAC/Services/src/PipelineControl.py | 6 +++--- SAS/OTDB_Services/CMakeLists.txt | 4 ++-- SAS/OTDB_Services/{getParset.py => getOTDBParset} | 0 SAS/OTDB_Services/{setStatus.py => setOTDBTreeStatus} | 0 5 files changed, 9 insertions(+), 9 deletions(-) rename SAS/OTDB_Services/{getParset.py => getOTDBParset} (100%) rename SAS/OTDB_Services/{setStatus.py => setOTDBTreeStatus} (100%) diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh index f9502dbe4d1..adb53b90b43 100755 --- a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -63,12 +63,12 @@ done # ======= Init # Mark as started -setStatus.py -o $OBSID -s active -B $SETSTATUS_BUS || true +setOTDBTreeStatus -o $OBSID -s active -B $SETSTATUS_BUS || true if [ -z "$PARSET" ]; then # Fetch parset PARSET=${LOFARROOT}/var/run/Observation${OBSID}.parset - getParset.py -o $OBSID >$PARSET + getOTDBParset -o $OBSID >$PARSET fi # ======= Run @@ -99,14 +99,14 @@ RESULT=$? # ======= Fini # Process the result -setStatus.py -o $OBSID -s completing -B $SETSTATUS_BUS || true +setOTDBTreeStatus -o $OBSID -s completing -B $SETSTATUS_BUS || true if [ $RESULT -eq 0 ]; then # Wait for feedback to propagate sleep 60 # Mark as succesful - setStatus.py -o $OBSID -s finished -B $SETSTATUS_BUS || true + setOTDBTreeStatus -o $OBSID -s finished -B $SETSTATUS_BUS || true fi # Propagate result to caller diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 30125f7028b..621c16c9732 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -30,7 +30,7 @@ The execution chains are as follows: [SCHEDULED] -> PipelineControl schedules - runPipeline.sh <obsid> || setStatus.py -o <obsid> -s aborted + runPipeline.sh <obsid> || setOTDBTreeStatus -o <obsid> -s aborted using two SLURM jobs, guaranteeing that pipelineAborted.sh is called in the following circumstances: @@ -49,7 +49,7 @@ The execution chains are as follows: - (wrap up) - state <- [FINISHED] -(setStatus.py) -> Calls +(setOTDBTreeStatus) -> Calls - state <- [ABORTED] ----------------------------- @@ -388,7 +388,7 @@ class PipelineControl(OTDBBusListener): "ssh {myhostname} '" "source {lofarroot}/lofarinit.sh && " - "setStatus.py -o {obsid} -s aborted -B {status_bus}" + "setOTDBTreeStatus -o {obsid} -s aborted -B {status_bus}" "'" .format( myhostname = getfqdn(), diff --git a/SAS/OTDB_Services/CMakeLists.txt b/SAS/OTDB_Services/CMakeLists.txt index bb1b514668a..2174576698b 100644 --- a/SAS/OTDB_Services/CMakeLists.txt +++ b/SAS/OTDB_Services/CMakeLists.txt @@ -6,8 +6,8 @@ lofar_find_package(Python 2.6 REQUIRED) include(PythonInstall) lofar_add_bin_scripts( - getParset.py - setStatus.py + getOTDBParset + setOTDBTreeStatus TreeService.py TreeStatusEvents.py ) diff --git a/SAS/OTDB_Services/getParset.py b/SAS/OTDB_Services/getOTDBParset similarity index 100% rename from SAS/OTDB_Services/getParset.py rename to SAS/OTDB_Services/getOTDBParset diff --git a/SAS/OTDB_Services/setStatus.py b/SAS/OTDB_Services/setOTDBTreeStatus similarity index 100% rename from SAS/OTDB_Services/setStatus.py rename to SAS/OTDB_Services/setOTDBTreeStatus -- GitLab From 234929224a27cff3a728ff0e3ea8aa440177d7e9 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 2 Jun 2016 14:18:39 +0000 Subject: [PATCH 269/933] Task #8887: Default to 10cores/subband to fit better into CEP4 (which has 20 cores/node available) --- CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in index c6708c69332..76693d17ad7 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in @@ -1,5 +1,6 @@ [ndppp] nproc = 0 +nthreads = 10 [setupparmdb] nproc = 0 @@ -13,19 +14,48 @@ nproc = 0 [rficonsole] nproc = 0 +[imager_prepare] +nthreads = 10 + [long_baseline] nproc = 0 rficonsole_executable = /opt/aoflagger/bin/aoflagger +nthreads = 10 [dppp] max_per_node = 0 +nthreads = 10 [awimager] max_per_node = 0 +nthreads = 10 [rficonsole] executable = /opt/aoflagger/bin/aoflagger max_per_node = 0 +nthreads = 10 [imager_prepare] rficonsole_executable = /opt/aoflagger/bin/aoflagger +nthreads = 10 + +[imager_bbs] +nthreads = 10 + +[bbs_reducer] +nthreads = 10 + +[executable_args] +nthreads = 10 + +[casapy-imager] +nthreads = 10 + +[pythonplugin] +nthreads = 10 + +[python-calibrate-stand-alone] +nthreads = 10 + +[calibrate-stand-alone] +nthreads = 10 -- GitLab From e0babb51b184df6ea5873fb34c0fac6e3f6862d9 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 2 Jun 2016 14:19:04 +0000 Subject: [PATCH 270/933] Task #8887: logging --- .../propagator.py | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index 8feba69b850..534ba9d2689 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -60,9 +60,11 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): if radb_task: otdb_task = self.otdb.taskGetTreeInfo(otdb_id=treeId) if otdb_task and (otdb_task['starttime'] != radb_task['starttime'] or otdb_task['stoptime'] != radb_task['endtime']): - self.radb.updateTaskAndResourceClaims(radb_task['id'], - starttime=otdb_task['starttime'], - endtime=otdb_task['stoptime']) + new_startime = otdb_task['starttime'] + new_endtime = otdb_task['stoptime'] + + logger.info("Updating task %s (otdb_id=%s, status=%s) startime to \'%s\' and endtime to \'%s\'", radb_task['id'], treeId, radb_task['status'], new_startime, new_endtime) + self.radb.updateTaskAndResourceClaims(radb_task['id'], starttime=new_startime, endtime=new_endtime) def onObservationPrepared(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'prepared') @@ -93,23 +95,16 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): # we do know however that it will run after its predecessors, and after 'now' # reflect that in radb for pipelines task = self.radb.getTask(otdb_id=treeId) - logger.info("task=%s", task) if task and task['type'] == 'pipeline': predecessor_tasks = [self.radb.getTask(pid) for pid in task['predecessor_ids']] - logger.info("predecessor_tasks=%s", predecessor_tasks) if predecessor_tasks: pred_endtimes = [t['endtime'] for t in predecessor_tasks] max_pred_endtime = max(pred_endtimes) - logger.info("max_pred_endtime=%s", max_pred_endtime) - min_startime = max([max_pred_endtime, datetime.utcnow()]) - logger.info("min_startime=%s", min_startime) - - self.radb.updateTaskAndResourceClaims(task['id'], - starttime=min_startime, - endtime=min_startime + timedelta(seconds=task['duration'])) + new_startime = max([max_pred_endtime, datetime.utcnow()]) + new_endtime = new_startime + timedelta(seconds=task['duration']) - task = self.radb.getTask(otdb_id=treeId) - logger.info("task=%s", task) + logger.info("Updating task %s (otdb_id=%s, status=queued) startime to \'%s\' and endtime to \'%s\'", task['id'], treeId, new_startime, new_endtime) + self.radb.updateTaskAndResourceClaims(task['id'], starttime=new_startime, endtime=new_endtime) def onObservationStarted(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'active') @@ -120,9 +115,11 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): if radb_task and radb_task['type'] == 'pipeline': otdb_task = self.otdb.taskGetTreeInfo(otdb_id=treeId) if otdb_task: - self.radb.updateTaskAndResourceClaims(radb_task['id'], - starttime=otdb_task['starttime'], - endtime=otdb_task['starttime'] + timedelta(seconds=radb_task['duration'])) + new_startime = otdb_task['starttime'] + new_endtime = new_startime + timedelta(seconds=radb_task['duration']) + + logger.info("Updating task %s (otdb_id=%s, status=active) startime to \'%s\' and endtime to \'%s\'", radb_task['id'], treeId, new_startime, new_endtime) + self.radb.updateTaskAndResourceClaims(radb_task['id'], starttime=new_startime, endtime=new_endtime) def onObservationCompleting(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'completing') @@ -136,16 +133,17 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): if radb_task and radb_task['type'] == 'pipeline': otdb_task = self.otdb.taskGetTreeInfo(otdb_id=treeId) if otdb_task: - self.radb.updateTaskAndResourceClaims(radb_task['id'], - endtime=otdb_task['stoptime']) + new_endtime = otdb_task['stoptime'] + + logger.info("Updating task %s (otdb_id=%s, status=finished) endtime to \'%s\'", radb_task['id'], treeId, new_endtime) + self.radb.updateTaskAndResourceClaims(radb_task['id'], endtime=new_endtime) def onObservationAborted(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'aborted') def main(): # Check the invocation arguments - parser = OptionParser("%prog [options]", - description='runs the resourceassignment database service') + parser = OptionParser("%prog [options]", description='runs the resourceassignment database service') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') parser.add_option("--otdb_notification_busname", dest="otdb_notification_busname", type="string", default=DEFAULT_OTDB_NOTIFICATION_BUSNAME, -- GitLab From 1bd5692ba6359c606862108651db652478891c78 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 2 Jun 2016 14:40:25 +0000 Subject: [PATCH 271/933] Task #8887: also update stoptime for aborted status --- .../propagator.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index 534ba9d2689..f5ff7fec159 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -124,23 +124,30 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): def onObservationCompleting(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'completing') - def onObservationFinished(self, treeId, modificationTime): - self._update_radb_task_status(treeId, 'finished') - - # otdb adjusts stoptime when finishing, - # reflect that in radb for pipelines + def _updateStopTime(self, treeId): radb_task = self.radb.getTask(otdb_id=treeId) - if radb_task and radb_task['type'] == 'pipeline': + if radb_task: otdb_task = self.otdb.taskGetTreeInfo(otdb_id=treeId) - if otdb_task: + if otdb_task and (otdb_task['starttime'] != radb_task['starttime'] or otdb_task['stoptime'] != radb_task['endtime']): new_endtime = otdb_task['stoptime'] - logger.info("Updating task %s (otdb_id=%s, status=finished) endtime to \'%s\'", radb_task['id'], treeId, new_endtime) + logger.info("Updating task %s (otdb_id=%s, status=%s) endtime to \'%s\'", radb_task['id'], treeId, radb_task['status'], new_endtime) self.radb.updateTaskAndResourceClaims(radb_task['id'], endtime=new_endtime) + def onObservationFinished(self, treeId, modificationTime): + self._update_radb_task_status(treeId, 'finished') + + # otdb adjusts stoptime when finishing, + # reflect that in radb for pipelines + self._updateStopTime(treeId) + def onObservationAborted(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'aborted') + # otdb adjusts stoptime when aborted, + # reflect that in radb for pipelines + self._updateStopTime(treeId) + def main(): # Check the invocation arguments parser = OptionParser("%prog [options]", description='runs the resourceassignment database service') -- GitLab From 2b8b7db8e32fba553945187e6c06b8f6550231ed Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 2 Jun 2016 15:03:54 +0000 Subject: [PATCH 272/933] Task #8887: Direct stderr to stdout to obtain interleaved output --- MAC/Services/src/PipelineControl.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 621c16c9732..e91f8ea7223 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -338,7 +338,6 @@ class PipelineControl(OTDBBusListener): "--nodes=24", # Define better places to write the output - os.path.expandvars("--error=/data/log/runPipeline-%s.stderr" % (otdbId,)), os.path.expandvars("--output=/data/log/runPipeline-%s.log" % (otdbId,)), ] @@ -403,7 +402,6 @@ class PipelineControl(OTDBBusListener): "--dependency=afternotok:%s" % slurm_job_id, "--kill-on-invalid-dep=yes", "--requeue", - "--error=/data/log/abort-trigger-%s.stderr" % (otdbId,), "--output=/data/log/abort-trigger-%s.log" % (otdbId,), ] ) -- GitLab From 0f6f37a571ab0fcb1cd3309150f9ae9ade18b74e Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Thu, 2 Jun 2016 15:09:39 +0000 Subject: [PATCH 273/933] Task #8021: Added check for existing target to lofar_create_target_symlink() --- CMake/LofarMacros.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMake/LofarMacros.cmake b/CMake/LofarMacros.cmake index a0de05215df..58b58ba135a 100644 --- a/CMake/LofarMacros.cmake +++ b/CMake/LofarMacros.cmake @@ -213,6 +213,10 @@ if(NOT DEFINED LOFAR_MACROS_INCLUDED) # use of the generator expression $<TARGET_FILE>. # -------------------------------------------------------------------------- macro(lofar_create_target_symlink _target _symlink) + if(NOT TARGET _target) + message(SEND_ERROR + "Cannot create symbolic link to non-existing target ${_target}") + endif(NOT TARGET _target) if(POLICY CMP0026) set(_location $<TARGET_FILE:${_target}>) else(POLICY CMP0026) -- GitLab From 5aa4877de3fcc15c8e75680548d9be73bec8d83c Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Thu, 2 Jun 2016 16:02:23 +0000 Subject: [PATCH 274/933] Task #8021: Reverted latest commit (r34602); it causes problems I don't yet understand. --- CMake/LofarMacros.cmake | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CMake/LofarMacros.cmake b/CMake/LofarMacros.cmake index 58b58ba135a..a0de05215df 100644 --- a/CMake/LofarMacros.cmake +++ b/CMake/LofarMacros.cmake @@ -213,10 +213,6 @@ if(NOT DEFINED LOFAR_MACROS_INCLUDED) # use of the generator expression $<TARGET_FILE>. # -------------------------------------------------------------------------- macro(lofar_create_target_symlink _target _symlink) - if(NOT TARGET _target) - message(SEND_ERROR - "Cannot create symbolic link to non-existing target ${_target}") - endif(NOT TARGET _target) if(POLICY CMP0026) set(_location $<TARGET_FILE:${_target}>) else(POLICY CMP0026) -- GitLab From c5cedc66a8ca5e7891bdc4012d1e1f55dbcabcae Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Sat, 4 Jun 2016 15:15:12 +0000 Subject: [PATCH 275/933] Task #8021: Added check for existing target to lofar_create_target_symlink(). Take two, which is hopefully correct now. --- CMake/LofarMacros.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMake/LofarMacros.cmake b/CMake/LofarMacros.cmake index a0de05215df..faeb2187463 100644 --- a/CMake/LofarMacros.cmake +++ b/CMake/LofarMacros.cmake @@ -213,6 +213,10 @@ if(NOT DEFINED LOFAR_MACROS_INCLUDED) # use of the generator expression $<TARGET_FILE>. # -------------------------------------------------------------------------- macro(lofar_create_target_symlink _target _symlink) + if(NOT TARGET ${_target}) + message(SEND_ERROR + "Cannot create symbolic link to non-existing target ${_target}") + endif(NOT TARGET ${_target}) if(POLICY CMP0026) set(_location $<TARGET_FILE:${_target}>) else(POLICY CMP0026) -- GitLab From 63dbc881d20903ca85039feca1e453969e83133f Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Sun, 5 Jun 2016 21:38:34 +0000 Subject: [PATCH 276/933] Task #9462: applysolution in gaincal now works, preparations for tec solver --- CEP/DP3/DPPP/include/DPPP/ApplyCal.h | 6 +- CEP/DP3/DPPP/include/DPPP/GainCal.h | 22 +- CEP/DP3/DPPP/include/DPPP/StefCal.h | 4 +- CEP/DP3/DPPP/include/DPPP/phasefitter.h | 8 +- CEP/DP3/DPPP/src/ApplyCal.cc | 4 +- CEP/DP3/DPPP/src/GainCal.cc | 332 +++++++++++++++++++----- CEP/DP3/DPPP/src/StefCal.cc | 92 +++++-- CEP/DP3/DPPP/src/phasefitter.cc | 8 +- CEP/DP3/DPPP/test/tGainCal.run | 40 ++- 9 files changed, 399 insertions(+), 117 deletions(-) diff --git a/CEP/DP3/DPPP/include/DPPP/ApplyCal.h b/CEP/DP3/DPPP/include/DPPP/ApplyCal.h index cc11330a97b..113b39c2a5e 100644 --- a/CEP/DP3/DPPP/include/DPPP/ApplyCal.h +++ b/CEP/DP3/DPPP/include/DPPP/ApplyCal.h @@ -79,13 +79,15 @@ namespace LOFAR { static void invert (casa::DComplex* v, double sigmaMMSE=0); // Apply a diagonal Jones matrix to the 2x2 visibilities matrix: A.V.B^H - static void applyDiag (casa::DComplex* gainA, casa::DComplex* gainB, + static void applyDiag (const casa::DComplex* gainA, + const casa::DComplex* gainB, casa::Complex* vis, float* weight, bool* flag, uint bl, uint chan, bool updateWeights, FlagCounter& flagCounter); // Apply a full Jones matrix to the 2x2 visibilities matrix: A.V.B^H - static void applyFull (casa::DComplex* gainA, casa::DComplex* gainB, + static void applyFull (const casa::DComplex* gainA, + const casa::DComplex* gainB, casa::Complex* vis, float* weight, bool* flag, uint bl, uint chan, bool updateWeights, FlagCounter& flagCounter); diff --git a/CEP/DP3/DPPP/include/DPPP/GainCal.h b/CEP/DP3/DPPP/include/DPPP/GainCal.h index c3c51a1d742..6181c6f5107 100644 --- a/CEP/DP3/DPPP/include/DPPP/GainCal.h +++ b/CEP/DP3/DPPP/include/DPPP/GainCal.h @@ -88,6 +88,12 @@ namespace LOFAR { // Perform stefcal (polarized or unpolarized) void stefcal(); + // Apply the solution + void applySolution(DPBuffer& buf, const casa::Cube<casa::DComplex>& invsol); + + // Invert solution (for applying it) + casa::Cube<casa::DComplex> invertSol(const casa::Cube<casa::DComplex>& sol); + // Counts the number of antennas with non-flagged data, // Set a map for the used antennas in iS, returns the number of antennas void setAntennaMaps (const casa::Bool* flag, uint freqCell); @@ -103,13 +109,16 @@ namespace LOFAR { // Initialize the parmdb void initParmDB(); + // Get parmdbname from itsMode + string parmName(); + // Write out the solutions of the current parameter chunk (timeslotsperparmupdate) void writeSolutions (double startTime); //# Data members. DPInput* itsInput; string itsName; - DPBuffer itsBuf; + vector<DPBuffer> itsBuf; bool itsUseModelColumn; casa::Cube<casa::Complex> itsModelData; string itsParmDBName; @@ -120,10 +129,12 @@ namespace LOFAR { uint itsDebugLevel; bool itsDetectStalling; + bool itsApplySolution; + vector<Baseline> itsBaselines; vector<casa::Matrix<casa::DComplex> > itsPrevSol; // previous solution, for propagating solutions, for each freq - vector<casa::Cube<casa::DComplex> > itsSols; // for every timeslot, nSt x nCr x nFreqCells + vector<casa::Cube<casa::DComplex> > itsSols; // for every timeslot, nCr x nSt x nFreqCells std::vector<StefCal> iS; @@ -148,9 +159,12 @@ namespace LOFAR { uint itsNonconverged; uint itsStalled; vector<uint> itsNIter; // Total iterations made (for converged, stalled and nonconverged) - uint itsTimeStep; // Timestep within parameter update + uint itsStepInParmUpdate; // Timestep within parameter update double itsChunkStartTime; // First time value of chunk to be stored - uint itsNTimes; // Timestep within solint + uint itsStepInSolInt; // Timestep within solint + + FlagCounter itsFlagCounter; + NSTimer itsTimer; NSTimer itsTimerPredict; NSTimer itsTimerSolve; diff --git a/CEP/DP3/DPPP/include/DPPP/StefCal.h b/CEP/DP3/DPPP/include/DPPP/StefCal.h index 354469d0cc6..b6a62182c9f 100644 --- a/CEP/DP3/DPPP/include/DPPP/StefCal.h +++ b/CEP/DP3/DPPP/include/DPPP/StefCal.h @@ -59,7 +59,7 @@ namespace LOFAR { // Returns the solution. The return matrix has a length of maxAntennas, // which is zero for antennas for which no solution was computed. // The mapping is stored in the antenna map - casa::Matrix<casa::DComplex> getSolution(); + casa::Matrix<casa::DComplex> getSolution(bool setNaNs); // Returns a reference to the visibility matrix casa::Array<casa::DComplex>& getVis() { @@ -115,6 +115,8 @@ namespace LOFAR { uint _nUn; // number of unknowns uint _nCr; // number of correlations (1 or 4) uint _nSp; // number that is two for scalarphase, one else + uint _badIters; // number of bad iterations, for stalling detection + uint _veryBadIters; // number of iterations where solution got worse uint _solInt; // solution interval uint _nChan; // number of channels string _mode; // diagonal, scalarphase, fulljones or phaseonly diff --git a/CEP/DP3/DPPP/include/DPPP/phasefitter.h b/CEP/DP3/DPPP/include/DPPP/phasefitter.h index 878cf7ce6e7..0f3aec2e164 100644 --- a/CEP/DP3/DPPP/include/DPPP/phasefitter.h +++ b/CEP/DP3/DPPP/include/DPPP/phasefitter.h @@ -53,8 +53,10 @@ class PhaseFitter * @ref FitTECModelParameters(). * @param alpha Found value for the alpha parameter. * @param beta Found value for the beta parameter. + * + * @returns Cost of the found solution. */ - void FitDataToTECModel(double& alpha, double& beta); + double FitDataToTECModel(double& alpha, double& beta); /** * Fits the given phase values to a TEC model using prior estimates of the @@ -66,8 +68,10 @@ class PhaseFitter * @todo No fast method has been implemented -- instead it will perform a full parameter search. * @param alpha Estimate of alpha parameter on input, found value on output. * @param beta Estimate of beta parameter on input, found value on output. + * + * @returns Cost of the found solution. */ - void FitDataToTECModelWithInitialValues(double& alpha, double& beta) + double FitDataToTECModelWithInitialValues(double& alpha, double& beta) { return FitDataToTECModel(alpha, beta); } diff --git a/CEP/DP3/DPPP/src/ApplyCal.cc b/CEP/DP3/DPPP/src/ApplyCal.cc index 337c6476cd0..402dbac3abb 100644 --- a/CEP/DP3/DPPP/src/ApplyCal.cc +++ b/CEP/DP3/DPPP/src/ApplyCal.cc @@ -498,7 +498,7 @@ namespace LOFAR { itsParms.resize(numParms, numAnts, tfDomainSize); } - void ApplyCal::applyDiag (DComplex* gainA, DComplex* gainB, + void ApplyCal::applyDiag (const DComplex* gainA, const DComplex* gainB, Complex* vis, float* weight, bool* flag, uint bl, uint chan, bool updateWeights, FlagCounter& flagCounter) { @@ -546,7 +546,7 @@ namespace LOFAR { v[3] = v0 * invDet; } - void ApplyCal::applyFull (DComplex* gainA, DComplex* gainB, + void ApplyCal::applyFull (const DComplex* gainA, const DComplex* gainB, Complex* vis, float* weight, bool* flag, uint bl, uint chan, bool updateWeights, FlagCounter& flagCounter) { diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index f4ac44ecf91..9454139959f 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -24,6 +24,7 @@ #include <lofar_config.h> #include <DPPP/GainCal.h> #include <DPPP/Simulate.h> +#include <DPPP/ApplyCal.h> #include <DPPP/phasefitter.h> #include <DPPP/CursorUtilCasa.h> #include <DPPP/DPBuffer.h> @@ -72,6 +73,7 @@ namespace LOFAR { itsMode (parset.getString (prefix + "caltype")), itsDebugLevel (parset.getInt (prefix + "debuglevel", 0)), itsDetectStalling (parset.getBool (prefix + "detectstalling", true)), + itsApplySolution (parset.getBool (prefix + "applysolution", false)), itsBaselines (), itsMaxIter (parset.getInt (prefix + "maxiter", 50)), itsTolerance (parset.getDouble (prefix + "tolerance", 1.e-5)), @@ -86,9 +88,9 @@ namespace LOFAR { itsConverged (0), itsNonconverged (0), itsStalled (0), - itsTimeStep (0), + itsStepInParmUpdate (0), itsChunkStartTime(0), - itsNTimes (0) + itsStepInSolInt (0) { if (itsParmDBName=="") { itsParmDBName=parset.getString("msin")+"/instrument"; @@ -111,9 +113,16 @@ namespace LOFAR { itsNIter.resize(3,0); + if (itsApplySolution) { + itsBuf.resize(itsSolInt); + } else { + itsBuf.resize(1); + } + ASSERT(itsMode=="diagonal" || itsMode=="phaseonly" || itsMode=="fulljones" || itsMode=="scalarphase" || - itsMode=="amplitudeonly" || itsMode=="scalaramplitude"); + itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || + itsMode=="tec"); } GainCal::~GainCal() @@ -133,8 +142,10 @@ namespace LOFAR { } else { itsPredictStep.updateInfo(infoIn); } - info().setWriteData(); - info().setWriteFlags(); + if (itsApplySolution) { + info().setWriteData(); + info().setWriteFlags(); + } for (uint i=0; i<nBl; ++i) { itsBaselines.push_back (Baseline(info().getAnt1()[i], @@ -169,11 +180,14 @@ namespace LOFAR { if ((freqCell+1)*itsNChan>info().nchan()) { // Last cell can be smaller chMax-=((freqCell+1)*itsNChan)%info().nchan(); } - iS.push_back(StefCal(itsSolInt, chMax, itsMode, itsTolerance, - info().antennaNames().size(), itsDetectStalling, - itsDebugLevel)); + iS.push_back(StefCal(itsSolInt, chMax, + (itsMode=="tec"?"scalarphase":itsMode), + itsTolerance, info().antennaNames().size(), + itsDetectStalling, itsDebugLevel)); } + itsFlagCounter.init(getInfo()); + itsChunkStartTime = info().startTime(); } @@ -192,6 +206,7 @@ namespace LOFAR { os << " max iter: " << itsMaxIter << endl; os << " tolerance: " << itsTolerance << endl; os << " mode: " << itsMode << endl; + os << " apply solution: " << boolalpha << itsApplySolution << endl; os << " propagate solutions: " << boolalpha << itsPropagateSolutions << endl; os << " detect stalling: " << boolalpha << itsDetectStalling << endl; os << " use model column: " << boolalpha << itsUseModelColumn << endl; @@ -235,15 +250,23 @@ namespace LOFAR { bool GainCal::process (const DPBuffer& bufin) { itsTimer.start(); - itsBuf.referenceFilled (bufin); - itsInput->fetchUVW(bufin, itsBuf, itsTimer); - itsInput->fetchWeights(bufin, itsBuf, itsTimer); - itsInput->fetchFullResFlags(bufin, itsBuf, itsTimer); - Cube<Complex> dataCube=itsBuf.getData(); + uint bufIndex=0; + + if (itsApplySolution) { + bufIndex=itsStepInSolInt; + itsBuf[bufIndex].copy(bufin); + } else { + itsBuf[0].referenceFilled (bufin); + } + itsInput->fetchUVW(bufin, itsBuf[bufIndex], itsTimer); + itsInput->fetchWeights(bufin, itsBuf[bufIndex], itsTimer); + itsInput->fetchFullResFlags(bufin, itsBuf[bufIndex], itsTimer); + + Cube<Complex> dataCube=itsBuf[bufIndex].getData(); Complex* data=dataCube.data(); - float* weight = itsBuf.getWeights().data(); - const Bool* flag=itsBuf.getFlags().data(); + float* weight = itsBuf[bufIndex].getWeights().data(); + const Bool* flag=itsBuf[bufIndex].getFlags().data(); // Simulate. // @@ -253,24 +276,24 @@ namespace LOFAR { itsTimerPredict.start(); if (itsUseModelColumn) { - itsInput->getModelData (itsBuf.getRowNrs(), itsModelData); - if (itsApplyBeamToModelColumn) { + itsInput->getModelData (itsBuf[bufIndex].getRowNrs(), itsModelData); + if (itsApplyBeamToModelColumn) { // TODO: double check this // Temporarily put model data in data column for applybeam step // ApplyBeam step will copy the buffer so no harm is done - itsBuf.getData()=itsModelData; - itsApplyBeamStep.process(itsBuf); + itsBuf[bufIndex].getData()=itsModelData; + itsApplyBeamStep.process(itsBuf[bufIndex]); //Put original data back in data column - itsBuf.getData()=dataCube; + itsBuf[bufIndex].getData()=dataCube; } } else { // Predict - itsPredictStep.process(itsBuf); + itsPredictStep.process(itsBuf[bufIndex]); } itsTimerPredict.stop(); itsTimerFill.start(); - if (itsNTimes==0) { + if (itsStepInSolInt==0) { // Start new solution interval for (uint freqCell=0; freqCell<itsNFreqCells; freqCell++) { @@ -287,27 +310,94 @@ namespace LOFAR { } itsTimerFill.stop(); - if (itsNTimes==itsSolInt-1) { + if (itsStepInSolInt==itsSolInt-1) { // Solve past solution interval stefcal(); - itsTimeStep++; - itsNTimes=0; + itsStepInParmUpdate++; + + if (itsApplySolution) { + Cube<DComplex> invsol = invertSol(itsSols.back()); + for (uint stepInSolInt=0; stepInSolInt<itsSolInt; stepInSolInt++) { + applySolution(itsBuf[stepInSolInt], invsol); + getNextStep()->process(itsBuf[stepInSolInt]); + } + } + + itsStepInSolInt=0; } else { - itsNTimes++; + itsStepInSolInt++; } itsTimer.stop(); - if (itsTimeStep == itsTimeSlotsPerParmUpdate) { + if (itsStepInParmUpdate == itsTimeSlotsPerParmUpdate) { writeSolutions(itsChunkStartTime); itsChunkStartTime += itsSolInt * itsTimeSlotsPerParmUpdate * info().timeInterval(); itsSols.clear(); - itsTimeStep = 0; + itsStepInParmUpdate = 0; + } + + if (!itsApplySolution) { + getNextStep()->process(itsBuf[bufIndex]); } - getNextStep()->process(itsBuf); return false; } + Cube<DComplex> GainCal::invertSol(const Cube<DComplex>& sol) { + Cube<DComplex> invsol = sol.copy(); + uint nCr = invsol.shape()[0]; + + // Invert copy of solutions + uint nSt = invsol.shape()[1]; + for (uint st=0; st<nSt; ++st) { + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + if (nCr==4) { + ApplyCal::invert(&invsol(0,st,freqCell)); + } else { + for (uint cr=0; cr<nCr; ++cr) { + invsol(cr, st, freqCell) = 1./invsol(cr, st, freqCell); + } + } + } + } + + return invsol; + } + + void GainCal::applySolution(DPBuffer& buf, const Cube<DComplex>& invsol) { + uint nbl = buf.getData().shape()[2]; + Complex* data = buf.getData().data(); + float* weight = buf.getWeights().data(); // Not initialized yet + bool* flag = buf.getFlags().data(); + uint nchan = buf.getData().shape()[1]; + + uint nCr = invsol.shape()[0]; + + for (size_t bl=0; bl<nbl; ++bl) { + for (size_t chan=0;chan<nchan;chan++) { + uint antA = info().getAnt1()[bl]; + uint antB = info().getAnt2()[bl]; + uint freqCell = chan / itsNChan; + if (nCr>2) { + ApplyCal::applyFull( &invsol(0, antA, freqCell), + &invsol(0, antB, freqCell), + &data[bl * 4 * nchan + chan * 4 ], + &weight[bl * 4 * nchan + chan * 4 ], // Not passing weights, any pointer should do + &flag[ bl * 4 * nchan + chan * 4 ], + bl, chan, false, itsFlagCounter); // Update weights is disabled here + } + else { + ApplyCal::applyDiag( &invsol(0, antA, freqCell), + &invsol(0, antB, freqCell), + &data[bl * 4 * nchan + chan * 4 ], + &weight[bl * 4 * nchan + chan * 4 ], // Not passing weights, any pointer should do + &flag[ bl * 4 * nchan + chan * 4 ], + bl, chan, false, itsFlagCounter); // Update weights is disabled here + } + } + } + } + // Fills itsVis and itsMVis as matrices with all 00 polarizations in the // top left, all 11 polarizations in the bottom right, etc. void GainCal::fillMatrices (casa::Complex* model, casa::Complex* data, float* weight, @@ -328,18 +418,18 @@ namespace LOFAR { } for (uint cr=0;cr<nCr;++cr) { - iS[ch/itsNChan].getVis() (IPosition(6,ant1,cr/2,itsNTimes,ch%itsNChan,cr%2,ant2)) = + iS[ch/itsNChan].getVis() (IPosition(6,ant1,cr/2,itsStepInSolInt,ch%itsNChan,cr%2,ant2)) = DComplex(data [bl*nCr*nCh+ch*nCr+cr]) * DComplex(sqrt(weight[bl*nCr*nCh+ch*nCr+cr])); - iS[ch/itsNChan].getMVis()(IPosition(6,ant1,cr/2,itsNTimes,ch%itsNChan,cr%2,ant2)) = + iS[ch/itsNChan].getMVis()(IPosition(6,ant1,cr/2,itsStepInSolInt,ch%itsNChan,cr%2,ant2)) = DComplex(model[bl*nCr*nCh+ch*nCr+cr]) * DComplex(sqrt(weight[bl*nCr*nCh+ch*nCr+cr])); // conjugate transpose - iS[ch/itsNChan].getVis() (IPosition(6,ant2,cr%2,itsNTimes,ch%itsNChan,cr/2,ant1)) = + iS[ch/itsNChan].getVis() (IPosition(6,ant2,cr%2,itsStepInSolInt,ch%itsNChan,cr/2,ant1)) = DComplex(conj(data [bl*nCr*nCh+ch*nCr+cr])) * DComplex(sqrt(weight[bl*nCr*nCh+ch*nCr+cr])); - iS[ch/itsNChan].getMVis()(IPosition(6,ant2,cr%2,itsNTimes,ch%itsNChan,cr/2,ant1)) = + iS[ch/itsNChan].getMVis()(IPosition(6,ant2,cr%2,itsStepInSolInt,ch%itsNChan,cr/2,ant1)) = DComplex(conj(model[bl*nCr*nCh+ch*nCr+cr] )) * DComplex(sqrt(weight[bl*nCr*nCh+ch*nCr+cr])); } @@ -379,6 +469,7 @@ namespace LOFAR { if (dataPerAntenna(ant)>nCr*itsMinBLperAnt) { iS[freqCell].getStationFlagged()[ant]=false; // Index in stefcal numbering } else { + //cout<<"flagging station "<<ant<<", "<<dataPerAntenna(ant)<<endl; iS[freqCell].getStationFlagged()[ant]=true; // Not enough data } } @@ -398,23 +489,26 @@ namespace LOFAR { uint iter=0; PhaseFitter fitter(itsNFreqCells); - // Set frequency data for TEC fitter - double* nu = fitter.FrequencyData(); - for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { - double meanfreq=0; - uint chmin=itsNChan*freqCell; - uint chmax=min(info().nchan(), chmin+itsNChan); - meanfreq = std::accumulate(info().chanFreqs().data()+chmin, - info().chanFreqs().data()+chmax, 0.0); + if (itsMode=="tec") { + // Set frequency data for TEC fitter + double* nu = fitter.FrequencyData(); + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + double meanfreq=0; + uint chmin=itsNChan*freqCell; + uint chmax=min(info().nchan(), chmin+itsNChan); - nu[freqCell] = meanfreq / (chmax-chmin); - //cout<<"freqCell:" <<freqCell<<", meanfreq: "<<meanfreq/(chmax-chmin)<<endl; + meanfreq = std::accumulate(info().chanFreqs().data()+chmin, + info().chanFreqs().data()+chmax, 0.0); + + nu[freqCell] = meanfreq / (chmax-chmin); + } } std::vector<StefCal::Status> converged(itsNFreqCells,StefCal::NOTCONVERGED); for (;iter<itsMaxIter;++iter) { bool allConverged=true; +#pragma omp parallel for for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { if (converged[freqCell]==StefCal::CONVERGED) { // Do another step when stalled and not all converged continue; @@ -426,9 +520,87 @@ namespace LOFAR { itsNIter[0] += iter; } } + + double alpha=0., beta=0.; + if (itsMode=="tec") { + casa::Matrix<casa::DComplex> tecsol(itsNFreqCells, info().antennaNames().size()); + + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + casa::Matrix<casa::DComplex> sol = iS[freqCell].getSolution(false); + for (uint ant=0; ant<info().antennaNames().size(); ++ant) { + tecsol(freqCell, ant) = sol(ant, 0)/sol(0, 0); + } + } + + uint nSt = info().antennaNames().size(); + for (uint ant=0; ant<nSt; ++ant) { + uint numpoints=0; + double cost=0; + double* phases = fitter.PhaseData(); + double* weights = fitter.WeightData(); + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + if (iS[freqCell].getStationFlagged()[ant%nSt]) { + phases[freqCell] = 0; + weights[freqCell] = 0; + } else { + phases[freqCell] = arg(tecsol(freqCell, ant)); + weights[freqCell] = 1; + numpoints++; + } + } + + if (itsDebugLevel>0 && ant==1) { + //cout<<endl<<"phases["<<ant<<"]=["; + cout<<"unfitted["<<iter<<"]=["; + uint freqCell=0; + for (; freqCell<itsNFreqCells-1; ++freqCell) { + cout<<phases[freqCell]<<","; + } + cout<<phases[freqCell]<<"];"<<endl; + } + if (numpoints>1) { + cost=fitter.FitDataToTECModel(alpha, beta); + // Update solution in stefcal + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + iS[freqCell].getSolution(false)(ant, 0) = polar(1., phases[freqCell]); + } + } else if (itsDebugLevel>5) { + cout<<"Not enough points ("<<numpoints<<") for antenna "<<ant<<endl; + } + if (itsDebugLevel>1 && ant==1) { + cout<<"fitted["<<iter<<"]=["; + uint freqCell=0; + for (; freqCell<itsNFreqCells-1; ++freqCell) { + cout<<phases[freqCell]<<","; + } + cout<<phases[freqCell]<<"]"<<endl; + } + if (itsDebugLevel>0 && ant==1) { + cout << "fitdata["<<iter<<"]=[" << alpha << ", " << beta << ", " << cost << "];" << endl; + } + } + } + if (allConverged) { break; } + + if (itsDebugLevel>1) { // Only antenna 1 + cout<<"phases["<<iter<<"]=["; + uint freqCell=0; + for (; freqCell<itsNFreqCells-1; ++freqCell) { + cout<<arg(iS[freqCell].getSolution(false)(1, 0)/iS[freqCell].getSolution(false)(0,0))<<","; + } + cout<<arg(iS[freqCell].getSolution(false)(1, 0)/iS[freqCell].getSolution(false)(0,0))<<"];"<<endl; + } + if (itsDebugLevel>2) { // Statistics about nStalled + cout<<"convstatus["<<iter<<"]=["; + uint freqCell=0; + for (; freqCell<itsNFreqCells-1; ++freqCell) { + cout<<converged[freqCell]<<","; + } + cout<<converged[freqCell]<<"]"<<endl; + } } // End niter for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { @@ -443,26 +615,26 @@ namespace LOFAR { // Stefcal terminated (either by maxiter or by converging) - Cube<DComplex> allg(iS[0].numCorrelations(), info().antennaNames().size(), itsNFreqCells); + Cube<DComplex> sol(iS[0].numCorrelations(), info().antennaNames().size(), itsNFreqCells); uint transpose[2][4] = { { 0, 1, 0, 0 }, { 0, 2, 1, 3 } }; uint nSt = info().antennaNames().size(); for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { - casa::Matrix<casa::DComplex> sol = iS[freqCell].getSolution(); + casa::Matrix<casa::DComplex> tmpsol = iS[freqCell].getSolution(true); for (uint st=0; st<nSt; st++) { for (uint cr=0; cr<iS[0].nCr(); ++cr) { uint crt=transpose[iS[0].numCorrelations()/4][cr]; // Conjugate transpose ! (only for numCorrelations = 4) - allg(crt, st, freqCell) = conj(sol(st, cr)); // Conjugate transpose + sol(crt, st, freqCell) = conj(tmpsol(st, cr)); // Conjugate transpose if (itsMode=="diagonal" || itsMode=="phaseonly" || itsMode=="amplitudeonly") { - allg(crt+1, st, freqCell) = conj(sol(st+nSt,cr)); // Conjugate transpose + sol(crt+1, st, freqCell) = conj(tmpsol(st+nSt,cr)); // Conjugate transpose } } } } - itsSols.push_back(allg); + itsSols.push_back(sol); itsTimerSolve.stop(); } @@ -483,9 +655,11 @@ namespace LOFAR { resolution[0] = freqWidth * itsNChan; resolution[1] = info().timeInterval() * itsSolInt; itsParmDB->setDefaultSteps(resolution); - string name=(itsMode=="commonscalarphase"?"CommonScalarPhase:*":"Gain:*"); - if (!itsParmDB->getNames(name).empty()) { - DPLOG_WARN_STR ("Solutions for "<<name<<" already in "<<itsParmDBName + + string parmname=parmName()+"*"; + + if (!itsParmDB->getNames(parmname).empty()) { + DPLOG_WARN_STR ("Solutions for "<<parmname<<" already in "<<itsParmDBName <<", these are removed"); // Specify entire domain of this MS; only to specify that the existing // values should be deleted for this domain @@ -499,7 +673,7 @@ namespace LOFAR { info().chanFreqs()[0] - freqWidth * 0.5, freqWidth * getInfo().chanFreqs().size(), 1)); - itsParmDB->deleteValues(name, BBS::Box( + itsParmDB->deleteValues(parmname, BBS::Box( fdomAxis->start(), tdomAxis->start(), fdomAxis->end(), tdomAxis->end(), true)); } @@ -538,6 +712,22 @@ namespace LOFAR { } } + string GainCal::parmName() { + string name; + if (itsMode=="scalarphase") { + name=string("CommonScalarPhase:"); + } else if (itsMode=="scalaramplitude") { + name=string("CommonScalarAmplitude:"); + } else if (itsMode=="tec") { + name=string("TEC:"); + } + else { + name=string("Gain:"); + } + + return name; + } + void GainCal::writeSolutions(double startTime) { itsTimer.start(); itsTimerWrite.start(); @@ -597,25 +787,23 @@ namespace LOFAR { itsMode=="amplitudeonly") && (pol==1||pol==2)) { continue; } - if ((itsMode=="scalarphase" || itsMode=="scalaramplitude" ) && pol>0) { + if ((itsMode=="scalarphase" || itsMode=="scalaramplitude" || + itsMode=="tec") && pol>0) { continue; } int realimmax; if (itsMode=="phaseonly" || itsMode=="scalarphase" || - itsMode=="amplitudeonly" || itsMode=="scalaramplitude") { + itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || + itsMode=="tec") { realimmax=1; } else { realimmax=2; } for (int realim=0; realim<realimmax; ++realim) { // For real and imaginary - string name; + string name = parmName(); - if (itsMode=="scalarphase") { - name=string("CommonScalarPhase:")+suffix; - } else if (itsMode=="scalaramplitude") { - name=string("CommonScalarAmplitude:")+suffix; - } else { - name=string("Gain:") + str0101[pol]; + if (itsMode!="scalarphase" && itsMode!="scalaramplitude") { + name+=str0101[pol]; if (itsMode=="phaseonly") { name=name+"Phase:"; } else if (itsMode=="amplitudeonly") { @@ -623,8 +811,10 @@ namespace LOFAR { } else { name=name+strri[realim]; } - name+=suffix; } + + name+=suffix; + // Collect its solutions for all times and frequency cells in a single array. for (uint ts=0; ts<ntime; ++ts) { for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { @@ -640,8 +830,14 @@ namespace LOFAR { } else { values(freqCell, ts) = imag(itsSols[ts](pol/3,st,freqCell)); } - } else if (itsMode=="scalarphase" || itsMode=="phaseonly") { + } else if (itsMode=="scalarphase" || itsMode=="phaseonly" || + itsMode=="tec") { values(freqCell, ts) = arg(itsSols[ts](pol/3,st,freqCell)); + } else if (itsMode=="scalaramplitude" || itsMode=="amplitudeonly") { + values(freqCell, ts) = abs(itsSols[ts](pol/3,st,freqCell)); + } + else { + THROW (Exception, "Unhandled mode"); } } } @@ -677,8 +873,16 @@ namespace LOFAR { itsTimer.start(); //Solve remaining time slots if any - if (itsNTimes!=0) { + if (itsStepInSolInt!=0) { stefcal(); + + if (itsApplySolution) { + Cube<DComplex> invsol = invertSol(itsSols.back()); + for (uint stepInSolInt=0; stepInSolInt<itsStepInSolInt; stepInSolInt++) { + applySolution(itsBuf[stepInSolInt], invsol); + getNextStep()->process(itsBuf[stepInSolInt]); + } + } } itsTimer.stop(); diff --git a/CEP/DP3/DPPP/src/StefCal.cc b/CEP/DP3/DPPP/src/StefCal.cc index 539524e6184..75d205de4d3 100644 --- a/CEP/DP3/DPPP/src/StefCal.cc +++ b/CEP/DP3/DPPP/src/StefCal.cc @@ -40,6 +40,8 @@ namespace LOFAR { double tolerance, uint maxAntennas, bool detectStalling, uint debugLevel) : _nSt (maxAntennas), + _badIters (0), + _veryBadIters (0), _solInt (solInt), _nChan (nChan), _mode (mode), @@ -99,6 +101,9 @@ namespace LOFAR { _dgx=1.0e30; _dgs.clear(); + _badIters=0; + _veryBadIters=0; + if (initSolutions) { double ginit=1.0; if (_mode != "phaseonly" && _mode != "scalarphase" ) { @@ -134,8 +139,6 @@ namespace LOFAR { } else { _g=ginit; } - - _gx = _g; } else { // Take care of NaNs in solution for (uint ant=0; ant<_nUn; ++ant) { double ginit=0; @@ -175,6 +178,9 @@ namespace LOFAR { } StefCal::Status StefCal::doStep(uint iter) { + _gxx = _gx; + _gx = _g; + if (_mode=="fulljones") { doStep_polarized(); doStep_polarized(); @@ -300,14 +306,43 @@ namespace LOFAR { } else if (_mode=="amplitudeonly" || _mode=="scalaramplitude") { _g(st1,0) = abs(_g(st1,0)); } + + if (_debugLevel>2) { + cout<<endl<<"gi=["; + uint ant=0; + for (; ant<_nUn-1; ++ant) { + cout<<_g(ant,0)<<","; + } + cout<<_g(ant,0)<<"]"<<endl; + } } } - casa::Matrix<casa::DComplex> StefCal::getSolution() { - for (uint ant=0; ant<_nUn; ++ant) { - if (_stationFlagged[ant%_nSt]) { - for (uint cr=0; cr<_nCr; ++cr) { - _g(ant,cr)=std::numeric_limits<double>::quiet_NaN(); + casa::Matrix<casa::DComplex> StefCal::getSolution(bool setNaNs) { + if (setNaNs && _debugLevel>0) { + cout<<endl<<"dg=["; + uint iter=0; + for (; iter<_dgs.size()-1; ++iter) { + cout<<_dgs[iter]<<","; + } + cout<<_dgs[iter]<<"]"<<endl; + } + + if (_debugLevel>2) { + cout<<endl<<"g=["; + uint ant=0; + for (; ant<_nUn-1; ++ant) { + cout<<_g(ant,0)<<","; + } + cout<<_g(ant,0)<<"]"<<endl; + } + + if (setNaNs) { + for (uint ant=0; ant<_nUn; ++ant) { + if (_stationFlagged[ant%_nSt]) { + for (uint cr=0; cr<_nCr; ++cr) { + _g(ant,cr)=std::numeric_limits<double>::quiet_NaN(); + } } } } @@ -331,28 +366,34 @@ namespace LOFAR { double c2 = 1.2; double dgxx; bool threestep = false; - int badIters=0; - int maxBadIters=5; + uint maxBadIters=3; int sstep=0; - if (_detectStalling && iter > 10 && _dgx-_dg <= 5.0e-3*_dg) { - // This iteration did not improve much upon the previous - // Stalling detection only after 20 iterations, to account for - // ''startup problems'' - if (_debugLevel>3) { - cout<<"**"<<endl; + if (_detectStalling && iter > 3) { + double improvement = _dgx-_dg; + + if (abs(improvement) < 5.0e-2*_dg) { + // This iteration did not improve much upon the previous + // Stalling detection only after 4 iterations, to account for + // ''startup problems'' + if (_debugLevel>3) { + cout<<"**"<<endl; + } + _badIters++; + } else if (improvement < 0) { + _veryBadIters++; + } else { + //TODO slingergedrag + _badIters=0; } - badIters++; - } else { - badIters=0; - } - if (badIters>=maxBadIters && _detectStalling) { - if (_debugLevel>3) { - cout<<"Detected stall"<<endl; + if (_badIters>=maxBadIters || _veryBadIters > maxBadIters) { + if (_debugLevel>3) { + cout<<"Detected stall"<<endl; + } + return STALLED; } - return STALLED; } dgxx = _dgx; @@ -371,7 +412,7 @@ namespace LOFAR { fronormg=sqrt(fronormg); _dg = fronormdiff/fronormg; - if (_debugLevel>1) { + if (_debugLevel>0) { _dgs.push_back(_dg); } @@ -439,9 +480,6 @@ namespace LOFAR { sstep = sstep - 1; } } - _gxx = _gx; - _gx = _g; - return NOTCONVERGED; } } //# end namespace diff --git a/CEP/DP3/DPPP/src/phasefitter.cc b/CEP/DP3/DPPP/src/phasefitter.cc index 91b987d81ab..ccc034f1788 100644 --- a/CEP/DP3/DPPP/src/phasefitter.cc +++ b/CEP/DP3/DPPP/src/phasefitter.cc @@ -38,7 +38,7 @@ double PhaseFitter::fitTECModelBeta(double alpha, double betaEstimate) const { void PhaseFitter::bruteForceSearchTECModel(double& lowerAlpha, double& upperAlpha, double& beta) const { double minCost = std::numeric_limits<double>::max(); - double alphaOversampling = 128; + double alphaOversampling = 256; //size_t betaOversampling = 16; double dAlpha = upperAlpha - lowerAlpha; const double bandw = _frequencies.back() - _frequencies.front(); @@ -94,16 +94,18 @@ void PhaseFitter::fillDataWithTECModel(double alpha, double beta) void PhaseFitter::FitTECModelParameters(double& alpha, double& beta) const { - double lowerAlpha = 0.0, upperAlpha = 40000.0e6; + double lowerAlpha = -40000.0e6, upperAlpha = 40000.0e6; bruteForceSearchTECModel(lowerAlpha, upperAlpha, beta); alpha = (lowerAlpha + upperAlpha) * 0.5; //beta = fitBeta(alpha, beta); alpha = ternarySearchTECModelAlpha(lowerAlpha, upperAlpha, beta); } -void PhaseFitter::FitDataToTECModel(double& alpha, double& beta) +double PhaseFitter::FitDataToTECModel(double& alpha, double& beta) { FitTECModelParameters(alpha, beta); + double cost = TECModelCost(alpha, beta); fillDataWithTECModel(alpha, beta); + return cost; } diff --git a/CEP/DP3/DPPP/test/tGainCal.run b/CEP/DP3/DPPP/test/tGainCal.run index ce64da56f69..2309449fe93 100755 --- a/CEP/DP3/DPPP/test/tGainCal.run +++ b/CEP/DP3/DPPP/test/tGainCal.run @@ -23,19 +23,18 @@ tar zxf ${srcdir}/tGainCal.tab.tgz # Create expected taql output. echo " select result of 0 rows" > taql.ref -# Create MODEL_DATA so that residual can be computed +echo "Creating MODEL_DATA so that residual can be computed" ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=MODEL_DATA steps=[predict] predict.sourcedb=tNDPPP-generic.MS/sky predict.usebeammodel=false -exit 3 echo; echo "Test caltype=diagonal"; echo ../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.propagatesolutions=true gaincal.solint=1 ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal -# Compare the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima. +echo "Comparing the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima." $taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out diff taql.out taql.ref || exit 1 -# Check that not everything was flagged +echo "Checking that not everything was flagged" $taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out diff taql.out taql.ref || exit 1 @@ -44,31 +43,48 @@ echo; echo "Test caltype=diagonal with timeslotsperparmupdate=4"; echo ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_TPP steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal-tpp $taqlexe 'select from tNDPP-generic.MS where not all(near(DPPP_DIAGONAL, DPPP_DIAGONAL_TPP))' -# Compare the difference between applying with timeslotsperparmupdate = default and timeslotsperparmupdate=1 +echo "Comparing the difference between applying with timeslotsperparmupdate = default and timeslotsperparmupdate=1" $taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out diff taql.out taql.ref || exit 1 -# Check that not everything was flagged +echo "Checking that not everything was flagged" $taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out diff taql.out taql.ref || exit 1 echo; echo "Test caltype=fulljones"; echo -../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-fulljones gaincal.usebeammodel=false gaincal.caltype=fulljones gaincal.solint=1 +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_FULLJONES_GAINCAL steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-fulljones gaincal.usebeammodel=false gaincal.caltype=fulljones gaincal.solint=1 gaincal.applysolution=true ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_FULLJONES steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-fulljones -# Compare the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima. +echo "Comparing the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima." $taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_FULLJONES-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_FULLJONES-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out diff taql.out taql.ref || exit 1 -# Check that not everything was flagged +echo "Comparing the solutions from gaincal + applycal with gaincal directly" +$taqlexe 'select from tNDPPP-generic.MS where not(all(DPPP_FULLJONES ~= DPPP_FULLJONES))' > taql.out +diff taql.out taql.ref || exit 1 +echo "Checking that not everything was flagged" $taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out diff taql.out taql.ref || exit 1 echo; echo "Test caltype=diagonal, nchan=2"; echo -../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal-nchan gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 gaincal.nchan=2 +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_NCHAN_GAINCAL steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal-nchan gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 gaincal.nchan=2 gaincal.applysolution=true ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_NCHAN steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal-nchan -# Compare the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima. +echo "Comparing the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima." $taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL_NCHAN-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL_NCHAN-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out diff taql.out taql.ref || exit 1 -# Check that not everything was flagged + +echo "Comparing the solutions from gaincal + applycal with gaincal directly" +$taqlexe 'select from tNDPPP-generic.MS where not(all(DPPP_DIAGONAL_NCHAN_GAINCAL ~= DPPP_DIAGONAL_NCHAN))' > taql.out +diff taql.out taql.ref || exit 1 + +echo "Checking that not everything was flagged" $taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out diff taql.out taql.ref || exit 1 + +echo; echo "Test caltype=diagonal, nchan=2, solint=7"; echo +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_NCHAN_7_GAINCAL steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal-nchan gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 gaincal.nchan=2 gaincal.applysolution=true +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_NCHAN_7 steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal-nchan + +echo "Comparing the solutions from gaincal + applycal with gaincal directly" +$taqlexe 'select from tNDPPP-generic.MS where not(all(DPPP_DIAGONAL_NCHAN_7_GAINCAL ~= DPPP_DIAGONAL_NCHAN_7))' > taql.out +diff taql.out taql.ref || exit 1 + -- GitLab From af0ac4e7c4596160cc6dd89386ab8ceb1e09d415 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Mon, 6 Jun 2016 07:41:29 +0000 Subject: [PATCH 277/933] Task #9462: gaincal.applysolution now works, pull out tec stuff for now --- .gitattributes | 2 - CEP/DP3/DPPP/include/DPPP/CMakeLists.txt | 2 +- CEP/DP3/DPPP/include/DPPP/GainCal.h | 1 - CEP/DP3/DPPP/include/DPPP/phasefitter.h | 221 ----------------------- CEP/DP3/DPPP/src/CMakeLists.txt | 1 - CEP/DP3/DPPP/src/GainCal.cc | 81 +-------- CEP/DP3/DPPP/src/phasefitter.cc | 111 ------------ 7 files changed, 2 insertions(+), 417 deletions(-) delete mode 100644 CEP/DP3/DPPP/include/DPPP/phasefitter.h delete mode 100644 CEP/DP3/DPPP/src/phasefitter.cc diff --git a/.gitattributes b/.gitattributes index 7e6fc5a17fb..83ff5ec0109 100644 --- a/.gitattributes +++ b/.gitattributes @@ -854,7 +854,6 @@ CEP/DP3/DPPP/include/DPPP/Stokes.h -text CEP/DP3/DPPP/include/DPPP/SubtractMixed.h -text CEP/DP3/DPPP/include/DPPP/SubtractNew.h -text CEP/DP3/DPPP/include/DPPP/UVWCalculator.h -text -CEP/DP3/DPPP/include/DPPP/phasefitter.h -text CEP/DP3/DPPP/package.dox -text CEP/DP3/DPPP/share/HBAdefault -text CEP/DP3/DPPP/share/LBAdefault -text @@ -884,7 +883,6 @@ CEP/DP3/DPPP/src/Stokes.cc -text CEP/DP3/DPPP/src/SubtractMixed.cc -text CEP/DP3/DPPP/src/SubtractNew.cc -text CEP/DP3/DPPP/src/__init__.py -text -CEP/DP3/DPPP/src/phasefitter.cc -text CEP/DP3/DPPP/src/taqlflagger -text CEP/DP3/DPPP/test/findenv.run_tmpl -text CEP/DP3/DPPP/test/tApplyBeam.run -text diff --git a/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt b/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt index 07b6c83e8f2..c92e64372df 100644 --- a/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt +++ b/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt @@ -16,7 +16,7 @@ set(inst_HEADERS DemixerNew.h DemixInfo.h DemixWorker.h ApplyBeam.h ApplyBeam.tcc Predict.h - GainCal.h StefCal.h phasefitter.h + GainCal.h StefCal.h ) # Create symbolic link to include directory. diff --git a/CEP/DP3/DPPP/include/DPPP/GainCal.h b/CEP/DP3/DPPP/include/DPPP/GainCal.h index 6181c6f5107..fb3f72b404b 100644 --- a/CEP/DP3/DPPP/include/DPPP/GainCal.h +++ b/CEP/DP3/DPPP/include/DPPP/GainCal.h @@ -29,7 +29,6 @@ #include <DPPP/DPInput.h> #include <DPPP/DPBuffer.h> -#include <DPPP/phasefitter.h> #include <DPPP/StefCal.h> #include <DPPP/Patch.h> #include <DPPP/Predict.h> diff --git a/CEP/DP3/DPPP/include/DPPP/phasefitter.h b/CEP/DP3/DPPP/include/DPPP/phasefitter.h deleted file mode 100644 index 0f3aec2e164..00000000000 --- a/CEP/DP3/DPPP/include/DPPP/phasefitter.h +++ /dev/null @@ -1,221 +0,0 @@ -/** - * @file phasefitter.h Implements TEC model phase filter @ref PhaseFitter. - * @author André Offringa - * @date 2016-04-06 - */ - -#ifndef PHASE_FITTER_H -#define PHASE_FITTER_H - -#include <vector> -#include <cmath> -#include <cstring> - -/** - * Phase fitter that can force phase solutions over frequency onto a TEC model. - * To use: - * - Construct and set the frequencies (with @ref FrequencyData() ) and if already possible - * the weights (@ref WeightData()). - * - Perform a calibration iteration. - * - Set the phase values (@ref PhaseData()) and, if not yet done, the weights (@ref WeightData()) - * to the current phase solutions / weights of a single antenna. - * - Call @ref FitDataToTECModel(). - * - Read the new phase values. - * - When fitting multiple polarizations, the above steps should be done twice for each - * diagonal value of the Jones matrix. Also repeat for all antennae. - * - Continue the iteration with the new phase values. - */ -class PhaseFitter -{ - public: - /** - * Construct a phase fitter for the given number of channels. - * Weights are initialized to unity. The fitting accuracy is - * initialized to 1e-6. - * @param channelCount number of channels. - */ - PhaseFitter(size_t channelCount) : - _phases(channelCount, 0.0), - _frequencies(channelCount, 0.0), - _weights(channelCount, 1.0), - _fittingAccuracy(1e-6) - { } - - ~PhaseFitter() = default; - PhaseFitter(const PhaseFitter&) = delete; - PhaseFitter& operator=(const PhaseFitter&) = delete; - - /** - * Fits the given phase values to a TEC model. This function is - * robust even when phase wrapping occurs. - * After this call, the @ref PhaseData() satisfy the TEC model. - * The TEC model has two parameters and are fitted as described in - * @ref FitTECModelParameters(). - * @param alpha Found value for the alpha parameter. - * @param beta Found value for the beta parameter. - * - * @returns Cost of the found solution. - */ - double FitDataToTECModel(double& alpha, double& beta); - - /** - * Fits the given phase values to a TEC model using prior estimates of the - * model parameters. This method is similar to @ref FitDataToTECModel(), - * except that it will use the provided initial values of alpha and - * beta to speed up the solution. When the provided initial values - * are not accurate, the fit might not be accurate. - * - * @todo No fast method has been implemented -- instead it will perform a full parameter search. - * @param alpha Estimate of alpha parameter on input, found value on output. - * @param beta Estimate of beta parameter on input, found value on output. - * - * @returns Cost of the found solution. - */ - double FitDataToTECModelWithInitialValues(double& alpha, double& beta) - { - return FitDataToTECModel(alpha, beta); - } - - /** - * Fit the data and get the best fitting parameters. The model - * used is f(nu) = alpha/nu + beta, with possible 2pi wrappings in the - * data. - * The phase data is not changed. - * The alpha parameter is linearly related to the TEC. The beta parameter - * is a constant phase offset, given in radians. - * The fitting algorithm uses a combination of brute force searching and - * binary-like searching (ternary search). - * @param alpha Will be set to the fitted value for the alpha parameter - * (value on input is not used). - * @param beta Will be set to the fitted value for the beta parameter - * (value on input is not used). - */ - void FitTECModelParameters(double& alpha, double& beta) const; - - /** - * Get a pointer to the array of phase values. This array should be filled with the - * phases of solutions before calling one of the fit methods. @ref FitDataToTECModel() - * sets this array to the fitted phases. - * Normally, these values should be set to std::arg(z) or atan2(z.imag(), z.real()), where - * z is a complex solution for one polarizations. All phases should correspond to the same - * polarizations, i.e., different polarizations (xx/yy/ll/rr, etc.) should be independently - * fitted. - * @returns Array of @ref Size() doubles with the phases. - */ - double* PhaseData() { return _phases.data(); } - - /** - * Get a constant pointer to the array of values. - * @returns Constant array of @ref Size() doubles with the phases. - */ - const double* PhaseData() const { return _phases.data(); } - - /** - * Get a pointer to the array of frequency values. This array should be set to the - * frequency values of the channels, such that FrequencyData()[ch] is the frequency - * corresponding to the phase value PhaseData()[ch]. The fitter will not change this - * array. - * @returns Array of @ref Size() doubles with the frequencies in Hz. - */ - double* FrequencyData() { return _frequencies.data(); } - - /** - * Constant frequency data. - * @returns Constant array of @ref Size() doubles with the frequencies in Hz. - */ - const double* FrequencyData() const { return _frequencies.data(); } - - /** - * This array should be filled with the weights - * of the channel phase solutions. If the solver supports weights during solving, this value should - * be set to the sum of weights of all visibilities that are used for the solution of - * this channel. If the solver does not support weights, it should be set to the number - * of unflagged visibilities used by the solver to generate the corresponding phase. Another - * way of saying this, is that the weights should be set to the esimated inverse variance of the phase - * solutions. - * - * The use of weights will make sure that noisy channels do not bias the result. Weighting is - * for example helpful to avoid that the few remaining samples in a badly RFI contaminated - * channels cause the fit to be inaccurate. - * - * While the weights could be different for each antenna solution, generally the weight of a channel - * is constant over the antennas. The latter implies that the weights can be set once before - * the solution starts, and only the @ref PhaseData() need to be changed within solution - * iterations. - * - * The weights are initially set to one. - * - * @returns Array of @ref Size() doubles with the weights. - */ - double* WeightData() { return _weights.data(); } - - /** - * Constant array of weights, as described above. - * @returns Constant array of @ref Size() doubles with weights. - */ - const double* WeightData() const { return _weights.data(); } - - /** - * Number of channels used for the fitter. - * @returns Number of channels. - */ - size_t Size() const { return _phases.size(); } - - /** - * Get the fitting accuracy. The fitter will stop once this accuracy is approximately reached. - * The default value is 1e-6. - * @returns Fitting accuracy. - */ - double FittingAccuracy() const { return _fittingAccuracy; } - - /** - * Change the fitting accuracy. See @ref FittingAccuracy(). - * @param newAccuracy New accuracy. - */ - void SetFittingAccuracy(double newAccuracy) { _fittingAccuracy = newAccuracy; } - - /** - * Evaluate the cost function for given TEC model parameters. The higher the - * cost, the worser the data fit the given parameters. - * @param alpha TEC parameter - * @param beta Phase offset parameter - * @returns sum of | nu_i / alpha + beta - theta_i |, and each sum term is - * phase unwrapped. - */ - double TECModelCost(double alpha, double beta) const; - - /** - * Like @ref TECModelFunc(), but 2-pi wrapped. - * @param nu Frequency in Hz - * @param alpha TEC parameter (in undefined units) - * @param beta Phase offset parameter (in radians) - * @returns | nu_i / alpha + beta | % 2pi - */ - static double TECModelFunc(double nu, double alpha, double beta) - { - return alpha / nu + beta; - } - - /** - * Like @ref TECModelFunc(), but 2-pi wrapped. - * @param nu Frequency in Hz - * @param alpha TEC parameter (in undefined units) - * @param beta Phase offset parameter (in radians) - * @returns | nu_i / alpha + beta | % 2pi - */ - static double TECModelFuncWrapped(double nu, double alpha, double beta) - { - return fmod(alpha / nu + beta, 2*M_PI); - } - private: - std::vector<double> _phases, _frequencies, _weights; - double _fittingAccuracy; - - double fitTECModelBeta(double alpha, double betaEstimate) const; - void bruteForceSearchTECModel(double& lowerAlpha, double& upperAlpha, double& beta) const; - double ternarySearchTECModelAlpha(double startAlpha, double endAlpha, double& beta) const; - void fillDataWithTECModel(double alpha, double beta); - -}; - -#endif diff --git a/CEP/DP3/DPPP/src/CMakeLists.txt b/CEP/DP3/DPPP/src/CMakeLists.txt index 0c4077b777b..51598d1c8b1 100644 --- a/CEP/DP3/DPPP/src/CMakeLists.txt +++ b/CEP/DP3/DPPP/src/CMakeLists.txt @@ -20,7 +20,6 @@ lofar_add_library(dppp DemixerNew.cc DemixInfo.cc DemixWorker.cc Predict.cc ApplyBeam.cc - phasefitter.cc ) lofar_add_bin_program(NDPPP NDPPP.cc) diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index 9454139959f..20280fada09 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -25,7 +25,6 @@ #include <DPPP/GainCal.h> #include <DPPP/Simulate.h> #include <DPPP/ApplyCal.h> -#include <DPPP/phasefitter.h> #include <DPPP/CursorUtilCasa.h> #include <DPPP/DPBuffer.h> #include <DPPP/DPInfo.h> @@ -121,8 +120,7 @@ namespace LOFAR { ASSERT(itsMode=="diagonal" || itsMode=="phaseonly" || itsMode=="fulljones" || itsMode=="scalarphase" || - itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || - itsMode=="tec"); + itsMode=="amplitudeonly" || itsMode=="scalaramplitude"); } GainCal::~GainCal() @@ -488,23 +486,6 @@ namespace LOFAR { uint iter=0; - PhaseFitter fitter(itsNFreqCells); - - if (itsMode=="tec") { - // Set frequency data for TEC fitter - double* nu = fitter.FrequencyData(); - for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { - double meanfreq=0; - uint chmin=itsNChan*freqCell; - uint chmax=min(info().nchan(), chmin+itsNChan); - - meanfreq = std::accumulate(info().chanFreqs().data()+chmin, - info().chanFreqs().data()+chmax, 0.0); - - nu[freqCell] = meanfreq / (chmax-chmin); - } - } - std::vector<StefCal::Status> converged(itsNFreqCells,StefCal::NOTCONVERGED); for (;iter<itsMaxIter;++iter) { bool allConverged=true; @@ -521,66 +502,6 @@ namespace LOFAR { } } - double alpha=0., beta=0.; - if (itsMode=="tec") { - casa::Matrix<casa::DComplex> tecsol(itsNFreqCells, info().antennaNames().size()); - - for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { - casa::Matrix<casa::DComplex> sol = iS[freqCell].getSolution(false); - for (uint ant=0; ant<info().antennaNames().size(); ++ant) { - tecsol(freqCell, ant) = sol(ant, 0)/sol(0, 0); - } - } - - uint nSt = info().antennaNames().size(); - for (uint ant=0; ant<nSt; ++ant) { - uint numpoints=0; - double cost=0; - double* phases = fitter.PhaseData(); - double* weights = fitter.WeightData(); - for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { - if (iS[freqCell].getStationFlagged()[ant%nSt]) { - phases[freqCell] = 0; - weights[freqCell] = 0; - } else { - phases[freqCell] = arg(tecsol(freqCell, ant)); - weights[freqCell] = 1; - numpoints++; - } - } - - if (itsDebugLevel>0 && ant==1) { - //cout<<endl<<"phases["<<ant<<"]=["; - cout<<"unfitted["<<iter<<"]=["; - uint freqCell=0; - for (; freqCell<itsNFreqCells-1; ++freqCell) { - cout<<phases[freqCell]<<","; - } - cout<<phases[freqCell]<<"];"<<endl; - } - if (numpoints>1) { - cost=fitter.FitDataToTECModel(alpha, beta); - // Update solution in stefcal - for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { - iS[freqCell].getSolution(false)(ant, 0) = polar(1., phases[freqCell]); - } - } else if (itsDebugLevel>5) { - cout<<"Not enough points ("<<numpoints<<") for antenna "<<ant<<endl; - } - if (itsDebugLevel>1 && ant==1) { - cout<<"fitted["<<iter<<"]=["; - uint freqCell=0; - for (; freqCell<itsNFreqCells-1; ++freqCell) { - cout<<phases[freqCell]<<","; - } - cout<<phases[freqCell]<<"]"<<endl; - } - if (itsDebugLevel>0 && ant==1) { - cout << "fitdata["<<iter<<"]=[" << alpha << ", " << beta << ", " << cost << "];" << endl; - } - } - } - if (allConverged) { break; } diff --git a/CEP/DP3/DPPP/src/phasefitter.cc b/CEP/DP3/DPPP/src/phasefitter.cc deleted file mode 100644 index ccc034f1788..00000000000 --- a/CEP/DP3/DPPP/src/phasefitter.cc +++ /dev/null @@ -1,111 +0,0 @@ -#include <lofar_config.h> -#include "DPPP/phasefitter.h" - -#include <limits> - -double PhaseFitter::TECModelCost(double alpha, double beta) const -{ - double costVal = 0.0; - for(int i=0; i!=Size(); ++i) { - double estphase = TECModelFuncWrapped(_frequencies[i], alpha, beta); - double dCost = fmod(std::fabs(estphase - _phases[i]), 2.0*M_PI); - if(dCost > M_PI) dCost = 2.0*M_PI - dCost; - dCost *= _weights[i]; - costVal += dCost; - } - return costVal; -} - -double PhaseFitter::fitTECModelBeta(double alpha, double betaEstimate) const { - double weightSum = 0.0; - for(size_t iter=0; iter!=3; ++iter) { - double sum = 0.0; - for(size_t i=0; i!=Size(); ++i) { - double p = _phases[i], e = TECModelFunc(_frequencies[i], alpha, betaEstimate); - double dist = fmod(p - e, 2.0*M_PI); - if(dist < -M_PI) - dist += 2.0*M_PI; - else if(dist > M_PI) - dist -= 2.0*M_PI; - sum += dist * _weights[i]; - weightSum += _weights[i]; - } - betaEstimate = betaEstimate + sum / weightSum; - } - return fmod(betaEstimate, 2.0*M_PI); -} - -void PhaseFitter::bruteForceSearchTECModel(double& lowerAlpha, double& upperAlpha, double& beta) const -{ - double minCost = std::numeric_limits<double>::max(); - double alphaOversampling = 256; - //size_t betaOversampling = 16; - double dAlpha = upperAlpha - lowerAlpha; - const double bandw = _frequencies.back() - _frequencies.front(); - int alphaIndex = 0; - for(int i=0; i!=alphaOversampling; ++i) { - // make r between [0, 1] - double r = double(i)/alphaOversampling; - double alpha = lowerAlpha + r*dAlpha; - double curBeta = fitTECModelBeta(alpha, beta); - double costVal = TECModelCost(alpha, curBeta); - if(costVal < minCost) { - beta = curBeta; - minCost = costVal; - alphaIndex = i; - } - } - double newLowerAlpha = double(alphaIndex-1)/alphaOversampling*dAlpha + lowerAlpha; - upperAlpha = double(alphaIndex+1)/alphaOversampling*dAlpha + lowerAlpha; - lowerAlpha = newLowerAlpha; -} - -double PhaseFitter::ternarySearchTECModelAlpha(double startAlpha, double endAlpha, double& beta) const -{ - size_t iter = 0; - double dCost, lAlpha, rAlpha; - do { - lAlpha = startAlpha + (endAlpha - startAlpha) * (1.0/3.0); - rAlpha = startAlpha + (endAlpha - startAlpha) * (2.0/3.0); - double lBeta = fitTECModelBeta(lAlpha, beta); - double rBeta = fitTECModelBeta(rAlpha, beta); - double lCost = TECModelCost(lAlpha, lBeta); - double rCost = TECModelCost(rAlpha, rBeta); - if(lCost < rCost) { - endAlpha = rAlpha; - beta = lBeta; - } else { - startAlpha = lAlpha; - beta = rBeta; - } - dCost = std::fabs(lCost - rCost); - ++iter; - } while(dCost > _fittingAccuracy && iter < 100); - double finalAlpha = (lAlpha + rAlpha) * 0.5; - beta = fitTECModelBeta(finalAlpha, beta); - return finalAlpha; -} - -void PhaseFitter::fillDataWithTECModel(double alpha, double beta) -{ - for(size_t ch=0; ch!=Size(); ++ch) - _phases[ch] = TECModelFunc(_frequencies[ch], alpha, beta); -} - -void PhaseFitter::FitTECModelParameters(double& alpha, double& beta) const -{ - double lowerAlpha = -40000.0e6, upperAlpha = 40000.0e6; - bruteForceSearchTECModel(lowerAlpha, upperAlpha, beta); - alpha = (lowerAlpha + upperAlpha) * 0.5; - //beta = fitBeta(alpha, beta); - alpha = ternarySearchTECModelAlpha(lowerAlpha, upperAlpha, beta); -} - -double PhaseFitter::FitDataToTECModel(double& alpha, double& beta) -{ - FitTECModelParameters(alpha, beta); - double cost = TECModelCost(alpha, beta); - fillDataWithTECModel(alpha, beta); - return cost; -} - -- GitLab From e3c36c9720574bf3d0632350ae9ef96a9b19db96 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 6 Jun 2016 08:26:08 +0000 Subject: [PATCH 278/933] Task #8887: added constrain trigger to update_specification_successor_startendtimes --- .../sql/add_triggers.sql | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_triggers.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_triggers.sql index d8df06ab623..d9c301f6bcd 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_triggers.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_triggers.sql @@ -143,3 +143,43 @@ CREATE TRIGGER trigger_before_insert_conflict_reason_do_task_status_check EXECUTE PROCEDURE resource_allocation.before_insert_conflict_reason_do_task_status_check(); --------------------------------------------------------------------------------------------------------------------- + +DROP TRIGGER IF EXISTS trigger_specification_update_successor_startendtimes ON resource_allocation.specification; +DROP FUNCTION IF EXISTS resource_allocation.update_specification_successor_startendtimes(); + +CREATE OR REPLACE FUNCTION resource_allocation.update_specification_successor_startendtimes() + RETURNS trigger AS +$BODY$ +DECLARE +task RECORD; +suc_task RECORD; +successor_task_id int; +moved_end_seconds double precision; +BEGIN + IF NEW.starttime <> OLD.starttime OR NEW.endtime <> OLD.endtime THEN + SELECT EXTRACT(epoch FROM age(NEW.endtime, OLD.endtime)) INTO moved_end_seconds; + FOR task IN SELECT * FROM resource_allocation.task_view tv WHERE tv.specification_id = NEW.id LOOP + IF task.successor_ids IS NOT NULL THEN + FOREACH successor_task_id IN ARRAY task.successor_ids LOOP + FOR suc_task IN SELECT * FROM resource_allocation.task_view tv WHERE tv.id = successor_task_id LOOP + UPDATE resource_allocation.specification SET (starttime, endtime) = (starttime + moved_end_seconds * interval '1 second', endtime + moved_end_seconds * interval '1 second') WHERE id = suc_task.specification_id; + END LOOP; + END LOOP; + END IF; + END LOOP; + END IF; +RETURN NEW; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100; +ALTER FUNCTION resource_allocation.update_specification_successor_startendtimes() + OWNER TO resourceassignment; + +CREATE TRIGGER trigger_specification_update_successor_startendtimes + AFTER UPDATE + ON resource_allocation.specification + FOR EACH ROW + EXECUTE PROCEDURE resource_allocation.update_specification_successor_startendtimes(); + +--------------------------------------------------------------------------------------------------------------------- -- GitLab From d21f8de23d7c1e483b82674554df60f413f05b14 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 6 Jun 2016 14:03:27 +0000 Subject: [PATCH 279/933] Task #8887: added unique constrain to task_predecessor --- .../ResourceAssignmentDatabase/sql/create_database.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql index df864051e51..5002a2e19c1 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql @@ -155,7 +155,8 @@ CREATE TABLE resource_allocation.task_predecessor ( id serial NOT NULL, task_id integer NOT NULL REFERENCES resource_allocation.task(id) ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE, predecessor_id integer NOT NULL REFERENCES resource_allocation.task(id) ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE, - PRIMARY KEY (id) + PRIMARY KEY (id), + CONSTRAINT task_predecessor_unique UNIQUE (task_id, predecessor_id) ) WITH (OIDS=FALSE); ALTER TABLE resource_allocation.task_predecessor OWNER TO resourceassignment; -- GitLab From 3f4470e921b328199b76ba5e77585f137e760c1c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 6 Jun 2016 14:04:59 +0000 Subject: [PATCH 280/933] Task #8887: modified specification update trigger to check for min_starttime, swap start-endtime, etc --- .../sql/add_resource_allocation_statics.sql | 2 +- .../sql/add_triggers.sql | 87 +++++++++++++++---- 2 files changed, 69 insertions(+), 20 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql index 4717ae01028..47919fcdfa3 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql @@ -8,7 +8,7 @@ INSERT INTO resource_allocation.task_status VALUES (200, 'prepared'), (300, 'app INSERT INTO resource_allocation.task_type VALUES (0, 'observation'),(1, 'pipeline'); -- We'll need more types INSERT INTO resource_allocation.resource_claim_status VALUES (0, 'claimed'), (1, 'allocated'), (2, 'conflict'); INSERT INTO resource_allocation.resource_claim_property_type VALUES (0, 'nr_of_is_files'),(1, 'nr_of_cs_files'),(2, 'nr_of_uv_files'),(3, 'nr_of_im_files'),(4, 'nr_of_img_files'),(5, 'nr_of_pulp_files'),(6, 'nr_of_cs_stokes'),(7, 'nr_of_is_stokes'),(8, 'is_file_size'),(9, 'cs_file_size'),(10, 'uv_file_size'),(11, 'im_file_size'),(12, 'img_file_size'),(13, 'nr_of_pulp_files'),(14, 'nr_of_tabs'); -INSERT INTO resource_allocation.config VALUES (0, 'max_fill_percentage_cep4', '85.00'), (1, 'claim_timeout', '172800'); -- Just some values 172800 is two days in seconds +INSERT INTO resource_allocation.config VALUES (0, 'max_fill_percentage_cep4', '85.00'), (1, 'claim_timeout', '172800'), (2, 'min_inter_task_delay', '60'); -- Just some values 172800 is two days in seconds INSERT INTO resource_allocation.conflict_reason VALUES (1, 'Not enough total free storage space'), diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_triggers.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_triggers.sql index d9c301f6bcd..bb754b359b9 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_triggers.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_triggers.sql @@ -144,42 +144,91 @@ CREATE TRIGGER trigger_before_insert_conflict_reason_do_task_status_check --------------------------------------------------------------------------------------------------------------------- -DROP TRIGGER IF EXISTS trigger_specification_update_successor_startendtimes ON resource_allocation.specification; -DROP FUNCTION IF EXISTS resource_allocation.update_specification_successor_startendtimes(); +DROP TRIGGER IF EXISTS trigger_specification_insertupdate_check_startendtimes ON resource_allocation.specification; +DROP FUNCTION IF EXISTS resource_allocation.on_insertupdate_check_specification_startendtimes(); -CREATE OR REPLACE FUNCTION resource_allocation.update_specification_successor_startendtimes() +CREATE OR REPLACE FUNCTION resource_allocation.on_insertupdate_check_specification_startendtimes() RETURNS trigger AS $BODY$ DECLARE task RECORD; +pred_task RECORD; suc_task RECORD; +predecessor_task_id int; successor_task_id int; -moved_end_seconds double precision; +moved_seconds double precision; +duration double precision; +max_pred_endtime timestamp := '1900-01-01 00:00:00'; +tmp_time timestamp; +min_starttime timestamp; +min_inter_task_delay int; BEGIN - IF NEW.starttime <> OLD.starttime OR NEW.endtime <> OLD.endtime THEN - SELECT EXTRACT(epoch FROM age(NEW.endtime, OLD.endtime)) INTO moved_end_seconds; - FOR task IN SELECT * FROM resource_allocation.task_view tv WHERE tv.specification_id = NEW.id LOOP - IF task.successor_ids IS NOT NULL THEN - FOREACH successor_task_id IN ARRAY task.successor_ids LOOP - FOR suc_task IN SELECT * FROM resource_allocation.task_view tv WHERE tv.id = successor_task_id LOOP - UPDATE resource_allocation.specification SET (starttime, endtime) = (starttime + moved_end_seconds * interval '1 second', endtime + moved_end_seconds * interval '1 second') WHERE id = suc_task.specification_id; - END LOOP; - END LOOP; - END IF; - END LOOP; + --swap start/end time if needed + IF NEW.starttime > NEW.endtime THEN + RAISE NOTICE 'NEW.starttime > NEW.endtime'; + tmp_time := NEW.starttime; + NEW.starttime := NEW.endtime; + NEW.endtime := tmp_time; END IF; + + --store task duration + SELECT EXTRACT(epoch FROM age(NEW.endtime, NEW.starttime)) INTO duration; + + --deterimine max_pred_endtime + FOR task IN SELECT * FROM resource_allocation.task_view tv WHERE tv.specification_id = NEW.id LOOP + IF task.predecessor_ids IS NOT NULL THEN + FOREACH predecessor_task_id IN ARRAY task.predecessor_ids LOOP + FOR pred_task IN SELECT * FROM resource_allocation.task_view tv WHERE tv.id = predecessor_task_id LOOP + IF pred_task.endtime > max_pred_endtime THEN + max_pred_endtime := pred_task.endtime; + END IF; + END LOOP; + END LOOP; + END IF; + END LOOP; + + --check if spec is before max_pred_endtime, correct if needed. + IF max_pred_endtime > '1900-01-01 00:00:00' THEN + SELECT c.value::integer INTO min_inter_task_delay FROM resource_allocation.config c WHERE c.name = 'min_inter_task_delay'; + IF min_inter_task_delay IS NULL THEN + min_inter_task_delay := 0; + END IF; + min_starttime := max_pred_endtime + min_inter_task_delay * interval '1 second'; + IF min_starttime > NEW.starttime THEN + NEW.starttime := min_starttime; + NEW.endtime := min_starttime + duration * interval '1 second'; + END IF; + END IF; + + --move successor tasks by same amount if needed + IF TG_OP = 'UPDATE' THEN + IF NEW.endtime <> OLD.endtime THEN + SELECT EXTRACT(epoch FROM age(NEW.endtime, OLD.endtime)) INTO moved_seconds; + FOR task IN SELECT * FROM resource_allocation.task_view tv WHERE tv.specification_id = NEW.id LOOP + IF task.successor_ids IS NOT NULL THEN + FOREACH successor_task_id IN ARRAY task.successor_ids LOOP + FOR suc_task IN SELECT * FROM resource_allocation.task_view tv WHERE tv.id = successor_task_id LOOP + UPDATE resource_allocation.specification SET (starttime, endtime) = (starttime + moved_seconds * interval '1 second', endtime + moved_seconds * interval '1 second') WHERE id = suc_task.specification_id; + END LOOP; + END LOOP; + END IF; + END LOOP; + END IF; + END IF; + RETURN NEW; END; $BODY$ LANGUAGE plpgsql VOLATILE COST 100; -ALTER FUNCTION resource_allocation.update_specification_successor_startendtimes() +ALTER FUNCTION resource_allocation.on_insertupdate_check_specification_startendtimes() OWNER TO resourceassignment; -CREATE TRIGGER trigger_specification_update_successor_startendtimes - AFTER UPDATE +CREATE TRIGGER trigger_specification_insertupdate_check_startendtimes + BEFORE INSERT OR UPDATE ON resource_allocation.specification FOR EACH ROW - EXECUTE PROCEDURE resource_allocation.update_specification_successor_startendtimes(); + EXECUTE PROCEDURE resource_allocation.on_insertupdate_check_specification_startendtimes(); --------------------------------------------------------------------------------------------------------------------- + -- GitLab From 2b7b2684789aa1b74804a0665b4a6ef21f7b70fd Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 6 Jun 2016 14:25:11 +0000 Subject: [PATCH 281/933] Task #8887: error checking --- SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 644e75bdc2d..d11e2c17d7e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -243,7 +243,7 @@ class RADatabase: def updateTaskStatusForOtdbId(self, otdb_id, task_status, commit=True): '''converts task_status and task_type to id's in case one and/or the other are strings''' - if task_status and isinstance(task_status, basestring): + if task_status is not None and isinstance(task_status, basestring): #convert task_status string to task_status.id task_status = self.getTaskStatusId(task_status) @@ -962,7 +962,7 @@ class RADatabase: def updateResourceClaims(self, resource_claim_ids, resource_id=None, task_id=None, starttime=None, endtime=None, status=None, session_id=None, claim_size=None, username=None, user_id=None, validate=True, commit=True): if not resource_claim_ids: - return + return True logger.info("updateResourceClaims for %d claims" % len(resource_claim_ids)) @@ -1046,6 +1046,8 @@ class RADatabase: if starttime or endtime: task = self.getTask(task_id) + if task == None: + return False updated &= self.updateSpecification(task['specification_id'], starttime=starttime, endtime=endtime, commit=False) if (starttime or endtime or claim_status is not None or session_id is not None or -- GitLab From 2f6a7b9604bc3c947413de1e028eab3a7929f20f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 6 Jun 2016 14:25:32 +0000 Subject: [PATCH 282/933] Task #8887: _updateStartStopTimesFromSpecification --- .../propagator.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index f5ff7fec159..58b5cb7bcf4 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -52,26 +52,31 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): if not result or 'updated' not in result or not result['updated']: logger.warning("could not update task with otdb_id %s to status %s" % (otdb_id, task_status)) - def _updateStartStopTimes(self, treeId): + def _updateStartStopTimesFromSpecification(self, treeId): # cep2 jobs still get scheduled via old scheduler # so, if the start/endtime were changed in the old scheduler # and the times are different to the radb times, then propagate these to radb - radb_task = self.radb.getTask(otdb_id=treeId) - if radb_task: - otdb_task = self.otdb.taskGetTreeInfo(otdb_id=treeId) - if otdb_task and (otdb_task['starttime'] != radb_task['starttime'] or otdb_task['stoptime'] != radb_task['endtime']): - new_startime = otdb_task['starttime'] - new_endtime = otdb_task['stoptime'] + try: + radb_task = self.radb.getTask(otdb_id=treeId) + if radb_task: + spec = self.otdb.taskGetSpecification(otdb_id=treeId)['specification'] + new_startime = spec['ObsSW.Observation.startTime'] + new_endtime = spec['ObsSW.Observation.stopTime'] + + new_startime = datetime.strptime(new_startime, ('%Y-%m-%d %H:%M:%S.%f' if '.' in new_startime else '%Y-%m-%d %H:%M:%S')) + new_endtime = datetime.strptime(new_endtime, ('%Y-%m-%d %H:%M:%S.%f' if '.' in new_endtime else '%Y-%m-%d %H:%M:%S')) - logger.info("Updating task %s (otdb_id=%s, status=%s) startime to \'%s\' and endtime to \'%s\'", radb_task['id'], treeId, radb_task['status'], new_startime, new_endtime) + logger.info("Updating task (otdb_id=%s, radb_id=%s, status=%s) startime to \'%s\' and endtime to \'%s\'", treeId, radb_task['id'], radb_task['status'], new_startime, new_endtime) self.radb.updateTaskAndResourceClaims(radb_task['id'], starttime=new_startime, endtime=new_endtime) + except Exception as e: + logger.error(e) def onObservationPrepared(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'prepared') def onObservationApproved(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'approved') - self._updateStartStopTimes(treeId) + self._updateStartStopTimesFromSpecification(treeId) def onObservationOnHold(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'on_hold') @@ -84,7 +89,7 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): def onObservationScheduled(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'scheduled') - self._updateStartStopTimes(treeId) + self._updateStartStopTimesFromSpecification(treeId) def onObservationQueued(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'queued') -- GitLab From 4f28a1105659512e9402755b8a5c675669d3efa4 Mon Sep 17 00:00:00 2001 From: Pieter Donker <donker@astron.nl> Date: Tue, 7 Jun 2016 12:00:37 +0000 Subject: [PATCH 283/933] Task #9166: station sensitivity fixes, I2C timing fix in RSPDriver and Analog heartbeat fix in BeamServer --- MAC/APL/PAC/ITRFBeamServer/src/AnaBeamMgr.cc | 106 +++---- MAC/APL/PAC/ITRFBeamServer/src/BeamServer.cc | 37 ++- MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc | 1 + .../ITRFBeamServer/src/iBeamServer.conf.in | 4 +- MAC/APL/PIC/RSP_Driver/src/CRSyncWrite.cc | 18 +- MAC/APL/PIC/RSP_Driver/src/Cache.cc | 80 +++--- MAC/APL/PIC/RSP_Driver/src/GetRCUCmd.cc | 8 +- MAC/APL/PIC/RSP_Driver/src/GetSplitterCmd.cc | 3 +- .../PIC/RSP_Driver/src/HBAProtocolWrite.cc | 195 ++++++------- MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.h | 25 +- MAC/APL/PIC/RSP_Driver/src/HBAResultRead.cc | 4 +- .../PIC/RSP_Driver/src/RCUProtocolWrite.cc | 20 +- MAC/APL/PIC/RSP_Driver/src/Scheduler.cc | 34 +-- MAC/APL/PIC/RSP_Driver/src/SerdesRead.cc | 4 +- MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc | 25 +- MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc | 14 +- MAC/APL/PIC/RSP_Driver/src/rspctl.cc | 259 +++++++++++++++++- MAC/APL/PIC/RSP_Driver/src/rspctl.h | 21 ++ 18 files changed, 579 insertions(+), 279 deletions(-) diff --git a/MAC/APL/PAC/ITRFBeamServer/src/AnaBeamMgr.cc b/MAC/APL/PAC/ITRFBeamServer/src/AnaBeamMgr.cc index 8eaaf40446c..b6e6f01a179 100644 --- a/MAC/APL/PAC/ITRFBeamServer/src/AnaBeamMgr.cc +++ b/MAC/APL/PAC/ITRFBeamServer/src/AnaBeamMgr.cc @@ -69,7 +69,7 @@ AnaBeamMgr::AnaBeamMgr(uint nrRCUsPerRing, // initialize size of array for HBA delay calculation itsHBAdelays.resize(itsSC.nrRSPs * NR_RCUS_PER_RSPBOARD, N_HBA_ELEM_PER_TILE); - + itsHBAdelays(Range::all(), Range::all()) = 0; } // @@ -96,7 +96,7 @@ bool AnaBeamMgr::addBeam(const AnalogueBeam& beam) if (iter != itsBeams.end()) { LOG_ERROR_STR("Beam " << beamName << " already in my admistration"); return (false); - } + } // remember the beam itsBeams[beamName] = beam; @@ -120,13 +120,13 @@ bool AnaBeamMgr::addBeam(const AnalogueBeam& beam) // // deleteBeam(beam) // -void AnaBeamMgr::deleteBeam(const string& beamName) +void AnaBeamMgr::deleteBeam(const string& beamName) { map<string, AnalogueBeam>::const_iterator iter = itsBeams.find(beamName); if (iter == itsBeams.end()) { LOG_ERROR_STR("Beam " << beamName << " is not in my administration, it cannot be deleted"); return; - } + } itsBeams.erase(beamName); // delete all pointings @@ -153,7 +153,7 @@ bool AnaBeamMgr::addPointing(const string& beamName, const Pointing& newPt) if (iter == itsBeams.end()) { LOG_ERROR_STR("Beam " << beamName << " is not in my administration, pointing rejected"); return (false); - } + } // add the pointing of this beam PointingInfo PI; @@ -197,7 +197,7 @@ void AnaBeamMgr::activateBeams(const Timestamp& now) iter->active = false; } bool beamIsActive(iter->active); // remember state of this beam - + // delete old (expired) pointings of this beam while (iter != end && iter->beam.name() == beamName && iter->pointing.endTime() < now) { LOG_DEBUG_STR("Removing pointing " << beamName << ":" << iter->pointing); @@ -209,12 +209,12 @@ void AnaBeamMgr::activateBeams(const Timestamp& now) LOG_INFO_STR("Beam " << beamName << " has ended"); continue; } - + if (beamIsActive) { // active beams should stay active. iter->active = true; // make that pointing the active one itsActiveRCUs |= iter->beam.rcuMask(); // update occupied rcus } - else { // beam is not active try to activate it + else { // beam is not active try to activate it // activate the beam when that is possible. if (conflictingRCUs.none() && iter->pointing.time() <= now) { iter->active = true; @@ -222,7 +222,7 @@ void AnaBeamMgr::activateBeams(const Timestamp& now) LOG_INFO_STR("Beam " << beamName << " is switched ON"); } } - + // skip rest of the pointings of this beam while (iter != end && iter->beam.name() == beamName) { ++iter; @@ -275,7 +275,7 @@ void AnaBeamMgr::calculateHBAdelays(RTC::Timestamp targetTime, CasaConverter& aJ showAdmin(); LOG_INFO_STR("Calculating HBAdelays for time " << targetTime); - itsHBAdelays(Range::all(), Range::all()) = 0; + //itsHBAdelays(Range::all(), Range::all()) = 0; itsTargetTime = targetTime; // When have to do the calculations on the HBA, HBA0 and HBA1 field. (at least we have to check those fields). @@ -300,7 +300,7 @@ void AnaBeamMgr::calculateHBAdelays(RTC::Timestamp targetTime, CasaConverter& aJ continue; // try next field } - // we found an active beam in this HBA field, calculate the delays for all + // we found an active beam in this HBA field, calculate the delays for all // active beams in this field // Get geographical location of antennaField in ITRF @@ -319,7 +319,7 @@ void AnaBeamMgr::calculateHBAdelays(RTC::Timestamp targetTime, CasaConverter& aJ (itsTileRelPos(i,2) * itsTileRelPos(i,2))); } LOG_DEBUG_STR("tileRelLength:" << tileRelLength); - + // for all active pointings in this antennafield while (pIter != pEnd) { // must be of the same antenna field. @@ -369,45 +369,53 @@ void AnaBeamMgr::calculateHBAdelays(RTC::Timestamp targetTime, CasaConverter& aJ } // Do the HBA_1 antennas use a different rotation and are we calculating such an antenna? - int HBAdeltaOffset = (itsDiffHBArotations && (rcu/N_POL >= (itsSC.nrHBAs/2)) + int HBAdeltaOffset = (itsDiffHBArotations && (rcu/N_POL >= (itsSC.nrHBAs/2)) ? N_HBA_ELEM_PER_TILE : 0); for (int element = 0; element < N_HBA_ELEM_PER_TILE; ++element) { // calculate tile delay. double delay = ( (sourceJ2000xyz(0,0) * tileRelPosJ2000(HBAdeltaOffset + element,0)) + (sourceJ2000xyz(0,1) * tileRelPosJ2000(HBAdeltaOffset + element,1)) + - (sourceJ2000xyz(0,2) * tileRelPosJ2000(HBAdeltaOffset + element,2)) + (sourceJ2000xyz(0,2) * tileRelPosJ2000(HBAdeltaOffset + element,2)) ) / SPEED_OF_LIGHT_MS; - + // signal front stays in the middle of the tile delay += itsMeanElementDelay; -// LOG_DEBUG_STR("antenna="<<rcu/2 <<", pol="<<rcu%2 <<", element="<<element <<", delay("<<rcu<<","<<element<<")="<<delay); - // calculate approximate DelayStepNr - int delayStepNr = static_cast<int>(delay / 0.5E-9); - // range check for delayStepNr, max. 32 steps (0..31) - delayStepNr = MIN2(delayStepNr, maxStepNr); // limit against upper boundary + int delayStepNr = static_cast<int>((itsHBAdelays(rcu, element) & 0x7c) >> 2); // set actual delay + + // search for next step + for (int i = 0; i < maxStepNr; ++i) { + if (fabs(delay - itsDelaySteps(i)) <= 0.2E-9) { + delayStepNr = i; + break; + } + } + + if (fabs(delay - itsDelaySteps(delayStepNr)) > 0.6E-9) { + LOG_INFO_STR("delay diff > 0.75E-9, calculate approximate delaynr"); + delayStepNr = static_cast<int>(fabs(delay) / 0.5E-9); + } + + delayStepNr = MIN2(delayStepNr, maxStepNr); // limit against upper boundary delayStepNr = MAX2(0, delayStepNr); // limit against lower boundary - - // look for nearest matching delay step in range "delayStepNr - 2 .. delayStepNr + 2" - double minDelayDiff = fabs(delay - itsDelaySteps(delayStepNr)); - double difference; - int minStepNr = delayStepNr; - int stepMinusTwo = MAX2(0, delayStepNr-2); // limit check to element 0 - int stepPlusTwo = MIN2(maxStepNr, delayStepNr+2); // limit check to element 31 - for (int i = stepMinusTwo; i <= stepPlusTwo; i++){ - if (i == delayStepNr) - continue; // skip approximate nr - difference = fabs(delay - itsDelaySteps(i)); - if (difference < minDelayDiff) { - minStepNr = i; - minDelayDiff = difference; - } - } - delayStepNr = minStepNr; - - // bit1=0.25nS(not used), bit2=0.5nS, bit3=1nS, bit4=2nS, bit5=4nS, bit6=8nS - itsHBAdelays(rcu,element) = (delayStepNr * 4) + (1 << 7); // assign + + if (itsDiffHBArotations) { + if ((rcu == 0) && (rcu%2 == 0)) { + LOG_INFO_STR("HBA-0 delay, element="<<element <<", requested delay="<<delay <<", real delay="<<itsDelaySteps(delayStepNr)); + } + if ((rcu == 48) && (rcu%2 == 0)) { + LOG_INFO_STR("HBA-1 delay, element="<<element <<", requested delay="<<delay <<", real delay="<<itsDelaySteps(delayStepNr)); + } + } + else { + if ((rcu == 0) && (rcu%2 == 0)) { + LOG_INFO_STR("HBA delay, element="<<element <<", requested delay="<<delay <<", real delay="<<itsDelaySteps(delayStepNr)); + } + } + + // bit1=0.25nS(not used), bit2=0.5nS, bit3=1nS, bit4=2nS, bit5=4nS, bit6=8nS + itsHBAdelays(rcu, element) = (delayStepNr << 2) + (1 << 7); // assign } // for element } // rcus ++pIter; @@ -423,7 +431,7 @@ void AnaBeamMgr::sendHBAdelays(GCF::TM::GCFPortInterface& port) // note: the RSPDriver expects settings for the active RCU's only. // TODO: CHANGE THIS SO WE CAN POINT THE UNUSED TILES TO ZENITH request.settings().resize(itsActiveRCUs.count(), N_HBA_ELEM_PER_TILE); - int nrRCUs = itsActiveRCUs.size(); + int nrRCUs = itsActiveRCUs.size(); int requestIdx(0); for (int r = 0; r < nrRCUs; r++) { if (itsActiveRCUs.test(r)) { @@ -449,7 +457,7 @@ void AnaBeamMgr::sendHBAdelays(GCF::TM::GCFPortInterface& port) // --------------- READ CONFIGURATIONFILES --------------- // -// getAllHBADeltas +// getAllHBADeltas // void AnaBeamMgr::getAllHBADeltas(const string& filename) { @@ -472,14 +480,14 @@ void AnaBeamMgr::getAllHBADeltas(const string& filename) LOG_DEBUG_STR("HBADeltas comment = " << itsName); getline(itsFile, itsName); // read name or comment } - + if ("" == itsName) { itsFile.close(); return; } - + LOG_DEBUG_STR("HBADeltas name = " << itsName); - + // Now comment lines are skipped, so we can read the full array. itsFile >> itsTileRelPos; // read HBA deltas array LOG_DEBUG_STR("HBADeltas = " << itsTileRelPos); @@ -492,7 +500,7 @@ void AnaBeamMgr::getAllHBADeltas(const string& filename) } // -// getAllHBAElementDelays +// getAllHBAElementDelays // void AnaBeamMgr::getAllHBAElementDelays(const string& filename) { @@ -502,7 +510,7 @@ void AnaBeamMgr::getAllHBAElementDelays(const string& filename) LOG_DEBUG_STR("Trying to read the HBA element delay steps from file " << filename); // open new file - if (itsFile.is_open()) { + if (itsFile.is_open()) { itsFile.close(); } itsFile.open(filename.c_str()); @@ -516,14 +524,14 @@ void AnaBeamMgr::getAllHBAElementDelays(const string& filename) LOG_DEBUG_STR("HBA ElementDelays comment = " << itsName); getline(itsFile, itsName); // read name or comment } - + if ("" == itsName) { itsFile.close(); return; } LOG_DEBUG_STR("HBA ElementDelays Name = " << itsName); - + // Now comment lines are skipped, so we can read the full array. itsFile >> itsDelaySteps; // read HBA element delays array //itsDelaySteps *= 1E-9; // convert from nSec to Secs diff --git a/MAC/APL/PAC/ITRFBeamServer/src/BeamServer.cc b/MAC/APL/PAC/ITRFBeamServer/src/BeamServer.cc index 1c1d08bf26f..7d0143d7c6f 100644 --- a/MAC/APL/PAC/ITRFBeamServer/src/BeamServer.cc +++ b/MAC/APL/PAC/ITRFBeamServer/src/BeamServer.cc @@ -827,7 +827,7 @@ GCFEvent::TResult BeamServer::beamalloc_state(GCFEvent& event, GCFPortInterface& IBSBeamallocackEvent beamallocack; if (ack.status == CAL_Protocol::CAL_SUCCESS) { // && -//TODO (ack.subarray.SPW().getNumSubbands() >= +//TODO (GET_CONFIG("CalServer.N_SUBBANDS", i) >= //TODO (int)itsBeamTransaction.getBeam()->allocation()().size()) ) { LOG_INFO_STR("Got subscription to subarray " << ack.subarray.name()); @@ -879,7 +879,7 @@ GCFEvent::TResult BeamServer::beamalloc_state(GCFEvent& event, GCFPortInterface& // Timer event may be from one of the pointing timers, ignore them LOG_INFO_STR(">>> TimerEvent on port " << port.getName() << " while in alloc state, ignoring it"); return (GCFEvent::HANDLED); -// return ((&port==itsAnaHeartbeat) ? GCFEvent::NEXT_STATE : GCFEvent::HANDLED); TODO: FIX THIS +// return ((&port==itsAnaHeartbeat) ? GCFEvent::NEXT_STATE : GCFEvent::HANDLED); // TODO: FIX THIS } IBSBeamallocackEvent beamallocack; LOG_ERROR_STR("Timeout on starting the calibration of beam " << itsBeamTransaction.getBeam()->name()); @@ -1109,14 +1109,19 @@ int BeamServer::beampointto_action(IBSPointtoEvent& ptEvent, // The analogue must be started several seconds before the observationstart time because the I2C bus // needs several seconds to pass the information - int activationTime = _idealStartTime(Timestamp::now().sec(), - ptEvent.pointing.time(), HBA_MIN_INTERVAL, - itsLastHBACalculationTime+itsHBAUpdateInterval, HBA_MIN_INTERVAL, itsHBAUpdateInterval); - int timeShift(ptEvent.pointing.time().sec() - activationTime); - // update pointing + int activationTime = _idealStartTime( Timestamp::now().sec(), + ptEvent.pointing.time(), + HBA_MIN_INTERVAL, + itsLastHBACalculationTime + itsHBAUpdateInterval, + HBA_MIN_INTERVAL, + itsHBAUpdateInterval); + + int timeShift(ptEvent.pointing.time().sec() - activationTime); + + // update pointing ptEvent.pointing.setTime(Timestamp(activationTime,0)); if (ptEvent.pointing.duration() && timeShift) { - ptEvent.pointing.setDuration(ptEvent.pointing.duration()+timeShift); + ptEvent.pointing.setDuration(ptEvent.pointing.duration()); LOG_INFO_STR("Extended duration of " << beamIter->second->name() << " with " << timeShift << " seconds because starttime was shifted"); } @@ -1127,7 +1132,9 @@ int BeamServer::beampointto_action(IBSPointtoEvent& ptEvent, return (IBS_UNKNOWN_BEAM_ERR); } - itsAnaHeartbeat->setTimer(activationTime - Timestamp::now().sec()); + itsAnaHeartbeat->cancelAllTimers(); + itsAnaHeartbeat->setTimer(activationTime - Timestamp::now().sec(), 0, itsHBAUpdateInterval, 0); // restart analog beam hartbeat + // itsAnaHeartbeat->setTimer(activationTime - Timestamp::now().sec()); LOG_INFO_STR("Analogue beam for beam " << beamIter->second->name() << " will be active at " << Timestamp(activationTime+HBA_MIN_INTERVAL, 0)); LOG_INFO_STR("Analogue pointing for beam " << beamIter->second->name() << " will be send at " << Timestamp(activationTime, 0)); @@ -1152,12 +1159,16 @@ int BeamServer::beampointto_action(IBSPointtoEvent& ptEvent, // int BeamServer::_idealStartTime (int now, int t1, int d1, int t2, int d2, int p2) const { - int t1start = t1-d1; // ideal starttime + int t1start = t1 - d1; // ideal starttime if (t1start < now) // not before now ofcourse t1start = now; - int nearestt2 = (t1start<=t2 ? t2 : t2+((t1-t2)/p2)*p2); - if (t1start > nearestt2 && t1start < nearestt2+d2) // not during heartbeat period - t1start = nearestt2; + + if (t1start <= (t2 - p2 + d2)) // if it is to fast after previous update + t1start = t2 - p2 + d2 + 1; + + //int nearestt2 = (t1start <= t2 ? t2 : t2 + ((t1 - t2) / p2) * p2); + //if (t1start > nearestt2 && t1start < nearestt2 + d2) // not during heartbeat period + // t1start = nearestt2; return (t1start); } diff --git a/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc b/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc index c53e2665b15..3005237338d 100644 --- a/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc +++ b/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc @@ -242,6 +242,7 @@ GCFEvent::TResult beamctl::create_subarray(GCFEvent& event, GCFPortInterface& po TRAN(beamctl::final); } else { cout << "Calserver accepted settings." << endl; + sleep(3 + 4); TRAN(beamctl::create_beam); } } diff --git a/MAC/APL/PAC/ITRFBeamServer/src/iBeamServer.conf.in b/MAC/APL/PAC/ITRFBeamServer/src/iBeamServer.conf.in index 751bdde9873..e96a31d9af9 100644 --- a/MAC/APL/PAC/ITRFBeamServer/src/iBeamServer.conf.in +++ b/MAC/APL/PAC/ITRFBeamServer/src/iBeamServer.conf.in @@ -11,10 +11,10 @@ BeamServer.EnableSetHBA = 1 BeamServer.EnableStaticCalibration = 1 # -# normal HBAUpdateInterval >= 10, for testing use +# normal HBAUpdateInterval >= 10, for testing use # (4, 6 or 8) secs. Note: 4 sec is absolute minimum! # -BeamServer.HBAUpdateInterval= 180 # >=10, normally 300 +BeamServer.HBAUpdateInterval= 10 # >=10, normally 10 BeamServer.UpdateInterval = 1 # should be 1 for normal operation BeamServer.ComputeInterval = 1 # can be 1 for ITRFBeamserver, was 10 for old BeamServer diff --git a/MAC/APL/PIC/RSP_Driver/src/CRSyncWrite.cc b/MAC/APL/PIC/RSP_Driver/src/CRSyncWrite.cc index 59e29812400..7974531e20e 100644 --- a/MAC/APL/PIC/RSP_Driver/src/CRSyncWrite.cc +++ b/MAC/APL/PIC/RSP_Driver/src/CRSyncWrite.cc @@ -42,13 +42,15 @@ using namespace RTC; CRSyncWrite::CRSyncWrite(GCFPortInterface& board_port, int board_id) : SyncAction(board_port, board_id, NR_BLPS_PER_RSPBOARD) { - - //ASSERTSTR(delaySteps.extent(firstDim) == NR_BLPS_PER_RSPBOARD, "Expected " << NR_BLPS_PER_RSPBOARD << + + //ASSERTSTR(delaySteps.extent(firstDim) == NR_BLPS_PER_RSPBOARD, "Expected " << NR_BLPS_PER_RSPBOARD << // " delay-steps not " << delaySteps.extent(firstDim)); itsDelays.resize(NR_BLPS_PER_RSPBOARD); itsCounters.resize(NR_BLPS_PER_RSPBOARD); - + itsDelays = 0; + itsCounters = 0; + //itsDelays.resize(delaySteps.shape()); //itsCounters.resize(delaySteps.shape()); //itsDelays = delaySteps; @@ -77,17 +79,17 @@ void CRSyncWrite::sendrequest() uint16 blpid(0); EPACrControlEvent CRSyncEvent; if (Cache::getInstance().getState().crcontrol().get(getBoardId()) == RTC::RegisterState::READ) { - + int sliceBegin = getBoardId() * NR_BLPS_PER_RSPBOARD; int sliceEnd = sliceBegin + NR_BLPS_PER_RSPBOARD - 1; itsDelays = Cache::getInstance().getBack().getPPSdelays()(Range(sliceBegin, sliceEnd)); - + setNumIndices(max(itsDelays)+1); - + // start of cycle, log unfinished cycles for (int b = 0; b < NR_BLPS_PER_RSPBOARD; b++) { if (itsCounters(b) && itsCounters(b) != itsDelays(b)) { - LOG_WARN(formatString("PPS delay[%d][%]: %d out of %d written", + LOG_WARN(formatString("PPS delay[%d][%d]: %d out of %d written", getBoardId(), b, itsDelays(b)-itsCounters(b), itsDelays(b))); } } @@ -104,7 +106,7 @@ void CRSyncWrite::sendrequest() blpid |= mask; itsCounters(b)--; } - } + } CRSyncEvent.hdr.set(MEPHeader::CR_SYNCDELAY_HDR, blpid); CRSyncEvent.control = 0x01; // SyncEdge=rise, SyncDelay=Increment } diff --git a/MAC/APL/PIC/RSP_Driver/src/Cache.cc b/MAC/APL/PIC/RSP_Driver/src/Cache.cc index e8c130e2cc8..9cfa3510045 100644 --- a/MAC/APL/PIC/RSP_Driver/src/Cache.cc +++ b/MAC/APL/PIC/RSP_Driver/src/Cache.cc @@ -141,7 +141,7 @@ CacheBuffer::CacheBuffer(Cache* cache) : m_cache(cache) m_clock = GET_CONFIG("RSPDriver.DEFAULT_SAMPLING_FREQUENCY", i); reset(); // reset by allocating memory and settings default values - + // print sizes of the cache LOG_DEBUG_STR("m_beamletweights().size() =" << m_beamletweights().size() * sizeof(complex<int16>)); LOG_DEBUG_STR("m_subbandselection.crosslets().size()=" << m_subbandselection.crosslets().size() * sizeof(uint16)); @@ -176,22 +176,22 @@ CacheBuffer::CacheBuffer(Cache* cache) : m_cache(cache) LOG_DEBUG_STR("itsFixedAttenuations.size() =" << sizeof(itsFixedAttenuations)); LOG_DEBUG_STR("itsAttenuationStepSize.size() =" << sizeof(itsAttenuationStepSize)); LOG_INFO_STR(formatString("CacheBuffer size = %d bytes", - m_beamletweights().size() - + m_subbandselection.crosslets().size() - + m_subbandselection.beamlets().size() - + m_rcusettings().size() - + m_hbasettings().size() - + m_hbareadings().size() - + m_rsusettings().size() - + m_wgsettings().size() - + m_subbandstats().size() - + m_beamletstats().size() - + m_xcstats().size() + m_beamletweights().size() + + m_subbandselection.crosslets().size() + + m_subbandselection.beamlets().size() + + m_rcusettings().size() + + m_hbasettings().size() + + m_hbareadings().size() + + m_rsusettings().size() + + m_wgsettings().size() + + m_subbandstats().size() + + m_beamletstats().size() + + m_xcstats().size() + m_systemstatus.board().size() - + m_versions.bp().size() - + m_versions.ap().size() - + m_tdstatus.board().size() - + m_spustatus.subrack().size() + + m_versions.bp().size() + + m_versions.ap().size() + + m_tdstatus.board().size() + + m_spustatus.subrack().size() + m_tbbsettings().size() + m_bypasssettings().size() + m_bypasssettings_bp().size() @@ -249,10 +249,10 @@ void CacheBuffer::reset(void) itsBitsPerSample = MAX_BITS_PER_SAMPLE; itsSDOBitsPerSample = MAX_BITS_PER_SAMPLE; - - m_beamletweights().resize( BeamletWeights::SINGLE_TIMESTEP, + + m_beamletweights().resize( BeamletWeights::SINGLE_TIMESTEP, StationSettings::instance()->nrRcus(), - MAX_NR_BM_BANKS, + MAX_NR_BM_BANKS, MEPHeader::N_BEAMLETS); m_beamletweights() = complex<int16>(25,36); // TODO remove this code!!! @@ -274,7 +274,7 @@ void CacheBuffer::reset(void) (MAX_BITS_PER_SAMPLE/MIN_BITS_PER_SAMPLE), MEPHeader::N_BEAMLETS ); m_subbandselection.beamlets() = 0; - + if (GET_CONFIG("RSPDriver.IDENTITY_WEIGHTS", i)) { // these weights ensure that the beamlet statistics // exactly match the subband statistics @@ -285,7 +285,7 @@ void CacheBuffer::reset(void) // int firstSubband = GET_CONFIG("RSPDriver.FIRST_SUBBAND", i); for (int rcu = 0; rcu < m_subbandselection.beamlets().extent(firstDim); rcu++) { - for (int bank = 0; bank < (MAX_BITS_PER_SAMPLE/MIN_BITS_PER_SAMPLE); bank++) { + for (int bank = 0; bank < (MAX_BITS_PER_SAMPLE/MIN_BITS_PER_SAMPLE); bank++) { for (int lane = 0; lane < MEPHeader::N_SERDES_LANES; lane++) { int start(lane*(MEPHeader::N_BEAMLETS/MEPHeader::N_SERDES_LANES)); int stop (start + maxBeamletsPerRSP(itsBitsPerSample)); @@ -333,9 +333,9 @@ void CacheBuffer::reset(void) m_subbandstats().resize(StationSettings::instance()->nrRcus(), MEPHeader::N_SUBBANDS); m_subbandstats() = 0; - + // Number of cep streams -> in normal mode 4, in splitmode 8. - int maxStreams = 8; + int maxStreams = 8; m_beamletstats().resize((maxStreams/MEPHeader::N_SERDES_LANES) * N_POL, (MAX_BITS_PER_SAMPLE/MIN_BITS_PER_SAMPLE) * MEPHeader::N_BEAMLETS); m_beamletstats() = 0; @@ -382,14 +382,14 @@ void CacheBuffer::reset(void) m_bypasssettings().resize(StationSettings::instance()->nrBlps()); BypassSettings::Control control; m_bypasssettings() = control; - + // BypassSettings (BP) LOG_INFO_STR("Resizing bypass array to: " << StationSettings::instance()->maxRspBoards()); m_bypasssettings_bp().resize(StationSettings::instance()->maxRspBoards()); BypassSettings::Control controlbp; controlbp.setSDO(1); m_bypasssettings_bp() = controlbp; - + // clear rawdatablock itsRawDataBlock.address = 0; itsRawDataBlock.offset = 0; @@ -404,40 +404,40 @@ void CacheBuffer::reset(void) // clear I2C flag itsI2Cuser = NONE; - + // set Splitter not active itsSplitterActive = false; // set CEP port enabled itsCepEnabled0 = false; itsCepEnabled1 = false; - + // Latency status itsLatencys().resize(StationSettings::instance()->nrRspBoards()); RADLatency radlatencyinit; memset(&radlatencyinit, 0, sizeof(RADLatency)); itsLatencys() = radlatencyinit; itsSwappedXY.reset(); - + // BitMode itsBitModeInfo().resize(StationSettings::instance()->nrRspBoards()); RSRBeamMode bitmodeinfo; bitmodeinfo.bm_select = 0; bitmodeinfo.bm_max = 0; itsBitModeInfo() = bitmodeinfo; - + // SDO default Mode selection int sdo_mode = 0; int bits_per_sample = GET_CONFIG("RSPDriver.SDO_MODE", i); if (bits_per_sample == 8) { sdo_mode = 1; } else if (bits_per_sample == 5) { sdo_mode = 2; } else if (bits_per_sample == 4) { sdo_mode = 3; } - + itsSDOModeInfo().resize(StationSettings::instance()->nrRspBoards()); RSRSDOMode sdomodeinfo; sdomodeinfo.bm_select = sdo_mode; sdomodeinfo.bm_max = 3; itsSDOModeInfo() = sdomodeinfo; - + // SDO default subband selection itsSDOSelection.subbands().resize(StationSettings::instance()->nrRcus(), (MAX_BITS_PER_SAMPLE/MIN_BITS_PER_SAMPLE), @@ -455,7 +455,7 @@ void CacheBuffer::reset(void) } // for each bank } readPPSdelaySettings(); - + itsAttenuationStepSize = 0.25; try { itsAttenuationStepSize = GET_CONFIG("RSPDriver.ATT_STEP_SIZE", f); } catch (APSException&) { LOG_INFO_STR("RSPDriver.ATT_STEP_SIZE not found"); } @@ -467,13 +467,13 @@ void CacheBuffer::reset(void) itsFixedAttenuations(rcumode) = 0.0; try { itsFixedAttenuations(rcumode) = GET_CONFIG(key, f); } catch (APSException&) { LOG_INFO_STR(formatString("RSPDriver.FIXED_ATT_MODE_%d not found", rcumode)); } - } + } } SerdesBuffer& CacheBuffer::getSdsReadBuffer(int rspBoardNr) { - ASSERTSTR(rspBoardNr >= 0 && rspBoardNr < MAX_N_RSPBOARDS, + ASSERTSTR(rspBoardNr >= 0 && rspBoardNr < MAX_N_RSPBOARDS, "RSPboard index out of range in getting serdesReadBuffer: " << rspBoardNr); return (itsSdsReadBuffer[rspBoardNr]); } @@ -488,7 +488,11 @@ void CacheBuffer::setTimestamp(const RTC::Timestamp& timestamp) // void CacheBuffer::readPPSdelaySettings() { - ConfigLocator CL; + if (m_clock == 0) { + LOG_INFO_STR("Skip loading PPS delay settings, clock = 0 MHz"); + return; + } + ConfigLocator CL; string filename = (CL.locate(GET_CONFIG_STRING(formatString("RSPDriver.PPSdelayFile%u", m_clock)))); LOG_DEBUG_STR("Trying to load the PPS delay settings for " << m_clock << " MHz from file: " << filename); @@ -523,7 +527,7 @@ void CacheBuffer::readPPSdelaySettings() } else { LOG_ERROR_STR("File " << filename << " contains " << delayValues.extent(firstDim) - << " values, expected " << nrRspBoards * NR_BLPS_PER_RSPBOARD + << " values, expected " << nrRspBoards * NR_BLPS_PER_RSPBOARD << " values, WILL NOT USE THEM!"); } std::ostringstream logStream; @@ -598,8 +602,8 @@ void Cache::resetI2Cuser() } m_front->setI2Cuser(busUser); m_back->setI2Cuser (busUser); - LOG_INFO_STR("new I2Cuser = " << ((busUser == NONE) ? "NONE" : - ((busUser == HBA) ? "HBA" : + LOG_INFO_STR("new I2Cuser = " << ((busUser == NONE) ? "NONE" : + ((busUser == HBA) ? "HBA" : ((busUser == RCU_R) ? "RCU_R" : "RCU_W")))); } diff --git a/MAC/APL/PIC/RSP_Driver/src/GetRCUCmd.cc b/MAC/APL/PIC/RSP_Driver/src/GetRCUCmd.cc index 5e49ffc29be..8a20f977cad 100644 --- a/MAC/APL/PIC/RSP_Driver/src/GetRCUCmd.cc +++ b/MAC/APL/PIC/RSP_Driver/src/GetRCUCmd.cc @@ -66,7 +66,7 @@ void GetRCUCmd::ack(CacheBuffer& cache) ack.settings()(result_rcu).setRaw(cache.getRCUSettings()()(cache_rcu).getRaw()); } else { - LOG_WARN(formatString("invalid RCU index %d, there are only %d RCU's", cache_rcu, + LOG_WARN(formatString("invalid RCU index %d, there are only %d RCU's", cache_rcu, StationSettings::instance()->nrRcus())); } @@ -82,8 +82,8 @@ void GetRCUCmd::apply(CacheBuffer& cache, bool setModFlag) { // someone else using the I2C bus? I2Cuser busUser = cache.getI2Cuser(); - LOG_INFO_STR("GetRCU::apply : " << ((busUser == NONE) ? "NONE" : - ((busUser == HBA) ? "HBA" : + LOG_DEBUG_STR("GetRCU::apply : " << ((busUser == NONE) ? "NONE" : + ((busUser == HBA) ? "HBA" : ((busUser == RCU_R) ? "RCU_R" : "RCU_W")))); if ((cache.getI2Cuser() != NONE) && (cache.getI2Cuser() != RCU_R)) { postponeExecution(true); @@ -96,7 +96,7 @@ void GetRCUCmd::apply(CacheBuffer& cache, bool setModFlag) for (int cache_rcu = 0; cache_rcu < StationSettings::instance()->nrRcus(); cache_rcu++) { if (setModFlag && m_event->rcumask.test(cache_rcu)) { cache.getCache().getState().rcuread().write(cache_rcu); - } + } } // for } diff --git a/MAC/APL/PIC/RSP_Driver/src/GetSplitterCmd.cc b/MAC/APL/PIC/RSP_Driver/src/GetSplitterCmd.cc index a38993cfdaf..15385044160 100644 --- a/MAC/APL/PIC/RSP_Driver/src/GetSplitterCmd.cc +++ b/MAC/APL/PIC/RSP_Driver/src/GetSplitterCmd.cc @@ -127,10 +127,11 @@ void GetSplitterCmd::apply(CacheBuffer& cache, bool setModFlag) // the sdsReadBuffer if for storing the results. SerdesBuffer& SerdesBuf = cache.getSdsWriteBuffer(); SerdesBuf.newCommand(ASK_SERDES_CMD, ASK_SERDES_CMD_LEN); +#if 0 string hd; hexdump(hd, SerdesBuf.getBufferPtr(), SerdesBuf.getDataLen()); LOG_INFO_STR(hd); - +#endif // mark registers that the serdes registers should be written. if (setModFlag) { for (int b= 0; b < StationSettings::instance()->nrRspBoards(); b++) { diff --git a/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.cc b/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.cc index 63a2e252057..5b8ccb4bfa8 100644 --- a/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.cc +++ b/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.cc @@ -53,41 +53,41 @@ namespace LOFAR { // the indices that should be patched. // int HBAProtocolWrite::i2c_protocol_patch_indices[] = { - 8, 9, - 51 + ( 0*17), // stride is 17, server 1 - 51 + ( 1*17), - 51 + ( 2*17), - 51 + ( 3*17), - 51 + ( 4*17), - 51 + ( 5*17), - 51 + ( 6*17), - 51 + ( 7*17), - 51 + ( 8*17), - 51 + ( 9*17), - 51 + (10*17), - 51 + (11*17), - 51 + (12*17), - 51 + (13*17), - 51 + (14*17), - 51 + (15*17), // server 16 + 13, 14, + 56 + ( 0*17), // stride is 17, server 1 + 56 + ( 1*17), + 56 + ( 2*17), + 56 + ( 3*17), + 56 + ( 4*17), + 56 + ( 5*17), + 56 + ( 6*17), + 56 + ( 7*17), + 56 + ( 8*17), + 56 + ( 9*17), + 56 + (10*17), + 56 + (11*17), + 56 + (12*17), + 56 + (13*17), + 56 + (14*17), + 56 + (15*17), // server 16 }; int HBAProtocolWrite::i2c_result_patch_indices[] = { - 4 + ( 0*7), // stride is 7, server 1 - 4 + ( 1*7), - 4 + ( 2*7), - 4 + ( 3*7), - 4 + ( 4*7), - 4 + ( 5*7), - 4 + ( 6*7), - 4 + ( 7*7), - 4 + ( 8*7), - 4 + ( 9*7), - 4 + (10*7), - 4 + (11*7), - 4 + (12*7), - 4 + (13*7), - 4 + (14*7), - 4 + (15*7), // server 16 + 5 + ( 0*7), // stride is 7, server 1 + 5 + ( 1*7), + 5 + ( 2*7), + 5 + ( 3*7), + 5 + ( 4*7), + 5 + ( 5*7), + 5 + ( 6*7), + 5 + ( 7*7), + 5 + ( 8*7), + 5 + ( 9*7), + 5 + (10*7), + 5 + (11*7), + 5 + (12*7), + 5 + (13*7), + 5 + (14*7), + 5 + (15*7), // server 16 }; #ifndef HBA_WRITE_DELAYS @@ -107,23 +107,29 @@ namespace LOFAR { 0x13, // PROTOCOL_C_END }; - uint8 HBAProtocolWrite::i2c_result[HBAProtocolWrite::RESULT_SIZE] - = { - // Expected protocol result (4 bytes) - 0x00, // PROTOCOL_WRITE_BYTE went OK + uint8 HBAProtocolWrite::i2c_result[HBAProtocolWrite::RESULT_SIZE] + = { + // Expected protocol result (4 bytes) + 0x00, // PROTOCOL_WRITE_BYTE went OK - 0xBB, // Read LED value - 0x00, // PROTOCOL_READ_BYTE went OK + 0xBB, // Read LED value + 0x00, // PROTOCOL_READ_BYTE went OK - 0x00, // PROTOCOL_C_END went OK - }; + 0x00, // PROTOCOL_C_END went OK + }; #else - uint8 HBAProtocolWrite::i2c_protocol[HBAProtocolWrite::PROTOCOL_SIZE/* 42 + 5 + 16 * 17 + 1 = 320 bytes*/] + uint8 HBAProtocolWrite::i2c_protocol[HBAProtocolWrite::PROTOCOL_SIZE/* 5 + 42 + 5 + 16 * 17 + 1 = 325 bytes*/] = { // Table 32 t/m 37 from LOFAR-ASTRON-MEM-175 (Revision 1.3) + 0x12, // PROTOCOL_C_WAIT, used to prevent power peeks. + 0x00, // <<< replace with data, wait byte 0 >>> + 0x00, // <<< replace with data, wait byte 1 >>> + 0x00, // <<< replace with data, wait byte 2 >>> + 0x00, // <<< replace with data, wait byte 3 >>> + // Table 32 // Instruct client to do a broadcast access to the 16 HBA delay servers (42 bytes) 0x0D, // PROTOCOL_C_WRITE_BLOCK_NO_CNT @@ -136,8 +142,8 @@ namespace LOFAR { 0, // Server X-delay register ID 0, // First server (initialization will add offset) 15, // Last server - 0xBB, // X-delay data for server 0 (offset = PROTOCOL_DELAY_OFFSET) - 0xBB, // Y-delay data for server 0 + 0xBB, // X-delay data for server 0 (offset = PROTOCOL_DELAY_OFFSET) + 0xBB, // Y-delay data for server 0 0xBB, // X-delay data for server 1 0xBB, // Y-delay data for server 1 0xBB, // X-delay data for server 2 @@ -182,15 +188,16 @@ namespace LOFAR { 4>>1, // Client i2c slave address 0, // Request register access command 4, // Count of number of data bytes to write - 0, // Server address 0 + 0, // Server address 0 3, // Payload length, including this octet 5, // Function: get word (document LOFAR-ASTRON-MEM-175 Revision 1.3 has an error here (value 2)) 0, // Server X-delay register ID - + // Table 35 // Wait to allow for the multicast to finish 0x12, // PROTOCOL_C_WAIT (5 bytes) - 0x00, 0xA8, 0x61, 0x00, // 0x0061A800 = 6.4e6 * 5ns = 32ms pause between write request register and read response register + + 0x00, 0xA8, 0x61, 0x00, // 0x0061A800 = 6.4e6 * 5ns = 32ms pause between write request register and read response register // 0x00, 0x09, 0x3D, 0x00, // 0x003D0900 = 4e6 * 5ns = 20ms pause between write request register and read response register // Table 36 @@ -201,28 +208,30 @@ namespace LOFAR { 4, // Count of number of data bytes to read (document LOFAR-ASTRON-MEM-175 has an error here (value 3)) // Repeat tables 34,35,36 fo servers 1 to 15 - 0x0D, 4>>1, 0, 4, 1, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 1 - 0x0D, 4>>1, 0, 4, 2, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 2 - 0x0D, 4>>1, 0, 4, 3, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 3 - 0x0D, 4>>1, 0, 4, 4, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 4 - 0x0D, 4>>1, 0, 4, 5, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 5 - 0x0D, 4>>1, 0, 4, 6, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 6 - 0x0D, 4>>1, 0, 4, 7, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 7 - 0x0D, 4>>1, 0, 4, 8, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 8 - 0x0D, 4>>1, 0, 4, 9, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 9 - 0x0D, 4>>1, 0, 4, 10, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 10 - 0x0D, 4>>1, 0, 4, 11, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 11 - 0x0D, 4>>1, 0, 4, 12, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 12 - 0x0D, 4>>1, 0, 4, 13, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 13 - 0x0D, 4>>1, 0, 4, 14, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 14 - 0x0D, 4>>1, 0, 4, 15, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 15 + 0x0D, 4>>1, 0, 4, 1, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 1 + 0x0D, 4>>1, 0, 4, 2, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 2 + 0x0D, 4>>1, 0, 4, 3, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 3 + 0x0D, 4>>1, 0, 4, 4, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 4 + 0x0D, 4>>1, 0, 4, 5, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 5 + 0x0D, 4>>1, 0, 4, 6, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 6 + 0x0D, 4>>1, 0, 4, 7, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 7 + 0x0D, 4>>1, 0, 4, 8, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 8 + 0x0D, 4>>1, 0, 4, 9, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 9 + 0x0D, 4>>1, 0, 4, 10, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 10 + 0x0D, 4>>1, 0, 4, 11, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 11 + 0x0D, 4>>1, 0, 4, 12, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 12 + 0x0D, 4>>1, 0, 4, 13, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 13 + 0x0D, 4>>1, 0, 4, 14, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 14 + 0x0D, 4>>1, 0, 4, 15, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 15 // Mark the end of the protocol list (1 byte) 0x13, // PROTOCOL_C_END }; - uint8 HBAProtocolWrite::i2c_result[HBAProtocolWrite::RESULT_SIZE/* 2 + 7*16 + 1 = 115 bytes*/] + uint8 HBAProtocolWrite::i2c_result[HBAProtocolWrite::RESULT_SIZE/* 3 + 7*16 + 1 = 116 bytes*/] = { + 0x00, // PROTOCOL_C_WAIT went OK + 0x00, // PROTOCOL_C_WRITE_BLOCK_NO_CNT went OK 0x00, // PROTOCOL_C_WAIT went OK @@ -236,21 +245,21 @@ namespace LOFAR { 0xBB, // Y-delay data from server X 0x00, // PROTOCOL_C_READ_BLOCK_NO_CNT went OK - 0, 0, 1 + 128, 3, 0xBB, 0xBB, 0, // server 1 result - 0, 0, 2 + 128, 3, 0xBB, 0xBB, 0, // server 2 result - 0, 0, 3 + 128, 3, 0xBB, 0xBB, 0, // server 3 result - 0, 0, 4 + 128, 3, 0xBB, 0xBB, 0, // server 4 result - 0, 0, 5 + 128, 3, 0xBB, 0xBB, 0, // server 5 result - 0, 0, 6 + 128, 3, 0xBB, 0xBB, 0, // server 6 result - 0, 0, 7 + 128, 3, 0xBB, 0xBB, 0, // server 7 result - 0, 0, 8 + 128, 3, 0xBB, 0xBB, 0, // server 8 result - 0, 0, 9 + 128, 3, 0xBB, 0xBB, 0, // server 9 result - 0, 0, 10 + 128, 3, 0xBB, 0xBB, 0, // server 10 result - 0, 0, 11 + 128, 3, 0xBB, 0xBB, 0, // server 11 result - 0, 0, 12 + 128, 3, 0xBB, 0xBB, 0, // server 12 result - 0, 0, 13 + 128, 3, 0xBB, 0xBB, 0, // server 13 result - 0, 0, 14 + 128, 3, 0xBB, 0xBB, 0, // server 14 result - 0, 0, 15 + 128, 3, 0xBB, 0xBB, 0, // server 15 result + 0x00, 0x00, 1 + 128, 3, 0xBB, 0xBB, 0x00, // server 1 result + 0x00, 0x00, 2 + 128, 3, 0xBB, 0xBB, 0x00, // server 2 result + 0x00, 0x00, 3 + 128, 3, 0xBB, 0xBB, 0x00, // server 3 result + 0x00, 0x00, 4 + 128, 3, 0xBB, 0xBB, 0x00, // server 4 result + 0x00, 0x00, 5 + 128, 3, 0xBB, 0xBB, 0x00, // server 5 result + 0x00, 0x00, 6 + 128, 3, 0xBB, 0xBB, 0x00, // server 6 result + 0x00, 0x00, 7 + 128, 3, 0xBB, 0xBB, 0x00, // server 7 result + 0x00, 0x00, 8 + 128, 3, 0xBB, 0xBB, 0x00, // server 8 result + 0x00, 0x00, 9 + 128, 3, 0xBB, 0xBB, 0x00, // server 9 result + 0x00, 0x00, 10 + 128, 3, 0xBB, 0xBB, 0x00, // server 10 result + 0x00, 0x00, 11 + 128, 3, 0xBB, 0xBB, 0x00, // server 11 result + 0x00, 0x00, 12 + 128, 3, 0xBB, 0xBB, 0x00, // server 12 result + 0x00, 0x00, 13 + 128, 3, 0xBB, 0xBB, 0x00, // server 13 result + 0x00, 0x00, 14 + 128, 3, 0xBB, 0xBB, 0x00, // server 14 result + 0x00, 0x00, 15 + 128, 3, 0xBB, 0xBB, 0x00, // server 15 result // Marks the end of the protocol list (1 byte) 0x00, // PROTOCOL_C_END went OK @@ -309,7 +318,7 @@ void HBAProtocolWrite::sendrequest() setContinue(true); return; } - + if ((regStates.hbaprotocol().get(global_blp * N_POL) != RTC::RegisterState::WRITE) && (regStates.hbaprotocol().get(global_blp * N_POL + 1) != RTC::RegisterState::WRITE)) { regStates.hbaprotocol().unmodified(global_blp * N_POL); @@ -330,14 +339,14 @@ void HBAProtocolWrite::sendrequest() neverDeleteData); // add extra wait to prevent power peaks when switching - // 1 tick = 5ns, 16000000 (80ms), 600000 (3ms) - uint32 wait = 16000000 + (600000 * global_blp); - LOG_DEBUG_STR(formatString("HBAPotocolWrite add wait %f sec", wait * (1./200e6))); - memcpy(i2c_protocol + PROTOCOL_WAIT_OFFSET, &wait, 4); + // 200MHz, 1 tick = 5ns, 600000 = 3ms + uint32 wait = 600000 * global_blp; + LOG_INFO_STR(formatString("HBAPotocolWrite add wait %f sec for global_blp %d", wait * (1./200e6), global_blp)); + memcpy(i2c_protocol + PROTOCOL_WAIT_OFFSET_1, &wait, 4); delays(Range::all(), 0) = Cache::getInstance().getBack().getHBASettings()()(global_blp * N_POL, Range::all()); delays(Range::all(), 1) = Cache::getInstance().getBack().getHBASettings()()(global_blp * N_POL + 1, Range::all()); - + // copy set delays to i2c_result which is the expected result uint8* cur = i2c_result + RESULT_DELAY_OFFSET; for (int elem = 0; elem < N_HBA_ELEM_PER_TILE; elem++){ @@ -355,13 +364,13 @@ void HBAProtocolWrite::sendrequest() EPARcuProtocolEvent rcuprotocol; rcuprotocol.hdr.set(MEPHeader::RCU_PROTOCOLY_HDR, 1 << (getCurrentIndex() / N_WRITES), MEPHeader::WRITE, sizeof(i2c_protocol)); rcuprotocol.protocol.setBuffer(i2c_protocol, sizeof(i2c_protocol)); - -#if 0 + +#if 0 string tmpbuf; hexdump (tmpbuf, i2c_protocol, sizeof(i2c_protocol)); LOG_INFO_STR("HBA WRITE: " << tmpbuf); -#endif - +#endif + m_hdr = rcuprotocol.hdr; // remember header to match with ack getBoardPort().send(rcuprotocol); } @@ -376,13 +385,13 @@ void HBAProtocolWrite::sendrequest() uint8 clear[RESULT_SIZE]; memset(clear, 0xBB, RESULT_SIZE); // clear result rcuresultwrite.payload.setBuffer(clear, RESULT_SIZE); - -#if 0 + +#if 0 string tmpbuf; hexdump (tmpbuf, clear, sizeof(clear)); LOG_INFO_STR("HBA RESULT WRITE: " << tmpbuf); -#endif - +#endif + m_hdr = rcuresultwrite.hdr; // remember header to match with ack getBoardPort().send(rcuresultwrite); } @@ -414,7 +423,7 @@ GCFEvent::TResult HBAProtocolWrite::handleack(GCFEvent& event, GCFPortInterface& return GCFEvent::NOT_HANDLED; } - if ((getCurrentIndex() % N_WRITES) == 1) { + if ((getCurrentIndex() % N_WRITES) == 1) { // Mark modification as applied when write of RCU result register has completed Cache::getInstance().getState().hbaprotocol().schedule_wait2read(global_blp * N_POL); Cache::getInstance().getState().hbaprotocol().schedule_wait2read(global_blp * N_POL + 1); diff --git a/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.h b/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.h index c97e6b1ccea..2cbe71ff561 100644 --- a/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.h +++ b/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.h @@ -71,18 +71,19 @@ namespace LOFAR { #define HBA_WRITE_DELAYS #ifdef HBA_WRITE_DELAYS - static const int PROTOCOL_SIZE = 320; - static const int RESULT_SIZE = 115; - - static const int PROTOCOL_WAIT_OFFSET = 43; // offset of wait settings in i2c_protocol - static const int PROTOCOL_DELAY_OFFSET = 10; // offset of delay settings in i2c_protocol - static const int RESULT_DELAY_OFFSET = 6; // offset of delay settings in i2c_result - static const int RESULT_DELAY_STRIDE = 7; // nof bytes to next pair of X,Y delays + static const int PROTOCOL_SIZE = 325; + static const int RESULT_SIZE = 116; + + static const int PROTOCOL_WAIT_OFFSET_1 = 1; // offset of wait settings in i2c_protocol + static const int PROTOCOL_WAIT_OFFSET_2 = 48; // offset of wait settings in i2c_protocol + static const int PROTOCOL_DELAY_OFFSET = 15; // offset of delay settings in i2c_protocol + static const int RESULT_DELAY_OFFSET = 7; // offset of delay settings in i2c_result + static const int RESULT_DELAY_STRIDE = 7; // nof bytes to next pair of X,Y delays #else - static const int PROTOCOL_SIZE = 8; - static const int RESULT_SIZE = 4; - static const int PROTOCOL_LED_OFFSET = 3; - static const int RESULT_LED_OFFSET = 1; + static const int PROTOCOL_SIZE = 8; + static const int RESULT_SIZE = 4; + static const int PROTOCOL_LED_OFFSET = 3; + static const int RESULT_LED_OFFSET = 1; #endif static int i2c_protocol_patch_indices[]; @@ -100,5 +101,5 @@ namespace LOFAR { }; }; }; - + #endif /* HBAPROTOCOLWRITE_H_ */ diff --git a/MAC/APL/PIC/RSP_Driver/src/HBAResultRead.cc b/MAC/APL/PIC/RSP_Driver/src/HBAResultRead.cc index 9ed44cafa1f..40e53b2d5b0 100644 --- a/MAC/APL/PIC/RSP_Driver/src/HBAResultRead.cc +++ b/MAC/APL/PIC/RSP_Driver/src/HBAResultRead.cc @@ -83,7 +83,7 @@ GCFEvent::TResult HBAResultRead::handleack(GCFEvent& event, GCFPortInterface& /* LOG_WARN("HBAResultRead::handleack:: unexpected ack"); return GCFEvent::NOT_HANDLED; } - + EPARcuResultEvent ack(event); uint8 global_blp = (getBoardId() * NR_BLPS_PER_RSPBOARD) + getCurrentIndex(); @@ -126,7 +126,7 @@ GCFEvent::TResult HBAResultRead::handleack(GCFEvent& event, GCFPortInterface& /* Cache::getInstance().getState().hbaprotocol().read_ack(global_blp * N_POL); Cache::getInstance().getState().hbaprotocol().read_ack(global_blp * N_POL + 1); } else { - LOG_WARN_STR("HBAResultRead: unexpected I2C result response for element(s):" << faultyElements << + LOG_WARN_STR("HBAResultRead: unexpected I2C result response for element(s):" << faultyElements << " of antenna " << (int)(global_blp)); Cache::getInstance().getState().hbaprotocol().read_error(global_blp * N_POL); Cache::getInstance().getState().hbaprotocol().read_error(global_blp * N_POL + 1); diff --git a/MAC/APL/PIC/RSP_Driver/src/RCUProtocolWrite.cc b/MAC/APL/PIC/RSP_Driver/src/RCUProtocolWrite.cc index 109b2b766ca..6edaad7eae7 100644 --- a/MAC/APL/PIC/RSP_Driver/src/RCUProtocolWrite.cc +++ b/MAC/APL/PIC/RSP_Driver/src/RCUProtocolWrite.cc @@ -50,7 +50,7 @@ namespace LOFAR { 0x00, // <<< replace with data, wait byte 0 >>> 0x00, // <<< replace with data, wait byte 1 >>> 0x00, // <<< replace with data, wait byte 2 >>> - 0x00, // <<< replace with data, wait byte 3 >>> + 0x00, // <<< replace with data, wait byte 3 >>> 0x0F, // PROTOCOL_C_SEND_BLOCK 0x01, // I2C address for RCU 0x03, // size @@ -63,14 +63,14 @@ namespace LOFAR { 0x13, // PROTOCOL_C_END }; - uint8 RCUProtocolWrite::i2c_protocol_read[] = { + uint8 RCUProtocolWrite::i2c_protocol_read[] = { 0x10, // PROTOCOL_C_RECEIVE_BLOCK 0x01, // I2C adress for RCU 0x03, // requested size 0x13, // PROTOCOL_C_END }; - uint8 RCUProtocolWrite::i2c_result_write[] = { + uint8 RCUProtocolWrite::i2c_result_write[] = { 0x00, // PROTOCOL_C_WAIT OK 0x00, // PROTOCOL_C_SEND_BLOCK OK 0xAA, // <<< replace with expected data >>> @@ -80,7 +80,7 @@ namespace LOFAR { 0x00, // PROTOCOL_C_END OK }; - uint8 RCUProtocolWrite::i2c_result_read[] = { + uint8 RCUProtocolWrite::i2c_result_read[] = { 0xAA, // <<< replace with expected data >>> 0xAA, // <<< replace with expected data >>> 0xAA, // <<< replace with expected data >>> @@ -143,19 +143,19 @@ void RCUProtocolWrite::sendrequest() if (writeCmdRequested) { // reverse and copy control bytes into i2c_protocol_write RCUSettings::Control& rcucontrol = Cache::getInstance().getBack().getRCUSettings()()((global_rcu)); - + // add waits while turning on hbas to reduce power peaks. // if RCU enable changed or rcumode changed // if rcumode > 0 if (rcucontrol.isModeModified()) { // wait between two RCUs is set to maximum, so that an international station // running on 160MHz clock can finisch the job in 1 second. - // in clock ticks, 1500000 = 7.5msec on 200MHz, 9.4msec on 160MHz - uint32 wait = 660000 * global_rcu; + // in clock ticks, 600000 = 3msec on 200MHz + uint32 wait = 600000 * global_rcu; LOG_DEBUG_STR(formatString("RCUProtocolWrite add wait rcu %d = %f sec", global_rcu, wait * (1./200e6))); memcpy(i2c_protocol_write+1, &wait, 4); } - + uint32 control = htonl(rcucontrol.getRaw()); memcpy(i2c_protocol_write+8, &control, 3); @@ -230,10 +230,10 @@ GCFEvent::TResult RCUProtocolWrite::handleack(GCFEvent& event, GCFPortInterface& if ((getCurrentIndex() % N_WRITES) == 1) { // Mark modification as applied when write of RCU result register has completed if (m_hdr.m_fields.payload_length == RESULT_WRITE_SIZE) { - Cache::getInstance().getState().rcuprotocol().schedule_wait1read(global_rcu); + Cache::getInstance().getState().rcuprotocol().schedule_wait2read(global_rcu); } else { - Cache::getInstance().getState().rcuread().schedule_wait1read(global_rcu); + Cache::getInstance().getState().rcuread().schedule_wait2read(global_rcu); } } diff --git a/MAC/APL/PIC/RSP_Driver/src/Scheduler.cc b/MAC/APL/PIC/RSP_Driver/src/Scheduler.cc index d7d47622f41..cddd07f4dbe 100644 --- a/MAC/APL/PIC/RSP_Driver/src/Scheduler.cc +++ b/MAC/APL/PIC/RSP_Driver/src/Scheduler.cc @@ -115,7 +115,8 @@ GCFEvent::TResult Scheduler::run(GCFEvent& event, GCFPortInterface& /*port*/) if (timeout->usec >= 1e6-1000) { topofsecond++; // round to next whole second } - if (timeout->usec > 2000 && timeout->usec < 1e6 - 2000) { + //if (timeout->usec > 2000 && timeout->usec < 1e6 - 2000) { + if (timeout->usec > 15000 && timeout->usec < 1e6 - 15000) { // changed to 15000, because itsPPSdelay in RSPDriver is added to sync to hardware LOG_WARN_STR("Scheduler time too far off from top off second: usec=" << timeout->usec); } setCurrentTime(topofsecond, 0); @@ -135,7 +136,7 @@ GCFEvent::TResult Scheduler::run(GCFEvent& event, GCFPortInterface& /*port*/) // // dispatch (event, port) // -// Dispatch the(ack) message to the right SyncAction and wake up the next SyncAction in the queue +// Dispatch the(ack) message to the right SyncAction and wake up the next SyncAction in the queue // when the current SyncAction is complete. GCFEvent::TResult Scheduler::dispatch(GCFEvent& event, GCFPortInterface& port) { @@ -148,7 +149,7 @@ GCFEvent::TResult Scheduler::dispatch(GCFEvent& event, GCFPortInterface& port) // has not yet reached its 'final' state. vector<SyncAction*>::iterator sa; int i(0); - // note: m_syncaction = map< GCFPortInterface*, std::vector<SyncAction*> > + // note: m_syncaction = map< GCFPortInterface*, std::vector<SyncAction*> > for (sa = m_syncactions[&port].begin(); sa != m_syncactions[&port].end(); sa++, i++) { if (!(*sa)->hasCompleted()) { // stil busy @@ -165,7 +166,7 @@ GCFEvent::TResult Scheduler::dispatch(GCFEvent& event, GCFPortInterface& port) else { // this SyncAction was ready, pass a timer event to the next action. sync_completed = true; - current_event = &timer; + current_event = &timer; } } itsSyncErrors[&port] |= (*sa)->hasErrors(); @@ -191,7 +192,7 @@ bool Scheduler::syncHasCompleted() { for (map< GCFPortInterface*, bool >::iterator it = m_sync_completed.begin(); it != m_sync_completed.end(); it++) { - if (!(*it).second) { + if (!(*it).second) { return(false); } } @@ -317,8 +318,8 @@ void Scheduler::enter(Ptr<Command> command, QueueID queue, bool immediateApplyAl Timestamp scheduled_time = command->getTimestamp(); // process READ commmand immediately if that is possible, - if ((command->getOperation() == Command::READ) && - (scheduled_time == Timestamp(0,0)) && + if ((command->getOperation() == Command::READ) && + (scheduled_time == Timestamp(0,0)) && (command->readFromCache()) && (queue != Scheduler::PERIODIC)) { LOG_INFO_STR("Applying command " << command->name() << " immediately"); @@ -344,13 +345,12 @@ void Scheduler::enter(Ptr<Command> command, QueueID queue, bool immediateApplyAl } /* determine at which time the command can actually be carried out */ - if (scheduled_time.sec() < m_current_time.sec() + SCHEDULING_DELAY) { - if (scheduled_time.sec() > 0) { // filter Timestamp(0,0) case + if (scheduled_time.sec() < m_current_time.sec() + (long)SCHEDULING_DELAY) { + if ((scheduled_time.sec() > 0L) && (m_current_time.sec() - scheduled_time.sec()) != 0L) { // filter Timestamp(0,0) case LOG_WARN(formatString("command %s missed deadline by %d seconds", command->name().c_str(), m_current_time.sec() - scheduled_time.sec())); } - scheduled_time = m_current_time + (long)SCHEDULING_DELAY; } @@ -424,7 +424,7 @@ void Scheduler::scheduleCommands() } /* detect late commands, but just execute them */ - if (command->getTimestamp() <= m_current_time + (long)(scheduling_offset - SYNC_INTERVAL_INT)) { + if (command->getTimestamp() < m_current_time + (long)(scheduling_offset - SYNC_INTERVAL_INT)) { LOG_WARN_STR("command '" << command->name() << "' is late, timestamp=" << command->getTimestamp() << ", current_time=" << m_current_time); } @@ -455,7 +455,7 @@ void Scheduler::scheduleCommands() } /* detect late commands, but just execute them */ - if (command->getTimestamp() <= m_current_time + (long)(scheduling_offset - SYNC_INTERVAL_INT)) { + if (command->getTimestamp() < m_current_time + (long)(scheduling_offset - SYNC_INTERVAL_INT)) { LOG_WARN_STR("periodic command '" << command->name() << "' is late, timestamp=" << command->getTimestamp() << ", current_time=" << m_current_time); } @@ -529,7 +529,7 @@ void Scheduler::initiateSync(GCFEvent& event) itsSyncErrors[iter->first] = false; // also mark all commands as not completed yet. - for (vector<SyncAction*>::iterator sa = iter->second.begin(); sa != iter->second.end(); sa++) { + for (vector<SyncAction*>::iterator sa = iter->second.begin(); sa != iter->second.end(); sa++) { (*sa)->setCompleted(false); } @@ -615,12 +615,12 @@ void Scheduler::completeCommands() while (!m_done_queue.empty()) { Ptr<Command> command = m_done_queue.top(); m_done_queue.pop(); - - // If command->getPort() = 0, an ack for this command is already send in the enter function + + // If command->getPort() = 0, an ack for this command is already send in the enter function if (command->getPort() != 0) { command->complete(Cache::getInstance().getFront()); } - + // re-timestamp periodic commands for the next period if (command->getPeriod()) { // Set the next time at which the periodic command should be executed. @@ -633,7 +633,7 @@ void Scheduler::completeCommands() // // To correctly compute the next time at which a periodic command should execute, take // the absolute current time and add the period, but make sure the periodic command - // continues to be executed on the grid defined by the period. E.g. if a command is + // continues to be executed on the grid defined by the period. E.g. if a command is // executed every 4 seconds starting on second 1, so 1,5,9, etc and some PPSes are missed // lets say PPS 10,11,12,13 (and current time is 13) then it should continue at time 17. // diff --git a/MAC/APL/PIC/RSP_Driver/src/SerdesRead.cc b/MAC/APL/PIC/RSP_Driver/src/SerdesRead.cc index c8f8f572172..e4446eabdac 100644 --- a/MAC/APL/PIC/RSP_Driver/src/SerdesRead.cc +++ b/MAC/APL/PIC/RSP_Driver/src/SerdesRead.cc @@ -76,9 +76,11 @@ void SerdesRead::sendrequest() setContinue(true); setFinished(); SerdesBuffer& rdBuf = Cache::getInstance().getBack().getSdsReadBuffer(getBoardId()); - string hd; +#if 0 + string hd; hexdump (hd, rdBuf.getBufferPtr(), dataOffset+1); LOG_INFO_STR(hd); +#endif return; } diff --git a/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc b/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc index f1ad9c67a5b..121ff5d3ffb 100644 --- a/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc +++ b/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc @@ -54,7 +54,7 @@ void SetHBACmd::ack(CacheBuffer& /*cache*/) ack.timestamp = getTimestamp(); ack.status = RSP_SUCCESS; - + getPort()->send(ack); } @@ -78,18 +78,27 @@ void SetHBACmd::apply(CacheBuffer& cache, bool setModFlag) bool delays_changed; for (int cache_rcu = 0; cache_rcu < StationSettings::instance()->nrRcus(); cache_rcu++) { if (m_event->rcumask.test(cache_rcu)) { // check if rcu is selected - + + if (cache.getRCUSettings()()(cache_rcu).getMode() < 5) + continue; // et only if in HBA mode + // check if changed delays_changed = false; - for (int i = 0; i < 16; ++i) { + + //LOG_INFO_STR("new hba delays=" << cache.getHBASettings()()(cache_rcu, Range::all())); + //LOG_INFO_STR("old hba delays=" << m_event->settings()(event_rcu, Range::all())); + + for (int i = 0; i < 16; ++i) { if (cache.getHBASettings()()(cache_rcu, Range::all())(i) != m_event->settings()(event_rcu, Range::all())(i) ) { delays_changed = true; } } if (!delays_changed) { - LOG_DEBUG_STR("Skip updating rcu " << cache_rcu << ", value not changed"); + LOG_INFO_STR("Skip updating rcu " << cache_rcu << ", value not changed"); } - + + //delays_changed = true; + cache.getHBASettings()()(cache_rcu, Range::all()) = m_event->settings()(event_rcu, Range::all()); if (setModFlag && delays_changed) { cache.getCache().getState().hbaprotocol().write(cache_rcu); @@ -117,11 +126,11 @@ void SetHBACmd::setTimestamp(const Timestamp& timestamp) bool SetHBACmd::validate() const { LOG_INFO_STR( - "validate-> rcus=" << m_event->rcumask.count() - << " dims=" << m_event->settings().dimensions() + "validate-> rcus=" << m_event->rcumask.count() + << " dims=" << m_event->settings().dimensions() << " ext_firt=" << m_event->settings().extent(firstDim) << " elements=" << m_event->settings().extent(secondDim) ); - + return ((m_event->rcumask.count() <= (unsigned int)StationSettings::instance()->nrRcus()) && (2 == m_event->settings().dimensions()) && (m_event->rcumask.count() == (unsigned int)m_event->settings().extent(firstDim)) // check number off selected rcus diff --git a/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc b/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc index 939cad40b2f..fea50c24384 100644 --- a/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc +++ b/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc @@ -91,15 +91,13 @@ void SetRCUCmd::apply(CacheBuffer& cache, bool setModFlag) // Apply delays and attenuation when mode was changed. if (m_event->settings()(eventRcu).isModeModified()) { mode = m_event->settings()(eventRcu).getMode(); - // if mode changed be sure RCU is enabled, is needed to reduce poweron current on hba's - /* - if (mode > 0) { - cache.getRCUSettings()()(cache_rcu).setEnable(1); - } - else { - cache.getRCUSettings()()(cache_rcu).setEnable(0); + + // clear HBA delays if mode < than 5 is selected + if (mode < 5) { + cache.getHBASettings()()(cache_rcu, Range::all()) = 0; + cache.getHBAReadings()()(cache_rcu, Range::all()) = 0; } - */ + cache.getRCUSettings()()(cache_rcu).setDelay( (uint8) ((delayStep/2.0 + cableSettings->getDelay(cache_rcu, mode)) / delayStep)); diff --git a/MAC/APL/PIC/RSP_Driver/src/rspctl.cc b/MAC/APL/PIC/RSP_Driver/src/rspctl.cc index 6a700cdb953..6f8912cd98b 100644 --- a/MAC/APL/PIC/RSP_Driver/src/rspctl.cc +++ b/MAC/APL/PIC/RSP_Driver/src/rspctl.cc @@ -538,6 +538,211 @@ GCFEvent::TResult RCUCommand::ack(GCFEvent& e) return GCFEvent::HANDLED; } +// Easy command set most settings with one command +ModeCommand::ModeCommand(GCFPortInterface& port) : + Command (port), + itsMode (0), + itsClock (0), + itsStage (0) +{ +} + +void ModeCommand::send() +{ + switch (itsStage) { + case 0: { + RSPGetclockEvent getclock; + getclock.timestamp = Timestamp(0,0); + getclock.cache = true; + m_rspport.send(getclock); + } break; + + case 1: { + RSPSetclockEvent setclock; + setclock.timestamp = Timestamp(0,0); + if (itsClock == 160) + setclock.clock = 200; + else + setclock.clock = 160; + m_rspport.send(setclock); + logMessage(cout, formatString("Set clock to %dMHz", setclock.clock)); + } break; + + case 2: { + RSPSetrcuEvent setrcu; + setrcu.timestamp = Timestamp(0,0); + setrcu.rcumask = getRCUMask(); + + setrcu.settings().resize(1); + + setrcu.settings()(0).setMode((RCUSettings::Control::RCUMode)itsMode); + logMessage(cout,formatString("Set rcumode to %d", itsMode)); + + if (itsMode > 0) { + setrcu.settings()(0).setEnable(true); + logMessage(cout, "Enable inputs from RCU's"); + } else { + setrcu.settings()(0).setEnable(false); + logMessage(cout, "Disable inputs from RCU's"); + } + m_rspport.send(setrcu); + } break; + + case 3: { + RSPSetbypassEvent request; + + request.timestamp = Timestamp(0,0); + request.rcumask = getRCUMask(); + request.settings().resize(1); + bool siOn; + if (itsMode == 5) { + logMessage(cout, "Enable Spectral inversion"); + siOn = true; + } + else { + logMessage(cout, "Disable Spectral inversion"); + siOn = false; + } + request.settings()(0).setXSI(siOn); + request.settings()(0).setYSI(siOn); + + m_rspport.send(request); + } break; + + case 4: { + RSPSethbaEvent sethba; + sethba.timestamp = Timestamp(0,0); + sethba.rcumask = getRCUMask(); + sethba.settings().resize(sethba.rcumask.count(), N_HBA_ELEM_PER_TILE); + sethba.settings() = 0; // discharge hba elements for one second. + logMessage(cout, "Discharge HBA elements for 1 second"); + m_rspport.send(sethba); + } break; + + case 5: { + RSPSethbaEvent sethba; + sethba.timestamp = Timestamp(0,0); + sethba.rcumask = getRCUMask(); + sethba.settings().resize(sethba.rcumask.count(), N_HBA_ELEM_PER_TILE); + sethba.settings() = 253; + logMessage(cout, "Set HBA delays to 253"); + m_rspport.send(sethba); + } break; + + } +} + +GCFEvent::TResult ModeCommand::ack(GCFEvent& e) +{ + GCFEvent::TResult status = GCFEvent::HANDLED; + + switch (e.signal) { + case RSP_GETCLOCKACK: { + RSPGetclockackEvent ack(e); + if (RSP_SUCCESS != ack.status) { + if (ack.status == RSP_BUSY) { + logMessage(cerr,"Error: driver busy."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + else { + logMessage(cerr,"Error: RSP_GETCLOCK command failed."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + } + else { + itsClock = ack.clock; + logMessage(cout,formatString("Sample frequency: clock=%dMHz", ack.clock)); + + ++itsStage; + if ((itsClock == 160) && (itsMode == 6)) + ++itsStage; // No clock change needed + + if ((itsClock == 200) && (itsMode != 6)) + ++itsStage; // No clock change needed + send(); + } + } break; + + case RSP_SETCLOCKACK: { + RSPSetclockackEvent ack(e); + if (RSP_SUCCESS != ack.status) { + if (ack.status == RSP_BUSY) { + logMessage(cerr,"Error: clock NOT set, driver busy."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + else { + logMessage(cerr,"Error: RSP_SETCLOCK command failed."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + } + logMessage(cout,"Wait 30 seconds until clock is changed"); + sleep(30); + ++itsStage; + send(); + } break; + + case RSP_SETRCUACK: { + RSPSetrcuackEvent ack(e); + if (ack.status != RSP_SUCCESS) { + logMessage(cerr,"Error: RSP_SETRCU command failed."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + ++itsStage; + send(); + } break; + + case RSP_SETBYPASSACK: { + RSPSetbypassackEvent ack(e); + + std::ostringstream msg; + msg << "setSIack.timestamp=" << ack.timestamp; + logMessage(cout, msg.str()); + + if (ack.status != RSP_SUCCESS) { + logMessage(cerr, "Error: RSP_SETSI command failed."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + + if (itsMode < 5) { + GCFScheduler::instance()->stop(); + } + else { + logMessage(cout,formatString("rcumode > 5 (%d), send hba delay", itsMode)); + ++itsStage; + send(); + } + } break; + + case RSP_SETHBAACK: { + RSPSethbaackEvent ack(e); + if (ack.status != RSP_SUCCESS) { + logMessage(cerr,"Error: RSP_SETHBA command failed."); + rspctl_exit_code = EXIT_FAILURE; + } + if (itsStage < 5) { + sleep(1); + ++itsStage; + send(); + } + else { + GCFScheduler::instance()->stop(); + } + } break; + + default: { + status = GCFEvent::NOT_HANDLED; + } break; + + } // switch + + return status; +} // Swap X Y on RCU SWAPXYCommand::SWAPXYCommand(GCFPortInterface& port) : Command(port) @@ -715,7 +920,7 @@ GCFEvent::TResult BitmodeCommand::ack(GCFEvent& e) return status; } -// set subbands +// set subbands SDOCommand::SDOCommand(GCFPortInterface& port) : Command (port), itsBitsPerSample(0) @@ -749,11 +954,11 @@ void SDOCommand::send() setsdo.rcumask = getRCUMask(); logMessage(cerr,formatString("rcumask.count()=%d",setsdo.rcumask.count())); - + int MAX_SDO_SUBBANDS_PER_PLANE = 36; int MAX_SDO_PLANES = 4; int MAX_SDO_SUBBANDS = MAX_SDO_SUBBANDS_PER_PLANE * MAX_SDO_PLANES; - + if (static_cast<int>(itsSubbandlist.size()) > MAX_SDO_SUBBANDS) { logMessage(cerr,formatString("Error: too many subbands selected max=%d", MAX_SDO_SUBBANDS)); rspctl_exit_code = EXIT_FAILURE; @@ -803,7 +1008,7 @@ GCFEvent::TResult SDOCommand::ack(GCFEvent& e) itsBitsPerSample = n_bits_per_sample; send(); // bits per sample received get selected subbands. } break; - + case RSP_GETSDOACK: { RSPGetsdoackEvent ack(e); bitset<MAX_RCUS> mask = getRCUMask(); @@ -847,7 +1052,7 @@ GCFEvent::TResult SDOCommand::ack(GCFEvent& e) status = GCFEvent::NOT_HANDLED; break; } - + if (e.signal != RSP_GETSDOMODEACK) { GCFScheduler::instance()->stop(); } @@ -916,7 +1121,7 @@ GCFEvent::TResult SDOmodeCommand::ack(GCFEvent& e) break; case 3: cout << formatString("RSP[%02u]: 16/8/5/4 : %2d\n", rsp, ack.bits_per_sample[rsp]); - break; + break; default: break; } } @@ -1150,7 +1355,7 @@ GCFEvent::TResult RSUCommand::ack(GCFEvent& e) logMessage(cerr,"Error: RSP_SETRSU command failed."); rspctl_exit_code = EXIT_FAILURE; } - + } } } @@ -3525,7 +3730,7 @@ GCFEvent::TResult RSPCtl::doCommand(GCFEvent& e, GCFPortInterface& port) case RSP_GETSDOMODEACK: case RSP_SETSDOACK: case RSP_GETSDOACK: - + status = itsCommand->ack(e); // handle the acknowledgement gClockChanged = false; gBitmodeChanged = false; @@ -3639,6 +3844,12 @@ static void usage(bool exportMode) cout << " --rcuenable[=0] # enable (or disable) input from RCU's" << endl; cout << endl; cout << "rspctl --specinv[=0] [--select=<set>] # enable (or disable) spectral inversion" << endl; + + cout << "rspctl --mode=[0..7] [--select=<set>] # set rcumode in a specific mode" << endl; + cout << " # enable(or disable) input from RCU's" << endl; + cout << " # enable(or disable) spectral inversion" << endl; + cout << " # set the hbadelays to 253" << endl; + cout << endl; cout << "--- Signalprocessing -----------------------------------------------------------------------------------------" << endl; cout << "rspctl --weights [--select=<set>] # get weights as complex values" << endl; @@ -3771,6 +3982,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) { "sdoenable", optional_argument, 0, 'J' }, { "bitmode", optional_argument, 0, 'K' }, { "latency", no_argument, 0, 'L' }, + { "mode", required_argument, 0, 'M' }, { "phase", required_argument, 0, 'P' }, { "tdstatus", no_argument, 0, 'Q' }, { "realdelays", optional_argument, 0, 'R' }, @@ -3788,7 +4000,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) realDelays = false; while (1) { int option_index = 0; - int c = getopt_long(argc, argv, "a::b:c::d:e::g::hi:j::k::l:m:n:o::p::qr::s::t::vw::xy:z::A:BC::D:E::G:H::I::J::K::LP:QR::ST::VXY::Z::1:2:", long_options, &option_index); + int c = getopt_long(argc, argv, "a::b:c::d:e::g::hi:j::k::l:m:n:o::p::qr::s::t::vw::xy:z::A:BC::D:E::G:H::I::J::K::LM:P:QR::ST::VXY::Z::1:2:", long_options, &option_index); if (c == -1) // end of argument list reached? break; @@ -4195,7 +4407,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) bitmodecommand->bitmode(bitmode); } } break; - + case 'j': // --sdo { if (command) @@ -4217,7 +4429,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) } } break; - + case 'k': // --sdomode { if (command) @@ -4459,7 +4671,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) } break; - case 'J': // --sdoenable + case 'J': // --sdoenable { if (command) delete command; @@ -4474,7 +4686,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) } } break; - + case 'Y': // --datastream { if (command) @@ -4566,6 +4778,27 @@ Command* RSPCtl::parse_options(int argc, char** argv) } break; + case 'M': { // mode + if (!optarg) { + logMessage(cerr,"Error: option requires an argument"); + rspctl_exit_code = EXIT_FAILURE; + return 0; + } + int mode = atoi(optarg); + if (mode < 0 || mode > 7) { + logMessage(cerr,"Error: wrong mode must be in range 0..7"); + rspctl_exit_code = EXIT_FAILURE; + return 0; + } + + ModeCommand* modecommand = new ModeCommand(*itsRSPDriver); + command = modecommand; + command->set_ndevices(m_nrcus); + + modecommand->setReceiverMode(mode); + //itsNeedClockOnce = true; + } break; + case '1': // readblock=RSPboard,hexAddress,offset,(datalen|filename) case '2': { // writeblock=RSPboard,hexAddress,offset,(datalen|filename) // allocate the right command diff --git a/MAC/APL/PIC/RSP_Driver/src/rspctl.h b/MAC/APL/PIC/RSP_Driver/src/rspctl.h index 0cf23cb8d3f..54775cc3c33 100644 --- a/MAC/APL/PIC/RSP_Driver/src/rspctl.h +++ b/MAC/APL/PIC/RSP_Driver/src/rspctl.h @@ -270,6 +270,27 @@ private: RCUSettings::Control m_control; }; + +// +// class ModeCommand +// +class ModeCommand : public Command +{ +public: + ModeCommand(GCFPortInterface& port); + virtual ~ModeCommand() {} + virtual void send(); + virtual GCFEvent::TResult ack(GCFEvent& e); + + void setReceiverMode(int mode) { itsMode = mode; } + +private: + int itsMode; + int itsClock; + int itsStage; +}; + + // // class HBACommand // -- GitLab From 2c12124e8d71c3299c0db0fc3a8998f9dfda6268 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 7 Jun 2016 12:22:58 +0000 Subject: [PATCH 284/933] Task #9166: Backported station sensitivity fixes into 2.16 release --- MAC/APL/PAC/ITRFBeamServer/src/AnaBeamMgr.cc | 106 +++---- MAC/APL/PAC/ITRFBeamServer/src/BeamServer.cc | 37 ++- MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc | 1 + .../ITRFBeamServer/src/iBeamServer.conf.in | 4 +- MAC/APL/PIC/RSP_Driver/src/CRSyncWrite.cc | 18 +- MAC/APL/PIC/RSP_Driver/src/Cache.cc | 80 +++--- MAC/APL/PIC/RSP_Driver/src/GetRCUCmd.cc | 8 +- MAC/APL/PIC/RSP_Driver/src/GetSplitterCmd.cc | 3 +- .../PIC/RSP_Driver/src/HBAProtocolWrite.cc | 195 ++++++------- MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.h | 25 +- MAC/APL/PIC/RSP_Driver/src/HBAResultRead.cc | 4 +- .../PIC/RSP_Driver/src/RCUProtocolWrite.cc | 20 +- MAC/APL/PIC/RSP_Driver/src/Scheduler.cc | 34 +-- MAC/APL/PIC/RSP_Driver/src/SerdesRead.cc | 4 +- MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc | 25 +- MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc | 14 +- MAC/APL/PIC/RSP_Driver/src/rspctl.cc | 259 +++++++++++++++++- MAC/APL/PIC/RSP_Driver/src/rspctl.h | 21 ++ 18 files changed, 579 insertions(+), 279 deletions(-) diff --git a/MAC/APL/PAC/ITRFBeamServer/src/AnaBeamMgr.cc b/MAC/APL/PAC/ITRFBeamServer/src/AnaBeamMgr.cc index 8eaaf40446c..b6e6f01a179 100644 --- a/MAC/APL/PAC/ITRFBeamServer/src/AnaBeamMgr.cc +++ b/MAC/APL/PAC/ITRFBeamServer/src/AnaBeamMgr.cc @@ -69,7 +69,7 @@ AnaBeamMgr::AnaBeamMgr(uint nrRCUsPerRing, // initialize size of array for HBA delay calculation itsHBAdelays.resize(itsSC.nrRSPs * NR_RCUS_PER_RSPBOARD, N_HBA_ELEM_PER_TILE); - + itsHBAdelays(Range::all(), Range::all()) = 0; } // @@ -96,7 +96,7 @@ bool AnaBeamMgr::addBeam(const AnalogueBeam& beam) if (iter != itsBeams.end()) { LOG_ERROR_STR("Beam " << beamName << " already in my admistration"); return (false); - } + } // remember the beam itsBeams[beamName] = beam; @@ -120,13 +120,13 @@ bool AnaBeamMgr::addBeam(const AnalogueBeam& beam) // // deleteBeam(beam) // -void AnaBeamMgr::deleteBeam(const string& beamName) +void AnaBeamMgr::deleteBeam(const string& beamName) { map<string, AnalogueBeam>::const_iterator iter = itsBeams.find(beamName); if (iter == itsBeams.end()) { LOG_ERROR_STR("Beam " << beamName << " is not in my administration, it cannot be deleted"); return; - } + } itsBeams.erase(beamName); // delete all pointings @@ -153,7 +153,7 @@ bool AnaBeamMgr::addPointing(const string& beamName, const Pointing& newPt) if (iter == itsBeams.end()) { LOG_ERROR_STR("Beam " << beamName << " is not in my administration, pointing rejected"); return (false); - } + } // add the pointing of this beam PointingInfo PI; @@ -197,7 +197,7 @@ void AnaBeamMgr::activateBeams(const Timestamp& now) iter->active = false; } bool beamIsActive(iter->active); // remember state of this beam - + // delete old (expired) pointings of this beam while (iter != end && iter->beam.name() == beamName && iter->pointing.endTime() < now) { LOG_DEBUG_STR("Removing pointing " << beamName << ":" << iter->pointing); @@ -209,12 +209,12 @@ void AnaBeamMgr::activateBeams(const Timestamp& now) LOG_INFO_STR("Beam " << beamName << " has ended"); continue; } - + if (beamIsActive) { // active beams should stay active. iter->active = true; // make that pointing the active one itsActiveRCUs |= iter->beam.rcuMask(); // update occupied rcus } - else { // beam is not active try to activate it + else { // beam is not active try to activate it // activate the beam when that is possible. if (conflictingRCUs.none() && iter->pointing.time() <= now) { iter->active = true; @@ -222,7 +222,7 @@ void AnaBeamMgr::activateBeams(const Timestamp& now) LOG_INFO_STR("Beam " << beamName << " is switched ON"); } } - + // skip rest of the pointings of this beam while (iter != end && iter->beam.name() == beamName) { ++iter; @@ -275,7 +275,7 @@ void AnaBeamMgr::calculateHBAdelays(RTC::Timestamp targetTime, CasaConverter& aJ showAdmin(); LOG_INFO_STR("Calculating HBAdelays for time " << targetTime); - itsHBAdelays(Range::all(), Range::all()) = 0; + //itsHBAdelays(Range::all(), Range::all()) = 0; itsTargetTime = targetTime; // When have to do the calculations on the HBA, HBA0 and HBA1 field. (at least we have to check those fields). @@ -300,7 +300,7 @@ void AnaBeamMgr::calculateHBAdelays(RTC::Timestamp targetTime, CasaConverter& aJ continue; // try next field } - // we found an active beam in this HBA field, calculate the delays for all + // we found an active beam in this HBA field, calculate the delays for all // active beams in this field // Get geographical location of antennaField in ITRF @@ -319,7 +319,7 @@ void AnaBeamMgr::calculateHBAdelays(RTC::Timestamp targetTime, CasaConverter& aJ (itsTileRelPos(i,2) * itsTileRelPos(i,2))); } LOG_DEBUG_STR("tileRelLength:" << tileRelLength); - + // for all active pointings in this antennafield while (pIter != pEnd) { // must be of the same antenna field. @@ -369,45 +369,53 @@ void AnaBeamMgr::calculateHBAdelays(RTC::Timestamp targetTime, CasaConverter& aJ } // Do the HBA_1 antennas use a different rotation and are we calculating such an antenna? - int HBAdeltaOffset = (itsDiffHBArotations && (rcu/N_POL >= (itsSC.nrHBAs/2)) + int HBAdeltaOffset = (itsDiffHBArotations && (rcu/N_POL >= (itsSC.nrHBAs/2)) ? N_HBA_ELEM_PER_TILE : 0); for (int element = 0; element < N_HBA_ELEM_PER_TILE; ++element) { // calculate tile delay. double delay = ( (sourceJ2000xyz(0,0) * tileRelPosJ2000(HBAdeltaOffset + element,0)) + (sourceJ2000xyz(0,1) * tileRelPosJ2000(HBAdeltaOffset + element,1)) + - (sourceJ2000xyz(0,2) * tileRelPosJ2000(HBAdeltaOffset + element,2)) + (sourceJ2000xyz(0,2) * tileRelPosJ2000(HBAdeltaOffset + element,2)) ) / SPEED_OF_LIGHT_MS; - + // signal front stays in the middle of the tile delay += itsMeanElementDelay; -// LOG_DEBUG_STR("antenna="<<rcu/2 <<", pol="<<rcu%2 <<", element="<<element <<", delay("<<rcu<<","<<element<<")="<<delay); - // calculate approximate DelayStepNr - int delayStepNr = static_cast<int>(delay / 0.5E-9); - // range check for delayStepNr, max. 32 steps (0..31) - delayStepNr = MIN2(delayStepNr, maxStepNr); // limit against upper boundary + int delayStepNr = static_cast<int>((itsHBAdelays(rcu, element) & 0x7c) >> 2); // set actual delay + + // search for next step + for (int i = 0; i < maxStepNr; ++i) { + if (fabs(delay - itsDelaySteps(i)) <= 0.2E-9) { + delayStepNr = i; + break; + } + } + + if (fabs(delay - itsDelaySteps(delayStepNr)) > 0.6E-9) { + LOG_INFO_STR("delay diff > 0.75E-9, calculate approximate delaynr"); + delayStepNr = static_cast<int>(fabs(delay) / 0.5E-9); + } + + delayStepNr = MIN2(delayStepNr, maxStepNr); // limit against upper boundary delayStepNr = MAX2(0, delayStepNr); // limit against lower boundary - - // look for nearest matching delay step in range "delayStepNr - 2 .. delayStepNr + 2" - double minDelayDiff = fabs(delay - itsDelaySteps(delayStepNr)); - double difference; - int minStepNr = delayStepNr; - int stepMinusTwo = MAX2(0, delayStepNr-2); // limit check to element 0 - int stepPlusTwo = MIN2(maxStepNr, delayStepNr+2); // limit check to element 31 - for (int i = stepMinusTwo; i <= stepPlusTwo; i++){ - if (i == delayStepNr) - continue; // skip approximate nr - difference = fabs(delay - itsDelaySteps(i)); - if (difference < minDelayDiff) { - minStepNr = i; - minDelayDiff = difference; - } - } - delayStepNr = minStepNr; - - // bit1=0.25nS(not used), bit2=0.5nS, bit3=1nS, bit4=2nS, bit5=4nS, bit6=8nS - itsHBAdelays(rcu,element) = (delayStepNr * 4) + (1 << 7); // assign + + if (itsDiffHBArotations) { + if ((rcu == 0) && (rcu%2 == 0)) { + LOG_INFO_STR("HBA-0 delay, element="<<element <<", requested delay="<<delay <<", real delay="<<itsDelaySteps(delayStepNr)); + } + if ((rcu == 48) && (rcu%2 == 0)) { + LOG_INFO_STR("HBA-1 delay, element="<<element <<", requested delay="<<delay <<", real delay="<<itsDelaySteps(delayStepNr)); + } + } + else { + if ((rcu == 0) && (rcu%2 == 0)) { + LOG_INFO_STR("HBA delay, element="<<element <<", requested delay="<<delay <<", real delay="<<itsDelaySteps(delayStepNr)); + } + } + + // bit1=0.25nS(not used), bit2=0.5nS, bit3=1nS, bit4=2nS, bit5=4nS, bit6=8nS + itsHBAdelays(rcu, element) = (delayStepNr << 2) + (1 << 7); // assign } // for element } // rcus ++pIter; @@ -423,7 +431,7 @@ void AnaBeamMgr::sendHBAdelays(GCF::TM::GCFPortInterface& port) // note: the RSPDriver expects settings for the active RCU's only. // TODO: CHANGE THIS SO WE CAN POINT THE UNUSED TILES TO ZENITH request.settings().resize(itsActiveRCUs.count(), N_HBA_ELEM_PER_TILE); - int nrRCUs = itsActiveRCUs.size(); + int nrRCUs = itsActiveRCUs.size(); int requestIdx(0); for (int r = 0; r < nrRCUs; r++) { if (itsActiveRCUs.test(r)) { @@ -449,7 +457,7 @@ void AnaBeamMgr::sendHBAdelays(GCF::TM::GCFPortInterface& port) // --------------- READ CONFIGURATIONFILES --------------- // -// getAllHBADeltas +// getAllHBADeltas // void AnaBeamMgr::getAllHBADeltas(const string& filename) { @@ -472,14 +480,14 @@ void AnaBeamMgr::getAllHBADeltas(const string& filename) LOG_DEBUG_STR("HBADeltas comment = " << itsName); getline(itsFile, itsName); // read name or comment } - + if ("" == itsName) { itsFile.close(); return; } - + LOG_DEBUG_STR("HBADeltas name = " << itsName); - + // Now comment lines are skipped, so we can read the full array. itsFile >> itsTileRelPos; // read HBA deltas array LOG_DEBUG_STR("HBADeltas = " << itsTileRelPos); @@ -492,7 +500,7 @@ void AnaBeamMgr::getAllHBADeltas(const string& filename) } // -// getAllHBAElementDelays +// getAllHBAElementDelays // void AnaBeamMgr::getAllHBAElementDelays(const string& filename) { @@ -502,7 +510,7 @@ void AnaBeamMgr::getAllHBAElementDelays(const string& filename) LOG_DEBUG_STR("Trying to read the HBA element delay steps from file " << filename); // open new file - if (itsFile.is_open()) { + if (itsFile.is_open()) { itsFile.close(); } itsFile.open(filename.c_str()); @@ -516,14 +524,14 @@ void AnaBeamMgr::getAllHBAElementDelays(const string& filename) LOG_DEBUG_STR("HBA ElementDelays comment = " << itsName); getline(itsFile, itsName); // read name or comment } - + if ("" == itsName) { itsFile.close(); return; } LOG_DEBUG_STR("HBA ElementDelays Name = " << itsName); - + // Now comment lines are skipped, so we can read the full array. itsFile >> itsDelaySteps; // read HBA element delays array //itsDelaySteps *= 1E-9; // convert from nSec to Secs diff --git a/MAC/APL/PAC/ITRFBeamServer/src/BeamServer.cc b/MAC/APL/PAC/ITRFBeamServer/src/BeamServer.cc index 1c1d08bf26f..7d0143d7c6f 100644 --- a/MAC/APL/PAC/ITRFBeamServer/src/BeamServer.cc +++ b/MAC/APL/PAC/ITRFBeamServer/src/BeamServer.cc @@ -827,7 +827,7 @@ GCFEvent::TResult BeamServer::beamalloc_state(GCFEvent& event, GCFPortInterface& IBSBeamallocackEvent beamallocack; if (ack.status == CAL_Protocol::CAL_SUCCESS) { // && -//TODO (ack.subarray.SPW().getNumSubbands() >= +//TODO (GET_CONFIG("CalServer.N_SUBBANDS", i) >= //TODO (int)itsBeamTransaction.getBeam()->allocation()().size()) ) { LOG_INFO_STR("Got subscription to subarray " << ack.subarray.name()); @@ -879,7 +879,7 @@ GCFEvent::TResult BeamServer::beamalloc_state(GCFEvent& event, GCFPortInterface& // Timer event may be from one of the pointing timers, ignore them LOG_INFO_STR(">>> TimerEvent on port " << port.getName() << " while in alloc state, ignoring it"); return (GCFEvent::HANDLED); -// return ((&port==itsAnaHeartbeat) ? GCFEvent::NEXT_STATE : GCFEvent::HANDLED); TODO: FIX THIS +// return ((&port==itsAnaHeartbeat) ? GCFEvent::NEXT_STATE : GCFEvent::HANDLED); // TODO: FIX THIS } IBSBeamallocackEvent beamallocack; LOG_ERROR_STR("Timeout on starting the calibration of beam " << itsBeamTransaction.getBeam()->name()); @@ -1109,14 +1109,19 @@ int BeamServer::beampointto_action(IBSPointtoEvent& ptEvent, // The analogue must be started several seconds before the observationstart time because the I2C bus // needs several seconds to pass the information - int activationTime = _idealStartTime(Timestamp::now().sec(), - ptEvent.pointing.time(), HBA_MIN_INTERVAL, - itsLastHBACalculationTime+itsHBAUpdateInterval, HBA_MIN_INTERVAL, itsHBAUpdateInterval); - int timeShift(ptEvent.pointing.time().sec() - activationTime); - // update pointing + int activationTime = _idealStartTime( Timestamp::now().sec(), + ptEvent.pointing.time(), + HBA_MIN_INTERVAL, + itsLastHBACalculationTime + itsHBAUpdateInterval, + HBA_MIN_INTERVAL, + itsHBAUpdateInterval); + + int timeShift(ptEvent.pointing.time().sec() - activationTime); + + // update pointing ptEvent.pointing.setTime(Timestamp(activationTime,0)); if (ptEvent.pointing.duration() && timeShift) { - ptEvent.pointing.setDuration(ptEvent.pointing.duration()+timeShift); + ptEvent.pointing.setDuration(ptEvent.pointing.duration()); LOG_INFO_STR("Extended duration of " << beamIter->second->name() << " with " << timeShift << " seconds because starttime was shifted"); } @@ -1127,7 +1132,9 @@ int BeamServer::beampointto_action(IBSPointtoEvent& ptEvent, return (IBS_UNKNOWN_BEAM_ERR); } - itsAnaHeartbeat->setTimer(activationTime - Timestamp::now().sec()); + itsAnaHeartbeat->cancelAllTimers(); + itsAnaHeartbeat->setTimer(activationTime - Timestamp::now().sec(), 0, itsHBAUpdateInterval, 0); // restart analog beam hartbeat + // itsAnaHeartbeat->setTimer(activationTime - Timestamp::now().sec()); LOG_INFO_STR("Analogue beam for beam " << beamIter->second->name() << " will be active at " << Timestamp(activationTime+HBA_MIN_INTERVAL, 0)); LOG_INFO_STR("Analogue pointing for beam " << beamIter->second->name() << " will be send at " << Timestamp(activationTime, 0)); @@ -1152,12 +1159,16 @@ int BeamServer::beampointto_action(IBSPointtoEvent& ptEvent, // int BeamServer::_idealStartTime (int now, int t1, int d1, int t2, int d2, int p2) const { - int t1start = t1-d1; // ideal starttime + int t1start = t1 - d1; // ideal starttime if (t1start < now) // not before now ofcourse t1start = now; - int nearestt2 = (t1start<=t2 ? t2 : t2+((t1-t2)/p2)*p2); - if (t1start > nearestt2 && t1start < nearestt2+d2) // not during heartbeat period - t1start = nearestt2; + + if (t1start <= (t2 - p2 + d2)) // if it is to fast after previous update + t1start = t2 - p2 + d2 + 1; + + //int nearestt2 = (t1start <= t2 ? t2 : t2 + ((t1 - t2) / p2) * p2); + //if (t1start > nearestt2 && t1start < nearestt2 + d2) // not during heartbeat period + // t1start = nearestt2; return (t1start); } diff --git a/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc b/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc index c53e2665b15..3005237338d 100644 --- a/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc +++ b/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc @@ -242,6 +242,7 @@ GCFEvent::TResult beamctl::create_subarray(GCFEvent& event, GCFPortInterface& po TRAN(beamctl::final); } else { cout << "Calserver accepted settings." << endl; + sleep(3 + 4); TRAN(beamctl::create_beam); } } diff --git a/MAC/APL/PAC/ITRFBeamServer/src/iBeamServer.conf.in b/MAC/APL/PAC/ITRFBeamServer/src/iBeamServer.conf.in index 751bdde9873..e96a31d9af9 100644 --- a/MAC/APL/PAC/ITRFBeamServer/src/iBeamServer.conf.in +++ b/MAC/APL/PAC/ITRFBeamServer/src/iBeamServer.conf.in @@ -11,10 +11,10 @@ BeamServer.EnableSetHBA = 1 BeamServer.EnableStaticCalibration = 1 # -# normal HBAUpdateInterval >= 10, for testing use +# normal HBAUpdateInterval >= 10, for testing use # (4, 6 or 8) secs. Note: 4 sec is absolute minimum! # -BeamServer.HBAUpdateInterval= 180 # >=10, normally 300 +BeamServer.HBAUpdateInterval= 10 # >=10, normally 10 BeamServer.UpdateInterval = 1 # should be 1 for normal operation BeamServer.ComputeInterval = 1 # can be 1 for ITRFBeamserver, was 10 for old BeamServer diff --git a/MAC/APL/PIC/RSP_Driver/src/CRSyncWrite.cc b/MAC/APL/PIC/RSP_Driver/src/CRSyncWrite.cc index 59e29812400..7974531e20e 100644 --- a/MAC/APL/PIC/RSP_Driver/src/CRSyncWrite.cc +++ b/MAC/APL/PIC/RSP_Driver/src/CRSyncWrite.cc @@ -42,13 +42,15 @@ using namespace RTC; CRSyncWrite::CRSyncWrite(GCFPortInterface& board_port, int board_id) : SyncAction(board_port, board_id, NR_BLPS_PER_RSPBOARD) { - - //ASSERTSTR(delaySteps.extent(firstDim) == NR_BLPS_PER_RSPBOARD, "Expected " << NR_BLPS_PER_RSPBOARD << + + //ASSERTSTR(delaySteps.extent(firstDim) == NR_BLPS_PER_RSPBOARD, "Expected " << NR_BLPS_PER_RSPBOARD << // " delay-steps not " << delaySteps.extent(firstDim)); itsDelays.resize(NR_BLPS_PER_RSPBOARD); itsCounters.resize(NR_BLPS_PER_RSPBOARD); - + itsDelays = 0; + itsCounters = 0; + //itsDelays.resize(delaySteps.shape()); //itsCounters.resize(delaySteps.shape()); //itsDelays = delaySteps; @@ -77,17 +79,17 @@ void CRSyncWrite::sendrequest() uint16 blpid(0); EPACrControlEvent CRSyncEvent; if (Cache::getInstance().getState().crcontrol().get(getBoardId()) == RTC::RegisterState::READ) { - + int sliceBegin = getBoardId() * NR_BLPS_PER_RSPBOARD; int sliceEnd = sliceBegin + NR_BLPS_PER_RSPBOARD - 1; itsDelays = Cache::getInstance().getBack().getPPSdelays()(Range(sliceBegin, sliceEnd)); - + setNumIndices(max(itsDelays)+1); - + // start of cycle, log unfinished cycles for (int b = 0; b < NR_BLPS_PER_RSPBOARD; b++) { if (itsCounters(b) && itsCounters(b) != itsDelays(b)) { - LOG_WARN(formatString("PPS delay[%d][%]: %d out of %d written", + LOG_WARN(formatString("PPS delay[%d][%d]: %d out of %d written", getBoardId(), b, itsDelays(b)-itsCounters(b), itsDelays(b))); } } @@ -104,7 +106,7 @@ void CRSyncWrite::sendrequest() blpid |= mask; itsCounters(b)--; } - } + } CRSyncEvent.hdr.set(MEPHeader::CR_SYNCDELAY_HDR, blpid); CRSyncEvent.control = 0x01; // SyncEdge=rise, SyncDelay=Increment } diff --git a/MAC/APL/PIC/RSP_Driver/src/Cache.cc b/MAC/APL/PIC/RSP_Driver/src/Cache.cc index e8c130e2cc8..9cfa3510045 100644 --- a/MAC/APL/PIC/RSP_Driver/src/Cache.cc +++ b/MAC/APL/PIC/RSP_Driver/src/Cache.cc @@ -141,7 +141,7 @@ CacheBuffer::CacheBuffer(Cache* cache) : m_cache(cache) m_clock = GET_CONFIG("RSPDriver.DEFAULT_SAMPLING_FREQUENCY", i); reset(); // reset by allocating memory and settings default values - + // print sizes of the cache LOG_DEBUG_STR("m_beamletweights().size() =" << m_beamletweights().size() * sizeof(complex<int16>)); LOG_DEBUG_STR("m_subbandselection.crosslets().size()=" << m_subbandselection.crosslets().size() * sizeof(uint16)); @@ -176,22 +176,22 @@ CacheBuffer::CacheBuffer(Cache* cache) : m_cache(cache) LOG_DEBUG_STR("itsFixedAttenuations.size() =" << sizeof(itsFixedAttenuations)); LOG_DEBUG_STR("itsAttenuationStepSize.size() =" << sizeof(itsAttenuationStepSize)); LOG_INFO_STR(formatString("CacheBuffer size = %d bytes", - m_beamletweights().size() - + m_subbandselection.crosslets().size() - + m_subbandselection.beamlets().size() - + m_rcusettings().size() - + m_hbasettings().size() - + m_hbareadings().size() - + m_rsusettings().size() - + m_wgsettings().size() - + m_subbandstats().size() - + m_beamletstats().size() - + m_xcstats().size() + m_beamletweights().size() + + m_subbandselection.crosslets().size() + + m_subbandselection.beamlets().size() + + m_rcusettings().size() + + m_hbasettings().size() + + m_hbareadings().size() + + m_rsusettings().size() + + m_wgsettings().size() + + m_subbandstats().size() + + m_beamletstats().size() + + m_xcstats().size() + m_systemstatus.board().size() - + m_versions.bp().size() - + m_versions.ap().size() - + m_tdstatus.board().size() - + m_spustatus.subrack().size() + + m_versions.bp().size() + + m_versions.ap().size() + + m_tdstatus.board().size() + + m_spustatus.subrack().size() + m_tbbsettings().size() + m_bypasssettings().size() + m_bypasssettings_bp().size() @@ -249,10 +249,10 @@ void CacheBuffer::reset(void) itsBitsPerSample = MAX_BITS_PER_SAMPLE; itsSDOBitsPerSample = MAX_BITS_PER_SAMPLE; - - m_beamletweights().resize( BeamletWeights::SINGLE_TIMESTEP, + + m_beamletweights().resize( BeamletWeights::SINGLE_TIMESTEP, StationSettings::instance()->nrRcus(), - MAX_NR_BM_BANKS, + MAX_NR_BM_BANKS, MEPHeader::N_BEAMLETS); m_beamletweights() = complex<int16>(25,36); // TODO remove this code!!! @@ -274,7 +274,7 @@ void CacheBuffer::reset(void) (MAX_BITS_PER_SAMPLE/MIN_BITS_PER_SAMPLE), MEPHeader::N_BEAMLETS ); m_subbandselection.beamlets() = 0; - + if (GET_CONFIG("RSPDriver.IDENTITY_WEIGHTS", i)) { // these weights ensure that the beamlet statistics // exactly match the subband statistics @@ -285,7 +285,7 @@ void CacheBuffer::reset(void) // int firstSubband = GET_CONFIG("RSPDriver.FIRST_SUBBAND", i); for (int rcu = 0; rcu < m_subbandselection.beamlets().extent(firstDim); rcu++) { - for (int bank = 0; bank < (MAX_BITS_PER_SAMPLE/MIN_BITS_PER_SAMPLE); bank++) { + for (int bank = 0; bank < (MAX_BITS_PER_SAMPLE/MIN_BITS_PER_SAMPLE); bank++) { for (int lane = 0; lane < MEPHeader::N_SERDES_LANES; lane++) { int start(lane*(MEPHeader::N_BEAMLETS/MEPHeader::N_SERDES_LANES)); int stop (start + maxBeamletsPerRSP(itsBitsPerSample)); @@ -333,9 +333,9 @@ void CacheBuffer::reset(void) m_subbandstats().resize(StationSettings::instance()->nrRcus(), MEPHeader::N_SUBBANDS); m_subbandstats() = 0; - + // Number of cep streams -> in normal mode 4, in splitmode 8. - int maxStreams = 8; + int maxStreams = 8; m_beamletstats().resize((maxStreams/MEPHeader::N_SERDES_LANES) * N_POL, (MAX_BITS_PER_SAMPLE/MIN_BITS_PER_SAMPLE) * MEPHeader::N_BEAMLETS); m_beamletstats() = 0; @@ -382,14 +382,14 @@ void CacheBuffer::reset(void) m_bypasssettings().resize(StationSettings::instance()->nrBlps()); BypassSettings::Control control; m_bypasssettings() = control; - + // BypassSettings (BP) LOG_INFO_STR("Resizing bypass array to: " << StationSettings::instance()->maxRspBoards()); m_bypasssettings_bp().resize(StationSettings::instance()->maxRspBoards()); BypassSettings::Control controlbp; controlbp.setSDO(1); m_bypasssettings_bp() = controlbp; - + // clear rawdatablock itsRawDataBlock.address = 0; itsRawDataBlock.offset = 0; @@ -404,40 +404,40 @@ void CacheBuffer::reset(void) // clear I2C flag itsI2Cuser = NONE; - + // set Splitter not active itsSplitterActive = false; // set CEP port enabled itsCepEnabled0 = false; itsCepEnabled1 = false; - + // Latency status itsLatencys().resize(StationSettings::instance()->nrRspBoards()); RADLatency radlatencyinit; memset(&radlatencyinit, 0, sizeof(RADLatency)); itsLatencys() = radlatencyinit; itsSwappedXY.reset(); - + // BitMode itsBitModeInfo().resize(StationSettings::instance()->nrRspBoards()); RSRBeamMode bitmodeinfo; bitmodeinfo.bm_select = 0; bitmodeinfo.bm_max = 0; itsBitModeInfo() = bitmodeinfo; - + // SDO default Mode selection int sdo_mode = 0; int bits_per_sample = GET_CONFIG("RSPDriver.SDO_MODE", i); if (bits_per_sample == 8) { sdo_mode = 1; } else if (bits_per_sample == 5) { sdo_mode = 2; } else if (bits_per_sample == 4) { sdo_mode = 3; } - + itsSDOModeInfo().resize(StationSettings::instance()->nrRspBoards()); RSRSDOMode sdomodeinfo; sdomodeinfo.bm_select = sdo_mode; sdomodeinfo.bm_max = 3; itsSDOModeInfo() = sdomodeinfo; - + // SDO default subband selection itsSDOSelection.subbands().resize(StationSettings::instance()->nrRcus(), (MAX_BITS_PER_SAMPLE/MIN_BITS_PER_SAMPLE), @@ -455,7 +455,7 @@ void CacheBuffer::reset(void) } // for each bank } readPPSdelaySettings(); - + itsAttenuationStepSize = 0.25; try { itsAttenuationStepSize = GET_CONFIG("RSPDriver.ATT_STEP_SIZE", f); } catch (APSException&) { LOG_INFO_STR("RSPDriver.ATT_STEP_SIZE not found"); } @@ -467,13 +467,13 @@ void CacheBuffer::reset(void) itsFixedAttenuations(rcumode) = 0.0; try { itsFixedAttenuations(rcumode) = GET_CONFIG(key, f); } catch (APSException&) { LOG_INFO_STR(formatString("RSPDriver.FIXED_ATT_MODE_%d not found", rcumode)); } - } + } } SerdesBuffer& CacheBuffer::getSdsReadBuffer(int rspBoardNr) { - ASSERTSTR(rspBoardNr >= 0 && rspBoardNr < MAX_N_RSPBOARDS, + ASSERTSTR(rspBoardNr >= 0 && rspBoardNr < MAX_N_RSPBOARDS, "RSPboard index out of range in getting serdesReadBuffer: " << rspBoardNr); return (itsSdsReadBuffer[rspBoardNr]); } @@ -488,7 +488,11 @@ void CacheBuffer::setTimestamp(const RTC::Timestamp& timestamp) // void CacheBuffer::readPPSdelaySettings() { - ConfigLocator CL; + if (m_clock == 0) { + LOG_INFO_STR("Skip loading PPS delay settings, clock = 0 MHz"); + return; + } + ConfigLocator CL; string filename = (CL.locate(GET_CONFIG_STRING(formatString("RSPDriver.PPSdelayFile%u", m_clock)))); LOG_DEBUG_STR("Trying to load the PPS delay settings for " << m_clock << " MHz from file: " << filename); @@ -523,7 +527,7 @@ void CacheBuffer::readPPSdelaySettings() } else { LOG_ERROR_STR("File " << filename << " contains " << delayValues.extent(firstDim) - << " values, expected " << nrRspBoards * NR_BLPS_PER_RSPBOARD + << " values, expected " << nrRspBoards * NR_BLPS_PER_RSPBOARD << " values, WILL NOT USE THEM!"); } std::ostringstream logStream; @@ -598,8 +602,8 @@ void Cache::resetI2Cuser() } m_front->setI2Cuser(busUser); m_back->setI2Cuser (busUser); - LOG_INFO_STR("new I2Cuser = " << ((busUser == NONE) ? "NONE" : - ((busUser == HBA) ? "HBA" : + LOG_INFO_STR("new I2Cuser = " << ((busUser == NONE) ? "NONE" : + ((busUser == HBA) ? "HBA" : ((busUser == RCU_R) ? "RCU_R" : "RCU_W")))); } diff --git a/MAC/APL/PIC/RSP_Driver/src/GetRCUCmd.cc b/MAC/APL/PIC/RSP_Driver/src/GetRCUCmd.cc index 5e49ffc29be..8a20f977cad 100644 --- a/MAC/APL/PIC/RSP_Driver/src/GetRCUCmd.cc +++ b/MAC/APL/PIC/RSP_Driver/src/GetRCUCmd.cc @@ -66,7 +66,7 @@ void GetRCUCmd::ack(CacheBuffer& cache) ack.settings()(result_rcu).setRaw(cache.getRCUSettings()()(cache_rcu).getRaw()); } else { - LOG_WARN(formatString("invalid RCU index %d, there are only %d RCU's", cache_rcu, + LOG_WARN(formatString("invalid RCU index %d, there are only %d RCU's", cache_rcu, StationSettings::instance()->nrRcus())); } @@ -82,8 +82,8 @@ void GetRCUCmd::apply(CacheBuffer& cache, bool setModFlag) { // someone else using the I2C bus? I2Cuser busUser = cache.getI2Cuser(); - LOG_INFO_STR("GetRCU::apply : " << ((busUser == NONE) ? "NONE" : - ((busUser == HBA) ? "HBA" : + LOG_DEBUG_STR("GetRCU::apply : " << ((busUser == NONE) ? "NONE" : + ((busUser == HBA) ? "HBA" : ((busUser == RCU_R) ? "RCU_R" : "RCU_W")))); if ((cache.getI2Cuser() != NONE) && (cache.getI2Cuser() != RCU_R)) { postponeExecution(true); @@ -96,7 +96,7 @@ void GetRCUCmd::apply(CacheBuffer& cache, bool setModFlag) for (int cache_rcu = 0; cache_rcu < StationSettings::instance()->nrRcus(); cache_rcu++) { if (setModFlag && m_event->rcumask.test(cache_rcu)) { cache.getCache().getState().rcuread().write(cache_rcu); - } + } } // for } diff --git a/MAC/APL/PIC/RSP_Driver/src/GetSplitterCmd.cc b/MAC/APL/PIC/RSP_Driver/src/GetSplitterCmd.cc index a38993cfdaf..15385044160 100644 --- a/MAC/APL/PIC/RSP_Driver/src/GetSplitterCmd.cc +++ b/MAC/APL/PIC/RSP_Driver/src/GetSplitterCmd.cc @@ -127,10 +127,11 @@ void GetSplitterCmd::apply(CacheBuffer& cache, bool setModFlag) // the sdsReadBuffer if for storing the results. SerdesBuffer& SerdesBuf = cache.getSdsWriteBuffer(); SerdesBuf.newCommand(ASK_SERDES_CMD, ASK_SERDES_CMD_LEN); +#if 0 string hd; hexdump(hd, SerdesBuf.getBufferPtr(), SerdesBuf.getDataLen()); LOG_INFO_STR(hd); - +#endif // mark registers that the serdes registers should be written. if (setModFlag) { for (int b= 0; b < StationSettings::instance()->nrRspBoards(); b++) { diff --git a/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.cc b/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.cc index 63a2e252057..5b8ccb4bfa8 100644 --- a/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.cc +++ b/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.cc @@ -53,41 +53,41 @@ namespace LOFAR { // the indices that should be patched. // int HBAProtocolWrite::i2c_protocol_patch_indices[] = { - 8, 9, - 51 + ( 0*17), // stride is 17, server 1 - 51 + ( 1*17), - 51 + ( 2*17), - 51 + ( 3*17), - 51 + ( 4*17), - 51 + ( 5*17), - 51 + ( 6*17), - 51 + ( 7*17), - 51 + ( 8*17), - 51 + ( 9*17), - 51 + (10*17), - 51 + (11*17), - 51 + (12*17), - 51 + (13*17), - 51 + (14*17), - 51 + (15*17), // server 16 + 13, 14, + 56 + ( 0*17), // stride is 17, server 1 + 56 + ( 1*17), + 56 + ( 2*17), + 56 + ( 3*17), + 56 + ( 4*17), + 56 + ( 5*17), + 56 + ( 6*17), + 56 + ( 7*17), + 56 + ( 8*17), + 56 + ( 9*17), + 56 + (10*17), + 56 + (11*17), + 56 + (12*17), + 56 + (13*17), + 56 + (14*17), + 56 + (15*17), // server 16 }; int HBAProtocolWrite::i2c_result_patch_indices[] = { - 4 + ( 0*7), // stride is 7, server 1 - 4 + ( 1*7), - 4 + ( 2*7), - 4 + ( 3*7), - 4 + ( 4*7), - 4 + ( 5*7), - 4 + ( 6*7), - 4 + ( 7*7), - 4 + ( 8*7), - 4 + ( 9*7), - 4 + (10*7), - 4 + (11*7), - 4 + (12*7), - 4 + (13*7), - 4 + (14*7), - 4 + (15*7), // server 16 + 5 + ( 0*7), // stride is 7, server 1 + 5 + ( 1*7), + 5 + ( 2*7), + 5 + ( 3*7), + 5 + ( 4*7), + 5 + ( 5*7), + 5 + ( 6*7), + 5 + ( 7*7), + 5 + ( 8*7), + 5 + ( 9*7), + 5 + (10*7), + 5 + (11*7), + 5 + (12*7), + 5 + (13*7), + 5 + (14*7), + 5 + (15*7), // server 16 }; #ifndef HBA_WRITE_DELAYS @@ -107,23 +107,29 @@ namespace LOFAR { 0x13, // PROTOCOL_C_END }; - uint8 HBAProtocolWrite::i2c_result[HBAProtocolWrite::RESULT_SIZE] - = { - // Expected protocol result (4 bytes) - 0x00, // PROTOCOL_WRITE_BYTE went OK + uint8 HBAProtocolWrite::i2c_result[HBAProtocolWrite::RESULT_SIZE] + = { + // Expected protocol result (4 bytes) + 0x00, // PROTOCOL_WRITE_BYTE went OK - 0xBB, // Read LED value - 0x00, // PROTOCOL_READ_BYTE went OK + 0xBB, // Read LED value + 0x00, // PROTOCOL_READ_BYTE went OK - 0x00, // PROTOCOL_C_END went OK - }; + 0x00, // PROTOCOL_C_END went OK + }; #else - uint8 HBAProtocolWrite::i2c_protocol[HBAProtocolWrite::PROTOCOL_SIZE/* 42 + 5 + 16 * 17 + 1 = 320 bytes*/] + uint8 HBAProtocolWrite::i2c_protocol[HBAProtocolWrite::PROTOCOL_SIZE/* 5 + 42 + 5 + 16 * 17 + 1 = 325 bytes*/] = { // Table 32 t/m 37 from LOFAR-ASTRON-MEM-175 (Revision 1.3) + 0x12, // PROTOCOL_C_WAIT, used to prevent power peeks. + 0x00, // <<< replace with data, wait byte 0 >>> + 0x00, // <<< replace with data, wait byte 1 >>> + 0x00, // <<< replace with data, wait byte 2 >>> + 0x00, // <<< replace with data, wait byte 3 >>> + // Table 32 // Instruct client to do a broadcast access to the 16 HBA delay servers (42 bytes) 0x0D, // PROTOCOL_C_WRITE_BLOCK_NO_CNT @@ -136,8 +142,8 @@ namespace LOFAR { 0, // Server X-delay register ID 0, // First server (initialization will add offset) 15, // Last server - 0xBB, // X-delay data for server 0 (offset = PROTOCOL_DELAY_OFFSET) - 0xBB, // Y-delay data for server 0 + 0xBB, // X-delay data for server 0 (offset = PROTOCOL_DELAY_OFFSET) + 0xBB, // Y-delay data for server 0 0xBB, // X-delay data for server 1 0xBB, // Y-delay data for server 1 0xBB, // X-delay data for server 2 @@ -182,15 +188,16 @@ namespace LOFAR { 4>>1, // Client i2c slave address 0, // Request register access command 4, // Count of number of data bytes to write - 0, // Server address 0 + 0, // Server address 0 3, // Payload length, including this octet 5, // Function: get word (document LOFAR-ASTRON-MEM-175 Revision 1.3 has an error here (value 2)) 0, // Server X-delay register ID - + // Table 35 // Wait to allow for the multicast to finish 0x12, // PROTOCOL_C_WAIT (5 bytes) - 0x00, 0xA8, 0x61, 0x00, // 0x0061A800 = 6.4e6 * 5ns = 32ms pause between write request register and read response register + + 0x00, 0xA8, 0x61, 0x00, // 0x0061A800 = 6.4e6 * 5ns = 32ms pause between write request register and read response register // 0x00, 0x09, 0x3D, 0x00, // 0x003D0900 = 4e6 * 5ns = 20ms pause between write request register and read response register // Table 36 @@ -201,28 +208,30 @@ namespace LOFAR { 4, // Count of number of data bytes to read (document LOFAR-ASTRON-MEM-175 has an error here (value 3)) // Repeat tables 34,35,36 fo servers 1 to 15 - 0x0D, 4>>1, 0, 4, 1, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 1 - 0x0D, 4>>1, 0, 4, 2, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 2 - 0x0D, 4>>1, 0, 4, 3, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 3 - 0x0D, 4>>1, 0, 4, 4, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 4 - 0x0D, 4>>1, 0, 4, 5, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 5 - 0x0D, 4>>1, 0, 4, 6, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 6 - 0x0D, 4>>1, 0, 4, 7, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 7 - 0x0D, 4>>1, 0, 4, 8, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 8 - 0x0D, 4>>1, 0, 4, 9, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 9 - 0x0D, 4>>1, 0, 4, 10, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 10 - 0x0D, 4>>1, 0, 4, 11, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 11 - 0x0D, 4>>1, 0, 4, 12, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 12 - 0x0D, 4>>1, 0, 4, 13, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 13 - 0x0D, 4>>1, 0, 4, 14, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 14 - 0x0D, 4>>1, 0, 4, 15, 3, 5, 0, 0x12, 0x00, 0x09, 0x3D, 0x00, 0x0E, 4>>1, 1, 4, // server 15 + 0x0D, 4>>1, 0, 4, 1, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 1 + 0x0D, 4>>1, 0, 4, 2, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 2 + 0x0D, 4>>1, 0, 4, 3, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 3 + 0x0D, 4>>1, 0, 4, 4, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 4 + 0x0D, 4>>1, 0, 4, 5, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 5 + 0x0D, 4>>1, 0, 4, 6, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 6 + 0x0D, 4>>1, 0, 4, 7, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 7 + 0x0D, 4>>1, 0, 4, 8, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 8 + 0x0D, 4>>1, 0, 4, 9, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 9 + 0x0D, 4>>1, 0, 4, 10, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 10 + 0x0D, 4>>1, 0, 4, 11, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 11 + 0x0D, 4>>1, 0, 4, 12, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 12 + 0x0D, 4>>1, 0, 4, 13, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 13 + 0x0D, 4>>1, 0, 4, 14, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 14 + 0x0D, 4>>1, 0, 4, 15, 3, 5, 0, 0x12, 0x00, 0xA8, 0x61, 0x00, 0x0E, 4>>1, 1, 4, // server 15 // Mark the end of the protocol list (1 byte) 0x13, // PROTOCOL_C_END }; - uint8 HBAProtocolWrite::i2c_result[HBAProtocolWrite::RESULT_SIZE/* 2 + 7*16 + 1 = 115 bytes*/] + uint8 HBAProtocolWrite::i2c_result[HBAProtocolWrite::RESULT_SIZE/* 3 + 7*16 + 1 = 116 bytes*/] = { + 0x00, // PROTOCOL_C_WAIT went OK + 0x00, // PROTOCOL_C_WRITE_BLOCK_NO_CNT went OK 0x00, // PROTOCOL_C_WAIT went OK @@ -236,21 +245,21 @@ namespace LOFAR { 0xBB, // Y-delay data from server X 0x00, // PROTOCOL_C_READ_BLOCK_NO_CNT went OK - 0, 0, 1 + 128, 3, 0xBB, 0xBB, 0, // server 1 result - 0, 0, 2 + 128, 3, 0xBB, 0xBB, 0, // server 2 result - 0, 0, 3 + 128, 3, 0xBB, 0xBB, 0, // server 3 result - 0, 0, 4 + 128, 3, 0xBB, 0xBB, 0, // server 4 result - 0, 0, 5 + 128, 3, 0xBB, 0xBB, 0, // server 5 result - 0, 0, 6 + 128, 3, 0xBB, 0xBB, 0, // server 6 result - 0, 0, 7 + 128, 3, 0xBB, 0xBB, 0, // server 7 result - 0, 0, 8 + 128, 3, 0xBB, 0xBB, 0, // server 8 result - 0, 0, 9 + 128, 3, 0xBB, 0xBB, 0, // server 9 result - 0, 0, 10 + 128, 3, 0xBB, 0xBB, 0, // server 10 result - 0, 0, 11 + 128, 3, 0xBB, 0xBB, 0, // server 11 result - 0, 0, 12 + 128, 3, 0xBB, 0xBB, 0, // server 12 result - 0, 0, 13 + 128, 3, 0xBB, 0xBB, 0, // server 13 result - 0, 0, 14 + 128, 3, 0xBB, 0xBB, 0, // server 14 result - 0, 0, 15 + 128, 3, 0xBB, 0xBB, 0, // server 15 result + 0x00, 0x00, 1 + 128, 3, 0xBB, 0xBB, 0x00, // server 1 result + 0x00, 0x00, 2 + 128, 3, 0xBB, 0xBB, 0x00, // server 2 result + 0x00, 0x00, 3 + 128, 3, 0xBB, 0xBB, 0x00, // server 3 result + 0x00, 0x00, 4 + 128, 3, 0xBB, 0xBB, 0x00, // server 4 result + 0x00, 0x00, 5 + 128, 3, 0xBB, 0xBB, 0x00, // server 5 result + 0x00, 0x00, 6 + 128, 3, 0xBB, 0xBB, 0x00, // server 6 result + 0x00, 0x00, 7 + 128, 3, 0xBB, 0xBB, 0x00, // server 7 result + 0x00, 0x00, 8 + 128, 3, 0xBB, 0xBB, 0x00, // server 8 result + 0x00, 0x00, 9 + 128, 3, 0xBB, 0xBB, 0x00, // server 9 result + 0x00, 0x00, 10 + 128, 3, 0xBB, 0xBB, 0x00, // server 10 result + 0x00, 0x00, 11 + 128, 3, 0xBB, 0xBB, 0x00, // server 11 result + 0x00, 0x00, 12 + 128, 3, 0xBB, 0xBB, 0x00, // server 12 result + 0x00, 0x00, 13 + 128, 3, 0xBB, 0xBB, 0x00, // server 13 result + 0x00, 0x00, 14 + 128, 3, 0xBB, 0xBB, 0x00, // server 14 result + 0x00, 0x00, 15 + 128, 3, 0xBB, 0xBB, 0x00, // server 15 result // Marks the end of the protocol list (1 byte) 0x00, // PROTOCOL_C_END went OK @@ -309,7 +318,7 @@ void HBAProtocolWrite::sendrequest() setContinue(true); return; } - + if ((regStates.hbaprotocol().get(global_blp * N_POL) != RTC::RegisterState::WRITE) && (regStates.hbaprotocol().get(global_blp * N_POL + 1) != RTC::RegisterState::WRITE)) { regStates.hbaprotocol().unmodified(global_blp * N_POL); @@ -330,14 +339,14 @@ void HBAProtocolWrite::sendrequest() neverDeleteData); // add extra wait to prevent power peaks when switching - // 1 tick = 5ns, 16000000 (80ms), 600000 (3ms) - uint32 wait = 16000000 + (600000 * global_blp); - LOG_DEBUG_STR(formatString("HBAPotocolWrite add wait %f sec", wait * (1./200e6))); - memcpy(i2c_protocol + PROTOCOL_WAIT_OFFSET, &wait, 4); + // 200MHz, 1 tick = 5ns, 600000 = 3ms + uint32 wait = 600000 * global_blp; + LOG_INFO_STR(formatString("HBAPotocolWrite add wait %f sec for global_blp %d", wait * (1./200e6), global_blp)); + memcpy(i2c_protocol + PROTOCOL_WAIT_OFFSET_1, &wait, 4); delays(Range::all(), 0) = Cache::getInstance().getBack().getHBASettings()()(global_blp * N_POL, Range::all()); delays(Range::all(), 1) = Cache::getInstance().getBack().getHBASettings()()(global_blp * N_POL + 1, Range::all()); - + // copy set delays to i2c_result which is the expected result uint8* cur = i2c_result + RESULT_DELAY_OFFSET; for (int elem = 0; elem < N_HBA_ELEM_PER_TILE; elem++){ @@ -355,13 +364,13 @@ void HBAProtocolWrite::sendrequest() EPARcuProtocolEvent rcuprotocol; rcuprotocol.hdr.set(MEPHeader::RCU_PROTOCOLY_HDR, 1 << (getCurrentIndex() / N_WRITES), MEPHeader::WRITE, sizeof(i2c_protocol)); rcuprotocol.protocol.setBuffer(i2c_protocol, sizeof(i2c_protocol)); - -#if 0 + +#if 0 string tmpbuf; hexdump (tmpbuf, i2c_protocol, sizeof(i2c_protocol)); LOG_INFO_STR("HBA WRITE: " << tmpbuf); -#endif - +#endif + m_hdr = rcuprotocol.hdr; // remember header to match with ack getBoardPort().send(rcuprotocol); } @@ -376,13 +385,13 @@ void HBAProtocolWrite::sendrequest() uint8 clear[RESULT_SIZE]; memset(clear, 0xBB, RESULT_SIZE); // clear result rcuresultwrite.payload.setBuffer(clear, RESULT_SIZE); - -#if 0 + +#if 0 string tmpbuf; hexdump (tmpbuf, clear, sizeof(clear)); LOG_INFO_STR("HBA RESULT WRITE: " << tmpbuf); -#endif - +#endif + m_hdr = rcuresultwrite.hdr; // remember header to match with ack getBoardPort().send(rcuresultwrite); } @@ -414,7 +423,7 @@ GCFEvent::TResult HBAProtocolWrite::handleack(GCFEvent& event, GCFPortInterface& return GCFEvent::NOT_HANDLED; } - if ((getCurrentIndex() % N_WRITES) == 1) { + if ((getCurrentIndex() % N_WRITES) == 1) { // Mark modification as applied when write of RCU result register has completed Cache::getInstance().getState().hbaprotocol().schedule_wait2read(global_blp * N_POL); Cache::getInstance().getState().hbaprotocol().schedule_wait2read(global_blp * N_POL + 1); diff --git a/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.h b/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.h index c97e6b1ccea..2cbe71ff561 100644 --- a/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.h +++ b/MAC/APL/PIC/RSP_Driver/src/HBAProtocolWrite.h @@ -71,18 +71,19 @@ namespace LOFAR { #define HBA_WRITE_DELAYS #ifdef HBA_WRITE_DELAYS - static const int PROTOCOL_SIZE = 320; - static const int RESULT_SIZE = 115; - - static const int PROTOCOL_WAIT_OFFSET = 43; // offset of wait settings in i2c_protocol - static const int PROTOCOL_DELAY_OFFSET = 10; // offset of delay settings in i2c_protocol - static const int RESULT_DELAY_OFFSET = 6; // offset of delay settings in i2c_result - static const int RESULT_DELAY_STRIDE = 7; // nof bytes to next pair of X,Y delays + static const int PROTOCOL_SIZE = 325; + static const int RESULT_SIZE = 116; + + static const int PROTOCOL_WAIT_OFFSET_1 = 1; // offset of wait settings in i2c_protocol + static const int PROTOCOL_WAIT_OFFSET_2 = 48; // offset of wait settings in i2c_protocol + static const int PROTOCOL_DELAY_OFFSET = 15; // offset of delay settings in i2c_protocol + static const int RESULT_DELAY_OFFSET = 7; // offset of delay settings in i2c_result + static const int RESULT_DELAY_STRIDE = 7; // nof bytes to next pair of X,Y delays #else - static const int PROTOCOL_SIZE = 8; - static const int RESULT_SIZE = 4; - static const int PROTOCOL_LED_OFFSET = 3; - static const int RESULT_LED_OFFSET = 1; + static const int PROTOCOL_SIZE = 8; + static const int RESULT_SIZE = 4; + static const int PROTOCOL_LED_OFFSET = 3; + static const int RESULT_LED_OFFSET = 1; #endif static int i2c_protocol_patch_indices[]; @@ -100,5 +101,5 @@ namespace LOFAR { }; }; }; - + #endif /* HBAPROTOCOLWRITE_H_ */ diff --git a/MAC/APL/PIC/RSP_Driver/src/HBAResultRead.cc b/MAC/APL/PIC/RSP_Driver/src/HBAResultRead.cc index 9ed44cafa1f..40e53b2d5b0 100644 --- a/MAC/APL/PIC/RSP_Driver/src/HBAResultRead.cc +++ b/MAC/APL/PIC/RSP_Driver/src/HBAResultRead.cc @@ -83,7 +83,7 @@ GCFEvent::TResult HBAResultRead::handleack(GCFEvent& event, GCFPortInterface& /* LOG_WARN("HBAResultRead::handleack:: unexpected ack"); return GCFEvent::NOT_HANDLED; } - + EPARcuResultEvent ack(event); uint8 global_blp = (getBoardId() * NR_BLPS_PER_RSPBOARD) + getCurrentIndex(); @@ -126,7 +126,7 @@ GCFEvent::TResult HBAResultRead::handleack(GCFEvent& event, GCFPortInterface& /* Cache::getInstance().getState().hbaprotocol().read_ack(global_blp * N_POL); Cache::getInstance().getState().hbaprotocol().read_ack(global_blp * N_POL + 1); } else { - LOG_WARN_STR("HBAResultRead: unexpected I2C result response for element(s):" << faultyElements << + LOG_WARN_STR("HBAResultRead: unexpected I2C result response for element(s):" << faultyElements << " of antenna " << (int)(global_blp)); Cache::getInstance().getState().hbaprotocol().read_error(global_blp * N_POL); Cache::getInstance().getState().hbaprotocol().read_error(global_blp * N_POL + 1); diff --git a/MAC/APL/PIC/RSP_Driver/src/RCUProtocolWrite.cc b/MAC/APL/PIC/RSP_Driver/src/RCUProtocolWrite.cc index 109b2b766ca..6edaad7eae7 100644 --- a/MAC/APL/PIC/RSP_Driver/src/RCUProtocolWrite.cc +++ b/MAC/APL/PIC/RSP_Driver/src/RCUProtocolWrite.cc @@ -50,7 +50,7 @@ namespace LOFAR { 0x00, // <<< replace with data, wait byte 0 >>> 0x00, // <<< replace with data, wait byte 1 >>> 0x00, // <<< replace with data, wait byte 2 >>> - 0x00, // <<< replace with data, wait byte 3 >>> + 0x00, // <<< replace with data, wait byte 3 >>> 0x0F, // PROTOCOL_C_SEND_BLOCK 0x01, // I2C address for RCU 0x03, // size @@ -63,14 +63,14 @@ namespace LOFAR { 0x13, // PROTOCOL_C_END }; - uint8 RCUProtocolWrite::i2c_protocol_read[] = { + uint8 RCUProtocolWrite::i2c_protocol_read[] = { 0x10, // PROTOCOL_C_RECEIVE_BLOCK 0x01, // I2C adress for RCU 0x03, // requested size 0x13, // PROTOCOL_C_END }; - uint8 RCUProtocolWrite::i2c_result_write[] = { + uint8 RCUProtocolWrite::i2c_result_write[] = { 0x00, // PROTOCOL_C_WAIT OK 0x00, // PROTOCOL_C_SEND_BLOCK OK 0xAA, // <<< replace with expected data >>> @@ -80,7 +80,7 @@ namespace LOFAR { 0x00, // PROTOCOL_C_END OK }; - uint8 RCUProtocolWrite::i2c_result_read[] = { + uint8 RCUProtocolWrite::i2c_result_read[] = { 0xAA, // <<< replace with expected data >>> 0xAA, // <<< replace with expected data >>> 0xAA, // <<< replace with expected data >>> @@ -143,19 +143,19 @@ void RCUProtocolWrite::sendrequest() if (writeCmdRequested) { // reverse and copy control bytes into i2c_protocol_write RCUSettings::Control& rcucontrol = Cache::getInstance().getBack().getRCUSettings()()((global_rcu)); - + // add waits while turning on hbas to reduce power peaks. // if RCU enable changed or rcumode changed // if rcumode > 0 if (rcucontrol.isModeModified()) { // wait between two RCUs is set to maximum, so that an international station // running on 160MHz clock can finisch the job in 1 second. - // in clock ticks, 1500000 = 7.5msec on 200MHz, 9.4msec on 160MHz - uint32 wait = 660000 * global_rcu; + // in clock ticks, 600000 = 3msec on 200MHz + uint32 wait = 600000 * global_rcu; LOG_DEBUG_STR(formatString("RCUProtocolWrite add wait rcu %d = %f sec", global_rcu, wait * (1./200e6))); memcpy(i2c_protocol_write+1, &wait, 4); } - + uint32 control = htonl(rcucontrol.getRaw()); memcpy(i2c_protocol_write+8, &control, 3); @@ -230,10 +230,10 @@ GCFEvent::TResult RCUProtocolWrite::handleack(GCFEvent& event, GCFPortInterface& if ((getCurrentIndex() % N_WRITES) == 1) { // Mark modification as applied when write of RCU result register has completed if (m_hdr.m_fields.payload_length == RESULT_WRITE_SIZE) { - Cache::getInstance().getState().rcuprotocol().schedule_wait1read(global_rcu); + Cache::getInstance().getState().rcuprotocol().schedule_wait2read(global_rcu); } else { - Cache::getInstance().getState().rcuread().schedule_wait1read(global_rcu); + Cache::getInstance().getState().rcuread().schedule_wait2read(global_rcu); } } diff --git a/MAC/APL/PIC/RSP_Driver/src/Scheduler.cc b/MAC/APL/PIC/RSP_Driver/src/Scheduler.cc index d7d47622f41..cddd07f4dbe 100644 --- a/MAC/APL/PIC/RSP_Driver/src/Scheduler.cc +++ b/MAC/APL/PIC/RSP_Driver/src/Scheduler.cc @@ -115,7 +115,8 @@ GCFEvent::TResult Scheduler::run(GCFEvent& event, GCFPortInterface& /*port*/) if (timeout->usec >= 1e6-1000) { topofsecond++; // round to next whole second } - if (timeout->usec > 2000 && timeout->usec < 1e6 - 2000) { + //if (timeout->usec > 2000 && timeout->usec < 1e6 - 2000) { + if (timeout->usec > 15000 && timeout->usec < 1e6 - 15000) { // changed to 15000, because itsPPSdelay in RSPDriver is added to sync to hardware LOG_WARN_STR("Scheduler time too far off from top off second: usec=" << timeout->usec); } setCurrentTime(topofsecond, 0); @@ -135,7 +136,7 @@ GCFEvent::TResult Scheduler::run(GCFEvent& event, GCFPortInterface& /*port*/) // // dispatch (event, port) // -// Dispatch the(ack) message to the right SyncAction and wake up the next SyncAction in the queue +// Dispatch the(ack) message to the right SyncAction and wake up the next SyncAction in the queue // when the current SyncAction is complete. GCFEvent::TResult Scheduler::dispatch(GCFEvent& event, GCFPortInterface& port) { @@ -148,7 +149,7 @@ GCFEvent::TResult Scheduler::dispatch(GCFEvent& event, GCFPortInterface& port) // has not yet reached its 'final' state. vector<SyncAction*>::iterator sa; int i(0); - // note: m_syncaction = map< GCFPortInterface*, std::vector<SyncAction*> > + // note: m_syncaction = map< GCFPortInterface*, std::vector<SyncAction*> > for (sa = m_syncactions[&port].begin(); sa != m_syncactions[&port].end(); sa++, i++) { if (!(*sa)->hasCompleted()) { // stil busy @@ -165,7 +166,7 @@ GCFEvent::TResult Scheduler::dispatch(GCFEvent& event, GCFPortInterface& port) else { // this SyncAction was ready, pass a timer event to the next action. sync_completed = true; - current_event = &timer; + current_event = &timer; } } itsSyncErrors[&port] |= (*sa)->hasErrors(); @@ -191,7 +192,7 @@ bool Scheduler::syncHasCompleted() { for (map< GCFPortInterface*, bool >::iterator it = m_sync_completed.begin(); it != m_sync_completed.end(); it++) { - if (!(*it).second) { + if (!(*it).second) { return(false); } } @@ -317,8 +318,8 @@ void Scheduler::enter(Ptr<Command> command, QueueID queue, bool immediateApplyAl Timestamp scheduled_time = command->getTimestamp(); // process READ commmand immediately if that is possible, - if ((command->getOperation() == Command::READ) && - (scheduled_time == Timestamp(0,0)) && + if ((command->getOperation() == Command::READ) && + (scheduled_time == Timestamp(0,0)) && (command->readFromCache()) && (queue != Scheduler::PERIODIC)) { LOG_INFO_STR("Applying command " << command->name() << " immediately"); @@ -344,13 +345,12 @@ void Scheduler::enter(Ptr<Command> command, QueueID queue, bool immediateApplyAl } /* determine at which time the command can actually be carried out */ - if (scheduled_time.sec() < m_current_time.sec() + SCHEDULING_DELAY) { - if (scheduled_time.sec() > 0) { // filter Timestamp(0,0) case + if (scheduled_time.sec() < m_current_time.sec() + (long)SCHEDULING_DELAY) { + if ((scheduled_time.sec() > 0L) && (m_current_time.sec() - scheduled_time.sec()) != 0L) { // filter Timestamp(0,0) case LOG_WARN(formatString("command %s missed deadline by %d seconds", command->name().c_str(), m_current_time.sec() - scheduled_time.sec())); } - scheduled_time = m_current_time + (long)SCHEDULING_DELAY; } @@ -424,7 +424,7 @@ void Scheduler::scheduleCommands() } /* detect late commands, but just execute them */ - if (command->getTimestamp() <= m_current_time + (long)(scheduling_offset - SYNC_INTERVAL_INT)) { + if (command->getTimestamp() < m_current_time + (long)(scheduling_offset - SYNC_INTERVAL_INT)) { LOG_WARN_STR("command '" << command->name() << "' is late, timestamp=" << command->getTimestamp() << ", current_time=" << m_current_time); } @@ -455,7 +455,7 @@ void Scheduler::scheduleCommands() } /* detect late commands, but just execute them */ - if (command->getTimestamp() <= m_current_time + (long)(scheduling_offset - SYNC_INTERVAL_INT)) { + if (command->getTimestamp() < m_current_time + (long)(scheduling_offset - SYNC_INTERVAL_INT)) { LOG_WARN_STR("periodic command '" << command->name() << "' is late, timestamp=" << command->getTimestamp() << ", current_time=" << m_current_time); } @@ -529,7 +529,7 @@ void Scheduler::initiateSync(GCFEvent& event) itsSyncErrors[iter->first] = false; // also mark all commands as not completed yet. - for (vector<SyncAction*>::iterator sa = iter->second.begin(); sa != iter->second.end(); sa++) { + for (vector<SyncAction*>::iterator sa = iter->second.begin(); sa != iter->second.end(); sa++) { (*sa)->setCompleted(false); } @@ -615,12 +615,12 @@ void Scheduler::completeCommands() while (!m_done_queue.empty()) { Ptr<Command> command = m_done_queue.top(); m_done_queue.pop(); - - // If command->getPort() = 0, an ack for this command is already send in the enter function + + // If command->getPort() = 0, an ack for this command is already send in the enter function if (command->getPort() != 0) { command->complete(Cache::getInstance().getFront()); } - + // re-timestamp periodic commands for the next period if (command->getPeriod()) { // Set the next time at which the periodic command should be executed. @@ -633,7 +633,7 @@ void Scheduler::completeCommands() // // To correctly compute the next time at which a periodic command should execute, take // the absolute current time and add the period, but make sure the periodic command - // continues to be executed on the grid defined by the period. E.g. if a command is + // continues to be executed on the grid defined by the period. E.g. if a command is // executed every 4 seconds starting on second 1, so 1,5,9, etc and some PPSes are missed // lets say PPS 10,11,12,13 (and current time is 13) then it should continue at time 17. // diff --git a/MAC/APL/PIC/RSP_Driver/src/SerdesRead.cc b/MAC/APL/PIC/RSP_Driver/src/SerdesRead.cc index c8f8f572172..e4446eabdac 100644 --- a/MAC/APL/PIC/RSP_Driver/src/SerdesRead.cc +++ b/MAC/APL/PIC/RSP_Driver/src/SerdesRead.cc @@ -76,9 +76,11 @@ void SerdesRead::sendrequest() setContinue(true); setFinished(); SerdesBuffer& rdBuf = Cache::getInstance().getBack().getSdsReadBuffer(getBoardId()); - string hd; +#if 0 + string hd; hexdump (hd, rdBuf.getBufferPtr(), dataOffset+1); LOG_INFO_STR(hd); +#endif return; } diff --git a/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc b/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc index f1ad9c67a5b..121ff5d3ffb 100644 --- a/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc +++ b/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc @@ -54,7 +54,7 @@ void SetHBACmd::ack(CacheBuffer& /*cache*/) ack.timestamp = getTimestamp(); ack.status = RSP_SUCCESS; - + getPort()->send(ack); } @@ -78,18 +78,27 @@ void SetHBACmd::apply(CacheBuffer& cache, bool setModFlag) bool delays_changed; for (int cache_rcu = 0; cache_rcu < StationSettings::instance()->nrRcus(); cache_rcu++) { if (m_event->rcumask.test(cache_rcu)) { // check if rcu is selected - + + if (cache.getRCUSettings()()(cache_rcu).getMode() < 5) + continue; // et only if in HBA mode + // check if changed delays_changed = false; - for (int i = 0; i < 16; ++i) { + + //LOG_INFO_STR("new hba delays=" << cache.getHBASettings()()(cache_rcu, Range::all())); + //LOG_INFO_STR("old hba delays=" << m_event->settings()(event_rcu, Range::all())); + + for (int i = 0; i < 16; ++i) { if (cache.getHBASettings()()(cache_rcu, Range::all())(i) != m_event->settings()(event_rcu, Range::all())(i) ) { delays_changed = true; } } if (!delays_changed) { - LOG_DEBUG_STR("Skip updating rcu " << cache_rcu << ", value not changed"); + LOG_INFO_STR("Skip updating rcu " << cache_rcu << ", value not changed"); } - + + //delays_changed = true; + cache.getHBASettings()()(cache_rcu, Range::all()) = m_event->settings()(event_rcu, Range::all()); if (setModFlag && delays_changed) { cache.getCache().getState().hbaprotocol().write(cache_rcu); @@ -117,11 +126,11 @@ void SetHBACmd::setTimestamp(const Timestamp& timestamp) bool SetHBACmd::validate() const { LOG_INFO_STR( - "validate-> rcus=" << m_event->rcumask.count() - << " dims=" << m_event->settings().dimensions() + "validate-> rcus=" << m_event->rcumask.count() + << " dims=" << m_event->settings().dimensions() << " ext_firt=" << m_event->settings().extent(firstDim) << " elements=" << m_event->settings().extent(secondDim) ); - + return ((m_event->rcumask.count() <= (unsigned int)StationSettings::instance()->nrRcus()) && (2 == m_event->settings().dimensions()) && (m_event->rcumask.count() == (unsigned int)m_event->settings().extent(firstDim)) // check number off selected rcus diff --git a/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc b/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc index 939cad40b2f..fea50c24384 100644 --- a/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc +++ b/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc @@ -91,15 +91,13 @@ void SetRCUCmd::apply(CacheBuffer& cache, bool setModFlag) // Apply delays and attenuation when mode was changed. if (m_event->settings()(eventRcu).isModeModified()) { mode = m_event->settings()(eventRcu).getMode(); - // if mode changed be sure RCU is enabled, is needed to reduce poweron current on hba's - /* - if (mode > 0) { - cache.getRCUSettings()()(cache_rcu).setEnable(1); - } - else { - cache.getRCUSettings()()(cache_rcu).setEnable(0); + + // clear HBA delays if mode < than 5 is selected + if (mode < 5) { + cache.getHBASettings()()(cache_rcu, Range::all()) = 0; + cache.getHBAReadings()()(cache_rcu, Range::all()) = 0; } - */ + cache.getRCUSettings()()(cache_rcu).setDelay( (uint8) ((delayStep/2.0 + cableSettings->getDelay(cache_rcu, mode)) / delayStep)); diff --git a/MAC/APL/PIC/RSP_Driver/src/rspctl.cc b/MAC/APL/PIC/RSP_Driver/src/rspctl.cc index 6a700cdb953..6f8912cd98b 100644 --- a/MAC/APL/PIC/RSP_Driver/src/rspctl.cc +++ b/MAC/APL/PIC/RSP_Driver/src/rspctl.cc @@ -538,6 +538,211 @@ GCFEvent::TResult RCUCommand::ack(GCFEvent& e) return GCFEvent::HANDLED; } +// Easy command set most settings with one command +ModeCommand::ModeCommand(GCFPortInterface& port) : + Command (port), + itsMode (0), + itsClock (0), + itsStage (0) +{ +} + +void ModeCommand::send() +{ + switch (itsStage) { + case 0: { + RSPGetclockEvent getclock; + getclock.timestamp = Timestamp(0,0); + getclock.cache = true; + m_rspport.send(getclock); + } break; + + case 1: { + RSPSetclockEvent setclock; + setclock.timestamp = Timestamp(0,0); + if (itsClock == 160) + setclock.clock = 200; + else + setclock.clock = 160; + m_rspport.send(setclock); + logMessage(cout, formatString("Set clock to %dMHz", setclock.clock)); + } break; + + case 2: { + RSPSetrcuEvent setrcu; + setrcu.timestamp = Timestamp(0,0); + setrcu.rcumask = getRCUMask(); + + setrcu.settings().resize(1); + + setrcu.settings()(0).setMode((RCUSettings::Control::RCUMode)itsMode); + logMessage(cout,formatString("Set rcumode to %d", itsMode)); + + if (itsMode > 0) { + setrcu.settings()(0).setEnable(true); + logMessage(cout, "Enable inputs from RCU's"); + } else { + setrcu.settings()(0).setEnable(false); + logMessage(cout, "Disable inputs from RCU's"); + } + m_rspport.send(setrcu); + } break; + + case 3: { + RSPSetbypassEvent request; + + request.timestamp = Timestamp(0,0); + request.rcumask = getRCUMask(); + request.settings().resize(1); + bool siOn; + if (itsMode == 5) { + logMessage(cout, "Enable Spectral inversion"); + siOn = true; + } + else { + logMessage(cout, "Disable Spectral inversion"); + siOn = false; + } + request.settings()(0).setXSI(siOn); + request.settings()(0).setYSI(siOn); + + m_rspport.send(request); + } break; + + case 4: { + RSPSethbaEvent sethba; + sethba.timestamp = Timestamp(0,0); + sethba.rcumask = getRCUMask(); + sethba.settings().resize(sethba.rcumask.count(), N_HBA_ELEM_PER_TILE); + sethba.settings() = 0; // discharge hba elements for one second. + logMessage(cout, "Discharge HBA elements for 1 second"); + m_rspport.send(sethba); + } break; + + case 5: { + RSPSethbaEvent sethba; + sethba.timestamp = Timestamp(0,0); + sethba.rcumask = getRCUMask(); + sethba.settings().resize(sethba.rcumask.count(), N_HBA_ELEM_PER_TILE); + sethba.settings() = 253; + logMessage(cout, "Set HBA delays to 253"); + m_rspport.send(sethba); + } break; + + } +} + +GCFEvent::TResult ModeCommand::ack(GCFEvent& e) +{ + GCFEvent::TResult status = GCFEvent::HANDLED; + + switch (e.signal) { + case RSP_GETCLOCKACK: { + RSPGetclockackEvent ack(e); + if (RSP_SUCCESS != ack.status) { + if (ack.status == RSP_BUSY) { + logMessage(cerr,"Error: driver busy."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + else { + logMessage(cerr,"Error: RSP_GETCLOCK command failed."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + } + else { + itsClock = ack.clock; + logMessage(cout,formatString("Sample frequency: clock=%dMHz", ack.clock)); + + ++itsStage; + if ((itsClock == 160) && (itsMode == 6)) + ++itsStage; // No clock change needed + + if ((itsClock == 200) && (itsMode != 6)) + ++itsStage; // No clock change needed + send(); + } + } break; + + case RSP_SETCLOCKACK: { + RSPSetclockackEvent ack(e); + if (RSP_SUCCESS != ack.status) { + if (ack.status == RSP_BUSY) { + logMessage(cerr,"Error: clock NOT set, driver busy."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + else { + logMessage(cerr,"Error: RSP_SETCLOCK command failed."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + } + logMessage(cout,"Wait 30 seconds until clock is changed"); + sleep(30); + ++itsStage; + send(); + } break; + + case RSP_SETRCUACK: { + RSPSetrcuackEvent ack(e); + if (ack.status != RSP_SUCCESS) { + logMessage(cerr,"Error: RSP_SETRCU command failed."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + ++itsStage; + send(); + } break; + + case RSP_SETBYPASSACK: { + RSPSetbypassackEvent ack(e); + + std::ostringstream msg; + msg << "setSIack.timestamp=" << ack.timestamp; + logMessage(cout, msg.str()); + + if (ack.status != RSP_SUCCESS) { + logMessage(cerr, "Error: RSP_SETSI command failed."); + rspctl_exit_code = EXIT_FAILURE; + GCFScheduler::instance()->stop(); + } + + if (itsMode < 5) { + GCFScheduler::instance()->stop(); + } + else { + logMessage(cout,formatString("rcumode > 5 (%d), send hba delay", itsMode)); + ++itsStage; + send(); + } + } break; + + case RSP_SETHBAACK: { + RSPSethbaackEvent ack(e); + if (ack.status != RSP_SUCCESS) { + logMessage(cerr,"Error: RSP_SETHBA command failed."); + rspctl_exit_code = EXIT_FAILURE; + } + if (itsStage < 5) { + sleep(1); + ++itsStage; + send(); + } + else { + GCFScheduler::instance()->stop(); + } + } break; + + default: { + status = GCFEvent::NOT_HANDLED; + } break; + + } // switch + + return status; +} // Swap X Y on RCU SWAPXYCommand::SWAPXYCommand(GCFPortInterface& port) : Command(port) @@ -715,7 +920,7 @@ GCFEvent::TResult BitmodeCommand::ack(GCFEvent& e) return status; } -// set subbands +// set subbands SDOCommand::SDOCommand(GCFPortInterface& port) : Command (port), itsBitsPerSample(0) @@ -749,11 +954,11 @@ void SDOCommand::send() setsdo.rcumask = getRCUMask(); logMessage(cerr,formatString("rcumask.count()=%d",setsdo.rcumask.count())); - + int MAX_SDO_SUBBANDS_PER_PLANE = 36; int MAX_SDO_PLANES = 4; int MAX_SDO_SUBBANDS = MAX_SDO_SUBBANDS_PER_PLANE * MAX_SDO_PLANES; - + if (static_cast<int>(itsSubbandlist.size()) > MAX_SDO_SUBBANDS) { logMessage(cerr,formatString("Error: too many subbands selected max=%d", MAX_SDO_SUBBANDS)); rspctl_exit_code = EXIT_FAILURE; @@ -803,7 +1008,7 @@ GCFEvent::TResult SDOCommand::ack(GCFEvent& e) itsBitsPerSample = n_bits_per_sample; send(); // bits per sample received get selected subbands. } break; - + case RSP_GETSDOACK: { RSPGetsdoackEvent ack(e); bitset<MAX_RCUS> mask = getRCUMask(); @@ -847,7 +1052,7 @@ GCFEvent::TResult SDOCommand::ack(GCFEvent& e) status = GCFEvent::NOT_HANDLED; break; } - + if (e.signal != RSP_GETSDOMODEACK) { GCFScheduler::instance()->stop(); } @@ -916,7 +1121,7 @@ GCFEvent::TResult SDOmodeCommand::ack(GCFEvent& e) break; case 3: cout << formatString("RSP[%02u]: 16/8/5/4 : %2d\n", rsp, ack.bits_per_sample[rsp]); - break; + break; default: break; } } @@ -1150,7 +1355,7 @@ GCFEvent::TResult RSUCommand::ack(GCFEvent& e) logMessage(cerr,"Error: RSP_SETRSU command failed."); rspctl_exit_code = EXIT_FAILURE; } - + } } } @@ -3525,7 +3730,7 @@ GCFEvent::TResult RSPCtl::doCommand(GCFEvent& e, GCFPortInterface& port) case RSP_GETSDOMODEACK: case RSP_SETSDOACK: case RSP_GETSDOACK: - + status = itsCommand->ack(e); // handle the acknowledgement gClockChanged = false; gBitmodeChanged = false; @@ -3639,6 +3844,12 @@ static void usage(bool exportMode) cout << " --rcuenable[=0] # enable (or disable) input from RCU's" << endl; cout << endl; cout << "rspctl --specinv[=0] [--select=<set>] # enable (or disable) spectral inversion" << endl; + + cout << "rspctl --mode=[0..7] [--select=<set>] # set rcumode in a specific mode" << endl; + cout << " # enable(or disable) input from RCU's" << endl; + cout << " # enable(or disable) spectral inversion" << endl; + cout << " # set the hbadelays to 253" << endl; + cout << endl; cout << "--- Signalprocessing -----------------------------------------------------------------------------------------" << endl; cout << "rspctl --weights [--select=<set>] # get weights as complex values" << endl; @@ -3771,6 +3982,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) { "sdoenable", optional_argument, 0, 'J' }, { "bitmode", optional_argument, 0, 'K' }, { "latency", no_argument, 0, 'L' }, + { "mode", required_argument, 0, 'M' }, { "phase", required_argument, 0, 'P' }, { "tdstatus", no_argument, 0, 'Q' }, { "realdelays", optional_argument, 0, 'R' }, @@ -3788,7 +4000,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) realDelays = false; while (1) { int option_index = 0; - int c = getopt_long(argc, argv, "a::b:c::d:e::g::hi:j::k::l:m:n:o::p::qr::s::t::vw::xy:z::A:BC::D:E::G:H::I::J::K::LP:QR::ST::VXY::Z::1:2:", long_options, &option_index); + int c = getopt_long(argc, argv, "a::b:c::d:e::g::hi:j::k::l:m:n:o::p::qr::s::t::vw::xy:z::A:BC::D:E::G:H::I::J::K::LM:P:QR::ST::VXY::Z::1:2:", long_options, &option_index); if (c == -1) // end of argument list reached? break; @@ -4195,7 +4407,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) bitmodecommand->bitmode(bitmode); } } break; - + case 'j': // --sdo { if (command) @@ -4217,7 +4429,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) } } break; - + case 'k': // --sdomode { if (command) @@ -4459,7 +4671,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) } break; - case 'J': // --sdoenable + case 'J': // --sdoenable { if (command) delete command; @@ -4474,7 +4686,7 @@ Command* RSPCtl::parse_options(int argc, char** argv) } } break; - + case 'Y': // --datastream { if (command) @@ -4566,6 +4778,27 @@ Command* RSPCtl::parse_options(int argc, char** argv) } break; + case 'M': { // mode + if (!optarg) { + logMessage(cerr,"Error: option requires an argument"); + rspctl_exit_code = EXIT_FAILURE; + return 0; + } + int mode = atoi(optarg); + if (mode < 0 || mode > 7) { + logMessage(cerr,"Error: wrong mode must be in range 0..7"); + rspctl_exit_code = EXIT_FAILURE; + return 0; + } + + ModeCommand* modecommand = new ModeCommand(*itsRSPDriver); + command = modecommand; + command->set_ndevices(m_nrcus); + + modecommand->setReceiverMode(mode); + //itsNeedClockOnce = true; + } break; + case '1': // readblock=RSPboard,hexAddress,offset,(datalen|filename) case '2': { // writeblock=RSPboard,hexAddress,offset,(datalen|filename) // allocate the right command diff --git a/MAC/APL/PIC/RSP_Driver/src/rspctl.h b/MAC/APL/PIC/RSP_Driver/src/rspctl.h index 0cf23cb8d3f..54775cc3c33 100644 --- a/MAC/APL/PIC/RSP_Driver/src/rspctl.h +++ b/MAC/APL/PIC/RSP_Driver/src/rspctl.h @@ -270,6 +270,27 @@ private: RCUSettings::Control m_control; }; + +// +// class ModeCommand +// +class ModeCommand : public Command +{ +public: + ModeCommand(GCFPortInterface& port); + virtual ~ModeCommand() {} + virtual void send(); + virtual GCFEvent::TResult ack(GCFEvent& e); + + void setReceiverMode(int mode) { itsMode = mode; } + +private: + int itsMode; + int itsClock; + int itsStage; +}; + + // // class HBACommand // -- GitLab From 94da353928310e503193d4b65400f613e76e0c69 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 7 Jun 2016 12:28:53 +0000 Subject: [PATCH 285/933] Task #9487: Backported fix for MAC unknown error 25 --- .../src/ObservationControl/ObservationControl.cc | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc b/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc index 0e3407d780e..f8669511d32 100644 --- a/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc +++ b/MAC/APL/MainCU/src/ObservationControl/ObservationControl.cc @@ -253,6 +253,10 @@ void ObservationControl::setState(CTState::CTstateNr newState) message = "Lost connection(s)"; reportState = RTDB_OBJ_STATE_BROKEN; break; + case CT_RESULT_EMERGENCY_TIMEOUT: + message = "Central controller did not die"; + reportState = RTDB_OBJ_STATE_BROKEN; + break; default: message = formatString("Unknown reason(%d)", itsQuitReason); reportState = RTDB_OBJ_STATE_BROKEN; @@ -535,7 +539,17 @@ GCFEvent::TResult ObservationControl::active_state(GCFEvent& event, GCFPortInter } else if (timerEvent.id == itsForcedQuitTimer) { LOG_WARN("QUITING BEFORE ALL CHILDREN DIED."); - itsQuitReason = CT_RESULT_EMERGENCY_TIMEOUT; + + bool centralControllerRunning = + (itsChildControl->countChilds(0, itsProcessType == "Observation" ? CNTLRTYPE_ONLINECTRL : CNTLRTYPE_OFFLINECTRL)); + + if (centralControllerRunning) { + LOG_FATAL("Central controller did not die."); + itsQuitReason = CT_RESULT_EMERGENCY_TIMEOUT; + } else { + // JDM #9487: For stations, a loss of connection is NOT fatal + } + TRAN(ObservationControl::finishing_state); } // some other timer? -- GitLab From 620d30f6f2085bbfb9efd54836890056bc9ac164 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 7 Jun 2016 13:10:23 +0000 Subject: [PATCH 286/933] Task #9189: Gaincal/Applycal improvements backported to 2.16 --- .gitattributes | 2 + CEP/DP3/DPPP/include/DPPP/ApplyCal.h | 27 +- CEP/DP3/DPPP/include/DPPP/CMakeLists.txt | 2 +- CEP/DP3/DPPP/include/DPPP/GainCal.h | 74 +- CEP/DP3/DPPP/include/DPPP/StefCal.h | 134 +++ CEP/DP3/DPPP/src/ApplyCal.cc | 256 +++--- CEP/DP3/DPPP/src/CMakeLists.txt | 2 +- CEP/DP3/DPPP/src/GainCal.cc | 997 ++++++++++------------- CEP/DP3/DPPP/src/StefCal.cc | 486 +++++++++++ CEP/DP3/DPPP/test/tGainCal.run | 61 +- CEP/DP3/DPPP/test/tGainCal.tab.tgz | Bin 171806 -> 212564 bytes CEP/DP3/DPPP/test/tGainCal_ref | 32 +- CMake/testscripts/assay | 2 +- 13 files changed, 1354 insertions(+), 721 deletions(-) create mode 100644 CEP/DP3/DPPP/include/DPPP/StefCal.h create mode 100644 CEP/DP3/DPPP/src/StefCal.cc diff --git a/.gitattributes b/.gitattributes index 6d9f3512a5b..b0667def31c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -849,6 +849,7 @@ CEP/DP3/DPPP/include/DPPP/ScaleData.h -text CEP/DP3/DPPP/include/DPPP/Simulate.h -text CEP/DP3/DPPP/include/DPPP/Simulator.h -text CEP/DP3/DPPP/include/DPPP/SourceDBUtil.h -text +CEP/DP3/DPPP/include/DPPP/StefCal.h -text CEP/DP3/DPPP/include/DPPP/Stokes.h -text CEP/DP3/DPPP/include/DPPP/SubtractMixed.h -text CEP/DP3/DPPP/include/DPPP/SubtractNew.h -text @@ -877,6 +878,7 @@ CEP/DP3/DPPP/src/ScaleData.cc -text CEP/DP3/DPPP/src/Simulate.cc -text CEP/DP3/DPPP/src/Simulator.cc -text CEP/DP3/DPPP/src/SourceDBUtil.cc -text +CEP/DP3/DPPP/src/StefCal.cc -text CEP/DP3/DPPP/src/Stokes.cc -text CEP/DP3/DPPP/src/SubtractMixed.cc -text CEP/DP3/DPPP/src/SubtractNew.cc -text diff --git a/CEP/DP3/DPPP/include/DPPP/ApplyCal.h b/CEP/DP3/DPPP/include/DPPP/ApplyCal.h index 6ecfa01bf96..113b39c2a5e 100644 --- a/CEP/DP3/DPPP/include/DPPP/ApplyCal.h +++ b/CEP/DP3/DPPP/include/DPPP/ApplyCal.h @@ -78,18 +78,27 @@ namespace LOFAR { // Invert a 2x2 matrix in place static void invert (casa::DComplex* v, double sigmaMMSE=0); - private: // Apply a diagonal Jones matrix to the 2x2 visibilities matrix: A.V.B^H - void applyDiag (casa::Complex* vis, float* weight, int antA, int antB, - int chan, int time); + static void applyDiag (const casa::DComplex* gainA, + const casa::DComplex* gainB, + casa::Complex* vis, float* weight, bool* flag, + uint bl, uint chan, bool updateWeights, + FlagCounter& flagCounter); // Apply a full Jones matrix to the 2x2 visibilities matrix: A.V.B^H - void applyFull (casa::Complex* vis, float* weight, int antA, int antB, - int chan, int time); + static void applyFull (const casa::DComplex* gainA, + const casa::DComplex* gainB, + casa::Complex* vis, float* weight, bool* flag, + uint bl, uint chan, bool updateWeights, + FlagCounter& flagCounter); + private: // Read parameters from the associated parmdb and store them in itsParms void updateParms (const double bufStartTime); + // If needed, show the flag counts. + virtual void showCounts (std::ostream&) const; + void initDataArrays(); //# Data members. @@ -104,15 +113,17 @@ namespace LOFAR { double itsSigmaMMSE; bool itsUpdateWeights; + uint itsCount; // number of steps + // Expressions to search for in itsParmDB vector<casa::String> itsParmExprs; // parameters, numparms, antennas, time x frequency - vector<vector<vector<casa::DComplex> > > itsParms; - uint itsTimeStep; + casa::Cube<casa::DComplex> itsParms; + uint itsTimeStep; // time step within current chunk uint itsNCorr; double itsTimeInterval; - double itsLastTime; + double itsLastTime; // last time of current chunk FlagCounter itsFlagCounter; bool itsUseAP; //# use ampl/phase or real/imag NSTimer itsTimer; diff --git a/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt b/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt index 926dfbeb5b1..c92e64372df 100644 --- a/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt +++ b/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt @@ -16,7 +16,7 @@ set(inst_HEADERS DemixerNew.h DemixInfo.h DemixWorker.h ApplyBeam.h ApplyBeam.tcc Predict.h - GainCal.h + GainCal.h StefCal.h ) # Create symbolic link to include directory. diff --git a/CEP/DP3/DPPP/include/DPPP/GainCal.h b/CEP/DP3/DPPP/include/DPPP/GainCal.h index 219027d50a5..fb3f72b404b 100644 --- a/CEP/DP3/DPPP/include/DPPP/GainCal.h +++ b/CEP/DP3/DPPP/include/DPPP/GainCal.h @@ -29,6 +29,7 @@ #include <DPPP/DPInput.h> #include <DPPP/DPBuffer.h> +#include <DPPP/StefCal.h> #include <DPPP/Patch.h> #include <DPPP/Predict.h> #include <ParmDB/ParmFacade.h> @@ -83,61 +84,58 @@ namespace LOFAR { private: - struct StefVecs { - casa::Matrix<casa::DComplex> g; - casa::Matrix<casa::DComplex> gold; - casa::Matrix<casa::DComplex> gx; - casa::Matrix<casa::DComplex> gxx; - casa::Matrix<casa::DComplex> h; - std::vector<casa::Matrix<casa::DComplex> > z; - }; - - void exportToMatlab(uint ch); - // Perform stefcal (polarized or unpolarized) - void stefcal(string mode, uint solint=1); + void stefcal(); + + // Apply the solution + void applySolution(DPBuffer& buf, const casa::Cube<casa::DComplex>& invsol); - // Counts the number of antennas with non-flagged data, adds this to - // dataPerAntenna - void countAntUsedNotFlagged (const casa::Bool* flag); + // Invert solution (for applying it) + casa::Cube<casa::DComplex> invertSol(const casa::Cube<casa::DComplex>& sol); - // Set a map for the used antennas - void setAntennaMaps (); + // Counts the number of antennas with non-flagged data, + // Set a map for the used antennas in iS, returns the number of antennas + void setAntennaMaps (const casa::Bool* flag, uint freqCell); // Remove rows and colums corresponding to antennas with too much // flagged data from vis and mvis void removeDeadAntennas (); // Fills the matrices itsVis and itsMVis - void fillMatrices (casa::Complex* model, casa::Complex* data, float* weight, - const casa::Bool* flag); + void fillMatrices (casa::Complex* model, casa::Complex* data, + float* weight, const casa::Bool* flag); + + // Initialize the parmdb + void initParmDB(); + + // Get parmdbname from itsMode + string parmName(); + + // Write out the solutions of the current parameter chunk (timeslotsperparmupdate) + void writeSolutions (double startTime); //# Data members. DPInput* itsInput; string itsName; - DPBuffer itsBuf; + vector<DPBuffer> itsBuf; bool itsUseModelColumn; casa::Cube<casa::Complex> itsModelData; string itsParmDBName; shared_ptr<BBS::ParmDB> itsParmDB; string itsMode; - uint itsTStep; uint itsDebugLevel; bool itsDetectStalling; - string itsStefcalVariant; - vector<Baseline> itsBaselines; + bool itsApplySolution; - casa::Array<casa::DComplex> itsVis; - casa::Array<casa::DComplex> itsMVis; + vector<Baseline> itsBaselines; - vector<casa::Matrix<casa::DComplex> > itsSols; // for every timeslot, nSt gains with vector of length nCr values - vector<vector<int> > itsAntUseds; - vector<vector<int> > itsAntMaps; + vector<casa::Matrix<casa::DComplex> > itsPrevSol; // previous solution, for propagating solutions, for each freq + vector<casa::Cube<casa::DComplex> > itsSols; // for every timeslot, nCr x nSt x nFreqCells - StefVecs iS; + std::vector<StefCal> iS; Predict itsPredictStep; ApplyBeam itsApplyBeamStep; // Beam step for applying beam to modelcol @@ -145,19 +143,27 @@ namespace LOFAR { bool itsApplyBeamToModelColumn; casa::Vector<casa::String> itsAntennaUsedNames; - casa::Vector<uint> itsDataPerAntenna; map<string,int> itsParmIdMap; //# -1 = new parm name uint itsMaxIter; double itsTolerance; - bool itsPropagateSolutions; - uint itsSolInt; - uint itsMinBLperAnt; + bool itsPropagateSolutions; // Not used currently, TODO: use this + uint itsSolInt; // Time cell size + uint itsNChan; // Frequency cell size + uint itsNFreqCells; + uint itsMinBLperAnt; + uint itsTimeSlotsPerParmUpdate; uint itsConverged; uint itsNonconverged; uint itsStalled; - uint itsNTimes; + vector<uint> itsNIter; // Total iterations made (for converged, stalled and nonconverged) + uint itsStepInParmUpdate; // Timestep within parameter update + double itsChunkStartTime; // First time value of chunk to be stored + uint itsStepInSolInt; // Timestep within solint + + FlagCounter itsFlagCounter; + NSTimer itsTimer; NSTimer itsTimerPredict; NSTimer itsTimerSolve; diff --git a/CEP/DP3/DPPP/include/DPPP/StefCal.h b/CEP/DP3/DPPP/include/DPPP/StefCal.h new file mode 100644 index 00000000000..b6a62182c9f --- /dev/null +++ b/CEP/DP3/DPPP/include/DPPP/StefCal.h @@ -0,0 +1,134 @@ +//# StefCal.h: Perform StefCal algorithm for gain calibration +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: StefCal.h 21598 2012-07-16 08:07:34Z diepen $ +//# +//# @author Tammo Jan Dijkema + +#ifndef DPPP_STEFCAL_H +#define DPPP_STEFCAL_H + +// @file +// @brief DPPP step class to apply a calibration correction to the data +#include <casa/Arrays/Cube.h> +#include <casa/Arrays/ArrayMath.h> + +namespace LOFAR { + + namespace DPPP { + // @ingroup NDPPP + + class StefCal + { + public: + enum Status {CONVERGED=1, NOTCONVERGED=2, STALLED=3}; + + // mode can be "diagonal", "fulljones", "phaseonly", "scalarphase" + StefCal(uint solInt, uint nChan, const string& mode, double tolerance, + uint maxAntennas, bool detectStalling, uint debugLevel); + + // Sets visibility matrices to zero + void resetVis(); + + // Initializes a new run of stefcal, resizes all internal vectors + // If initSolutions is false, you are responsible for setting them + // before running the solver. You could set the solutions to those + // of the previous time step. + void init(bool initSolutions); + + // Perform sone iteration of stefcal. Returns CONVERGED, NOTCONVERGED + // or STALLED + Status doStep(uint iter); + + // Returns the solution. The return matrix has a length of maxAntennas, + // which is zero for antennas for which no solution was computed. + // The mapping is stored in the antenna map + casa::Matrix<casa::DComplex> getSolution(bool setNaNs); + + // Returns a reference to the visibility matrix + casa::Array<casa::DComplex>& getVis() { + return _vis; + } + + // Returns a reference to the model visibility matrix + casa::Array<casa::DComplex>& getMVis() { + return _mvis; + } + + std::vector<bool>& getStationFlagged() { + return _stationFlagged; + } + + // Number of correlations in the solution (1,2 or 4) + uint numCorrelations() { + return _savedNCr; + } + + // Number of correlations (1 or 4) + uint nCr() { + return _nCr; + } + + // Clear antFlagged + void clearStationFlagged(); + + private: + // Number of unknowns (nSt or 2*nSt, depending on _mode) + uint nUn(); + + // Perform relaxation + Status relax(uint iter); + + void doStep_polarized(); + void doStep_unpolarized(); + + double getAverageUnflaggedSolution(); + + uint _savedNCr; + std::vector<bool> _stationFlagged ; // Contains true for totally flagged stations + casa::Array<casa::DComplex> _vis; // Visibility matrix + casa::Array<casa::DComplex> _mvis; // Model visibility matrix + casa::Matrix<casa::DComplex> _g; // Solution, indexed by station, correlation + casa::Matrix<casa::DComplex> _gx; // Previous solution + casa::Matrix<casa::DComplex> _gxx; // Solution before previous solution + casa::Matrix<casa::DComplex> _gold; // Previous solution + casa::Matrix<casa::DComplex> _h; // Hermitian transpose of previous solution + casa::Matrix<casa::DComplex> _z; // Internal stefcal vector + + uint _nSt; // number of stations in the current solution + uint _nUn; // number of unknowns + uint _nCr; // number of correlations (1 or 4) + uint _nSp; // number that is two for scalarphase, one else + uint _badIters; // number of bad iterations, for stalling detection + uint _veryBadIters; // number of iterations where solution got worse + uint _solInt; // solution interval + uint _nChan; // number of channels + string _mode; // diagonal, scalarphase, fulljones or phaseonly + double _tolerance; + bool _detectStalling; + uint _debugLevel; + + double _dg, _dgx; // previous convergence + std::vector<double> _dgs; // convergence history + }; + + } //# end namespace +} + +#endif diff --git a/CEP/DP3/DPPP/src/ApplyCal.cc b/CEP/DP3/DPPP/src/ApplyCal.cc index b0394aafe9e..402dbac3abb 100644 --- a/CEP/DP3/DPPP/src/ApplyCal.cc +++ b/CEP/DP3/DPPP/src/ApplyCal.cc @@ -54,6 +54,7 @@ namespace LOFAR { "timeslotsperparmupdate", 500)), itsSigmaMMSE (parset.getDouble (prefix + "MMSE.Sigma", 0)), itsUpdateWeights (parset.getBool (prefix + "updateweights", false)), + itsCount (0), itsTimeStep (0), itsNCorr (0), itsTimeInterval (-1), @@ -185,6 +186,23 @@ namespace LOFAR { initDataArrays(); itsFlagCounter.init(getInfo()); + + // Check that channels are evenly spaced + if (info().nchan()>1) { + Vector<Double> upFreq = info().chanFreqs()( + Slicer(IPosition(1,1), + IPosition(1,info().nchan()-1))); + Vector<Double> lowFreq = info().chanFreqs()( + Slicer(IPosition(1,0), + IPosition(1,info().nchan()-1))); + Double freqstep0=upFreq(0)-lowFreq(0); + // Compare up to 1kHz accuracy + bool regularChannels=allNearAbs(upFreq-lowFreq, freqstep0, 1.e3) && + allNearAbs(info().chanWidths(), + info().chanWidths()(0), 1.e3); + ASSERTSTR(regularChannels, + "ApplyCal requires evenly spaced channels."); + } } void ApplyCal::show (std::ostream& os) const @@ -230,30 +248,40 @@ namespace LOFAR { itsInput->fetchWeights (bufin, itsBuffer, itsTimer); float* weight = itsBuffer.getWeights().data(); + bool* flag = itsBuffer.getFlags().data(); + size_t nchan = itsBuffer.getData().shape()[1]; #pragma omp parallel for for (size_t bl=0; bl<nbl; ++bl) { for (size_t chan=0;chan<nchan;chan++) { - if (itsParms.size()>2) { - applyFull( &data[bl * itsNCorr * nchan + chan * itsNCorr ], - &weight[bl * itsNCorr * nchan + chan * itsNCorr ], - info().getAnt1()[bl], info().getAnt2()[bl], chan, itsTimeStep); + uint timeFreqOffset=(itsTimeStep*info().nchan())+chan; + uint antA = info().getAnt1()[bl]; + uint antB = info().getAnt2()[bl]; + if (itsParms.shape()[0]>2) { + applyFull( &itsParms(0, antA, timeFreqOffset), + &itsParms(0, antB, timeFreqOffset), + &data[bl * itsNCorr * nchan + chan * itsNCorr ], + &weight[bl * itsNCorr * nchan + chan * itsNCorr ], + &flag[ bl * itsNCorr * nchan + chan * itsNCorr ], + bl, chan, itsUpdateWeights, itsFlagCounter); } else { - applyDiag( &data[bl * itsNCorr * nchan + chan * itsNCorr ], - &weight[bl * itsNCorr * nchan + chan * itsNCorr ], - info().getAnt1()[bl], info().getAnt2()[bl], chan, itsTimeStep); + applyDiag( &itsParms(0, antA, timeFreqOffset), + &itsParms(0, antB, timeFreqOffset), + &data[bl * itsNCorr * nchan + chan * itsNCorr ], + &weight[bl * itsNCorr * nchan + chan * itsNCorr ], + &flag[ bl * itsNCorr * nchan + chan * itsNCorr ], + bl, chan, itsUpdateWeights, itsFlagCounter); } } } - MSReader::flagInfNaN(itsBuffer.getData(), itsBuffer.getFlags(), - itsFlagCounter); - itsTimer.stop(); getNextStep()->process(itsBuffer); - return false; + + itsCount++; + return true; } void ApplyCal::finish() @@ -268,7 +296,7 @@ namespace LOFAR { int numAnts = info().antennaNames().size(); // itsParms contains the parameters to a grid, first for all parameters - // (e.g. Gain:0:0 and Gain:1:1), next all antennas, next over frec * time + // (e.g. Gain:0:0 and Gain:1:1), next all antennas, next over freq * time // as returned by ParmDB vector<vector<vector<double> > > parmvalues; parmvalues.resize(itsParmExprs.size()); @@ -278,14 +306,16 @@ namespace LOFAR { uint numFreqs (info().chanFreqs().size()); double freqInterval (info().chanWidths()[0]); + if (numFreqs>1) { // Handle data with evenly spaced gaps between channels + freqInterval = info().chanFreqs()[1]-info().chanFreqs()[0]; + } double minFreq (info().chanFreqs()[0]-0.5*freqInterval); double maxFreq (info().chanFreqs()[numFreqs-1]+0.5*freqInterval); - itsLastTime = bufStartTime + itsTimeSlotsPerParmUpdate * itsTimeInterval; uint numTimes = itsTimeSlotsPerParmUpdate; double lastMSTime = info().startTime() + info().ntime() * itsTimeInterval; - if (itsLastTime > lastMSTime) { + if (itsLastTime > lastMSTime && !nearAbs(itsLastTime, lastMSTime, 1.e-3)) { itsLastTime = lastMSTime; numTimes = info().ntime() % itsTimeSlotsPerParmUpdate; } @@ -352,59 +382,59 @@ namespace LOFAR { if (itsCorrectType=="gain") { if (itsUseAP) { // Data as Amplitude / Phase - itsParms[0][ant][tf] = polar(parmvalues[0][ant][tf], - parmvalues[1][ant][tf]); - itsParms[1][ant][tf] = polar(parmvalues[2][ant][tf], - parmvalues[3][ant][tf]); + itsParms(0, ant, tf) = polar(parmvalues[0][ant][tf], + parmvalues[1][ant][tf]); + itsParms(1, ant, tf) = polar(parmvalues[2][ant][tf], + parmvalues[3][ant][tf]); } else { // Data as Real / Imaginary - itsParms[0][ant][tf] = DComplex(parmvalues[0][ant][tf], - parmvalues[1][ant][tf]); - itsParms[1][ant][tf] = DComplex(parmvalues[2][ant][tf], - parmvalues[3][ant][tf]); + itsParms(0, ant, tf) = DComplex(parmvalues[0][ant][tf], + parmvalues[1][ant][tf]); + itsParms(1, ant, tf) = DComplex(parmvalues[2][ant][tf], + parmvalues[3][ant][tf]); } } else if (itsCorrectType=="fulljones") { if (itsUseAP) { // Data as Amplitude / Phase - itsParms[0][ant][tf] = polar(parmvalues[0][ant][tf], - parmvalues[1][ant][tf]); - itsParms[1][ant][tf] = polar(parmvalues[2][ant][tf], - parmvalues[3][ant][tf]); - itsParms[2][ant][tf] = polar(parmvalues[4][ant][tf], - parmvalues[5][ant][tf]); - itsParms[3][ant][tf] = polar(parmvalues[6][ant][tf], - parmvalues[7][ant][tf]); + itsParms(0, ant, tf) = polar(parmvalues[0][ant][tf], + parmvalues[1][ant][tf]); + itsParms(1, ant, tf) = polar(parmvalues[2][ant][tf], + parmvalues[3][ant][tf]); + itsParms(2, ant, tf) = polar(parmvalues[4][ant][tf], + parmvalues[5][ant][tf]); + itsParms(3, ant, tf) = polar(parmvalues[6][ant][tf], + parmvalues[7][ant][tf]); } else { // Data as Real / Imaginary - itsParms[0][ant][tf] = DComplex(parmvalues[0][ant][tf], - parmvalues[1][ant][tf]); - itsParms[1][ant][tf] = DComplex(parmvalues[2][ant][tf], - parmvalues[3][ant][tf]); - itsParms[2][ant][tf] = DComplex(parmvalues[4][ant][tf], - parmvalues[5][ant][tf]); - itsParms[3][ant][tf] = DComplex(parmvalues[6][ant][tf], - parmvalues[7][ant][tf]); + itsParms(0, ant, tf) = DComplex(parmvalues[0][ant][tf], + parmvalues[1][ant][tf]); + itsParms(1, ant, tf) = DComplex(parmvalues[2][ant][tf], + parmvalues[3][ant][tf]); + itsParms(2, ant, tf) = DComplex(parmvalues[4][ant][tf], + parmvalues[5][ant][tf]); + itsParms(3, ant, tf) = DComplex(parmvalues[6][ant][tf], + parmvalues[7][ant][tf]); } } else if (itsCorrectType=="tec") { - itsParms[0][ant][tf]=polar(1., + itsParms(0, ant, tf)=polar(1., parmvalues[0][ant][tf] * -8.44797245e9 / freq); if (itsParmExprs.size() == 1) { // No TEC:0, only TEC: - itsParms[1][ant][tf]=polar(1., + itsParms(1, ant, tf)=polar(1., parmvalues[0][ant][tf] * -8.44797245e9 / freq); } else { // TEC:0 and TEC:1 - itsParms[1][ant][tf]=polar(1., + itsParms(1, ant, tf)=polar(1., parmvalues[1][ant][tf] * -8.44797245e9 / freq); } } else if (itsCorrectType=="clock") { - itsParms[0][ant][tf]=polar(1., + itsParms(0, ant, tf)=polar(1., parmvalues[0][ant][tf] * freq * casa::C::_2pi); if (itsParmExprs.size() == 1) { // No Clock:0, only Clock: - itsParms[1][ant][tf]=polar(1., + itsParms(1, ant, tf)=polar(1., parmvalues[0][ant][tf] * freq * casa::C::_2pi); } else { // Clock:0 and Clock:1 - itsParms[1][ant][tf]=polar(1., + itsParms(1, ant, tf)=polar(1., parmvalues[1][ant][tf] * freq * casa::C::_2pi); } } @@ -415,10 +445,10 @@ namespace LOFAR { } double sinv=sin(phi); double cosv=cos(phi); - itsParms[0][ant][tf] = cosv; - itsParms[1][ant][tf] = -sinv; - itsParms[2][ant][tf] = sinv; - itsParms[3][ant][tf] = cosv; + itsParms(0, ant, tf) = cosv; + itsParms(1, ant, tf) = -sinv; + itsParms(2, ant, tf) = sinv; + itsParms(3, ant, tf) = cosv; } else if (itsCorrectType=="rotationmeasure") { double lambda2 = casa::C::c / freq; @@ -429,21 +459,23 @@ namespace LOFAR { } double sinv = sin(chi); double cosv = cos(chi); - itsParms[0][ant][tf] = cosv; - itsParms[1][ant][tf] = -sinv; - itsParms[2][ant][tf] = sinv; - itsParms[3][ant][tf] = cosv; + itsParms(0, ant, tf) = cosv; + itsParms(1, ant, tf) = -sinv; + itsParms(2, ant, tf) = sinv; + itsParms(3, ant, tf) = cosv; } else if (itsCorrectType=="commonscalarphase") { - itsParms[0][ant][tf] = polar(1., parmvalues[0][ant][tf]); - itsParms[1][ant][tf] = polar(1., parmvalues[0][ant][tf]); + itsParms(0, ant, tf) = polar(1., parmvalues[0][ant][tf]); + itsParms(1, ant, tf) = polar(1., parmvalues[0][ant][tf]); } - // Invert diagonal corrections (not fulljones and commonrotationangle) - // For fulljones it will be handled in applyFull - if (itsInvert && itsParms.size()==2) { - itsParms[0][ant][tf] = 1./itsParms[0][ant][tf]; - itsParms[1][ant][tf] = 1./itsParms[1][ant][tf]; + // Invert (rotationmeasure and commonrotationangle are already inverted) + if (itsInvert && itsParms.shape()[0]==2) { + itsParms(0, ant, tf) = 1./itsParms(0, ant, tf); + itsParms(1, ant, tf) = 1./itsParms(1, ant, tf); + } + else if (itsInvert && itsCorrectType=="fulljones") { + invert(&itsParms(0, ant, tf),itsSigmaMMSE); } } } @@ -463,35 +495,39 @@ namespace LOFAR { numParms = 2; } - itsParms.resize(numParms); + itsParms.resize(numParms, numAnts, tfDomainSize); + } - for (uint parmNum=0;parmNum<numParms;parmNum++) { - itsParms[parmNum].resize(numAnts); - for (uint ant=0;ant<numAnts;++ant) { - itsParms[parmNum][ant].resize(tfDomainSize); + void ApplyCal::applyDiag (const DComplex* gainA, const DComplex* gainB, + Complex* vis, float* weight, bool* flag, + uint bl, uint chan, bool updateWeights, + FlagCounter& flagCounter) { + // If parameter is NaN or inf, do not apply anything and flag the data + if (! (isFinite(gainA[0].real()) && isFinite(gainA[0].imag()) && + isFinite(gainB[0].real()) && isFinite(gainB[0].imag()) && + isFinite(gainA[1].real()) && isFinite(gainA[1].imag()) && + isFinite(gainB[1].real()) && isFinite(gainB[1].imag())) ) { + // Only update flagcounter for first correlation + if (!flag[0]) { + flagCounter.incrChannel(chan); + flagCounter.incrBaseline(bl); + } + for (uint corr=0; corr<4; ++corr) { + flag[corr]=true; } + return; } - } - - void ApplyCal::applyDiag (Complex* vis, float* weight, int antA, - int antB, int chan, int time) { - int timeFreqOffset=(time*info().nchan())+chan; - - DComplex diag0A = itsParms[0][antA][timeFreqOffset]; - DComplex diag1A = itsParms[1][antA][timeFreqOffset]; - DComplex diag0B = itsParms[0][antB][timeFreqOffset]; - DComplex diag1B = itsParms[1][antB][timeFreqOffset]; - vis[0] *= diag0A * conj(diag0B); - vis[1] *= diag0A * conj(diag1B); - vis[2] *= diag1A * conj(diag0B); - vis[3] *= diag1A * conj(diag1B); + vis[0] *= gainA[0] * conj(gainB[0]); + vis[1] *= gainA[0] * conj(gainB[1]); + vis[2] *= gainA[1] * conj(gainB[0]); + vis[3] *= gainA[1] * conj(gainB[1]); - if (itsUpdateWeights) { - weight[0] /= norm(diag0A) * norm(diag0B); - weight[1] /= norm(diag0A) * norm(diag1B); - weight[2] /= norm(diag1A) * norm(diag0B); - weight[3] /= norm(diag1A) * norm(diag1B); + if (updateWeights) { + weight[0] /= norm(gainA[0]) * norm(gainB[0]); + weight[1] /= norm(gainA[0]) * norm(gainB[1]); + weight[2] /= norm(gainA[1]) * norm(gainB[0]); + weight[3] /= norm(gainA[1]) * norm(gainB[1]); } } @@ -510,26 +546,31 @@ namespace LOFAR { v[3] = v0 * invDet; } - void ApplyCal::applyFull (Complex* vis, float* weight, int antA, - int antB, int chan, int time) { - int timeFreqOffset=(time*info().nchan())+chan; - DComplex gainA[4]; - DComplex gainB[4]; - - gainA[0] = itsParms[0][antA][timeFreqOffset]; - gainA[1] = itsParms[1][antA][timeFreqOffset]; - gainA[2] = itsParms[2][antA][timeFreqOffset]; - gainA[3] = itsParms[3][antA][timeFreqOffset]; - - gainB[0] = itsParms[0][antB][timeFreqOffset]; - gainB[1] = itsParms[1][antB][timeFreqOffset]; - gainB[2] = itsParms[2][antB][timeFreqOffset]; - gainB[3] = itsParms[3][antB][timeFreqOffset]; - + void ApplyCal::applyFull (const DComplex* gainA, const DComplex* gainB, + Complex* vis, float* weight, bool* flag, + uint bl, uint chan, bool updateWeights, + FlagCounter& flagCounter) { DComplex gainAxvis[4]; - if (itsInvert && itsCorrectType=="fulljones") { - invert(gainA,itsSigmaMMSE); - invert(gainB,itsSigmaMMSE); + + // If parameter is NaN or inf, do not apply anything and flag the data + bool anyinfnan = false; + for (uint corr=0; corr<4; ++corr) { + if (! (isFinite(gainA[corr].real()) && isFinite(gainA[corr].imag()) && + isFinite(gainB[corr].real()) && isFinite(gainB[corr].imag())) ) { + anyinfnan = true; + break; + } + } + if (anyinfnan) { + // Only update flag counter for first correlation + if (!flag[0]) { + flagCounter.incrChannel(chan); + flagCounter.incrBaseline(bl); + } + for (uint corr=0; corr<4; ++corr) { + flag[corr]=true; + } + return; } // gainAxvis = gainA * vis @@ -556,8 +597,7 @@ namespace LOFAR { // The input covariance matrix C is assumed to be diagonal with elements // w_i (the weights), the result the diagonal of // (gainA kronecker gainB^H).C.(gainA kronecker gainB^H)^H - if (itsUpdateWeights) { - ASSERTSTR (itsInvert, "Updating weights has not been implemented for invert=false"); + if (updateWeights) { float cov[4], normGainA[4], normGainB[4]; for (uint i=0;i<4;++i) { cov[i]=1./weight[i]; @@ -591,5 +631,13 @@ namespace LOFAR { } } + void ApplyCal::showCounts (std::ostream& os) const + { + os << endl << "Flags set by ApplyCal " << itsName; + os << endl << "=======================" << endl; + itsFlagCounter.showBaseline (os, itsCount); + itsFlagCounter.showChannel (os, itsCount); + } + } //# end namespace } diff --git a/CEP/DP3/DPPP/src/CMakeLists.txt b/CEP/DP3/DPPP/src/CMakeLists.txt index f4409dd72c5..51598d1c8b1 100644 --- a/CEP/DP3/DPPP/src/CMakeLists.txt +++ b/CEP/DP3/DPPP/src/CMakeLists.txt @@ -16,7 +16,7 @@ lofar_add_library(dppp Apply.cc EstimateMixed.cc EstimateNew.cc Simulate.cc Simulator.cc SubtractMixed.cc SubtractNew.cc ModelComponent.cc PointSource.cc GaussianSource.cc Patch.cc - ModelComponentVisitor.cc GainCal.cc + ModelComponentVisitor.cc GainCal.cc StefCal.cc DemixerNew.cc DemixInfo.cc DemixWorker.cc Predict.cc ApplyBeam.cc diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index f4ae1ad6a59..20280fada09 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -24,6 +24,7 @@ #include <lofar_config.h> #include <DPPP/GainCal.h> #include <DPPP/Simulate.h> +#include <DPPP/ApplyCal.h> #include <DPPP/CursorUtilCasa.h> #include <DPPP/DPBuffer.h> #include <DPPP/DPInfo.h> @@ -58,8 +59,6 @@ using namespace casa; using namespace LOFAR::BBS; -/// Look at BBSKernel MeasurementExprLOFARUtil.cc and Apply.cc - namespace LOFAR { namespace DPPP { @@ -71,20 +70,26 @@ namespace LOFAR { itsUseModelColumn(parset.getBool (prefix + "usemodelcolumn", false)), itsParmDBName (parset.getString (prefix + "parmdb", "")), itsMode (parset.getString (prefix + "caltype")), - itsTStep (0), itsDebugLevel (parset.getInt (prefix + "debuglevel", 0)), itsDetectStalling (parset.getBool (prefix + "detectstalling", true)), - itsStefcalVariant(parset.getString (prefix + "stefcalvariant", "1c")), + itsApplySolution (parset.getBool (prefix + "applysolution", false)), itsBaselines (), itsMaxIter (parset.getInt (prefix + "maxiter", 50)), itsTolerance (parset.getDouble (prefix + "tolerance", 1.e-5)), - itsPropagateSolutions (parset.getBool(prefix + "propagatesolutions", false)), + itsPropagateSolutions + (parset.getBool(prefix + "propagatesolutions", true)), itsSolInt (parset.getInt(prefix + "solint", 1)), + itsNChan (parset.getInt(prefix + "nchan", 0)), + itsNFreqCells (0), itsMinBLperAnt (parset.getInt(prefix + "minblperant", 4)), + itsTimeSlotsPerParmUpdate + (parset.getInt(prefix + "timeslotsperparmupdate", 500)), itsConverged (0), itsNonconverged (0), itsStalled (0), - itsNTimes (0) + itsStepInParmUpdate (0), + itsChunkStartTime(0), + itsStepInSolInt (0) { if (itsParmDBName=="") { itsParmDBName=parset.getString("msin")+"/instrument"; @@ -104,8 +109,18 @@ namespace LOFAR { itsApplyBeamStep.setNextStep(DPStep::ShPtr(itsResultStep)); } } + + itsNIter.resize(3,0); + + if (itsApplySolution) { + itsBuf.resize(itsSolInt); + } else { + itsBuf.resize(1); + } + ASSERT(itsMode=="diagonal" || itsMode=="phaseonly" || - itsMode=="fulljones" || itsMode=="scalarphase"); + itsMode=="fulljones" || itsMode=="scalarphase" || + itsMode=="amplitudeonly" || itsMode=="scalaramplitude"); } GainCal::~GainCal() @@ -125,8 +140,10 @@ namespace LOFAR { } else { itsPredictStep.updateInfo(infoIn); } - info().setWriteData(); - info().setWriteFlags(); + if (itsApplySolution) { + info().setWriteData(); + info().setWriteFlags(); + } for (uint i=0; i<nBl; ++i) { itsBaselines.push_back (Baseline(info().getAnt1()[i], @@ -137,18 +154,39 @@ namespace LOFAR { itsSolInt=info().ntime(); } - itsSols.reserve(info().ntime()); + itsSols.reserve(itsTimeSlotsPerParmUpdate); + + if (itsNChan==0) { + itsNChan = info().nchan(); + } + if (itsNChan>info().nchan()) { + itsNChan=info().nchan(); + } + itsNFreqCells = info().nchan() / itsNChan; + if (itsNChan*itsNFreqCells<info().nchan()) { // If last freq cell is smaller + itsNFreqCells++; + } - // Read the antenna beam info from the MS. - // Only take the stations actually used. itsAntennaUsedNames.resize(info().antennaUsed().size()); - itsDataPerAntenna.resize(info().antennaNames().size()); - casa::Vector<int> antsUsed = info().antennaUsed(); for (int ant=0, nAnts=info().antennaUsed().size(); ant<nAnts; ++ant) { itsAntennaUsedNames[ant]=info().antennaNames()[info().antennaUsed()[ant]]; } - iS.z.resize(OpenMP::maxThreads()); + iS.reserve(itsNFreqCells); + uint chMax = itsNChan; + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + if ((freqCell+1)*itsNChan>info().nchan()) { // Last cell can be smaller + chMax-=((freqCell+1)*itsNChan)%info().nchan(); + } + iS.push_back(StefCal(itsSolInt, chMax, + (itsMode=="tec"?"scalarphase":itsMode), + itsTolerance, info().antennaNames().size(), + itsDetectStalling, itsDebugLevel)); + } + + itsFlagCounter.init(getInfo()); + + itsChunkStartTime = info().startTime(); } void GainCal::show (std::ostream& os) const @@ -161,12 +199,15 @@ namespace LOFAR { os << " (will be created)"; } os << endl; - os << " solint: " << itsSolInt <<endl; - os << " max iter: " << itsMaxIter << endl; - os << " tolerance: " << itsTolerance << endl; - os << " mode: " << itsMode << endl; - os << " detect stalling: " << boolalpha << itsDetectStalling << endl; - os << " use model column: " << boolalpha << itsUseModelColumn << endl; + os << " solint: " << itsSolInt <<endl; + os << " nchan: " << itsNChan <<endl; + os << " max iter: " << itsMaxIter << endl; + os << " tolerance: " << itsTolerance << endl; + os << " mode: " << itsMode << endl; + os << " apply solution: " << boolalpha << itsApplySolution << endl; + os << " propagate solutions: " << boolalpha << itsPropagateSolutions << endl; + os << " detect stalling: " << boolalpha << itsDetectStalling << endl; + os << " use model column: " << boolalpha << itsUseModelColumn << endl; if (!itsUseModelColumn) { itsPredictStep.show(os); } else if (itsApplyBeamToModelColumn) { @@ -199,20 +240,31 @@ namespace LOFAR { os << " "; os <<"Converged: "<<itsConverged<<", stalled: "<<itsStalled<<", non converged: "<<itsNonconverged<<endl; + os <<"Iters converged: " << (itsConverged==0?0:itsNIter[0]/itsConverged); + os << ", stalled: "<< (itsStalled ==0?0:itsNIter[1]/itsStalled); + os << ", non converged: "<<(itsNonconverged==0?0:itsNIter[2]/itsNonconverged)<<endl; } bool GainCal::process (const DPBuffer& bufin) { itsTimer.start(); - itsBuf.referenceFilled (bufin); - itsInput->fetchUVW(bufin, itsBuf, itsTimer); - itsInput->fetchWeights(bufin, itsBuf, itsTimer); - itsInput->fetchFullResFlags(bufin, itsBuf, itsTimer); - Cube<Complex> dataCube=itsBuf.getData(); + uint bufIndex=0; + + if (itsApplySolution) { + bufIndex=itsStepInSolInt; + itsBuf[bufIndex].copy(bufin); + } else { + itsBuf[0].referenceFilled (bufin); + } + itsInput->fetchUVW(bufin, itsBuf[bufIndex], itsTimer); + itsInput->fetchWeights(bufin, itsBuf[bufIndex], itsTimer); + itsInput->fetchFullResFlags(bufin, itsBuf[bufIndex], itsTimer); + + Cube<Complex> dataCube=itsBuf[bufIndex].getData(); Complex* data=dataCube.data(); - float* weight = itsBuf.getWeights().data(); - const Bool* flag=itsBuf.getFlags().data(); + float* weight = itsBuf[bufIndex].getWeights().data(); + const Bool* flag=itsBuf[bufIndex].getFlags().data(); // Simulate. // @@ -222,30 +274,33 @@ namespace LOFAR { itsTimerPredict.start(); if (itsUseModelColumn) { - itsInput->getModelData (itsBuf.getRowNrs(), itsModelData); - if (itsApplyBeamToModelColumn) { + itsInput->getModelData (itsBuf[bufIndex].getRowNrs(), itsModelData); + if (itsApplyBeamToModelColumn) { // TODO: double check this // Temporarily put model data in data column for applybeam step // ApplyBeam step will copy the buffer so no harm is done - itsBuf.getData()=itsModelData; - itsApplyBeamStep.process(itsBuf); + itsBuf[bufIndex].getData()=itsModelData; + itsApplyBeamStep.process(itsBuf[bufIndex]); //Put original data back in data column - itsBuf.getData()=dataCube; + itsBuf[bufIndex].getData()=dataCube; } } else { // Predict - itsPredictStep.process(itsBuf); + itsPredictStep.process(itsBuf[bufIndex]); } itsTimerPredict.stop(); itsTimerFill.start(); - if (itsNTimes==0) { - itsDataPerAntenna=0; - itsVis=0; - itsMVis=0; - countAntUsedNotFlagged(flag); - setAntennaMaps(); + if (itsStepInSolInt==0) { + // Start new solution interval + + for (uint freqCell=0; freqCell<itsNFreqCells; freqCell++) { + setAntennaMaps(flag, freqCell); + iS[freqCell].resetVis(); + } } + + // Store data in the stefcal object if (itsUseModelColumn && !itsApplyBeamToModelColumn) { fillMatrices(itsModelData.data(),data,weight,flag); } else { @@ -253,50 +308,126 @@ namespace LOFAR { } itsTimerFill.stop(); - if (itsNTimes==itsSolInt-1) { - stefcal(itsMode,itsSolInt); - itsNTimes=0; + if (itsStepInSolInt==itsSolInt-1) { + // Solve past solution interval + stefcal(); + itsStepInParmUpdate++; + + if (itsApplySolution) { + Cube<DComplex> invsol = invertSol(itsSols.back()); + for (uint stepInSolInt=0; stepInSolInt<itsSolInt; stepInSolInt++) { + applySolution(itsBuf[stepInSolInt], invsol); + getNextStep()->process(itsBuf[stepInSolInt]); + } + } + + itsStepInSolInt=0; } else { - itsNTimes++; + itsStepInSolInt++; } itsTimer.stop(); - itsTStep++; - getNextStep()->process(itsBuf); + + if (itsStepInParmUpdate == itsTimeSlotsPerParmUpdate) { + writeSolutions(itsChunkStartTime); + itsChunkStartTime += itsSolInt * itsTimeSlotsPerParmUpdate * info().timeInterval(); + itsSols.clear(); + itsStepInParmUpdate = 0; + } + + if (!itsApplySolution) { + getNextStep()->process(itsBuf[bufIndex]); + } return false; } + Cube<DComplex> GainCal::invertSol(const Cube<DComplex>& sol) { + Cube<DComplex> invsol = sol.copy(); + uint nCr = invsol.shape()[0]; + + // Invert copy of solutions + uint nSt = invsol.shape()[1]; + for (uint st=0; st<nSt; ++st) { + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + if (nCr==4) { + ApplyCal::invert(&invsol(0,st,freqCell)); + } else { + for (uint cr=0; cr<nCr; ++cr) { + invsol(cr, st, freqCell) = 1./invsol(cr, st, freqCell); + } + } + } + } + + return invsol; + } + + void GainCal::applySolution(DPBuffer& buf, const Cube<DComplex>& invsol) { + uint nbl = buf.getData().shape()[2]; + Complex* data = buf.getData().data(); + float* weight = buf.getWeights().data(); // Not initialized yet + bool* flag = buf.getFlags().data(); + uint nchan = buf.getData().shape()[1]; + + uint nCr = invsol.shape()[0]; + + for (size_t bl=0; bl<nbl; ++bl) { + for (size_t chan=0;chan<nchan;chan++) { + uint antA = info().getAnt1()[bl]; + uint antB = info().getAnt2()[bl]; + uint freqCell = chan / itsNChan; + if (nCr>2) { + ApplyCal::applyFull( &invsol(0, antA, freqCell), + &invsol(0, antB, freqCell), + &data[bl * 4 * nchan + chan * 4 ], + &weight[bl * 4 * nchan + chan * 4 ], // Not passing weights, any pointer should do + &flag[ bl * 4 * nchan + chan * 4 ], + bl, chan, false, itsFlagCounter); // Update weights is disabled here + } + else { + ApplyCal::applyDiag( &invsol(0, antA, freqCell), + &invsol(0, antB, freqCell), + &data[bl * 4 * nchan + chan * 4 ], + &weight[bl * 4 * nchan + chan * 4 ], // Not passing weights, any pointer should do + &flag[ bl * 4 * nchan + chan * 4 ], + bl, chan, false, itsFlagCounter); // Update weights is disabled here + } + } + } + } + // Fills itsVis and itsMVis as matrices with all 00 polarizations in the // top left, all 11 polarizations in the bottom right, etc. void GainCal::fillMatrices (casa::Complex* model, casa::Complex* data, float* weight, const casa::Bool* flag) { - vector<int>* antMap=&itsAntMaps[itsAntMaps.size()-1]; - const size_t nBl = info().nbaselines(); const size_t nCh = info().nchan(); const size_t nCr = 4; for (uint ch=0;ch<nCh;++ch) { for (uint bl=0;bl<nBl;++bl) { - int ant1=(*antMap)[info().getAnt1()[bl]]; - int ant2=(*antMap)[info().getAnt2()[bl]]; - if (ant1==ant2 || ant1==-1 || ant2 == -1 || flag[bl*nCr*nCh+ch*nCr]) { // Only check flag of cr==0 + int ant1=info().getAnt1()[bl]; + int ant2=info().getAnt2()[bl]; + if (ant1==ant2 || + iS[ch/itsNChan].getStationFlagged()[ant1] || + iS[ch/itsNChan].getStationFlagged()[ant2] || + flag[bl*nCr*nCh+ch*nCr]) { // Only check flag of cr==0 continue; } for (uint cr=0;cr<nCr;++cr) { - itsVis (IPosition(6,ant1,cr/2,ch,itsNTimes,cr%2,ant2)) = + iS[ch/itsNChan].getVis() (IPosition(6,ant1,cr/2,itsStepInSolInt,ch%itsNChan,cr%2,ant2)) = DComplex(data [bl*nCr*nCh+ch*nCr+cr]) * DComplex(sqrt(weight[bl*nCr*nCh+ch*nCr+cr])); - itsMVis(IPosition(6,ant1,cr/2,ch,itsNTimes,cr%2,ant2)) = + iS[ch/itsNChan].getMVis()(IPosition(6,ant1,cr/2,itsStepInSolInt,ch%itsNChan,cr%2,ant2)) = DComplex(model[bl*nCr*nCh+ch*nCr+cr]) * DComplex(sqrt(weight[bl*nCr*nCh+ch*nCr+cr])); // conjugate transpose - itsVis (IPosition(6,ant2,cr%2,ch,itsNTimes,cr/2,ant1)) = + iS[ch/itsNChan].getVis() (IPosition(6,ant2,cr%2,itsStepInSolInt,ch%itsNChan,cr/2,ant1)) = DComplex(conj(data [bl*nCr*nCh+ch*nCr+cr])) * DComplex(sqrt(weight[bl*nCr*nCh+ch*nCr+cr])); - itsMVis(IPosition(6,ant2,cr%2,ch,itsNTimes,cr/2,ant1)) = + iS[ch/itsNChan].getMVis()(IPosition(6,ant2,cr%2,itsStepInSolInt,ch%itsNChan,cr/2,ant1)) = DComplex(conj(model[bl*nCr*nCh+ch*nCr+cr] )) * DComplex(sqrt(weight[bl*nCr*nCh+ch*nCr+cr])); } @@ -304,532 +435,168 @@ namespace LOFAR { } } - - void GainCal::countAntUsedNotFlagged (const Bool* flag) { + void GainCal::setAntennaMaps (const Bool* flag, uint freqCell) { uint nCr=info().ncorr(); uint nCh=info().nchan(); uint nBl=info().nbaselines(); - // I assume antennas are numbered 0, 1, 2, ... + iS[freqCell].clearStationFlagged(); + + casa::Vector<casa::uInt> dataPerAntenna; // nAnt + dataPerAntenna.resize(info().antennaNames().size()); + dataPerAntenna=0; + for (uint bl=0;bl<nBl;++bl) { uint ant1=info().getAnt1()[bl]; uint ant2=info().getAnt2()[bl]; if (ant1==ant2) { continue; } - for (uint ch=0;ch<nCh;++ch) { + uint chmax=min((freqCell+1)*itsNChan, nCh); + for (uint ch=freqCell*itsNChan;ch<chmax;++ch) { for (uint cr=0;cr<nCr;++cr) { if (!flag[bl*nCr*nCh + ch*nCr + cr]) { - itsDataPerAntenna[ant1]++; - itsDataPerAntenna[ant2]++; + dataPerAntenna(ant1)++; + dataPerAntenna(ant2)++; } } } } - } - - void GainCal::setAntennaMaps () { - vector<int> antMap(info().antennaNames().size(),-1); - uint nCr=info().ncorr(); - - for (uint ant=0; ant<itsDataPerAntenna.size(); ++ant) { - if (itsDataPerAntenna[ant]>nCr*itsMinBLperAnt) { - antMap[ant] = 0; - } - } - vector<int> antUsed; - antUsed.reserve(info().antennaNames().size()); - for (uint i=0; i<antMap.size(); ++i) { - if (antMap[i] == 0) { - antMap[i] = antUsed.size(); - antUsed.push_back (i); + for (uint ant=0; ant<info().antennaNames().size(); ++ant) { + if (dataPerAntenna(ant)>nCr*itsMinBLperAnt) { + iS[freqCell].getStationFlagged()[ant]=false; // Index in stefcal numbering + } else { + //cout<<"flagging station "<<ant<<", "<<dataPerAntenna(ant)<<endl; + iS[freqCell].getStationFlagged()[ant]=true; // Not enough data } } - - itsAntUseds.push_back(antUsed); - itsAntMaps.push_back(antMap); - - uint nSt=antUsed.size(); - uint nCh=info().nchan(); - // initialize storage - itsVis.resize (IPosition(6,nSt,2,nCh,itsSolInt,2,nSt)); - itsMVis.resize(IPosition(6,nSt,2,nCh,itsSolInt,2,nSt)); } - void GainCal::stefcal (string mode, uint solInt) { - vector<double> dgs; - + void GainCal::stefcal () { itsTimerSolve.start(); - double f2 = -1.0; - double f3 = -0.5; - double f1 = 1 - f2 - f3; - double f2q = -0.5; - double f1q = 1 - f2q; - double omega = 0.5; - uint nomega = 24; - double c1 = 0.5; - double c2 = 1.2; - double dg =1.0e29; - double dgx =1.0e30; - double dgxx; - bool threestep = false; - int badIters=0; - int maxBadIters=5; - - uint nSt, nCr, nUn, nSp; // number of actual stations, - // number of correlations, - // number of unknowns - // number that is two for scalarphase, one else - - nSt=itsAntUseds[itsAntUseds.size()-1].size(); - if (mode=="fulljones") { - nUn=nSt; - nCr=4; - nSp=1; - } else if (mode=="scalarphase") { - nUn=nSt; - nCr=1; - nSp=2; - } else { - nUn=nSt*2; - nCr=1; - nSp=1; - } - uint nCh = info().nchan(); - - iS.g.resize(nUn,nCr); - iS.gold.resize(nUn,nCr); - iS.gx.resize(nUn,nCr); - iS.gxx.resize(nUn,nCr); - iS.h.resize(nUn,nCr); - uint numThreads=OpenMP::maxThreads(); - for (uint thread=0; thread<numThreads; ++thread) { - iS.z[thread].resize(nUn*nCh*solInt*nSp,nCr); - } - - // Initialize all vectors - double fronormvis=0; - double fronormmod=0; - - DComplex* t_vis_p=itsVis.data(); - DComplex* t_mvis_p=itsMVis.data(); - - uint vissize=itsVis.size(); - for (uint i=0;i<vissize;++i) { - fronormvis+=norm(t_vis_p[i]); - fronormmod+=norm(t_mvis_p[i]); - } - - fronormvis=sqrt(fronormvis); - fronormmod=sqrt(fronormmod); - double ginit=1; - if (nSt>0 && abs(fronormmod)>1.e-15) { - ginit=sqrt(fronormvis/fronormmod); - } - if (mode=="fulljones") { - for (uint st=0;st<nUn;++st) { - iS.g(st,0)=1.; - iS.g(st,1)=0.; - iS.g(st,2)=0.; - iS.g(st,3)=1.; + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + if (itsPropagateSolutions) { + iS[freqCell].init(false); + } else { + iS[freqCell].init(true); } - } else { - iS.g=ginit; } - iS.gx = iS.g; - int sstep=0; - uint iter=0; - if (nSt==0) { - iter=itsMaxIter; - } - for (;iter<itsMaxIter;++iter) { - iS.gold=iS.g; - - if (mode=="fulljones") { // ======================== Polarized ======================= - for (uint st=0;st<nSt;++st) { - iS.h(st,0)=conj(iS.g(st,0)); - iS.h(st,1)=conj(iS.g(st,1)); - iS.h(st,2)=conj(iS.g(st,2)); - iS.h(st,3)=conj(iS.g(st,3)); - } -//#pragma omp parallel for - for (uint st1=0;st1<nSt;++st1) { - uint thread=OpenMP::threadNum(); - DComplex* vis_p; - DComplex* mvis_p; - Vector<DComplex> w(nCr); - Vector<DComplex> t(nCr); - - for (uint time=0;time<solInt;++time) { - for (uint ch=0;ch<nCh;++ch) { - uint zoff=nSt*ch+nSt*nCh*time; - mvis_p=&itsMVis(IPosition(6,0,0,ch,time,0,st1,0)); for (uint st2=0;st2<nSt;++st2) { iS.z[thread](st2+zoff,0) = iS.h(st2,0) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,0,ch,time,0,st1)) - mvis_p=&itsMVis(IPosition(6,0,1,ch,time,0,st1,0)); for (uint st2=0;st2<nSt;++st2) { iS.z[thread](st2+zoff,0) += iS.h(st2,2) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,0,ch,time,1,st1)) - mvis_p=&itsMVis(IPosition(6,0,0,ch,time,1,st1,1)); for (uint st2=0;st2<nSt;++st2) { iS.z[thread](st2+zoff,1) = iS.h(st2,0) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,1,ch,time,0,st1)) - mvis_p=&itsMVis(IPosition(6,0,1,ch,time,1,st1,1)); for (uint st2=0;st2<nSt;++st2) { iS.z[thread](st2+zoff,1) += iS.h(st2,2) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,1,ch,time,1,st1)) - mvis_p=&itsMVis(IPosition(6,0,0,ch,time,0,st1,0)); for (uint st2=0;st2<nSt;++st2) { iS.z[thread](st2+zoff,2) = iS.h(st2,1) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,0,ch,time,0,st1)) - mvis_p=&itsMVis(IPosition(6,0,1,ch,time,0,st1,0)); for (uint st2=0;st2<nSt;++st2) { iS.z[thread](st2+zoff,2) += iS.h(st2,3) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,0,ch,time,1,st1)) - mvis_p=&itsMVis(IPosition(6,0,0,ch,time,1,st1,1)); for (uint st2=0;st2<nSt;++st2) { iS.z[thread](st2+zoff,3) = iS.h(st2,1) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,1,ch,time,0,st1)) - mvis_p=&itsMVis(IPosition(6,0,1,ch,time,1,st1,1)); for (uint st2=0;st2<nSt;++st2) { iS.z[thread](st2+zoff,3) += iS.h(st2,3) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,1,ch,time,1,st1)) - } - } - - w=0; - - for (uint time=0;time<solInt;++time) { - for (uint ch=0;ch<nCh;++ch) { - for (uint st2=0;st2<nSt;++st2) { - uint zoff=st2+nSt*ch+nSt*nCh*time; - w(0) += conj(iS.z[thread](zoff,0))*iS.z[thread](zoff,0) + conj(iS.z[thread](zoff,2))*iS.z[thread](zoff,2); - w(1) += conj(iS.z[thread](zoff,0))*iS.z[thread](zoff,1) + conj(iS.z[thread](zoff,2))*iS.z[thread](zoff,3); - w(3) += conj(iS.z[thread](zoff,1))*iS.z[thread](zoff,1) + conj(iS.z[thread](zoff,3))*iS.z[thread](zoff,3); - } - } - } - w(2)=conj(w(1)); - - t=0; - - for (uint time=0;time<solInt;++time) { - for (uint ch=0;ch<nCh;++ch) { - vis_p=&itsVis(IPosition(6,0,0,ch,time,0,st1)); for (uint st2=0;st2<nSt;++st2) { t(0) += conj(iS.z[thread](st2+nSt*ch+nSt*nCh*time,0)) * vis_p[st2]; }// itsVis(IPosition(6,st2,0,ch,time,0,st1)) - vis_p=&itsVis(IPosition(6,0,1,ch,time,0,st1)); for (uint st2=0;st2<nSt;++st2) { t(0) += conj(iS.z[thread](st2+nSt*ch+nSt*nCh*time,2)) * vis_p[st2]; }// itsVis(IPosition(6,st2,0,ch,time,1,st1)) - vis_p=&itsVis(IPosition(6,0,0,ch,time,1,st1)); for (uint st2=0;st2<nSt;++st2) { t(1) += conj(iS.z[thread](st2+nSt*ch+nSt*nCh*time,0)) * vis_p[st2]; }// itsVis(IPosition(6,st2,1,ch,time,0,st1)) - vis_p=&itsVis(IPosition(6,0,1,ch,time,1,st1)); for (uint st2=0;st2<nSt;++st2) { t(1) += conj(iS.z[thread](st2+nSt*ch+nSt*nCh*time,2)) * vis_p[st2]; }// itsVis(IPosition(6,st2,1,ch,time,1,st1)) - vis_p=&itsVis(IPosition(6,0,0,ch,time,0,st1)); for (uint st2=0;st2<nSt;++st2) { t(2) += conj(iS.z[thread](st2+nSt*ch+nSt*nCh*time,1)) * vis_p[st2]; }// itsVis(IPosition(6,st2,0,ch,time,0,st1)) - vis_p=&itsVis(IPosition(6,0,1,ch,time,0,st1)); for (uint st2=0;st2<nSt;++st2) { t(2) += conj(iS.z[thread](st2+nSt*ch+nSt*nCh*time,3)) * vis_p[st2]; }// itsVis(IPosition(6,st2,0,ch,time,1,st1)) - vis_p=&itsVis(IPosition(6,0,0,ch,time,1,st1)); for (uint st2=0;st2<nSt;++st2) { t(3) += conj(iS.z[thread](st2+nSt*ch+nSt*nCh*time,1)) * vis_p[st2]; }// itsVis(IPosition(6,st2,1,ch,time,0,st1)) - vis_p=&itsVis(IPosition(6,0,1,ch,time,1,st1)); for (uint st2=0;st2<nSt;++st2) { t(3) += conj(iS.z[thread](st2+nSt*ch+nSt*nCh*time,3)) * vis_p[st2]; }// itsVis(IPosition(6,st2,1,ch,time,1,st1)) - } - } - DComplex invdet= 1./(w(0) * w (3) - w(1)*w(2)); - iS.g(st1,0) = invdet * ( w(3) * t(0) - w(1) * t(2) ); - iS.g(st1,1) = invdet * ( w(3) * t(1) - w(1) * t(3) ); - iS.g(st1,2) = invdet * ( w(0) * t(2) - w(2) * t(0) ); - iS.g(st1,3) = invdet * ( w(0) * t(3) - w(2) * t(1) ); - - if (itsStefcalVariant=="2a") { - iS.h(st1,0)=conj(iS.g(st1,0)); - iS.h(st1,1)=conj(iS.g(st1,1)); - iS.h(st1,2)=conj(iS.g(st1,2)); - iS.h(st1,3)=conj(iS.g(st1,3)); - } - } - } else {// ======================== Nonpolarized ======================= - for (uint st=0;st<nUn;++st) { - iS.h(st,0)=conj(iS.g(st,0)); - } -//#pragma omp parallel for - for (uint st1=0;st1<nUn;++st1) { - uint thread=OpenMP::threadNum(); - DComplex* vis_p; - DComplex* mvis_p; - double ww=0; // Same as w, but specifically for pol==false - DComplex tt=0; // Same as t, but specifically for pol==false - - DComplex* z_p=iS.z[thread].data(); - mvis_p=&itsMVis(IPosition(6,0,0,0,0,st1/nSt,st1%nSt)); - vis_p = &itsVis(IPosition(6,0,0,0,0,st1/nSt,st1%nSt)); - for (uint st1pol=0;st1pol<nSp;++st1pol) { - for (uint time=0;time<solInt;++time) { - for (uint ch=0;ch<nCh;++ch) { - DComplex* h_p=iS.h.data(); - for (uint st2=0;st2<nUn;++st2) { - *z_p = h_p[st2] * *mvis_p; //itsMVis(IPosition(6,st2%nSt,st2/nSt,ch,time,st1/nSt,st1%nSt)); - ww+=norm(*z_p); - tt+=conj(*z_p) * *vis_p; //itsVis(IPosition(6,st2%nSt,st2/nSt,ch,time,st1/nSt,st1%nSt)); - mvis_p++; - vis_p++; - z_p++; - } - //cout<<"iS.z bij ch="<<ch<<"="<<iS.z<<endl<<"----"<<endl; - } - } - } - //cout<<"st1="<<st1%nSt<<(st1>=nSt?"y":"x")<<", t="<<tt<<" "; - //cout<<", w="<<ww<<" "; - iS.g(st1,0)=tt/ww; - //cout<<", g="<<iS.g(st1,0)<<endl; - if (itsMode=="phaseonly" || itsMode=="scalarphase") { - iS.g(st1,0)/=abs(iS.g(st1,0)); - } - - if (itsStefcalVariant=="2a") { - iS.h(st1,0)=conj(iS.g(st1,0)); - } else if (itsStefcalVariant=="2b") { - iS.h(st1,0)=0.8*iS.h(st1,0)-0.2*conj(iS.g(st1,0)); - } - } - if (itsStefcalVariant!="1c") { - double fronormdiff=0; - double fronormg=0; - for (uint ant=0;ant<nUn;++ant) { - for (uint cr=0;cr<nCr;++cr) { - DComplex diff=iS.g(ant,cr)-iS.gold(ant,cr); - fronormdiff+=abs(diff*diff); - fronormg+=abs(iS.g(ant,cr)*iS.g(ant,cr)); - } - } - fronormdiff=sqrt(fronormdiff); - fronormg=sqrt(fronormg); - - dg = fronormdiff/fronormg; - dgs.push_back(dg); - } - } // ============================== Relaxation ======================= - if (iter % 2 == 1 && itsStefcalVariant=="1c") { - if (itsDebugLevel>7) { - cout<<"iter: "<<iter<<endl; - } - if (itsDetectStalling && iter > 20 && dgx-dg <= 5.0e-3*dg) { - // This iteration did not improve much upon the previous - // Stalling detection only after 20 iterations, to account for - // ''startup problems'' - if (itsDebugLevel>3) { - cout<<"**"<<endl; - } - badIters++; - } else { - badIters=0; - } - - if (badIters>=maxBadIters && itsDetectStalling) { - if (itsDebugLevel>3) { - cout<<"Detected stall"<<endl; - } - itsStalled++; - break; - } - - dgxx = dgx; - dgx = dg; - - double fronormdiff=0; - double fronormg=0; - for (uint ant=0;ant<nUn;++ant) { - for (uint cr=0;cr<nCr;++cr) { - DComplex diff=iS.g(ant,cr)-iS.gold(ant,cr); - fronormdiff+=abs(diff*diff); - fronormg+=abs(iS.g(ant,cr)*iS.g(ant,cr)); - } - } - fronormdiff=sqrt(fronormdiff); - fronormg=sqrt(fronormg); - - dg = fronormdiff/fronormg; - if (itsDebugLevel>1) { - dgs.push_back(dg); - } - - if (dg <= itsTolerance) { - itsConverged++; - break; - } - - if (itsDebugLevel>7) { - cout<<"Averaged"<<endl; + std::vector<StefCal::Status> converged(itsNFreqCells,StefCal::NOTCONVERGED); + for (;iter<itsMaxIter;++iter) { + bool allConverged=true; +#pragma omp parallel for + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + if (converged[freqCell]==StefCal::CONVERGED) { // Do another step when stalled and not all converged + continue; } - for (uint ant=0;ant<nUn;++ant) { - for (uint cr=0;cr<nCr;++cr) { - iS.g(ant,cr) = (1-omega) * iS.g(ant,cr) + omega * iS.gold(ant,cr); - } + converged[freqCell] = iS[freqCell].doStep(iter); + if (converged[freqCell]==StefCal::NOTCONVERGED) { + allConverged = false; + } else if (converged[freqCell]==StefCal::CONVERGED) { + itsNIter[0] += iter; } + } - if (!threestep) { - threestep = (iter+1 >= nomega) || - ( max(dg,max(dgx,dgxx)) <= 1.0e-3 && dg<dgx && dgx<dgxx); - if (itsDebugLevel>7) { - cout<<"Threestep="<<boolalpha<<threestep<<endl; - } - } + if (allConverged) { + break; + } - if (threestep) { - if (sstep <= 0) { - if (dg <= c1 * dgx) { - if (itsDebugLevel>7) { - cout<<"dg<=c1*dgx"<<endl; - } - for (uint ant=0;ant<nUn;++ant) { - for (uint cr=0;cr<nCr;++cr) { - iS.g(ant,cr) = f1q * iS.g(ant,cr) + f2q * iS.gx(ant,cr); - } - } - } else if (dg <= dgx) { - if (itsDebugLevel>7) { - cout<<"dg<=dgx"<<endl; - } - for (uint ant=0;ant<nUn;++ant) { - for (uint cr=0;cr<nCr;++cr) { - iS.g(ant,cr) = f1 * iS.g(ant,cr) + f2 * iS.gx(ant,cr) + f3 * iS.gxx(ant,cr); - } - } - } else if (dg <= c2 *dgx) { - if (itsDebugLevel>7) { - cout<<"dg<=c2*dgx"<<endl; - } - iS.g = iS.gx; - sstep = 1; - } else { - //cout<<"else"<<endl; - iS.g = iS.gxx; - sstep = 2; - } - } else { - if (itsDebugLevel>7) { - cout<<"no sstep"<<endl; - } - sstep = sstep - 1; - } + if (itsDebugLevel>1) { // Only antenna 1 + cout<<"phases["<<iter<<"]=["; + uint freqCell=0; + for (; freqCell<itsNFreqCells-1; ++freqCell) { + cout<<arg(iS[freqCell].getSolution(false)(1, 0)/iS[freqCell].getSolution(false)(0,0))<<","; } - iS.gxx = iS.gx; - iS.gx = iS.g; - } - } - if (dg > itsTolerance && nSt>0) { - if (itsDetectStalling && badIters<maxBadIters) { - itsNonconverged++; - } else { - itsNonconverged++; + cout<<arg(iS[freqCell].getSolution(false)(1, 0)/iS[freqCell].getSolution(false)(0,0))<<"];"<<endl; } - if (itsDebugLevel>0) { - cerr<<"!"; - } - } - - if ((itsDebugLevel>1 && dg>itsTolerance) || itsDebugLevel>2) { - cout<<"t: "<<itsTStep<<", iter:"<<iter<<", dg=["; - if (dgs.size()>0) { - cout<<dgs[0]; + if (itsDebugLevel>2) { // Statistics about nStalled + cout<<"convstatus["<<iter<<"]=["; + uint freqCell=0; + for (; freqCell<itsNFreqCells-1; ++freqCell) { + cout<<converged[freqCell]<<","; + } + cout<<converged[freqCell]<<"]"<<endl; } - for (uint i=1;i<dgs.size();++i) { - cout<<","<<dgs[i]; + } // End niter + + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + switch (converged[freqCell]) { + case StefCal::CONVERGED: {itsConverged++; break;} + case StefCal::STALLED: {itsStalled++; itsNIter[1]+=iter; break;} + case StefCal::NOTCONVERGED: {itsNonconverged++; itsNIter[2]+=iter; break;} + default: + THROW(Exception, "Unknown converged status"); } - cout<<"]"<<endl; } - //for (uint ant2=0;ant2<nSt;++ant2) { - //cout<<"g["<<ant2<<"]={"<<g[ant2][0]<<", "<<g[ant2][1]<<", "<<g[ant2][2]<<", "<<g[ant2][3]<<"}"<<endl; - //cout<<"w["<<ant2<<"]={"<<w[ant2][0]<<", "<<w[ant2][1]<<", "<<w[ant2][2]<<", "<<w[ant2][3]<<"}"<<endl; - //} - // Stefcal terminated (either by maxiter or by converging) - // Let's save G... - itsSols.push_back(iS.g.copy()); - - if (itsDebugLevel>3) { - //cout<<"g="<<iS.g<<endl; - cout<<"g=["; - for (uint i=0;i<nUn;++i) { - cout<<iS.g(i,0).real()<<(iS.g(i,0).imag()>=0?"+":"")<<iS.g(i,0).imag()<<"j; "; - } - cout<<endl; - //THROW(Exception,"Klaar!"); - } - if (dg > itsTolerance && itsDebugLevel>1 && nSt>0) { - cout<<endl<<"Did not converge: dg="<<dg<<" tolerance="<<itsTolerance<<", nants="<<nSt<<endl; - if (itsDebugLevel>12) { - cout<<"g="<<iS.g<<endl; - exportToMatlab(0); - THROW(Exception,"Klaar!"); - } - } -// THROW(Exception,"Klaar!"); - itsTimerSolve.stop(); - } + Cube<DComplex> sol(iS[0].numCorrelations(), info().antennaNames().size(), itsNFreqCells); + uint transpose[2][4] = { { 0, 1, 0, 0 }, { 0, 2, 1, 3 } }; - void GainCal::exportToMatlab(uint ch) { - ofstream mFile; - uint nSt = itsMVis.shape()[0]; - mFile.open ("debug.txt"); - mFile << "# Created by NDPPP"<<endl; - mFile << "# name: V"<<endl; - mFile << "# type: complex matrix"<<endl; - mFile << "# rows: "<<2*nSt<<endl; - mFile << "# columns: "<<2*nSt<<endl; - - for (uint row=0;row<2*nSt;++row) { - for (uint col=0;col<2*nSt;++col) { - mFile << itsVis(IPosition(6,row%nSt,row/nSt,ch,0,col/nSt,col%nSt))<<" "; - } - mFile << endl; - } + uint nSt = info().antennaNames().size(); - mFile << endl; - mFile << "# name: Vm"<<endl; - mFile << "# type: complex matrix"<<endl; - mFile << "# rows: "<<nSt*2<<endl; - mFile << "# columns: "<<nSt*2<<endl; + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + casa::Matrix<casa::DComplex> tmpsol = iS[freqCell].getSolution(true); - for (uint row=0;row<nSt;++row) { - for (uint col=0;col<nSt;++col) { - mFile << itsMVis(IPosition(6,row%nSt,row/nSt,ch,0,col/nSt,col%nSt))<<" "; + for (uint st=0; st<nSt; st++) { + for (uint cr=0; cr<iS[0].nCr(); ++cr) { + uint crt=transpose[iS[0].numCorrelations()/4][cr]; // Conjugate transpose ! (only for numCorrelations = 4) + sol(crt, st, freqCell) = conj(tmpsol(st, cr)); // Conjugate transpose + if (itsMode=="diagonal" || itsMode=="phaseonly" || itsMode=="amplitudeonly") { + sol(crt+1, st, freqCell) = conj(tmpsol(st+nSt,cr)); // Conjugate transpose + } + } } - mFile << endl; } + itsSols.push_back(sol); - mFile.close(); - THROW(Exception,"Wrote output to debug.txt -- stopping now"); + itsTimerSolve.stop(); } - void GainCal::finish() - { - itsTimer.start(); - - //Solve remaining time slots if any - if (itsNTimes!=0) { - stefcal(itsMode,itsSolInt); - } - - itsTimerWrite.start(); - - uint nSt=info().antennaUsed().size(); - - uint ntime=itsSols.size(); - - // Construct solution grid. - const Vector<double>& freq = getInfo().chanFreqs(); - const Vector<double>& freqWidth = getInfo().chanWidths(); - BBS::Axis::ShPtr freqAxis(new BBS::RegularAxis(freq[0] - freqWidth[0] - * 0.5, getInfo().totalBW(), 1)); - BBS::Axis::ShPtr timeAxis(new BBS::RegularAxis - (info().startTime(), - info().timeInterval() * itsSolInt, ntime)); - BBS::Grid solGrid(freqAxis, timeAxis); - // Create domain grid. - BBS::Axis::ShPtr tdomAxis(new BBS::RegularAxis - (info().startTime(), - info().timeInterval() * itsSolInt * ntime, 1)); - BBS::Grid domainGrid(freqAxis, tdomAxis); - - // Open the ParmDB at the first write. - // In that way the instrumentmodel ParmDB can be in the MS directory. - if (! itsParmDB) { - itsParmDB = boost::shared_ptr<BBS::ParmDB> - (new BBS::ParmDB(BBS::ParmDBMeta("casa", itsParmDBName), - false)); - itsParmDB->lock(); - // Store the (freq, time) resolution of the solutions. - vector<double> resolution(2); - resolution[0] = freqWidth[0]; - resolution[1] = info().timeInterval() * itsSolInt; - itsParmDB->setDefaultSteps(resolution); - string name=(itsMode=="commonscalarphase"?"CommonScalarPhase:*":"Gain:*"); - if (!itsParmDB->getNames(name).empty()) { - DPLOG_WARN_STR ("Solutions for "<<name<<" already in "<<itsParmDBName - <<", these are removed"); - itsParmDB->deleteValues(name, BBS::Box( - freqAxis->start(), tdomAxis->start(), - freqAxis->end(), tdomAxis->end(), true - ) - ); - } + void GainCal::initParmDB() { + itsParmDB = boost::shared_ptr<BBS::ParmDB> + (new BBS::ParmDB(BBS::ParmDBMeta("casa", itsParmDBName), + false)); + itsParmDB->lock(); + // Store the (freq, time) resolution of the solutions. + + double freqWidth = getInfo().chanWidths()[0]; + if (getInfo().chanFreqs().size()>1) { // Handle data with evenly spaced gaps between channels + freqWidth = info().chanFreqs()[1]-info().chanFreqs()[0]; + } + + vector<double> resolution(2); + resolution[0] = freqWidth * itsNChan; + resolution[1] = info().timeInterval() * itsSolInt; + itsParmDB->setDefaultSteps(resolution); + + string parmname=parmName()+"*"; + + if (!itsParmDB->getNames(parmname).empty()) { + DPLOG_WARN_STR ("Solutions for "<<parmname<<" already in "<<itsParmDBName + <<", these are removed"); + // Specify entire domain of this MS; only to specify that the existing + // values should be deleted for this domain + BBS::Axis::ShPtr tdomAxis( + new BBS::RegularAxis( + info().startTime(), + info().ntime() * info().timeInterval(), + 1)); + BBS::Axis::ShPtr fdomAxis( + new BBS::RegularAxis( + info().chanFreqs()[0] - freqWidth * 0.5, + freqWidth * getInfo().chanFreqs().size(), 1)); + + itsParmDB->deleteValues(parmname, BBS::Box( + fdomAxis->start(), tdomAxis->start(), + fdomAxis->end(), tdomAxis->end(), true)); } // Write out default values, if they don't exist yet @@ -845,6 +612,16 @@ namespace LOFAR { } } + // Write out default phases + if (itsMode=="amplitudeonly" || itsMode=="scalaramplitude") { + itsParmDB->getDefValues(parmset, "Gain:0:0:Phase"); + if (parmset.empty()) { + ParmValueSet pvset(ParmValue(0.0)); + itsParmDB->putDefValue("Gain:0:0:Phase",pvset); + itsParmDB->putDefValue("Gain:1:1:Phase",pvset); + } + } + // Write out default gains if (itsMode=="diagonal" || itsMode=="fulljones") { itsParmDB->getDefValues(parmset, "Gain:0:0:Real"); @@ -854,75 +631,145 @@ namespace LOFAR { itsParmDB->putDefValue("Gain:1:1:Real",pvset); } } + } + + string GainCal::parmName() { + string name; + if (itsMode=="scalarphase") { + name=string("CommonScalarPhase:"); + } else if (itsMode=="scalaramplitude") { + name=string("CommonScalarAmplitude:"); + } else if (itsMode=="tec") { + name=string("TEC:"); + } + else { + name=string("Gain:"); + } + + return name; + } + + void GainCal::writeSolutions(double startTime) { + itsTimer.start(); + itsTimerWrite.start(); + + // Open the ParmDB at the first write. + // In that way the instrumentmodel ParmDB can be in the MS directory. + if (!itsParmDB) { + initParmDB(); + } // End initialization of parmdb + + uint ntime=itsSols.size(); + + // Construct solution grid for the current chunk + double freqWidth = getInfo().chanWidths()[0]; + if (getInfo().chanFreqs().size()>1) { // Handle data with evenly spaced gaps between channels + freqWidth = info().chanFreqs()[1]-info().chanFreqs()[0]; + } + BBS::Axis::ShPtr freqAxis( + new BBS::RegularAxis( + getInfo().chanFreqs()[0] - freqWidth * 0.5, + freqWidth*itsNChan, + itsNFreqCells)); + BBS::Axis::ShPtr timeAxis( + new BBS::RegularAxis( + startTime, + info().timeInterval() * itsSolInt, + ntime)); + BBS::Grid solGrid(freqAxis, timeAxis); + + // Construct domain grid for the current chunk + BBS::Axis::ShPtr tdomAxis( + new BBS::RegularAxis( + startTime, + ntime * info().timeInterval() * itsSolInt, + 1)); + BBS::Axis::ShPtr fdomAxis( + new BBS::RegularAxis( + info().chanFreqs()[0] - freqWidth * 0.5, + freqWidth * getInfo().chanFreqs().size(), 1)); + BBS::Grid domainGrid(fdomAxis, tdomAxis); // Write the solutions per parameter. - const char* str0101[] = {"0:0:","1:0:","0:1:","1:1:"}; // Conjugate transpose! + const char* str0101[] = {"0:0:","0:1:","1:0:","1:1:"}; const char* strri[] = {"Real:","Imag:"}; - Matrix<double> values(1, ntime); + Matrix<double> values(itsNFreqCells, ntime); DComplex sol; + uint nSt=info().antennaNames().size(); + for (size_t st=0; st<nSt; ++st) { uint seqnr = 0; // To take care of real and imaginary part - string suffix(itsAntennaUsedNames[st]); + string suffix(info().antennaNames()[st]); for (int pol=0; pol<4; ++pol) { // For 0101 - if ((itsMode=="diagonal" || itsMode=="phaseonly") && (pol==1||pol==2)) { + if ((itsMode=="diagonal" || itsMode=="phaseonly" || + itsMode=="amplitudeonly") && (pol==1||pol==2)) { continue; } - if (itsMode=="scalarphase" && pol>0) { + if ((itsMode=="scalarphase" || itsMode=="scalaramplitude" || + itsMode=="tec") && pol>0) { continue; } int realimmax; - if (itsMode=="phaseonly" || itsMode=="scalarphase") { + if (itsMode=="phaseonly" || itsMode=="scalarphase" || + itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || + itsMode=="tec") { realimmax=1; } else { realimmax=2; } for (int realim=0; realim<realimmax; ++realim) { // For real and imaginary - string name(string("Gain:") + - str0101[pol] + (itsMode=="phaseonly"?"Phase:":strri[realim]) + suffix); - if (itsMode=="scalarphase") { - name="CommonScalarPhase:"+suffix; + string name = parmName(); + + if (itsMode!="scalarphase" && itsMode!="scalaramplitude") { + name+=str0101[pol]; + if (itsMode=="phaseonly") { + name=name+"Phase:"; + } else if (itsMode=="amplitudeonly") { + name=name+"Ampl:"; + } else { + name=name+strri[realim]; + } } - // Collect its solutions for all times in a single array. + + name+=suffix; + + // Collect its solutions for all times and frequency cells in a single array. for (uint ts=0; ts<ntime; ++ts) { - if (itsAntMaps[ts][st]==-1) { - // No solution found, insert NaN - if (itsMode!="phaseonly" && itsMode!="scalarphase" && - realim==0 && (pol==0||pol==3)) { - values(0, ts) = std::numeric_limits<double>::quiet_NaN(); - } else { - values(0, ts) = std::numeric_limits<double>::quiet_NaN(); - } - } else { - int rst=itsAntMaps[ts][st]; // Real station + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { if (itsMode=="fulljones") { if (seqnr%2==0) { - values(0, ts) = real(itsSols[ts](rst,seqnr/2)); + values(freqCell, ts) = real(itsSols[ts](seqnr/2,st,freqCell)); } else { - values(0, ts) = -imag(itsSols[ts](rst,seqnr/2)); // Conjugate transpose! + values(freqCell, ts) = imag(itsSols[ts](seqnr/2,st,freqCell)); } } else if (itsMode=="diagonal") { - uint sSt=itsSols[ts].size()/2; if (seqnr%2==0) { - values(0, ts) = real(itsSols[ts](pol/3*sSt+rst,0)); // nSt times Gain:0:0 at the beginning, then nSt times Gain:1:1 + values(freqCell, ts) = real(itsSols[ts](pol/3,st,freqCell)); } else { - values(0, ts) = -imag(itsSols[ts](pol/3*sSt+rst,0)); // Conjugate transpose! + values(freqCell, ts) = imag(itsSols[ts](pol/3,st,freqCell)); } - } else if (itsMode=="scalarphase" || itsMode=="phaseonly") { - uint sSt=itsSols[ts].size()/2; - values(0, ts) = -arg(itsSols[ts](pol/3*sSt+rst,0)); // nSt times Gain:0:0 at the beginning, then nSt times Gain:1:1 + } else if (itsMode=="scalarphase" || itsMode=="phaseonly" || + itsMode=="tec") { + values(freqCell, ts) = arg(itsSols[ts](pol/3,st,freqCell)); + } else if (itsMode=="scalaramplitude" || itsMode=="amplitudeonly") { + values(freqCell, ts) = abs(itsSols[ts](pol/3,st,freqCell)); + } + else { + THROW (Exception, "Unhandled mode"); } } } - cout.flush(); seqnr++; BBS::ParmValue::ShPtr pv(new BBS::ParmValue()); pv->setScalars (solGrid, values); + BBS::ParmValueSet pvs(domainGrid, vector<BBS::ParmValue::ShPtr>(1, pv)); map<string,int>::const_iterator pit = itsParmIdMap.find(name); + if (pit == itsParmIdMap.end()) { // First time, so a new nameId will be set. // Check if the name was defined in the parmdb previously @@ -940,10 +787,34 @@ namespace LOFAR { itsTimerWrite.stop(); itsTimer.stop(); + } + + void GainCal::finish() + { + itsTimer.start(); + + //Solve remaining time slots if any + if (itsStepInSolInt!=0) { + stefcal(); + + if (itsApplySolution) { + Cube<DComplex> invsol = invertSol(itsSols.back()); + for (uint stepInSolInt=0; stepInSolInt<itsStepInSolInt; stepInSolInt++) { + applySolution(itsBuf[stepInSolInt], invsol); + getNextStep()->process(itsBuf[stepInSolInt]); + } + } + } + + itsTimer.stop(); + + if (!itsSols.empty()) { + writeSolutions(itsChunkStartTime); + } + // Let the next steps finish. getNextStep()->finish(); } - } //# end namespace } diff --git a/CEP/DP3/DPPP/src/StefCal.cc b/CEP/DP3/DPPP/src/StefCal.cc new file mode 100644 index 00000000000..75d205de4d3 --- /dev/null +++ b/CEP/DP3/DPPP/src/StefCal.cc @@ -0,0 +1,486 @@ +//# StefCal.cc: Perform StefCal algorithm for gain calibration +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: StefCal.cc 21598 2012-07-16 08:07:34Z diepen $ +//# +//# @author Tammo Jan Dijkema + +#include <lofar_config.h> +#include <DPPP/StefCal.h> +#include <DPPP/DPInput.h> + +#include <vector> +#include <algorithm> +#include <limits> + +#include <iostream> + +using namespace casa; + +namespace LOFAR { + namespace DPPP { + + StefCal::StefCal(uint solInt, uint nChan, const string& mode, + double tolerance, uint maxAntennas, + bool detectStalling, uint debugLevel) + : _nSt (maxAntennas), + _badIters (0), + _veryBadIters (0), + _solInt (solInt), + _nChan (nChan), + _mode (mode), + _tolerance (tolerance), + _detectStalling (detectStalling), + _debugLevel (debugLevel) + { + resetVis(); + + _nSt = maxAntennas; + if (_mode=="fulljones") { + _nCr=4; + _nSp=1; + _savedNCr=4; + } else if (_mode=="scalarphase" || _mode=="scalaramplitude") { + _nCr=1; + _nSp=2; + _savedNCr=1; + } else { // mode=="phaseonly", mode=="diagonal", mode=="amplitudeonly" + _nCr=1; + _nSp=1; + _savedNCr=2; + } + + _vis.resize(IPosition(6,_nSt,2,_solInt,_nChan,2,_nSt)); + _mvis.resize(IPosition(6,_nSt,2,_solInt,_nChan,2,_nSt)); + + if (_mode=="fulljones" || _mode=="scalarphase" || _mode=="scalaramplitude") { + _nUn = _nSt; + } else { // mode=="phaseonly", mode=="diagonal", mode=="amplitudeonly" + _nUn = 2*_nSt; + } + + _g.resize(_nUn,_nCr); + _gold.resize(_nUn,_nCr); + _gx.resize(_nUn,_nCr); + _gxx.resize(_nUn,_nCr); + _h.resize(_nUn,_nCr); + _z.resize(_nUn*_nChan*_solInt*_nSp,_nCr); + + _stationFlagged.resize(_nSt, false); + + init(true); + } + + void StefCal::resetVis() { + _vis=0; + _mvis=0; + } + + void StefCal::clearStationFlagged() { + fill(_stationFlagged.begin(), _stationFlagged.end(), false); + } + + void StefCal::init(bool initSolutions) { + _dg=1.0e29; + _dgx=1.0e30; + _dgs.clear(); + + _badIters=0; + _veryBadIters=0; + + if (initSolutions) { + double ginit=1.0; + if (_mode != "phaseonly" && _mode != "scalarphase" ) { + // Initialize solution with sensible amplitudes + double fronormvis=0; + double fronormmod=0; + + DComplex* t_vis_p=_vis.data(); + DComplex* t_mvis_p=_mvis.data(); + + uint vissize=_vis.size(); + for (uint i=0;i<vissize;++i) { + fronormvis+=norm(t_vis_p[i]); + fronormmod+=norm(t_mvis_p[i]); + } + + fronormvis=sqrt(fronormvis); + fronormmod=sqrt(fronormmod); + if (abs(fronormmod)>1.e-15) { + ginit=sqrt(fronormvis/fronormmod); + } else { + ginit=1.0; + } + } + + if (_nCr==4) { + for (uint ant=0;ant<_nUn;++ant) { + _g(ant,0)=ginit; + _g(ant,1)=0.; + _g(ant,2)=0.; + _g(ant,3)=ginit; + } + } else { + _g=ginit; + } + } else { // Take care of NaNs in solution + for (uint ant=0; ant<_nUn; ++ant) { + double ginit=0; + if (!isFinite(_g(ant,0).real()) ) { + if (ginit==0 && !_stationFlagged[ant%_nSt]) { + // Avoid calling getAverageUnflaggedSolution for stations that are always flagged + ginit = getAverageUnflaggedSolution(); + } + if (_nCr==4) { + _g(ant,0)=ginit; + _g(ant,1)=0.; + _g(ant,2)=0.; + _g(ant,3)=ginit; + } else { + _g(ant,0)=ginit; + } + } + } + } + } + + double StefCal::getAverageUnflaggedSolution() { + // Get average solution of unflagged antennas only once + double total=0.; + uint unflaggedstations=0; + for (uint ant2=0; ant2<_nUn; ++ant2) { + if (isFinite(_g(ant2,0).real())) { + total += abs(_g(ant2,0)); + unflaggedstations++; + if (_nCr==4) { + total += abs(_g(ant2,3)); + unflaggedstations++; + } + } + } + return total/unflaggedstations; + } + + StefCal::Status StefCal::doStep(uint iter) { + _gxx = _gx; + _gx = _g; + + if (_mode=="fulljones") { + doStep_polarized(); + doStep_polarized(); + return relax(2*iter); + } else { + doStep_unpolarized(); + doStep_unpolarized(); + return relax(2*iter); + } + } + + void StefCal::doStep_polarized() { + _gold = _g; + + for (uint st=0;st<_nSt;++st) { + _h(st,0)=conj(_g(st,0)); + _h(st,1)=conj(_g(st,1)); + _h(st,2)=conj(_g(st,2)); + _h(st,3)=conj(_g(st,3)); + } + + for (uint st1=0;st1<_nSt;++st1) { + if (_stationFlagged[st1]) { + continue; + } + + DComplex* vis_p; + DComplex* mvis_p; + Vector<DComplex> w(_nCr); + Vector<DComplex> t(_nCr); + + for (uint time=0;time<_solInt;++time) { + for (uint ch=0;ch<_nChan;++ch) { + uint zoff=_nSt*ch+_nSt*_nChan*time; + mvis_p=&_mvis(IPosition(6,0,0,time,ch,0,st1)); for (uint st2=0;st2<_nSt;++st2) { _z(st2+zoff,0) = _h(st2,0) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,0,time,ch,0,st1)) + mvis_p=&_mvis(IPosition(6,0,1,time,ch,0,st1)); for (uint st2=0;st2<_nSt;++st2) { _z(st2+zoff,0) += _h(st2,2) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,0,time,ch,1,st1)) + mvis_p=&_mvis(IPosition(6,0,0,time,ch,1,st1)); for (uint st2=0;st2<_nSt;++st2) { _z(st2+zoff,1) = _h(st2,0) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,1,time,ch,0,st1)) + mvis_p=&_mvis(IPosition(6,0,1,time,ch,1,st1)); for (uint st2=0;st2<_nSt;++st2) { _z(st2+zoff,1) += _h(st2,2) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,1,time,ch,1,st1)) + mvis_p=&_mvis(IPosition(6,0,0,time,ch,0,st1)); for (uint st2=0;st2<_nSt;++st2) { _z(st2+zoff,2) = _h(st2,1) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,0,time,ch,0,st1)) + mvis_p=&_mvis(IPosition(6,0,1,time,ch,0,st1)); for (uint st2=0;st2<_nSt;++st2) { _z(st2+zoff,2) += _h(st2,3) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,0,time,ch,1,st1)) + mvis_p=&_mvis(IPosition(6,0,0,time,ch,1,st1)); for (uint st2=0;st2<_nSt;++st2) { _z(st2+zoff,3) = _h(st2,1) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,1,time,ch,0,st1)) + mvis_p=&_mvis(IPosition(6,0,1,time,ch,1,st1)); for (uint st2=0;st2<_nSt;++st2) { _z(st2+zoff,3) += _h(st2,3) * mvis_p[st2]; } // itsMVis(IPosition(6,st2,1,time,ch,1,st1)) + } + } + + w=0; + + for (uint time=0;time<_solInt;++time) { + for (uint ch=0;ch<_nChan;++ch) { + for (uint st2=0;st2<_nSt;++st2) { + uint zoff=st2+_nSt*ch+_nSt*_nChan*time; + w(0) += conj(_z(zoff,0))*_z(zoff,0) + conj(_z(zoff,2))*_z(zoff,2); + w(1) += conj(_z(zoff,0))*_z(zoff,1) + conj(_z(zoff,2))*_z(zoff,3); + w(3) += conj(_z(zoff,1))*_z(zoff,1) + conj(_z(zoff,3))*_z(zoff,3); + } + } + } + w(2)=conj(w(1)); + + t=0; + + for (uint time=0;time<_solInt;++time) { + for (uint ch=0;ch<_nChan;++ch) { + vis_p=&_vis(IPosition(6,0,0,time,ch,0,st1)); for (uint st2=0;st2<_nSt;++st2) { t(0) += conj(_z(st2+_nSt*ch+_nSt*_nChan*time,0)) * vis_p[st2]; }// itsVis(IPosition(6,st2,0,time,ch,0,st1)) + vis_p=&_vis(IPosition(6,0,1,time,ch,0,st1)); for (uint st2=0;st2<_nSt;++st2) { t(0) += conj(_z(st2+_nSt*ch+_nSt*_nChan*time,2)) * vis_p[st2]; }// itsVis(IPosition(6,st2,0,time,ch,1,st1)) + vis_p=&_vis(IPosition(6,0,0,time,ch,1,st1)); for (uint st2=0;st2<_nSt;++st2) { t(1) += conj(_z(st2+_nSt*ch+_nSt*_nChan*time,0)) * vis_p[st2]; }// itsVis(IPosition(6,st2,1,time,ch,0,st1)) + vis_p=&_vis(IPosition(6,0,1,time,ch,1,st1)); for (uint st2=0;st2<_nSt;++st2) { t(1) += conj(_z(st2+_nSt*ch+_nSt*_nChan*time,2)) * vis_p[st2]; }// itsVis(IPosition(6,st2,1,time,ch,1,st1)) + vis_p=&_vis(IPosition(6,0,0,time,ch,0,st1)); for (uint st2=0;st2<_nSt;++st2) { t(2) += conj(_z(st2+_nSt*ch+_nSt*_nChan*time,1)) * vis_p[st2]; }// itsVis(IPosition(6,st2,0,time,ch,0,st1)) + vis_p=&_vis(IPosition(6,0,1,time,ch,0,st1)); for (uint st2=0;st2<_nSt;++st2) { t(2) += conj(_z(st2+_nSt*ch+_nSt*_nChan*time,3)) * vis_p[st2]; }// itsVis(IPosition(6,st2,0,time,ch,1,st1)) + vis_p=&_vis(IPosition(6,0,0,time,ch,1,st1)); for (uint st2=0;st2<_nSt;++st2) { t(3) += conj(_z(st2+_nSt*ch+_nSt*_nChan*time,1)) * vis_p[st2]; }// itsVis(IPosition(6,st2,1,time,ch,0,st1)) + vis_p=&_vis(IPosition(6,0,1,time,ch,1,st1)); for (uint st2=0;st2<_nSt;++st2) { t(3) += conj(_z(st2+_nSt*ch+_nSt*_nChan*time,3)) * vis_p[st2]; }// itsVis(IPosition(6,st2,1,time,ch,1,st1)) + } + } + DComplex invdet= 1./(w(0) * w (3) - w(1)*w(2)); + _g(st1,0) = invdet * ( w(3) * t(0) - w(1) * t(2) ); + _g(st1,1) = invdet * ( w(3) * t(1) - w(1) * t(3) ); + _g(st1,2) = invdet * ( w(0) * t(2) - w(2) * t(0) ); + _g(st1,3) = invdet * ( w(0) * t(3) - w(2) * t(1) ); + } + } + + void StefCal::doStep_unpolarized() { + _gold=_g; + + for (uint ant=0;ant<_nUn;++ant) { + _h(ant,0)=conj(_g(ant,0)); + } + + for (uint st1=0;st1<_nUn;++st1) { + if (_stationFlagged[st1%_nSt]) { + continue; + } + DComplex* vis_p; + DComplex* mvis_p; + double ww=0; // Same as w, but specifically for pol==false + DComplex tt=0; // Same as t, but specifically for pol==false + + DComplex* z_p=_z.data(); + mvis_p=&_mvis(IPosition(6,0,0,0,0,st1/_nSt,st1%_nSt)); + vis_p = &_vis(IPosition(6,0,0,0,0,st1/_nSt,st1%_nSt)); + for (uint st1pol=0;st1pol<_nSp;++st1pol) { + for (uint ch=0;ch<_nChan;++ch) { + for (uint time=0;time<_solInt;++time) { + DComplex* h_p=_h.data(); + for (uint st2=0;st2<_nUn;++st2) { + *z_p = h_p[st2] * *mvis_p; //itsMVis(IPosition(6,st2%nSt,st2/nSt,time,ch,st1/nSt,st1%nSt)); + ww+=norm(*z_p); + tt+=conj(*z_p) * *vis_p; //itsVis(IPosition(6,st2%nSt,st2/nSt,time,ch,st1/nSt,st1%nSt)); + mvis_p++; + vis_p++; + z_p++; + } + //cout<<"iS.z bij ch="<<ch<<"="<<iS.z<<endl<<"----"<<endl; + } + } + } + //cout<<"st1="<<st1%nSt<<(st1>=nSt?"y":"x")<<", t="<<tt<<" "; + //cout<<", w="<<ww<<" "; + _g(st1,0)=tt/ww; + //cout<<", g="<<iS.g(st1,0)<<endl; + if (_mode=="phaseonly" || _mode=="scalarphase") { + _g(st1,0)/=abs(_g(st1,0)); + } else if (_mode=="amplitudeonly" || _mode=="scalaramplitude") { + _g(st1,0) = abs(_g(st1,0)); + } + + if (_debugLevel>2) { + cout<<endl<<"gi=["; + uint ant=0; + for (; ant<_nUn-1; ++ant) { + cout<<_g(ant,0)<<","; + } + cout<<_g(ant,0)<<"]"<<endl; + } + } + } + + casa::Matrix<casa::DComplex> StefCal::getSolution(bool setNaNs) { + if (setNaNs && _debugLevel>0) { + cout<<endl<<"dg=["; + uint iter=0; + for (; iter<_dgs.size()-1; ++iter) { + cout<<_dgs[iter]<<","; + } + cout<<_dgs[iter]<<"]"<<endl; + } + + if (_debugLevel>2) { + cout<<endl<<"g=["; + uint ant=0; + for (; ant<_nUn-1; ++ant) { + cout<<_g(ant,0)<<","; + } + cout<<_g(ant,0)<<"]"<<endl; + } + + if (setNaNs) { + for (uint ant=0; ant<_nUn; ++ant) { + if (_stationFlagged[ant%_nSt]) { + for (uint cr=0; cr<_nCr; ++cr) { + _g(ant,cr)=std::numeric_limits<double>::quiet_NaN(); + } + } + } + } + + return _g; + } + + StefCal::Status StefCal::relax(uint iter) { + if (_nSt==0) { + return CONVERGED; + } + + double f2 = -1.0; + double f3 = -0.5; + double f1 = 1 - f2 - f3; + double f2q = -0.5; + double f1q = 1 - f2q; + double omega = 0.5; + uint nomega = 24; + double c1 = 0.5; + double c2 = 1.2; + double dgxx; + bool threestep = false; + uint maxBadIters=3; + + int sstep=0; + + if (_detectStalling && iter > 3) { + double improvement = _dgx-_dg; + + if (abs(improvement) < 5.0e-2*_dg) { + // This iteration did not improve much upon the previous + // Stalling detection only after 4 iterations, to account for + // ''startup problems'' + if (_debugLevel>3) { + cout<<"**"<<endl; + } + _badIters++; + } else if (improvement < 0) { + _veryBadIters++; + } else { + //TODO slingergedrag + _badIters=0; + } + + if (_badIters>=maxBadIters || _veryBadIters > maxBadIters) { + if (_debugLevel>3) { + cout<<"Detected stall"<<endl; + } + return STALLED; + } + } + + dgxx = _dgx; + _dgx = _dg; + + double fronormdiff=0; + double fronormg=0; + for (uint ant=0;ant<_nUn;++ant) { + for (uint cr=0;cr<_nCr;++cr) { + DComplex diff=_g(ant,cr)-_gold(ant,cr); + fronormdiff+=abs(diff*diff); + fronormg+=abs(_g(ant,cr)*_g(ant,cr)); + } + } + fronormdiff=sqrt(fronormdiff); + fronormg=sqrt(fronormg); + + _dg = fronormdiff/fronormg; + if (_debugLevel>0) { + _dgs.push_back(_dg); + } + + if (_dg <= _tolerance) { + return CONVERGED; + } + + if (_debugLevel>7) { + cout<<"Averaged"<<endl; + } + + for (uint ant=0;ant<_nUn;++ant) { + for (uint cr=0;cr<_nCr;++cr) { + _g(ant,cr) = (1-omega) * _g(ant,cr) + + omega * _gold(ant,cr); + } + } + + if (!threestep) { + threestep = (iter+1 >= nomega) || + ( max(_dg,max(_dgx,dgxx)) <= 1.0e-3 && _dg<_dgx && _dgx<dgxx); + if (_debugLevel>7) { + cout<<"Threestep="<<boolalpha<<threestep<<endl; + } + } + + if (threestep) { + if (sstep <= 0) { + if (_dg <= c1 * _dgx) { + if (_debugLevel>7) { + cout<<"dg<=c1*dgx"<<endl; + } + for (uint ant=0;ant<_nUn;++ant) { + for (uint cr=0;cr<_nCr;++cr) { + _g(ant,cr) = f1q * _g(ant,cr) + + f2q * _gx(ant,cr); + } + } + } else if (_dg <= _dgx) { + if (_debugLevel>7) { + cout<<"dg<=dgx"<<endl; + } + for (uint ant=0;ant<_nUn;++ant) { + for (uint cr=0;cr<_nCr;++cr) { + _g(ant,cr) = f1 * _g(ant,cr) + + f2 * _gx(ant,cr) + + f3 * _gxx(ant,cr); + } + } + } else if (_dg <= c2 *_dgx) { + if (_debugLevel>7) { + cout<<"dg<=c2*dgx"<<endl; + } + _g = _gx; + sstep = 1; + } else { + //cout<<"else"<<endl; + _g = _gxx; + sstep = 2; + } + } else { + if (_debugLevel>7) { + cout<<"no sstep"<<endl; + } + sstep = sstep - 1; + } + } + return NOTCONVERGED; + } + } //# end namespace +} diff --git a/CEP/DP3/DPPP/test/tGainCal.run b/CEP/DP3/DPPP/test/tGainCal.run index f13f5a4b3f8..2309449fe93 100755 --- a/CEP/DP3/DPPP/test/tGainCal.run +++ b/CEP/DP3/DPPP/test/tGainCal.run @@ -23,23 +23,68 @@ tar zxf ${srcdir}/tGainCal.tab.tgz # Create expected taql output. echo " select result of 0 rows" > taql.ref -# Create MODEL_DATA so that residual can be computed +echo "Creating MODEL_DATA so that residual can be computed" ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=MODEL_DATA steps=[predict] predict.sourcedb=tNDPPP-generic.MS/sky predict.usebeammodel=false - echo; echo "Test caltype=diagonal"; echo -../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 +../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.propagatesolutions=true gaincal.solint=1 + ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal -# Compare the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima. -$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where not near(dpppres,bbsres,1.e-2)' > taql.out +echo "Comparing the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima." +$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out +diff taql.out taql.ref || exit 1 +echo "Checking that not everything was flagged" +$taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out +diff taql.out taql.ref || exit 1 + +echo; echo "Test caltype=diagonal with timeslotsperparmupdate=4"; echo +../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal-tpp gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 gaincal.timeslotsperparmupdate=1 gaincal.propagatesolutions=false +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_TPP steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal-tpp +$taqlexe 'select from tNDPP-generic.MS where not all(near(DPPP_DIAGONAL, DPPP_DIAGONAL_TPP))' + +echo "Comparing the difference between applying with timeslotsperparmupdate = default and timeslotsperparmupdate=1" +$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out +diff taql.out taql.ref || exit 1 +echo "Checking that not everything was flagged" +$taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out diff taql.out taql.ref || exit 1 echo; echo "Test caltype=fulljones"; echo -../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-fulljones gaincal.usebeammodel=false gaincal.caltype=fulljones gaincal.solint=1 +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_FULLJONES_GAINCAL steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-fulljones gaincal.usebeammodel=false gaincal.caltype=fulljones gaincal.solint=1 gaincal.applysolution=true ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_FULLJONES steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-fulljones -# Compare the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima. -$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_FULLJONES-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_FULLJONES-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where not near(dpppres,bbsres,1.e-2)' > taql.out +echo "Comparing the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima." +$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_FULLJONES-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_FULLJONES-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out +diff taql.out taql.ref || exit 1 +echo "Comparing the solutions from gaincal + applycal with gaincal directly" +$taqlexe 'select from tNDPPP-generic.MS where not(all(DPPP_FULLJONES ~= DPPP_FULLJONES))' > taql.out +diff taql.out taql.ref || exit 1 +echo "Checking that not everything was flagged" +$taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out +diff taql.out taql.ref || exit 1 + +echo; echo "Test caltype=diagonal, nchan=2"; echo +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_NCHAN_GAINCAL steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal-nchan gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 gaincal.nchan=2 gaincal.applysolution=true +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_NCHAN steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal-nchan + +echo "Comparing the bbs residual with the dppp residual (solutions will not be equal, but residual should be equal). This avoids issues with local minima." +$taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL_NCHAN-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL_NCHAN-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out +diff taql.out taql.ref || exit 1 + +echo "Comparing the solutions from gaincal + applycal with gaincal directly" +$taqlexe 'select from tNDPPP-generic.MS where not(all(DPPP_DIAGONAL_NCHAN_GAINCAL ~= DPPP_DIAGONAL_NCHAN))' > taql.out +diff taql.out taql.ref || exit 1 + +echo "Checking that not everything was flagged" +$taqlexe 'select from tNDPPP-generic.MS where all(FLAG) groupby true having gcount()>100' > taql.out +diff taql.out taql.ref || exit 1 + +echo; echo "Test caltype=diagonal, nchan=2, solint=7"; echo +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_NCHAN_7_GAINCAL steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal-nchan gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 gaincal.nchan=2 gaincal.applysolution=true +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_NCHAN_7 steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal-nchan + +echo "Comparing the solutions from gaincal + applycal with gaincal directly" +$taqlexe 'select from tNDPPP-generic.MS where not(all(DPPP_DIAGONAL_NCHAN_7_GAINCAL ~= DPPP_DIAGONAL_NCHAN_7))' > taql.out diff taql.out taql.ref || exit 1 diff --git a/CEP/DP3/DPPP/test/tGainCal.tab.tgz b/CEP/DP3/DPPP/test/tGainCal.tab.tgz index e2e03418837dd2d4dc7441d769ac53709666b9c8..2b93f5b9d5af30ffc0d38ec677d6e1108382aab2 100644 GIT binary patch literal 212564 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ν#Va8o3j*POm@b`>?aeMkPQ$=1}$54ZmXR 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 literal 171806 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�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%0DWO=1|y$A0Ye-V%J zndex0$nN^a^B?6*KU8-<xNSMJs%2Ml>=WU#*B7j-UN71n`a-tinclK_LSLpVTz=zx zdC3dYM_kdD`2^lD6hv*~+wHhP`l^k?4augtj{EF>K#J63yZ-3Pt>3a*=gF!=`#)X1 z`fFzCx|$E0ye6L4bFZyn|C7Y{Cs**(lhzy4zdxKW_ma#0_Q8Ac&h=q>ua2iakiG9b z@0Zx8Z|Q>VG0yw1%daTE$NK-Rz^BXdVV_@z*nMF5^jALY^Vx#Cd-%^O?(epED)8u0 z^o8e}JDk@o+;1!X^@q`ii1&>5Z7zL?y|6#~!++Nkhs6(_zw7>QpUlsHvMctBFNn*% z=K1-cw8^9Od(`W{J-#H*v;O>lxp?yn&pyYf{tr(|I4-o~>Lk^L?|<x^d~^DuUvpo7 z(*GM@Y4zi1fzk6dt15L2YmzdX&tJ1Vviiqo?Jv{)yC3#H+P&kA^`n5h_UoQGxP1)S zz4YMQ@>>h$>x$HT<@ol$F6$S&Jj>)s;R{abTXp@=%}a8A{5Rx&uX)AcFuVVUZuQ<# zW&C=m;L3Dmi;bo1v$m=(vYI#Xo2F(@{qFGeGdcG9N4bAnIsAOs=4kW9>G7pHrh3ge zPkSpj?9=-HXA}RYE$g`UZ~62fxO)F(_4$1*^=lg~&Q8qko&PcBGH2(B#rLlI{CnEA zboRNBGp3ad*F)t)_dC3+uLww(Kl|%JcP@u}huaU=+>778M)!{gZ@$s<<(_dd?G8`8 zV-3U4&tu>JVE0dTZ^>r|ZY`J>y=HYy)5+r9%{AA)U;X=7D{gP=->3%5w896+!xz?H z_Iui1@1wSq-8As|*2jVJSAIptq{q1bTj&sPymos1ouw9E<U?nkU;9q}c=1B>qZ^pj z&cEe*rg?+8eQM+O^kCa~zjxhyjVt!=Q1bi2XfI>2s?FT_+=S}d59-V}p3FY={QTee zH@4gr!lBoKUv#@K4B~1HlQ?zwQ`Tg=M90aQvoCpEn&NYTX|<|}Chrzc-=G$G$B+li z@eK-_)@eN8X5G@TE0)!&At~^GyjbPW?k<rD?HR38Q*UZjzrSPrQpNX{h*xIfx$j%k zi=LI9-~0Sd^}C(V=LtUAQOEF?Pp#;o4Bu;=bOk@fIcGlee`zli_;jSNL!~PDCEsg> zxfT<&mm1hJ?0n?FchK+EG8W60uYV&JrAsHp{!^P!eCgbGzUDnU|5{w&R(+xS<Y0G0 z#n*@p){8AKD4*DGC2(u%BZv12``^TRT?oGZukb>)>I+l$s<V#v4xe_;{9WiI7yiP0 zN{Z~N)f?vbHrKgXe!RHxihTauaE_-_pFLh%BeVZSt)1VqjUp$0manVOPx>EQ_H$up z?RV`Xn%6n%Dmuc~i+vMzmfPmAbi(}B8%M=i_uKS*Hr+6<c2!VEz546Wrun~xKfIn> zeDvzXzmn|<`z78?=CtMdeVhN_KhqoAg{wj;_@?}hSt>A{?S1Ld*@vqSp05o2(0q7D zY_R5=$!z;96y<rZG(S*pulSl3!N228lzBrHb3G&L<8$R)ivRi-P073O`S9xcbSCj{ zPgXW0M(=XC^G#vJdBr<Zj^DGbU19%bcAc`=x4V3gWa8)7y__okc)|bu!oS`|<V{?e z^*O&gB}K;6Wm2u_+{Zii*sb3ix%Hb(<aV2T{Kppmiz#q?wyXB_uERfsct1a|yVG@a za?_L0jZgmRE5+2kIaTnxJY3XS@t+pUqYLcC7p=vngwB^?`1ho7$^9$5PuA`Ea*>(y zKG)w;dm;5}>*T{XZ2f<*y*x&>tY7HQ?90`K^XG6(j{RfH@T~W)LY>{5>ZH3muU5{l znpp8Ywef-d(jD)23P+yyVr%;U(Bg=9m_YT1BLxpW3w7S!FX8Y*VjjzD!3XbS#ZJd^ zc5_?~=TZE;sng_#bkYf%iJh%~odN~K1rDCAOn4aj{1eB9=tBqpOP?v-v!V3nPDgd^ zlYa~u{%liEj9q2?=JkrEkDubXCOu!QfAarNXWb*AC2BS0-OaVEu?hcp3jfYD+R$EA z)Mvdf-ezI8zux~=d65pj$HD#^n5~Q7%S8RLtm1o<{hsOTxAk5X`t0vp_Z(GEyt~5q z&E*wL|EEtl|4*fc-Gbp-qw;~e{)R%{PZRa}6d#)lbN@cGsHLjyzVnoT^LG|0rJs1# z%_y^l`$xOA=JY+b85i4QwY3(?9?aXZq~i6|Ehn~nX&UM4OLBa^{Mg_;M|<tGy%rOM zCEmB}+0Q62{a!D#B}@IaP}UQQ>_w|tcD7cQ+IK|DuVJxl|JvFZ^FS@{rDHCC(tE9T zC!0i$?N-^31a9}&D#ZKfuvf9vd<k#5*SJ^YY~wsFM}KWcf6Gbrn$v#hi=9-Mr!Z-D zL2CSk{9-oEh4Kv5Z~Xes%YVHop!!8x=8N<kNp+PsGZ*woTuv8!m2UAOeaX)XZRz7b zGlUAOYbv*X%X<4x%JuEL)mL=6&KbIOn;wpkd2iz9dEvz-iCVkK$pKHF82pUPnyO~Z zc(MEJ$JR~9<ev(sa0E@BEI4iXvZbe7B%Fi1Hu<=n6b=r$Jn155q>}37<*G|ng1&s+ zb2(q5*rK;=)-s=InU~M1y3N0CTDGtE5Fbm#{sQ|$R=r`9FXko9jat0bw%)2K!u#!m ztTn2P1u|Uw#e<$`_X=%s75HVR!`>gfBs4N?^|o1Oj9c^bFNyp-r}pmuN1L{-Q>KW` zE-jmUGtVrucj=>R$De;RH~Cm@b-6p2_3}~q4?G`BgANFJC^WKwiS6un3T|`PpZVjz z&(CYybcmpkN5SvL8?H||Z>SyqHvRc5Z}BhJrz)9UmwmBb`lz+{%464Jk~EUqzAo&D zoHqB|hrD+OejP8$KknPgHc#{7#rTKcR@k|AMx|X}`t<*g`!^(>HFxi~|9ier=kw*M z3vRMMiE{h+a>o8yg0}fjs&5|tqO*B>{&W2@#b3MPjlI*AcCEcF{98zHZ~mVXvekln ze@t|I&iAc5G49ulWluZmczv&_Sp0r^NA%!|Kl88dtNJ>Bg-v<6%^pRrz2&c0DX(4? zSZPxI<R$<2{={vT)7$x7zI@j2xBF<k$bO;coufQGiDt)qWbU$GWS6m8_oLorzWBcI zG#P=zHB09%+QWTuzPbLaFHDQ(?<{yVL-gYGx%DYMLKm<5-M#g$*5&`(4;SWtTku>& z*Lwb(mKW@CHedf~x}>Ymox3Mf;Qzfh`#R<onihQX@7VWf>&xlajvqBhXFEA{ui)O_ zKNbCN3;zAsn=i*DEp@o(SKM|5;fuYy0@bf6RlTx3U4OsxMfpkR?GJy|+?jY(*YV3N z_5P>w9es({-b?;o#&<D1Q>j}`xoW-Le@RdG&+m2THodUr|D(IgdD65(8JWLLxdlAN zKhAq^+tYl@yza8oyX=ts)!pyvUo5uRcQ5U|%NN!4<$H_8s`#?r@7b?<#Gv2r_A<%V zdBTygyCd3P>{cpX{cpli18e(e>1A$T4ooaO{Y^b%=H%ieFQJRY`+k1E6RR%p_u~IQ znGU*(p1+O!aXd1FU;p1zea3b{TdOxW?&TVV{ClKYJiqhNY5T9CF82#xc3*kgVHaro z<vH`-AHO<p_q>q4{(g?V=-%+{UrsdN|JIYp#m${QL*TF1@xSijN>#sa^o#2_pT8LX z<)#VW#s7Q0->j?bSZ{7;v7yp4f1m7gR^?v{mtWiWU1HJlCE7-tsz1JssFeIz{cHDK zX8YK^i!c5B-1GM2{6C8xT)(%Kv1{@4cXq$u{Y}@ZelyGLOhi!N&Sl?j2VOS0+BHe= z@S&{7t3<V%mE_*WZF3EL_hZ$ErLE~A+g}yuZTYvYG$Z$|s;zrf-xryemY0f`Jpb`| zj_Cu3>31DJJk@Ag?DoU_pDZ(X;$Mj-?^2fj9rAi7)K{#U@L*|J)8{k+pYyGSC5#83 zo)@tBT-h{rH78%~+y}MNoN-^}4o==KDD&O$py_<!n2(|dKkrkt*gfmPhhi4_oempx zW+doMdf-~lWWRXtrf7aIV@|n+j7^OTR(Z1M>nd2}TOHie{NVC`=YpS)S<cr?N_bk# z(!S5*!@jL7>#MpSoSe<s_uKlQsmwvqP?qzz-3z`6HXZI4ta;+zbp4N0!NvU?&zv~U zz4a^ziGT21PXA0*vrf~hEiC%7{ss4#SuUqF`HKt3yi=)orp%)KTv+Cp$-&>#g!jB! z&*EP<^}!Bamg_l+J03YVJ%6iY@!PoR@OR;wC)rKc|2Py}lx#}fFI4k$Q~jH}uLSri ze8Zc%9r(nS{wlB6JK-sE;-KM#=C2CuOBL9a9r#uVHLY^so7N=rq*3OA0{e1DzEh5T zX-zU3j(kqrh4%12Nf148Fl9os#e`<53C&y+nkyC9Zz{0gU~ihvQfMx4$zXMzqqE`u zdkWTbcX-_TU3ur)!j*sSy|DV3@J~5@j_*~Y_7&HE`^Fsqy>?1>sgCjYwc+<<3_MTQ z{5(7HV)FB&28Xw=x#qfL^O=hq_p1M$xG2@HY{vd7N%6C9{^{l&olvNAIrM8pRl&At zk!vk)ZG5sY<JpJROP{TOxA)^eeNL&iz`aRq=XU#__L=)xa`WqXac6Gtf7hR~Ir7dq z<Nj}#jj|`{2>y?dJX>RBcD6{dKQ&BT!#?J6?CBnjKUF*Pf=#}??!EVs;WW#Bo9OcR zJ=1P}sK0z{<?XL&AKKPl`W^ACm)m$#b6fUIz1#LH)2u)KyL`P$IwI}UP5Jld_N__$ zxA*Ojdk1G4o@xDi>+iLdI>CX{w(Wit&HMSFw!iVs&5LGgKCJ9sej|V0oGoV6TejLh zPutoJD!~sPyl~(2_S)Op#=6^Pu~ydy_JscVaCza8$SzO*CnXU}?1Fbbk$k$X{FAoU z{QlXtWhr%Ao4l%5r7!QvI{HBTn@Udgx|qUOJGY<bpH=C$aZg#`zh|ver?TIFnNXSb zT)%71_iB-Kv2R!0S@lQb@yX-oLcZCrk^S@Tv#82<r|(&=zuCF_kIc1R_rmP&^@CUD zt?ha${VD6dMt<$z;$mBMbKUPZUb&ZrgneBfv5nPMJ@Zyy_4}EIZh>+u<`-2Qn|UfV z+3WZ0`Kwmku~>E7eErcdZM#=n7FElIuKn@P>u33E<?R~hw3eM;TN^7?eoIPcu9r;0 zm%}Xc=XrlPlOBD1p59ZhpuP*!Iq!Y9I=I?Q(B@}t)8lQzF`o-BpYj!-{Q7h{^L_sV zzo%{I`}ch>$Deh~b@$oowlnOnSKpDOe(>`*v$c1gsy?ZDD)XS4aZkSM>j(9pybrw3 zKJcFHLAmya-+~8k?&jqCzo&G{?z0YxE7vbgWqIBxGpBA>>6K*56AyhRFsq($eBt(j z-+?czQO4)qU8_@f!w#JPdtWSj;oDjFFMjx!&(e8v-tYaFFD?0dUze#Za%tMHyj{xC zX08@*DmI^1aXj5P|5v_|)%w4(y)Wvcih}n3;a~a5#PfeN(}#P;U9Y+hPiyAcu~Jir zyZ7<Dj~=%Sw@Ywx%y}8ptReeDerB3GV`%+jiMU@ckHjx97r*gFt%9qs?wyB*oH<*} zYhPK0?@8P}N8PI1*6vtyq-(wO+HF>uJWT9OHmg4dm6{}%&RQF$y!PA@-7u+h7yS}! z&*-wZnL7m(7sx1Btvk=~dQ0@0;8-4(X9iJo*p|-@_~O*P^M*@wuj;?4JA#X+TXb!_ z-MiU^)$Hb}3zuV9`OaS5nAIFLy}9otkFnO)x!Xi99i6t))vqQ&<nu#u?u7v#f4sDC zSlDcJ{n-W!U9;6IDmI9SO?WeRzh&Id!}W8IPhF@qb!yM{s5urkil?4O7cAph(Gjq= zEAgsjq{TgFjt}4CYMr(|ENcB2%C1|(FA}9;Qy=r_+xipM`pUJRgkHET-m&81?bl~p z7tOEvJmp%U=nAtJn_HfJU7ou;?M2BipHjZ$fTzZlT65A~%JuSk8hE5O@=iUOWNLMm z@79Y2#qGV)%a%Xp>M3*CAhEVNbMnzVy#x8n(-m?yThCHqm~pi3-YmBp`xQAF*G%Pd zbaHl5n6h-;UtRMBf30`SdJx63pxbNlrBy{dKX<Ls32R+k>&7YCQnLF(Vwu&3zS-Sp zRXsk-emVT{TSdA}?1NWq7b;H2-!XZ+ykURby;&{^ewY5VMeoa;_1>_rOh_tjeq(a< zJEso4;u*dr3(xvlcb_Tsm220mTru1HqA=U<tA`FPxD~-2fBeSNsYgp@uZ=j8l-_t$ zhqw9h&qusA#Vf3xUl|=fs@Qwwcud#tb=e7_4bo?_YF8g}?mx_v)vFc1-q^q(Nc~0b zf|(wkn=eoFO>vyw{EN#=`_;X_kIbeNwNJe_^HODMSN^2zivfS6)z!bOEY_QRFi)h* zq4Qnt8vdRKuh+%7SFGROcOj(hnA_x~SHg5I33OjgG@Kc`m1pXiaEq53f7iz(M86WB zy*J)v&R@r?XIq%|m@=~J7tT*%={U!6ZTAc{vk9?Mw->~AzyFj}wKFg8irS&$U)%Gh zPk3-yPIk|2%k%RtzD?P?&gIN%{@6m>1l5_1K^E_}+n&y9O|QCUlJ`Ktu8=d-obiZ3 z|DA?*f&AG;wf8j`pFaG5uy^+Sg=c*-?v;P=o3};e{=fTGKkX+^ezH<z?xj6PKk#ke z`TuzJZ{g+gFV2)YC;$5|64~Ycd%4=OB_99mIiq;qwFPqUi#7H|hUGYLI8V93aG<RB zc7oEk-mR_~9{<;Czue3rb@Bg`keSba#d~E1ty=P5my!2*{XF?c^W@J(|4ZL9ajkCl zfw#Pm{+zG4_Ht&k05fk%eX+o!wJmv>Y?|>doGKF>m^f7?JV<kJ{`S9=^>62bmmljh z{>|69`CpeY=+FJPyu2?V;v0Y3U#ju{JN@+2N&mL92<#7M-*e)**#CCM^Ox>C`tRfy zuvYD#|A$jo<6nG>xWb=URBW0h8aG`!Ys2)Jncj-A5jk_b#3g0Fh-|*;CGG9qt(zwN zcFRT8j+<&M%vDas>u*-OFtw$;*b=42zFBBqYs8E$f3X{0UI8orXytEiXEKvY6IgZ5 zaK)GJh8DIL1e%w!gxbg5pV!EIET`<(LnUuV!M8e_EIei_^!P>25))TB!1eylorVWY ze^`&WHFGn`Oxc}&rn<GV<t9sJ$oG{6xkn#}E$cpOeCFtvf?Ij>5(^((U~>Afx-QBt z#57`kQbZl+4&nH!-8WxssMNiER=~G^7GG+h@9V2=oG&aUxn9lMZjr87YBcku#m$19 zIR&d%rt;6_GFkC+0@KcGNm^SKYY!Ewnft6|VyjGlvCO^y$NCnv_cAfzXFMkCyb{*X z9C)kWJSE_t#;){;?TJo$c}J})ci(1f4wBh+Fyl(g<Khn!>kU)SF1Hr=thDTBPo*I5 znnw-%7Zsk0eO8j<I8qW`uda6Ot8!kyfaaR$4&E2d*1cEOIaG^@{V15Vv+a;i!PT9* zA2@Fbbkv=5H($8_+Y1YUhYBst%+kA7er=xp_x_%={oED5?XNEXJAcZgDNiK-PgI-y z?f(`Ifvp?|Z))9r^Z#qXy|e%LkA1p3;jeszTjlG@TMwU1{aP*er?;gq%1mC<)SKye z0oyJKUGK#kXPB3*NmUI#^DS|U>OC3z9>1RBLV`>S%S3)kG&4M$+0%IPe8@)0s#3ip z3p>xVr>*?4&dY~eRBeyk7lpdlv)K(*qR&c|{Nj{*;<u|r?0a@xoaC(4&U5EkrT4J! zEneq$_{CM>y>~AwhOa*zuyKvw`@%)qvsa%ocp>Tg@9(QQK6n4lb4}yzaeV5?qb&U^ zqItWyYsJmC&kH?Ob{-M^eSC%K<FE7dcP8JdaBH@D`e);cn4bb~HTErNFJOGP%rh~f z+3N7)RT&Rg>FmwAQxWQSjAj1xZ(@4B=@C=i=05(t@U86S^vVfVQET{?@8voEuxbux zfY2RHJ4L&NekXQ4o*&nuEpnN4;iLZ7Ei0M-OJ_}~kDL3U<fy%E3)^IuLqdLayE#22 z{DnE)S=6qGbN##8WN@dkzJkl~yw#%g$KO|3hwi_q{LwSN_il8}qB!O6qVvDr+fjRD zdPVLPfg0A#GkQNRtQA~;@{ZuMhObF`jdLnWtJQd)TQ6U}`RB~6v>v^7!J<2D(mF@e zgm=0d$n5&KrKW#%;hIc^t0(rU)-)y<ntWzs=TAFS@Nv_zY?JS=iheL8#ouwcQmDLd z&(a%=#wE6)>65Cpm#)9Wt@<&?I^5PY>RZ`8t#19<@!Pk{7Ht0iO0)bk3zNC+KXX-6 zRo|}vxttkG{?1kv*dh4)zW1N}sH{akoBpe27=94_->t5)E_UaX{vY?Z{rMWR|I2?X z=0@q+|F6IQfBDJEpg;ND4u?PPkJ!Tc@wh_Ye_kGi{U6zNmQ<YE#`W*m|0K>Gp^xhg z-~T99nYZV${j4mh_kS21J8wLheza!pj-Ou@dg3GQ&i)-O@UOwndv2okW~;)xpV<T0 zw)NC#EVi{g=gU&17SnQi2B$ddA;WzdJ6ccGu5O5b{J)fq$t>-E^~wM0Pyc5rPI&Y` zFaLx0|MkVL8wCGOUw%T^_N%?6=cWH*j7INs`<FB1y{@nLb9`Il?O*Y`gHE0P72jW# zG+AF~-{bQ+{w~U9Ml)q<`V?w7V-Hwo7yh#K*|e~}?AJo0WJA9F4_VJOI)D6NIp=ZT zc?ox+3l?*xF<G6Hu{WJ}ZB}oXjLgL%#&$h%hvY)n#u=V1iz}Ul#N{^!9Y0XgJ^x~r zx&GCJ6uyu>(I@|Em25t~sHG#rE;+Eps+^ng<B2_=6n<2w1@X2wJP1%Vm+ueQRLMIx zXYm8o|JrLr%;JS6WPkYo`JepB|Mia-Hh#<BrlR0`{{O_bI<Lq3S#;U<{rS(lV%L|* z?fd@UpZ(9=S!lwsZ~sp``TFhu(i2Pm+Uqj${{GK=`)a2>+s6O)5`X@4ue|o)_>>2? z4#fYrpVkz8^}qiXhWd^>ldE;Un_M+{_fwKdHA%#tr9O1e{`Hg1t9ec)1orc=9zNc= z=&{Ph!c8wMTubI?S^2HDp4L52Q~aW^>z5<{x0UDZ%~cL(SoV0w$z6TL`z3s59W2{? zR&3?M*>kxkDQ(hGKlN(cqB*&8T#eH*X7{?RQhB<DTjWMN>#sX@Tl~4-1UK#zseSeN z-_(B%Ou6+A7mF(<x{0V>+~#!Z;!lPqk#vuF#>O(|&Rm@H@ZHP@?<QAVvGzN*Bl+E} z6#sJ;>h*IbTOOS2|4sJ$I^V+Q*LNKk-EqF^yT!$;;{Eca{gdzfR1vqo>C0}>cYN1z zO+CL=($8y+PVH-6dbhN#^OMcHQ`@fjI@{D8k=<R`ak8oTokdAgy~jR*jXFo(7;O7% z€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 M!mM~1c5pKQ08{kGGXMYp diff --git a/CEP/DP3/DPPP/test/tGainCal_ref b/CEP/DP3/DPPP/test/tGainCal_ref index edba85ea526..1c1549410ad 100755 --- a/CEP/DP3/DPPP/test/tGainCal_ref +++ b/CEP/DP3/DPPP/test/tGainCal_ref @@ -40,6 +40,36 @@ rm tGainCal-bbs-diagonal.parset rm -rf tNDPPP-generic.MS/instrument cp -r tNDPPP-generic.MS/definstrument tNDPPP-generic.MS/instrument +#### DIAGONAL-NCHAN + +cat > tGainCal-bbs-diagonal-nchan.parset <<EOF +Strategy.ChunkSize = 0 +Strategy.Steps = [solve,correct] + +Step.solve.Operation = SOLVE +Step.solve.Model.Sources = [] +Step.solve.Model.Beam.Enable = F +Step.solve.Model.Beam.UseChannelFreq = T +Step.solve.Model.Gain.Enable = T +Step.solve.Solve.Parms = ["Gain:0:0:*","Gain:1:1:*"] +Step.solve.Solve.CellSize.Freq = 2 +Step.solve.Solve.CellSize.Time = 4 +Step.solve.Solve.CellChunkSize = 10 +Step.solve.Solve.Options.UseSVD = T + +Step.correct.Operation = CORRECT +Step.correct.Model.Gain.Enable = T +Step.correct.Output.Column = BBS_DIAGONAL_NCHAN +EOF + +bbs-reducer tNDPPP-generic.MS tGainCal-bbs-diagonal-nchan.parset + +rm tGainCal-bbs-diagonal-nchan.parset + +#### Restore instrument table with only defaultvalues +rm -rf tNDPPP-generic.MS/instrument +cp -r tNDPPP-generic.MS/definstrument tNDPPP-generic.MS/instrument + #### FULLJONES cat > tGainCal-bbs-fulljones.parset <<EOF @@ -74,6 +104,6 @@ cp -r tNDPPP-generic.MS/definstrument tNDPPP-generic.MS/instrument #### Store output from BBS in separate table -taql 'select from (select BBS_DIAGONAL, BBS_FULLJONES from tNDPPP-generic.MS giving tGainCal.tab as plain)' +taql 'select from (select BBS_DIAGONAL, BBS_DIAGONAL_NCHAN, BBS_FULLJONES from tNDPPP-generic.MS giving tGainCal.tab as plain)' tar czf tGainCal.tab.tgz tGainCal.tab diff --git a/CMake/testscripts/assay b/CMake/testscripts/assay index d97460d4562..09256b2d7f6 100755 --- a/CMake/testscripts/assay +++ b/CMake/testscripts/assay @@ -190,7 +190,7 @@ # Define exit and interrupt handler. - trap 'rm -rf core ${PROG}_tmp*; \ + trap 'rm -rf core ${PROG}_tammop*; \ trap - 0 ; \ exit $STATUS' 0 1 2 3 15 -- GitLab From 5ec23153a577a332df53932857001ddad29792a8 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Wed, 8 Jun 2016 08:19:26 +0000 Subject: [PATCH 287/933] Task #9531: handle amplitude only solutions in parmdbplot --- .../BBSControl/scripts/parmdbplot.py | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/CEP/Calibration/BBSControl/scripts/parmdbplot.py b/CEP/Calibration/BBSControl/scripts/parmdbplot.py index 4b521093416..1d057bd4dba 100755 --- a/CEP/Calibration/BBSControl/scripts/parmdbplot.py +++ b/CEP/Calibration/BBSControl/scripts/parmdbplot.py @@ -287,9 +287,11 @@ class Parm: if self._calType == 'Clock': return QColor('#fff1bf') if self._calType == 'TEC': return QColor('#bfffda') if self._calType == 'CommonScalarPhase': return QColor('#ffbfbf') + if self._calType == 'CommonScalarAmplitude': return QColor('#ffbfbf') if self._calType == 'ScalarPhase': return QColor('#ffbf00') + if self._calType == 'ScalarAmplitude': return QColor('#ffbf00') if self._calType == 'RotationMeasure': return QColor('#84f0aa') - return QColor('#000000') + return QColor('#FFFFFF') def valueAmp(self, domain=None, resolution=None, asPolar=True): self.updateValue(domain, resolution) @@ -381,7 +383,7 @@ class Parm: def _readValue(self, domain=None, resolution=None): if self._elements is None: value = numpy.array(self.__fetch_value(self._name, domain, resolution)) - self._value = (numpy.zeros(value.shape), value) + self._value = (value, value) else: el0 = None if not self._elements[0] is None: @@ -729,39 +731,42 @@ class PlotWindow(QFrame): # Selector for double plot if self.calType == 'Gain' or self.calType == 'DirectionalGain': - double = True + plot_type = 'double' + elif self.calType == 'CommonScalarAmplitude': + plot_type = 'amp' else: self.usepointsCheck.setEnabled(False) - double = False + plot_type = 'ph' if not self.domain is None: for parm in self.selected_parms: - valuePhase = parm.valuePhase(self.domain, self.resolution, asPolar = self.polar, \ + if plot_type == 'double' or plot_type == 'ph': + valuePhase = parm.valuePhase(self.domain, self.resolution, asPolar = self.polar, \ unwrap_phase=self.unwrap_phase, reference_parm=self.reference_parm, sum_parms=self.sum_parm) - if self.axis == 0: # time on x-axis - phase.append(valuePhase[:, self.index]) - else: # freq on x-axis - phase.append(valuePhase[self.index, :]) - - if self.valuesonxaxis: - if self.axis == 0: # time on x-axis - xvalues.append((parm._times-parm._times[0])/60.) + if self.axis == 0: # time on x-axis + phase.append(valuePhase[:, self.index]) else: # freq on x-axis - xvalues.append(parm._freqs/1.e6) - else: - xvalues.append(range(len(phase[0]))) + phase.append(valuePhase[self.index, :]) - self.xminmax=[xvalues[0][0],xvalues[0][-1]] - - if double: + if plot_type == 'double' or plot_type == 'amp': valueAmp = parm.valueAmp(self.domain, self.resolution, asPolar = self.polar) if self.axis == 0: # time on x-axis amp.append(valueAmp[:, self.index]) else: # freq on x-axis amp.append(valueAmp[self.index, :]) + + if self.axis == 0: # time on x-axis + xvalues.append((parm._times-parm._times[0])/60.) + else: # freq on x-axis + xvalues.append(parm._freqs/1.e6) + + if not self.valuesonxaxis: + xvalues[-1] = range(len(xvalues[-1])) + + self.xminmax=[xvalues[0][0],xvalues[0][-1]] labels.append(parm._name) @@ -777,20 +782,24 @@ class PlotWindow(QFrame): phaselabel = "Phase (rad)" - if double: + if plot_type == 'double': # put nans to 0 [numpy.putmask(amp[i], amp[i]!=amp[i], 0) for i in xrange(len(amp))] [numpy.putmask(phase[i], phase[i]!=phase[i], 0) for i in xrange(len(phase))] if self.polar: - self.valminmax[0] = plot(self.fig, amp, x=xvalues, sub="211", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Amplitude",scatter=self.use_points) + self.valminmax[0] = plot(self.fig, amp, x=xvalues, sub="211", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Amplitude", scatter=self.use_points) self.valminmax[1] = plot(self.fig, phase, x=xvalues, clf=False, sub="212", stack=True, scatter=True, labels=labels, show_legend=legend, xlabel=xlabel, ylabel=phaselabel) else: - self.valminmax[0] = plot(self.fig, amp, x=xvalues, sub="211", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Real",scatter=self.use_points) - self.valminmax[1] = plot(self.fig, phase, x=xvalues, clf=False, sub="212", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Imaginary",scatter=self.use_points) - else: + self.valminmax[0] = plot(self.fig, amp, x=xvalues, sub="211", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Real", scatter=self.use_points) + self.valminmax[1] = plot(self.fig, phase, x=xvalues, clf=False, sub="212", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Imaginary", scatter=self.use_points) + elif plot_type == 'ph': # put nans to 0 [numpy.putmask(phase[i], phase[i]!=phase[i], 0) for i in xrange(len(phase))] self.valminmax[0] = plot(self.fig, phase, x=xvalues, sub="111", stack=True, scatter=True, labels=labels, show_legend=legend, xlabel=xlabel, ylabel=phaselabel) + elif plot_type == 'amp': + # put nans to 0 + [numpy.putmask(amp[i], amp[i]!=amp[i], 0) for i in xrange(len(amp))] + self.valminmax[0] = plot(self.fig, amp, x=xvalues, sub="111", labels=labels, show_legend=legend, xlabel=xlabel, ylabel="Amplitude", scatter=self.use_points) self.resize_plot() self.canvas.draw() -- GitLab From d62b909dae935b94dd491aa2dcfc9cc90d90cc4e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 8 Jun 2016 09:42:52 +0000 Subject: [PATCH 288/933] Task #9350: feature branch for story Cleanup and datamanagement -- GitLab From cdd4c64680a12819cdae0d7e1f35eee1e8d81c89 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 8 Jun 2016 10:39:22 +0000 Subject: [PATCH 289/933] Task #9351: initial skeleton for cleanup service, test and cmdline tool --- .gitattributes | 13 ++++ CMake/LofarPackageList.cmake | 7 +- SAS/CMakeLists.txt | 1 + SAS/DataManagement/CMakeLists.txt | 3 + .../CleanupService/CMakeLists.txt | 24 ++++++ SAS/DataManagement/CleanupService/__init__.py | 1 + SAS/DataManagement/CleanupService/cleanup | 10 +++ .../CleanupService/cleanupservice | 10 +++ .../CleanupService/cleanupservice.ini | 8 ++ SAS/DataManagement/CleanupService/config.py | 7 ++ SAS/DataManagement/CleanupService/rpc.py | 38 ++++++++++ SAS/DataManagement/CleanupService/service.py | 74 +++++++++++++++++++ .../CleanupService/test/CMakeLists.txt | 5 ++ .../test/test_cleanup_service_and_rpc.py | 54 ++++++++++++++ .../test/test_cleanup_service_and_rpc.run | 6 ++ .../test/test_cleanup_service_and_rpc.sh | 3 + 16 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 SAS/DataManagement/CMakeLists.txt create mode 100644 SAS/DataManagement/CleanupService/CMakeLists.txt create mode 100644 SAS/DataManagement/CleanupService/__init__.py create mode 100644 SAS/DataManagement/CleanupService/cleanup create mode 100644 SAS/DataManagement/CleanupService/cleanupservice create mode 100644 SAS/DataManagement/CleanupService/cleanupservice.ini create mode 100644 SAS/DataManagement/CleanupService/config.py create mode 100644 SAS/DataManagement/CleanupService/rpc.py create mode 100644 SAS/DataManagement/CleanupService/service.py create mode 100644 SAS/DataManagement/CleanupService/test/CMakeLists.txt create mode 100755 SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py create mode 100755 SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.run create mode 100755 SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.sh diff --git a/.gitattributes b/.gitattributes index 32f2277e1b6..e23842dbd59 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4728,6 +4728,19 @@ SAS/CleanupTool/src/schedulersettings.h -text SAS/CleanupTool/src/schedulesettingsdialog.cpp -text SAS/CleanupTool/src/schedulesettingsdialog.h -text SAS/CleanupTool/src/schedulesettingsdialog.ui -text +SAS/DataManagement/CMakeLists.txt -text +SAS/DataManagement/CleanupService/CMakeLists.txt -text +SAS/DataManagement/CleanupService/__init__.py -text +SAS/DataManagement/CleanupService/cleanup -text +SAS/DataManagement/CleanupService/cleanupservice -text +SAS/DataManagement/CleanupService/cleanupservice.ini -text +SAS/DataManagement/CleanupService/config.py -text +SAS/DataManagement/CleanupService/rpc.py -text +SAS/DataManagement/CleanupService/service.py -text +SAS/DataManagement/CleanupService/test/CMakeLists.txt -text +SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py -text +SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.run -text +SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.sh -text SAS/Feedback_Service/CMakeLists.txt -text SAS/Feedback_Service/package.dox -text SAS/Feedback_Service/src/CMakeLists.txt -text diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index f497ee5e199..6dd5fc4396e 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -1,7 +1,7 @@ # - Create for each LOFAR package a variable containing the absolute path to # its source directory. # -# Generated by gen_LofarPackageList_cmake.sh at ma apr 4 12:02:12 CEST 2016 +# Generated by gen_LofarPackageList_cmake.sh at wo 8 jun 2016 12:02:27 CEST # # ---- DO NOT EDIT ---- # @@ -85,7 +85,6 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(PPSTune_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCU/PPSTune) set(Firmware-Tools_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCU/Firmware/tools) set(MACTools_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCU/StationTest/MACTools) - set(MAC_Services_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Services) set(LTAIngest_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LTA/LTAIngest) set(ltastorageoverview_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LTA/ltastorageoverview) set(APLCommon_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/APL/APLCommon) @@ -122,6 +121,7 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(Deployment_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Deployment) set(Navigator2_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Navigator2) set(MACTools_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Tools) + set(MAC_Services_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Services) set(PVSS_Datapoints_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Deployment/data/PVSS) set(OTDB_Comps_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Deployment/data/OTDB) set(StaticMetaData_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Deployment/data/StaticMetaData) @@ -139,15 +139,16 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(OTDB_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/OTDB) set(OTB_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/OTB) set(OTDB_SQL_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/OTDB/sql) + set(QPIDInfrastructure_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/QPIDInfrastructure) set(Scheduler_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/Scheduler) set(SAS_Feedback_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/Feedback_Service) set(CleanupTool_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/CleanupTool) set(OTDB_Services_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/OTDB_Services) set(XML_generator_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/XML_generator) + set(CleanupService_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/DataManagement/CleanupService) set(MoMQueryService_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/MoM/MoMQueryService) set(jOTDB3_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/OTB/jOTDB3) set(OTB-Java_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/OTB/OTB) - set(QPIDInfrastructure_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/QPIDInfrastructure) set(RATaskSpecifiedService_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/ResourceAssignment/RATaskSpecifiedService) set(RAtoOTDBTaskSpecificationPropagator_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator) set(ResourceAssigner_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/ResourceAssignment/ResourceAssigner) diff --git a/SAS/CMakeLists.txt b/SAS/CMakeLists.txt index a328b52f5d3..979f7c320d7 100644 --- a/SAS/CMakeLists.txt +++ b/SAS/CMakeLists.txt @@ -12,3 +12,4 @@ lofar_add_package(XML_generator) add_subdirectory(MoM) add_subdirectory(ResourceAssignment) +add_subdirectory(DataManagement) diff --git a/SAS/DataManagement/CMakeLists.txt b/SAS/DataManagement/CMakeLists.txt new file mode 100644 index 00000000000..455c2be7bd0 --- /dev/null +++ b/SAS/DataManagement/CMakeLists.txt @@ -0,0 +1,3 @@ +# $Id: CMakeLists.txt 34529 2016-05-24 17:00:41Z mol $ + +lofar_add_package(CleanupService) diff --git a/SAS/DataManagement/CleanupService/CMakeLists.txt b/SAS/DataManagement/CleanupService/CMakeLists.txt new file mode 100644 index 00000000000..2c8116bef9b --- /dev/null +++ b/SAS/DataManagement/CleanupService/CMakeLists.txt @@ -0,0 +1,24 @@ +# $Id$ + +lofar_package(CleanupService 1.0 DEPENDS PyMessaging ResourceAssignmentService MoMQueryService) + +lofar_find_package(Python 2.6 REQUIRED) +include(PythonInstall) + +set(_py_files + __init__.py + config.py + rpc.py + service.py +) + +python_install(${_py_files} DESTINATION lofar/sas/datamanagement/cleanup) + +lofar_add_bin_scripts(cleanup cleanupservice) + +# supervisord config files +install(FILES + cleanupservice.ini + DESTINATION etc/supervisord.d) + +add_subdirectory(test) diff --git a/SAS/DataManagement/CleanupService/__init__.py b/SAS/DataManagement/CleanupService/__init__.py new file mode 100644 index 00000000000..fbbab2d1199 --- /dev/null +++ b/SAS/DataManagement/CleanupService/__init__.py @@ -0,0 +1 @@ +# $Id$ diff --git a/SAS/DataManagement/CleanupService/cleanup b/SAS/DataManagement/CleanupService/cleanup new file mode 100644 index 00000000000..430be29b3b9 --- /dev/null +++ b/SAS/DataManagement/CleanupService/cleanup @@ -0,0 +1,10 @@ +#!/usr/bin/python +# $Id: radbclient 33373 2016-01-22 11:01:15Z schaap $ + +''' +do cleanup actions on cep4 from the commandline +''' + +if __name__ == '__main__': + from lofar.sas.datamanagement.cleanup.rpc import main + main() diff --git a/SAS/DataManagement/CleanupService/cleanupservice b/SAS/DataManagement/CleanupService/cleanupservice new file mode 100644 index 00000000000..82c8f4c2e43 --- /dev/null +++ b/SAS/DataManagement/CleanupService/cleanupservice @@ -0,0 +1,10 @@ +#!/usr/bin/python +# $Id: radbservice 33373 2016-01-22 11:01:15Z schaap $ + +''' +runs the cleanup service +''' + +if __name__ == '__main__': + from lofar.sas.datamanagement.cleanup.service import main + main() diff --git a/SAS/DataManagement/CleanupService/cleanupservice.ini b/SAS/DataManagement/CleanupService/cleanupservice.ini new file mode 100644 index 00000000000..95b1a0b7521 --- /dev/null +++ b/SAS/DataManagement/CleanupService/cleanupservice.ini @@ -0,0 +1,8 @@ +[program:radbservice] +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec radbservice --log-queries' +user=lofarsys +stopsignal=INT ; KeyboardInterrupt +stopasgroup=true ; bash does not propagate signals +stdout_logfile=%(program_name)s.log +redirect_stderr=true +stderr_logfile=NONE diff --git a/SAS/DataManagement/CleanupService/config.py b/SAS/DataManagement/CleanupService/config.py new file mode 100644 index 00000000000..0e8dafdf6f6 --- /dev/null +++ b/SAS/DataManagement/CleanupService/config.py @@ -0,0 +1,7 @@ +#!/usr/bin/python +# $Id$ + +from lofar.messaging import adaptNameToEnvironment + +DEFAULT_BUSNAME = adaptNameToEnvironment('lofar.dm.command') +DEFAULT_SERVICENAME = 'CleanupService' diff --git a/SAS/DataManagement/CleanupService/rpc.py b/SAS/DataManagement/CleanupService/rpc.py new file mode 100644 index 00000000000..369c01cf4ec --- /dev/null +++ b/SAS/DataManagement/CleanupService/rpc.py @@ -0,0 +1,38 @@ +#!/usr/bin/python + +import logging +from lofar.messaging.RPC import RPC, RPCException, RPCWrapper +from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME + +logger = logging.getLogger(__name__) + +class CleanupRPC(RPCWrapper): + def __init__(self, busname=DEFAULT_BUSNAME, + servicename=DEFAULT_SERVICENAME, + broker=None): + super(CleanupRPC, self).__init__(busname, servicename, broker) + + def foo(self): + return self.rpc('foo') + +def main(): + import sys + from optparse import OptionParser + + # Check the invocation arguments + parser = OptionParser('%prog [options]', + description='do cleanup actions on cep4 from the commandline') + parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') + parser.add_option('-b', '--busname', dest='busname', type='string', default=DEFAULT_BUSNAME, help='Name of the bus exchange on the qpid broker, default: %s' % DEFAULT_BUSNAME) + parser.add_option('-s', '--servicename', dest='servicename', type='string', default=DEFAULT_SERVICENAME, help='Name for this service, default: %s' % DEFAULT_SERVICENAME) + parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') + (options, args) = parser.parse_args() + + if len(sys.argv) == 1: + parser.print_help() + + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.INFO if options.verbose else logging.WARN) + + with CleanupRPC(busname=options.busname, servicename=options.servicename, broker=options.broker) as rpc: + print rpc.foo() diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py new file mode 100644 index 00000000000..87b21b9bc4c --- /dev/null +++ b/SAS/DataManagement/CleanupService/service.py @@ -0,0 +1,74 @@ +#!/usr/bin/python +# $Id$ + +''' +Simple Service listening on momqueryservice.GetProjectDetails +which gives the project details for each requested mom object id + +Example usage: +service side: just run this service somewhere where it can access the momdb and +a qpid broker. +Make sure the bus exists: qpid-config add exchange topic <busname> + +client side: do a RPC call to the <busname>.GetProjectDetails with a +comma seperated string of mom2object id's as argument. +You get a dict of mom2id to project-details-dict back. + +with RPC(busname, 'GetProjectDetails') as getProjectDetails: + res, status = getProjectDetails(ids_string) + +''' +import logging +from optparse import OptionParser +from lofar.messaging import Service +from lofar.messaging import setQpidLogLevel +from lofar.messaging.Service import MessageHandlerInterface +from lofar.common.util import waitForInterrupt +from lofar.common.util import convertIntKeysToString +from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME + +logger = logging.getLogger(__name__) + +class CleanupHandler(MessageHandlerInterface): + def __init__(self, **kwargs): + super(CleanupHandler, self).__init__(**kwargs) + + self.service2MethodMap = {'foo': self._foo} + + def prepare_loop(self): + pass + + def _foo(self): + return 'foo' + +def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, verbose=False): + return Service(servicename, + CleanupHandler, + busname=busname, + broker=broker, + use_service_methods=True, + numthreads=2, + verbose=verbose) + +def main(): + # Check the invocation arguments + parser = OptionParser("%prog [options]", + description='runs the resourceassignment database service') + parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') + parser.add_option("-b", "--busname", dest="busname", type="string", default=DEFAULT_BUSNAME, help="Name of the bus exchange on the qpid broker, default: %s" % DEFAULT_BUSNAME) + parser.add_option("-s", "--servicename", dest="servicename", type="string", default=DEFAULT_SERVICENAME, help="Name for this service, default: %s" % DEFAULT_SERVICENAME) + parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') + (options, args) = parser.parse_args() + + setQpidLogLevel(logging.INFO) + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.DEBUG if options.verbose else logging.INFO) + + with createService(busname=options.busname, + servicename=options.servicename, + broker=options.broker, + verbose=options.verbose): + waitForInterrupt() + +if __name__ == '__main__': + main() diff --git a/SAS/DataManagement/CleanupService/test/CMakeLists.txt b/SAS/DataManagement/CleanupService/test/CMakeLists.txt new file mode 100644 index 00000000000..70fad498998 --- /dev/null +++ b/SAS/DataManagement/CleanupService/test/CMakeLists.txt @@ -0,0 +1,5 @@ +# $Id$ +include(LofarCTest) + +lofar_add_test(test_cleanup_service_and_rpc) + diff --git a/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py new file mode 100755 index 00000000000..3d6f441b0ce --- /dev/null +++ b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +import unittest +import uuid +import datetime +import logging +from lofar.messaging import Service +from lofar.sas.datamanagement.cleanup.service import createService +from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC +from qpid.messaging.exceptions import * + +try: + from qpid.messaging import Connection + from qpidtoollibs import BrokerAgent +except ImportError: + print 'Cannot run test without qpid tools' + print 'Please source qpid profile' + exit(3) + +connection = None +broker = None + +try: + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO) + logger = logging.getLogger(__name__) + + # setup broker connection + connection = Connection.establish('127.0.0.1') + broker = BrokerAgent(connection) + + # add test service busname + busname = 'test-lofarbus-%s' % (uuid.uuid1()) + broker.addExchange('topic', busname) + + class TestCleanupServiceAndRPC(unittest.TestCase): + def test(self): + '''basic test ''' + rpc = CleanupRPC(busname=busname) + self.assertEqual('foo', rpc.foo()) + + # create and run the service + with createService(busname=busname): + # and run all tests + unittest.main() + +except ConnectError as ce: + logger.error(ce) + exit(3) +finally: + # cleanup test bus and exit + if broker: + broker.delExchange(busname) + if connection: + connection.close() diff --git a/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.run b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.run new file mode 100755 index 00000000000..0f7141c5fce --- /dev/null +++ b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.run @@ -0,0 +1,6 @@ +#!/bin/bash + +# Run the unit test +source python-coverage.sh +python_coverage_test "cleanup*" test_cleanup_service_and_rpc.py + diff --git a/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.sh b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.sh new file mode 100755 index 00000000000..8994e8ed77a --- /dev/null +++ b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +./runctest.sh test_cleanup_service_and_rpc -- GitLab From 1e364366d540d6530acc84785dd888b91b8d8f5d Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Wed, 8 Jun 2016 11:47:57 +0000 Subject: [PATCH 290/933] Task 8256: Modified tgetparsetvalue so that it works when getparsetvalue is in ../src as well as when it is in ${CMAKE_BINARY_DIR}/bin --- LCS/Common/test/tgetparsetvalue.run | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/LCS/Common/test/tgetparsetvalue.run b/LCS/Common/test/tgetparsetvalue.run index af7baec8378..0a535cae12b 100755 --- a/LCS/Common/test/tgetparsetvalue.run +++ b/LCS/Common/test/tgetparsetvalue.run @@ -1,15 +1,17 @@ #!/bin/sh -../src/getparsetvalue tgetparsetvalue.parset key1 -../src/getparsetvalue tgetparsetvalue.parset key1 0 -../src/getparsetvalue tgetparsetvalue.parset key1 1 -../src/getparsetvalue tgetparsetvalue.parset key1 2 -../src/getparsetvalue tgetparsetvalue.parset key1 3 -../src/getparsetvalue tgetparsetvalue.parset key1 -1 -../src/getparsetvalue tgetparsetvalue.parset key1 -2 -../src/getparsetvalue tgetparsetvalue.parset key1 -3 -../src/getparsetvalue tgetparsetvalue.parset key1 -4 -../src/getparsetvalue tgetparsetvalue.parset key1.sub1 +PATH=$(cd ../src && pwd):$PATH -../src/getparsetvalue tgetparsetvalue.parset key0 -../src/getparsetvalue -d abc -d efgh tgetparsetvalue.parset key0 +getparsetvalue tgetparsetvalue.parset key1 +getparsetvalue tgetparsetvalue.parset key1 0 +getparsetvalue tgetparsetvalue.parset key1 1 +getparsetvalue tgetparsetvalue.parset key1 2 +getparsetvalue tgetparsetvalue.parset key1 3 +getparsetvalue tgetparsetvalue.parset key1 -1 +getparsetvalue tgetparsetvalue.parset key1 -2 +getparsetvalue tgetparsetvalue.parset key1 -3 +getparsetvalue tgetparsetvalue.parset key1 -4 +getparsetvalue tgetparsetvalue.parset key1.sub1 + +getparsetvalue tgetparsetvalue.parset key0 +getparsetvalue -d abc -d efgh tgetparsetvalue.parset key0 -- GitLab From 6cb8e8ad0e6ad21ac0dfa21ca7aef7605cc63e32 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Wed, 8 Jun 2016 12:47:36 +0000 Subject: [PATCH 291/933] Task #9192: added skip for supporting pipelines --- .../lib/RATaskSpecified.py | 23 +++++++++++++++---- .../lib/propagator.py | 2 +- .../lib/translator.py | 23 +++++++++++++++++-- .../calibration_pipeline.py | 8 +++---- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py b/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py index 863d3ea40d8..40047d15f4c 100755 --- a/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py +++ b/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py @@ -119,6 +119,7 @@ def resourceIndicatorsFromParset( parsetDict ): # ===================================== add("Observation.DataProducts.Output_Correlated.enabled", bool) add("Observation.DataProducts.Output_Correlated.storageClusterName") + add("Observation.DataProducts.Output_Correlated.identifications", strvector) add("Observation.ObservationControl.OnlineControl.Cobalt.Correlator.integrationTime") add("Observation.ObservationControl.OnlineControl.Cobalt.Correlator.nrChannelsPerSubband") # TODO: We need a service that computes these 3 values @@ -132,8 +133,10 @@ def resourceIndicatorsFromParset( parsetDict ): # ===================================== add("Observation.DataProducts.Output_IncoherentStokes.enabled", bool) add("Observation.DataProducts.Output_IncoherentStokes.storageClusterName") + add("Observation.DataProducts.Output_IncoherentStokes.identifications", strvector) add("Observation.DataProducts.Output_CoherentStokes.enabled", bool) add("Observation.DataProducts.Output_CoherentStokes.storageClusterName") + add("Observation.DataProducts.Output_CoherentStokes.identifications", strvector) add("Observation.ObservationControl.OnlineControl.Cobalt.BeamFormer.flysEye", bool) #add("Observation.ObservationControl.OnlineControl.Cobalt.BeamFormer.CoherentStokes.nrChannelsPerSubband") # only needed to determine Cobalt.blockSize add("Observation.ObservationControl.OnlineControl.Cobalt.BeamFormer.CoherentStokes.subbandsPerFile") @@ -157,16 +160,23 @@ def resourceIndicatorsFromParset( parsetDict ): # Calibrator / Averaging pipelines add("Observation.DataProducts.Output_Correlated.enabled", bool) add("Observation.DataProducts.Output_Correlated.storageClusterName") + add("Observation.DataProducts.Output_Correlated.identifications", strvector) add("Observation.DataProducts.Output_InstrumentModel.enabled", bool) add("Observation.DataProducts.Output_InstrumentModel.storageClusterName") + add("Observation.DataProducts.Output_InstrumentModel.identifications", strvector) add("Observation.DataProducts.Input_Correlated.enabled", bool) - add("Observation.DataProducts.Input_Correlated.skip", intvector) - add("Observation.ObservationControl.PythonControl.DPPP.demixer.demixfreqstep") - add("Observation.ObservationControl.PythonControl.DPPP.demixer.demixtimestep") + add("Observation.DataProducts.Input_Correlated.identifications", strvector) + #We don't care about skip add("Observation.DataProducts.Input_Correlated.skip", intvector) + add("Observation.DataProducts.Input_InstrumentModel.enabled", bool) + add("Observation.DataProducts.Input_InstrumentModel.identifications", strvector) + #We don't care about skip add("Observation.DataProducts.Input_Correlated.skip", intvector) + add("Observation.ObservationControl.PythonControl.DPPP.demixer.freqstep") + add("Observation.ObservationControl.PythonControl.DPPP.demixer.timestep") # Imaging pipeline add("Observation.DataProducts.Output_SkyImage.enabled", bool) add("Observation.DataProducts.Output_SkyImage.storageClusterName") + add("Observation.DataProducts.Output_SkyImage.identifications", strvector) add("Observation.ObservationControl.PythonControl.Imaging.slices_per_image") add("Observation.ObservationControl.PythonControl.Imaging.subbands_per_image") @@ -177,10 +187,13 @@ def resourceIndicatorsFromParset( parsetDict ): # Pulsar pipeline add("Observation.DataProducts.Output_Pulsar.enabled", bool) add("Observation.DataProducts.Output_Pulsar.storageClusterName") + add("Observation.DataProducts.Output_Pulsar.identifications", strvector) add("Observation.DataProducts.Input_CoherentStokes.enabled", bool) - add("Observation.DataProducts.Input_CoherentStokes.skip", intvector) + add("Observation.DataProducts.Input_CoherentStokes.identifications", strvector) + #We don't care about skip add("Observation.DataProducts.Input_CoherentStokes.skip", intvector) add("Observation.DataProducts.Input_IncoherentStokes.enabled", bool) - add("Observation.DataProducts.Input_IncoherentStokes.skip", intvector) + add("Observation.DataProducts.Input_IncoherentStokes.identifications", strvector) + #We don't care about skip add("Observation.DataProducts.Input_IncoherentStokes.skip", intvector) return subset diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py index f004949d510..cf44678c2ff 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py @@ -133,7 +133,7 @@ class RAtoOTDBPropagator(): project_name = "_".join(project[str(mom_id)]['project_name'].split()) except (RPCException, KeyError) as e: logger.error('Could not get project name from MoM for mom_id %s: %s' % (mom_id, str(e))) - logger.info('Using \'unknown\' as project name.') + logger.info("Using 'unknown' as project name.") project_name = 'unknown' otdb_info = self.translator.CreateParset(otdb_id, ra_info, project_name) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 2553dd80787..1d617569ba0 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -63,27 +63,32 @@ class RAtoOTDBTranslator(): sb_nr = 0 locations = [] filenames = [] + skip = [] result = {} - if 'saps' in storage_properties: ## It's a pipeline (no SAPs) + if 'saps' in storage_properties: ## It's an observation for sap in storage_properties["saps"]: ##We might need to sort saps? logging.debug('processing sap: %s' % sap) if "nr_of_uv_files" in sap['properties']: for _ in xrange(sap['properties']['nr_of_uv_files']): locations.append(self.locationPath(project_name, otdb_id)) filenames.append("L%d_SAP%03d_SB%03d_uv.MS" % (otdb_id, sap['sap_nr'], sb_nr)) + skip.append("0") sb_nr += 1 - else: + else: ## It's a pipeline (no SAPs) for _ in xrange(storage_properties['nr_of_uv_files']): locations.append(self.locationPath(project_name, otdb_id)) filenames.append("L%d_SB%03d_uv.MS" % (otdb_id, sb_nr)) + skip.append("0") sb_nr += 1 result[PREFIX + 'DataProducts.Output_Correlated.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_Correlated.filenames'] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.Output_Correlated.skip'] = '[' + to_csv_string(skip) + ']' return result def CreateCoherentStokes(self, otdb_id, storage_properties, project_name): locations = [] filenames = [] + skip = [] result = {} nr_stokes = storage_properties['nr_of_cs_stokes'] for sap in storage_properties["saps"]: ##We might need to sort saps? @@ -96,13 +101,16 @@ class RAtoOTDBTranslator(): for part in xrange(nr_parts): locations.append(self.locationPath(project_name, otdb_id)) filenames.append("L%d_SAP%03d_B%03d_S%d_P%03d_bf.h5" % (otdb_id, sap['sap_nr'], tab, stokes, part)) + skip.append("0") result[PREFIX + 'DataProducts.Output_CoherentStokes.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_CoherentStokes.filenames'] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.Output_CoherentStokes.skip'] = '[' + to_csv_string(skip) + ']' return result def CreateIncoherentStokes(self, otdb_id, storage_properties, project_name): locations = [] filenames = [] + skip = [] result = {} nr_stokes = storage_properties['nr_of_is_stokes'] for sap in storage_properties["saps"]: ##We might need to sort saps? @@ -115,47 +123,58 @@ class RAtoOTDBTranslator(): for part in xrange(nr_parts): locations.append(self.locationPath(project_name, otdb_id)) filenames.append("L%d_SAP%03d_B%03d_S%d_P%03d_bf.h5" % (otdb_id, sap['sap_nr'], tab, stokes, part)) + skip.append("0") result[PREFIX + 'DataProducts.Output_IncoherentStokes.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_IncoherentStokes.filenames'] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.Output_IncoherentStokes.skip'] = '[' + to_csv_string(skip) + ']' return result def CreateInstrumentModel(self, otdb_id, storage_properties, project_name): sb_nr = 0 locations = [] filenames = [] + skip = [] result = {} for _ in xrange(storage_properties['nr_of_im_files']): locations.append(self.locationPath(project_name, otdb_id)) filenames.append("L%d_SB%03d_inst.INST" % (otdb_id, sb_nr)) + skip.append("0") sb_nr += 1 result[PREFIX + 'DataProducts.Output_InstrumentModel.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_InstrumentModel.filenames'] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.Output_InstrumentModel.skip'] = '[' + to_csv_string(skip) + ']' return result def CreateSkyImage(self, otdb_id, storage_properties, project_name): sbg_nr = 0 locations = [] filenames = [] + skip = [] result = {} for _ in xrange(storage_properties['nr_of_img_files']): locations.append(self.locationPath(project_name, otdb_id)) filenames.append("L%d_SBG%03d_sky.IM" % (otdb_id, sbg_nr)) + skip.append("0") sbg_nr += 1 result[PREFIX + 'DataProducts.Output_SkyImage.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_SkyImage.filenames'] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.Output_SkyImage.skip'] = '[' + to_csv_string(skip) + ']' return result def CreatePulsarPipeline(self, otdb_id, storage_properties, project_name): p_nr = 0 locations = [] filenames = [] + skip = [] result = {} for _ in range(storage_properties['nr_of_pulp_files']): locations.append(self.locationPath(project_name, otdb_id)) filenames.append("L%d_P%03d_pulp.tgz" % (otdb_id, p_nr)) + skip.append("0") p_nr += 1 result[PREFIX + 'DataProducts.Output_Pulsar.locations'] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.Output_Pulsar.filenames'] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.Output_Pulsar.skip'] = '[' + to_csv_string(skip) + ']' return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py index bd3b060c5c1..a0a6e481ec8 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py @@ -42,8 +42,8 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): DATAPRODUCTS + 'Input_Correlated.enabled', DATAPRODUCTS + 'Output_InstrumentModel.enabled', DATAPRODUCTS + 'Output_Correlated.enabled', - PIPELINE + 'DPPP.demixer.demixfreqstep', - PIPELINE + 'DPPP.demixer.demixtimestep') + PIPELINE + 'DPPP.demixer.freqstep', + PIPELINE + 'DPPP.demixer.timestep') def _calculate(self, parset, input_files): """ Estimate for CalibrationPipeline. Also gets used for AveragingPipeline @@ -64,8 +64,8 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): logger.info('parset: %s ' % parset) result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} duration = self._getDuration(parset.getString('Observation.startTime'), parset.getString('Observation.stopTime')) - freq_step = parset.getInt(PIPELINE + 'DPPP.demixer.demixfreqstep', 1) #TODO, should these have defaults? - time_step = parset.getInt(PIPELINE + 'DPPP.demixer.demixtimestep', 1) + freq_step = parset.getInt(PIPELINE + 'DPPP.demixer.freqstep', 1) #TODO, should these have defaults? + time_step = parset.getInt(PIPELINE + 'DPPP.demixer.timestep', 1) reduction_factor = freq_step * time_step if not parset.getBool(DATAPRODUCTS + 'Output_Correlated.enabled'): -- GitLab From 7ac0081f34dba746cad74a4cfb3cdf60f8c2d401 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 8 Jun 2016 13:30:09 +0000 Subject: [PATCH 292/933] Task #8887: added io_type for claim_properties --- .../ResourceAssignmentDatabase/radb.py | 182 ++++++++---------- .../sql/add_resource_allocation_statics.sql | 1 + .../sql/create_database.sql | 20 +- 3 files changed, 100 insertions(+), 103 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index d11e2c17d7e..908d6a59b4d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -636,8 +636,26 @@ class RADatabase: raise KeyError('No such resource_claim_property_type: %s Valid values are: %s' % (type_name, ', '.join(self.getResourceClaimPropertyTypeNames()))) + def getResourceClaimPropertyIOTypes(self): + query = '''SELECT * from resource_allocation.resource_claim_property_io_type;''' + + return list(self._executeQuery(query, fetch=_FETCH_ALL)) + + def getResourceClaimPropertyIOTypeNames(self): + return [x['name'] for x in self.getResourceClaimPropertyIOTypes()] + + def getResourceClaimPropertyIOTypeId(self, io_type_name): + query = '''SELECT id from resource_allocation.resource_claim_property_io_type + WHERE name = %s;''' + result = self._executeQuery(query, [io_type_name], fetch=_FETCH_ONE) + + if result: + return result['id'] + + raise KeyError('No such resource_claim_property_io_type: %s Valid values are: %s' % (io_type_name, ', '.join(self.getResourceClaimPropertyIOTypeNames()))) + def getResourceClaimProperties(self, claim_ids=None, task_id=None): - query = '''SELECT rcpv.id, rcpv.resource_claim_id, rcpv.value, rcpv.type_id, rcpv.type_name, sap.number as sap_nr + query = '''SELECT rcpv.id, rcpv.resource_claim_id, rcpv.value, rcpv.type_id, rcpv.type_name, rcpv.io_type_id, rcpv.io_type_name, sap.number as sap_nr FROM resource_allocation.resource_claim_property_view rcpv LEFT JOIN resource_allocation.sap sap on rcpv.sap_id = sap.id''' @@ -667,15 +685,19 @@ class RADatabase: return properties - def insertResourceClaimProperty(self, claim_id, property_type, value, commit=True): - return self.insertResourceClaimProperties([(claim_id, property_type, value)], commit) + def insertResourceClaimProperty(self, claim_id, property_type, value, io_type, commit=True): + return self.insertResourceClaimProperties([(claim_id, property_type, value, io_type)], commit) def insertResourceClaimProperties(self, props, commit=True): if not props: return [] + # props is a list of tuples + # each tuple prop is encoded as: (claim_id, type, value, io_type, sap_nr) + # index: (0 , 1 , 2 , 3 , 4 ) + # first insert unique sap numbers - claim_sap_nrs = list(set([(p[0], p[3]) for p in props if p[3] is not None])) + claim_sap_nrs = list(set([(p[0], p[4]) for p in props if p[4] is not None])) sap_ids = self.insertSAPNumbers(claim_sap_nrs, False) if sap_ids == None: @@ -696,19 +718,25 @@ class RADatabase: type_strings = set([p[1] for p in props if isinstance(p[1], basestring)]) type_string2id = {t:self.getResourceClaimPropertyTypeId(t) for t in type_strings} + # convert all property io_type strings to id's + io_type_strings = set([p[3] for p in props if isinstance(p[3], basestring)]) + io_type_string2id = {t:self.getResourceClaimPropertyIOTypeId(t) for t in io_type_strings} + # finally we have all the info we need, # so we can build the bulk property insert query - insert_values = ','.join(self.cursor.mogrify('(%s, %s, %s, %s)', + insert_values = ','.join(self.cursor.mogrify('(%s, %s, %s, %s, %s)', (p[0], type_string2id[p[1]] if isinstance(p[1], basestring) else p[1], p[2], - claim_id2sap_nr2sap_id[p[0]].get(p[3]) if + io_type_string2id[p[3]] if + isinstance(p[3], basestring) else p[3], + claim_id2sap_nr2sap_id[p[0]].get(p[4]) if p[0] in claim_id2sap_nr2sap_id else None)) for p in props) query = '''INSERT INTO resource_allocation.resource_claim_property - (resource_claim_id, type_id, value, sap_id) + (resource_claim_id, type_id, value, io_type_id, sap_id) VALUES {values} RETURNING id;'''.format(values=insert_values) @@ -929,7 +957,7 @@ class RADatabase: properties = [] for claim_id, claim in zip(claimIds, claims): if 'properties' in claim and len(claim['properties']) > 0: - claim_props = [(claim_id, p['type'], p['value'], p.get('sap_nr')) for p in claim['properties']] + claim_props = [(claim_id, p['type'], p['value'], p.get('io_type', 0), p.get('sap_nr')) for p in claim['properties']] properties += claim_props if properties: @@ -1396,25 +1424,6 @@ if __name__ == '__main__': print '\n-- ' + str(method.__name__) + ' --' print '\n'.join([str(x) for x in method()]) - resultPrint(db.getTasks) - - for t in db.getTasks(): - print db.getTask(t['id']) - - exit() - - #print db.getResourceClaims(task_id=440) - #print - #print db.getResourceClaims(lower_bound=datetime.utcnow() + timedelta(days=9)) - #print - #print db.getResourceClaims(upper_bound=datetime.utcnow() + timedelta(days=19)) - #print - #print db.getResourceClaims(status='allocated') - #print - #print db.getResourceClaims(status='claimed') - #print - #print db.getResourceClaims(resource_type='storage') - #resultPrint(db.getTaskStatuses) #resultPrint(db.getTaskStatusNames) #resultPrint(db.getTaskTypes) @@ -1435,82 +1444,57 @@ if __name__ == '__main__': #print db.getTaskSuccessorIds() #resultPrint(db.getSpecifications) #resultPrint(db.getResourceClaims) - - #claims = db.getResourceClaims() - #db.updateTaskAndResourceClaims(claims[0]['task_id'], starttime=claims[1]['starttime'], endtime=claims[1]['endtime']) - #print - #print db.getResourceClaims() - - #resultPrint(db.getResourceClaims) - - - db.updateResourceAvailability(0, available_capacity=2) - exit(0) - - import pprint - pprint.pprint(db.getTaskConflictReasons()) - db.updateTask(21, task_status='conflict') - db.insertTaskConflicts(21, [1, 2, 3]) - pprint.pprint(db.getTaskConflictReasons()) - db.updateTask(21, task_status='scheduled') - pprint.pprint(db.getTaskConflictReasons()) - db.insertTaskConflicts(21, [1, 2, 3]) - pprint.pprint(db.getTaskConflictReasons()) - - pprint.pprint(db.getResourceClaimConflictReasons(task_ids=22)) - #pprint.pprint(db.getResourceUsages()) - - exit(0) - - for s in db.getSpecifications(): - db.deleteSpecification(s['id']) + resultPrint(db.getResourceClaimProperties) resources = db.getResources() - #task_id = db.insertSpecificationAndTask(1234, 5678, 600, 0, datetime.utcnow(), datetime.utcnow() + timedelta(hours=1), "", False)['task_id'] - #task = db.getTask(task_id) - - #claim = {'resource_id':resources[0]['id'], - #'starttime':task['starttime'], - #'endtime':task['endtime'], - #'status':'claimed', - #'claim_size':1} - #db.insertResourceClaims(task_id, [claim], 1, 'anonymous', -1, False) - - #claim = {'resource_id':resources[1]['id'], - #'starttime':task['starttime'], - #'endtime':task['endtime'], - #'status':'claimed', - #'claim_size':1, - #'properties':[{'type':'nr_of_is_files', 'value':10},{'type':'nr_of_cs_files', 'value':20}]} - #db.insertResourceClaims(task_id, [claim], 1, 'anonymous', -1, False) - - #claim = {'resource_id':resources[2]['id'], - #'starttime':task['starttime'], - #'endtime':task['endtime'], - #'status':'claimed', - #'claim_size':1, - #'properties':[{'type':'nr_of_is_files', 'value':10, 'sap_nr':0 }, - #{'type':'nr_of_cs_files', 'value':20, 'sap_nr':0}, - #{'type':'nr_of_uv_files', 'value':30, 'sap_nr':1},]} - #db.insertResourceClaims(task_id, [claim], 1, 'anonymous', -1, False) - - #claim = {'resource_id':resources[3]['id'], - #'starttime':task['starttime'], - #'endtime':task['endtime'], - #'status':'claimed', - #'claim_size':1, - #'properties':[{'type':'nr_of_is_files', 'value':15 }, - #{'type':'nr_of_cs_files', 'value':25 }, - #{'type':'nr_of_is_files', 'value':10, 'sap_nr':0 }, - #{'type':'nr_of_cs_files', 'value':20, 'sap_nr':0}, - #{'type':'nr_of_uv_files', 'value':30, 'sap_nr':1},]} - #db.insertResourceClaims(task_id, [claim], 1, 'anonymous', -1, False) + task_id = db.insertSpecificationAndTask(1234, 5678, 600, 0, datetime.utcnow(), datetime.utcnow() + timedelta(hours=1), "", False)['task_id'] + task = db.getTask(task_id) + + claim = {'resource_id':resources[0]['id'], + 'starttime':task['starttime'], + 'endtime':task['endtime'], + 'status':'claimed', + 'claim_size':1} + db.insertResourceClaims(task_id, [claim], 1, 'anonymous', -1, False) + + claim = {'resource_id':resources[1]['id'], + 'starttime':task['starttime'], + 'endtime':task['endtime'], + 'status':'claimed', + 'claim_size':1, + 'properties':[{'type':'nr_of_is_files', 'value':10},{'type':'nr_of_cs_files', 'value':20}]} + db.insertResourceClaims(task_id, [claim], 1, 'anonymous', -1, False) + + claim = {'resource_id':resources[2]['id'], + 'starttime':task['starttime'], + 'endtime':task['endtime'], + 'status':'claimed', + 'claim_size':1, + 'properties':[{'type':'nr_of_is_files', 'value':10, 'sap_nr':0 }, + {'type':'nr_of_cs_files', 'value':20, 'sap_nr':0}, + {'type':'nr_of_uv_files', 'value':30, 'sap_nr':1},]} + db.insertResourceClaims(task_id, [claim], 1, 'anonymous', -1, False) + + claim = {'resource_id':resources[3]['id'], + 'starttime':task['starttime'], + 'endtime':task['endtime'], + 'status':'claimed', + 'claim_size':1, + 'properties':[{'type':'nr_of_is_files', 'value':15 }, + {'type':'nr_of_cs_files', 'value':25 }, + {'type':'nr_of_is_files', 'value':10, 'sap_nr':0 }, + {'type':'nr_of_cs_files', 'value':20, 'sap_nr':0}, + {'type':'nr_of_uv_files', 'value':30, 'sap_nr':1},]} + db.insertResourceClaims(task_id, [claim], 1, 'anonymous', -1, False) + + db.commit() + import pprint + pprint.pprint(db.getResourceClaims(include_properties=True)) + print '\n'.join(str(x) for x in db.getResourceClaims(include_properties=True)) - #db.commit() - #import pprint - #pprint.pprint(db.getResourceClaims(include_properties=True)) - #print '\n'.join(str(x) for x in db.getResourceClaims(include_properties=True)) + db.deleteSpecification(task['specification_id']) + exit() #c = db.cursor diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql index 47919fcdfa3..8c2ba9cfcaf 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql @@ -8,6 +8,7 @@ INSERT INTO resource_allocation.task_status VALUES (200, 'prepared'), (300, 'app INSERT INTO resource_allocation.task_type VALUES (0, 'observation'),(1, 'pipeline'); -- We'll need more types INSERT INTO resource_allocation.resource_claim_status VALUES (0, 'claimed'), (1, 'allocated'), (2, 'conflict'); INSERT INTO resource_allocation.resource_claim_property_type VALUES (0, 'nr_of_is_files'),(1, 'nr_of_cs_files'),(2, 'nr_of_uv_files'),(3, 'nr_of_im_files'),(4, 'nr_of_img_files'),(5, 'nr_of_pulp_files'),(6, 'nr_of_cs_stokes'),(7, 'nr_of_is_stokes'),(8, 'is_file_size'),(9, 'cs_file_size'),(10, 'uv_file_size'),(11, 'im_file_size'),(12, 'img_file_size'),(13, 'nr_of_pulp_files'),(14, 'nr_of_tabs'); +INSERT INTO resource_allocation.resource_claim_property_io_type VALUES (0, 'output'),(1, 'input'); INSERT INTO resource_allocation.config VALUES (0, 'max_fill_percentage_cep4', '85.00'), (1, 'claim_timeout', '172800'), (2, 'min_inter_task_delay', '60'); -- Just some values 172800 is two days in seconds INSERT INTO resource_allocation.conflict_reason VALUES diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql index 5002a2e19c1..be6f730c23a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql @@ -31,6 +31,7 @@ DROP TABLE IF EXISTS resource_monitoring.resource_availability CASCADE; DROP TABLE IF EXISTS resource_monitoring.resource_capacity CASCADE; DROP TABLE IF EXISTS resource_allocation.resource_claim_property CASCADE; DROP TABLE IF EXISTS resource_allocation.resource_claim_property_type CASCADE; +DROP TABLE IF EXISTS resource_allocation.resource_claim_property_io_type CASCADE; DROP TABLE IF EXISTS resource_allocation.sap CASCADE; DROP TABLE IF EXISTS resource_allocation.conflict_reason CASCADE; DROP TABLE IF EXISTS resource_allocation.resource_claim_conflict_reason CASCADE; @@ -242,11 +243,20 @@ CREATE TABLE resource_allocation.resource_claim_property_type ( ALTER TABLE resource_allocation.resource_claim_property_type OWNER TO resourceassignment; +CREATE TABLE resource_allocation.resource_claim_property_io_type ( + id serial NOT NULL, + name text NOT NULL, + PRIMARY KEY (id) +) WITH (OIDS=FALSE); +ALTER TABLE resource_allocation.resource_claim_property_io_type + OWNER TO resourceassignment; + CREATE TABLE resource_allocation.resource_claim_property ( id serial NOT NULL, resource_claim_id integer NOT NULL REFERENCES resource_allocation.resource_claim ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE, sap_id integer REFERENCES resource_allocation.sap ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE, type_id integer NOT NULL REFERENCES resource_allocation.resource_claim_property_type DEFERRABLE INITIALLY IMMEDIATE, + io_type_id integer NOT NULL DEFAULT 0 REFERENCES resource_allocation.resource_claim_property_io_type DEFERRABLE INITIALLY IMMEDIATE, value bigint NOT NULL DEFAULT 1, PRIMARY KEY (id) ) WITH (OIDS=FALSE); @@ -345,14 +355,16 @@ COMMENT ON VIEW resource_allocation.resource_claim_extended_view IS 'extended view on resource_claim table, including resource_claim_status.name and the resource itself'; CREATE OR REPLACE VIEW resource_allocation.resource_claim_property_view AS - SELECT rcp.id, rcp.resource_claim_id, rcp.value, rcp.type_id, - rcpt.name AS type_name, rcp.sap_id + SELECT rcp.id, rcp.resource_claim_id, rcp.value, rcp.sap_id, + rcp.type_id, rcpt.name AS type_name, + rcp.io_type_id, rcpiot.name AS io_type_name FROM resource_allocation.resource_claim_property rcp - JOIN resource_allocation.resource_claim_property_type rcpt ON rcpt.id = rcp.type_id; + JOIN resource_allocation.resource_claim_property_type rcpt ON rcpt.id = rcp.type_id + JOIN resource_allocation.resource_claim_property_io_type rcpiot ON rcpiot.id = rcp.io_type_id; ALTER VIEW resource_allocation.resource_claim_property_view OWNER TO resourceassignment; COMMENT ON VIEW resource_allocation.resource_claim_property_view - IS 'plain view on resource_claim_property table, including resource_claim_property_type.name'; + IS 'plain view on resource_claim_property table, including resource_claim_property_type.name and resource_claim_property_io_type.name'; CREATE OR REPLACE VIEW resource_monitoring.resource_view AS SELECT rv.*, -- GitLab From 7735edd7cf3bf106e64c358649df824ca817dca9 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 8 Jun 2016 13:31:25 +0000 Subject: [PATCH 293/933] Task #8887: sane defaults for tasks (pipelines) with no start/end-time --- .../ResourceAssigner/lib/assignment.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index c5df521642a..a61d53628d3 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -128,6 +128,18 @@ class ResourceAssigner(): endTime = datetime.strptime(mainParset.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') except ValueError: logger.warning('cannot parse for start/end time from specification for otdb_id=%s', (otdb_id, )) + maxPredecessorEndTime = self.getMaxPredecessorEndTime(specification_tree) + + if maxPredecessorEndTime: + startTime = maxPredecessorEndTime + timedelta(minutes=1) + endTime = startTime + timedelta(hours=1) + logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s based on maxPredecessorEndTime (%s)', + startTime, endTime, otdb_id, maxPredecessorEndTime)) + else: + startTime = datetime.utcnow() + timedelta(hours=1) + timedelta(minutes=1) + endTime = startTime + timedelta(hours=1) + logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s one hour from now', + startTime, endTime, otdb_id)) # insert new task and specification in the radb # any existing specification and task with same otdb_id will be deleted automatically @@ -220,6 +232,17 @@ class ResourceAssigner(): except Exception as e: logger.error(e) + def getMaxPredecessorEndTime(self, specification_tree): + try: + predecessor_specs = [tree['specification'] for tree in specification_tree['predecessors']] + predecessor_endTimes = [datetime.strptime(spec.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') for spec in predecessor_specs] + + if predecessor_endTimes: + return max(predecessor_endTimes) + except Exception as e: + logger.error(e) + return None + def checkClusterIsCEP4(self, parset): # check storageClusterName for enabled DataProducts # if any storageClusterName is not CEP4, we do not accept this parset -- GitLab From 45c20874af11f61ad73775bacab8cbb0050a7122 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 8 Jun 2016 13:56:07 +0000 Subject: [PATCH 294/933] Task #8887: parse estimator results for input/output properties --- .../ResourceAssigner/lib/assignment.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index a61d53628d3..d0269013145 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -311,7 +311,7 @@ class ResourceAssigner(): claims = [] for resource_type_name, needed_claim_for_resource_type in needed_resources_for_task_type.items(): if resource_type_name in resource_types: - logger.info('claimResources: processing resource_type: %s' % resource_type_name) + logger.info('claimResources: processing resource_type: %s contents: %s' % (resource_type_name, needed_claim_for_resource_type)) db_resource_type_id = resource_types[resource_type_name] db_resources_for_type = [r for r in resources if r['type_id'] == db_resource_type_id] @@ -335,28 +335,33 @@ class ResourceAssigner(): claim['endtime'] += timedelta(days=31) # if the needed_claim_for_resource_type dict contains more kvp's, - # then the subdict contains groups of properties for the claim + # then the subdicts contains groups of properties for the claim if len(needed_claim_for_resource_type) > 1: claim['properties'] = [] - needed_prop_groups = next((v for k,v in needed_claim_for_resource_type.items() if isinstance(v, collections.Iterable))) - def processProperties(propertiesDict, sap_nr=None): + def processProperties(propertiesDict, sap_nr=None, is_input=False): for prop_type_name, prop_value in propertiesDict.items(): if prop_type_name in rc_property_types: rc_property_type_id = rc_property_types[prop_type_name] - property = {'type':rc_property_type_id, 'value':prop_value} + property = {'type':rc_property_type_id, + 'value':prop_value, + 'io_type': 'input' if is_input else 'output'} if sap_nr is not None: property['sap_nr'] = sap_nr claim['properties'].append(property) else: logger.error('claimResources: unknown prop_type:%s' % prop_type_name) - for group_name, needed_prop_group in needed_prop_groups.items(): - if group_name == 'saps': - for sap_dict in needed_prop_group: - processProperties(sap_dict['properties'], sap_dict['sap_nr']) - else: - processProperties(needed_prop_group) + subdicts = (k:v for k,v in needed_claim_for_resource_type.items() if isinstance(v, dict)) + for subdict_name, subdict in subdicts.items(): + logger.info('claimResources: processing resource_type: %s subdict_name: \'%s\' subdict_contents: %s' % (resource_type_name, subdict_name, subdict)) + is_input = 'input' in subdict_name.lower() + for group_name, needed_prop_group in subdict.items(): + if group_name == 'saps': + for sap_dict in needed_prop_group: + processProperties(sap_dict['properties'], sap_dict['sap_nr'], is_input) + else: + processProperties(needed_prop_group, is_input) logger.info('claimResources: created claim:%s' % claim) claims.append(claim) -- GitLab From 69e69194b79ea63d76cad0ee225cbf3babd88e12 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Wed, 8 Jun 2016 14:46:24 +0000 Subject: [PATCH 295/933] Task #9192: added identification handling for supporting pipelines --- .../base_resource_estimator.py | 37 +++++++++++++++++++ .../calibration_pipeline.py | 12 ++++-- .../resource_estimators/image_pipeline.py | 7 +++- .../longbaseline_pipeline.py | 7 +++- .../resource_estimators/observation.py | 17 ++++++--- .../resource_estimators/pulsar_pipeline.py | 11 +++++- 6 files changed, 78 insertions(+), 13 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py index 45b18dc87c7..20604c62da0 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py @@ -63,6 +63,43 @@ class BaseResourceEstimator(object): def _calculate(self, parset, input_files={}): raise NotImplementedError('calculate() in base class is called. Please implement calculate() in your subclass') + def _filterInputs(input_files, identifications): + """'input_files': + {'uv': {'nr_of_uv_files': 481, 'uv_file_size': 1482951104, + 'identifications':['mom.G355415.B2.1.C.SAP000.uv.dps','mom.G355415.B2.1.C.SAP001.uv.dps','mom.G355415.B2.1.C.SAP002.uv.dps']}, + 'saps': [{'sap_nr': 0, 'properties': {'nr_of_uv_files': 319}}, + {'sap_nr': 1, 'properties': {'nr_of_uv_files': 81}}, + {'sap_nr': 2, 'properties': {'nr_of_uv_files': 81}} + ]}}} + 'identifications': 'mom.G355415.B2.1.C.SAP000.uv.dps','mom.G355415.B2.1.C.SAP001.uv.dps' + """ + output_files = {} + if 'saps' in input_files: + output_files['saps'] = [] + for identification in identifications: + for data_type, data_properties in input_files.items(): + if data_type == 'saps': + continue + if identification in data_properties['identifications']: #This data_type matches an input identification + output_files[data_type] = data_properties.deepcopy() + if 'SAP' in identification: #Special case for identifications that contain a SAP number + # We would need to make this smarter if we can have the data from multiple SAPs as input. + # Or or different input sources of the same data_type + for s in identification.split('.'): # Find the SAP number + if 'SAP' in s: + try: + sap_nr = int(s[3:]) + except: + sap_nr = None + for sap in input_files['saps'].items(): + if sap['sap_nr'] == sap_nr: + for sap_data_type, sap_data_value in sap['properties']: + if sap_data_type in data_properties: # We found this SAP's nr_of_<data_type>_files + output_files[data_type][sap_data_type] = sap_data_value # We only count the nr_of_files from this SAP + output_files['saps'].append({'sap_nr': sap_nr, 'properties': {sap_data_type:sap_data_value}}) + return output_files + + def verify_and_estimate(self, parset, input_files={}): """ Create estimates for a single process based on its parset and input files""" if self._checkParsetForRequiredKeys(parset): diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py index a0a6e481ec8..dcbbc995506 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py @@ -40,8 +40,11 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_Correlated.enabled', + DATAPRODUCTS + 'Input_Correlated.identifications', DATAPRODUCTS + 'Output_InstrumentModel.enabled', + DATAPRODUCTS + 'Output_InstrumentModel.identifications', DATAPRODUCTS + 'Output_Correlated.enabled', + DATAPRODUCTS + 'Output_Correlated.identifications', PIPELINE + 'DPPP.demixer.freqstep', PIPELINE + 'DPPP.demixer.timestep') @@ -62,7 +65,10 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): """ logger.debug("start estimate '{}'".format(self.name)) logger.info('parset: %s ' % parset) - result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} + result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} + input_files = self._filterInputs(input_files, parset.getStringVector(DATAPRODUCTS + 'Input_Correlated.identifications')) + result['storage']['input_files'] = input_files + duration = self._getDuration(parset.getString('Observation.startTime'), parset.getString('Observation.stopTime')) freq_step = parset.getInt(PIPELINE + 'DPPP.demixer.freqstep', 1) #TODO, should these have defaults? time_step = parset.getInt(PIPELINE + 'DPPP.demixer.timestep', 1) @@ -86,12 +92,12 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): output_file_size = 0.0 new_size = input_file_size / float(reduction_factor) output_file_size = new_size + new_size / 64.0 * (1.0 + reduction_factor) + new_size / 2.0 - result['storage']['output_files']['uv'] = {'nr_of_uv_files': input_files['uv']['nr_of_uv_files'], 'uv_file_size': int(output_file_size)} + result['storage']['output_files']['uv'] = {'nr_of_uv_files': input_files['uv']['nr_of_uv_files'], 'uv_file_size': int(output_file_size), 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_Correlated.identifications')} logger.info("correlated_uv: {} files {} bytes each".format(result['storage']['output_files']['uv']['nr_of_uv_files'], result['storage']['output_files']['uv']['uv_file_size'])) if parset.getBool(DATAPRODUCTS + 'Output_InstrumentModel.enabled'): logger.info("calculate instrument-model data size") - result['storage']['output_files']['im'] = {'nr_of_im_files': input_files['uv']['nr_of_uv_files'], 'im_file_size': 1000} # 1 kB was hardcoded in the Scheduler + result['storage']['output_files']['im'] = {'nr_of_im_files': input_files['uv']['nr_of_uv_files'], 'im_file_size': 1000, 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_InstrumentModel.identifications')} # 1 kB was hardcoded in the Scheduler logger.info("correlated_im: {} files {} bytes each".format(result['storage']['output_files']['im']['nr_of_im_files'], result['storage']['output_files']['im']['im_file_size'])) # count total data size diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py index d05e28cf51d..5314e3b81ec 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py @@ -42,7 +42,9 @@ class ImagePipelineResourceEstimator(BaseResourceEstimator): self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_Correlated.enabled', + DATAPRODUCTS + 'Input_Correlated.identifications', DATAPRODUCTS + 'Output_SkyImage.enabled', + DATAPRODUCTS + 'Output_SkyImage.identifications', PIPELINE + 'Imaging.slices_per_image', PIPELINE + 'Imaging.subbands_per_image') @@ -63,6 +65,9 @@ class ImagePipelineResourceEstimator(BaseResourceEstimator): logger.debug("start estimate '{}'".format(self.name)) logger.info('parset: %s ' % parset) result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} + input_files = self._filterInputs(input_files, parset.getStringVector(DATAPRODUCTS + 'Input_Correlated.identifications')) + result['storage']['input_files'] = input_files + duration = self._getDuration(parset.getString('Observation.startTime'), parset.getString('Observation.stopTime')) slices_per_image = parset.getInt(PIPELINE + 'Imaging.slices_per_image', 0) #TODO, should these have defaults? subbands_per_image = parset.getInt(PIPELINE + 'Imaging.subbands_per_image', 0) @@ -87,7 +92,7 @@ class ImagePipelineResourceEstimator(BaseResourceEstimator): logger.debug("calculate sky image data size") result['storage']['output_files'] = {} nr_images = nr_input_subbands / (subbands_per_image * slices_per_image) - result['storage']['output_files']['img'] = {'nr_of_img_files': nr_images, 'img_file_size': 1000} # 1 kB was hardcoded in the Scheduler + result['storage']['output_files']['img'] = {'nr_of_img_files': nr_images, 'img_file_size': 1000, 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_SkyImage.identifications')} # 1 kB was hardcoded in the Scheduler logger.debug("sky_images: {} files {} bytes each".format(result['storage']['output_files']['img']['nr_of_img_files'], result['storage']['output_files']['img']['img_file_size'])) # count total data size diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py index 0219cf586af..7aa75217988 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py @@ -40,7 +40,9 @@ class LongBaselinePipelineResourceEstimator(BaseResourceEstimator): self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_Correlated.enabled', + DATAPRODUCTS + 'Input_Correlated.identifications', DATAPRODUCTS + 'Output_Correlated.enabled', + DATAPRODUCTS + 'Output_Correlated.identifications', PIPELINE + 'LongBaseline.subbandgroups_per_ms', PIPELINE + 'LongBaseline.subbands_per_subbandgroup') @@ -61,6 +63,9 @@ class LongBaselinePipelineResourceEstimator(BaseResourceEstimator): logger.debug("start estimate '{}'".format(self.name)) logger.info('parset: %s ' % parset) result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} + input_files = self._filterInputs(input_files, parset.getStringVector(DATAPRODUCTS + 'Input_Correlated.identifications')) + result['storage']['input_files'] = input_files + duration = self._getDuration(parset.getString('Observation.startTime'), parset.getString('Observation.stopTime')) subbandgroups_per_ms = parset.getInt(PIPELINE + 'LongBaseline.subbandgroups_per_ms', 0) #TODO, should these have defaults? subbands_per_subbandgroup = parset.getInt(PIPELINE + 'LongBaseline.subbands_per_subbandgroup', 0) @@ -85,7 +90,7 @@ class LongBaselinePipelineResourceEstimator(BaseResourceEstimator): logger.debug("calculate correlated data size") result['storage']['output_files'] = {} nr_output_files = nr_input_files / (subbands_per_subbandgroup * subbandgroups_per_ms) - result['storage']['output_files']['uv'] = {'nr_of_uv_files': nr_output_files, 'uv_file_size': 1000} # 1 kB was hardcoded in the Scheduler + result['storage']['output_files']['uv'] = {'nr_of_uv_files': nr_output_files, 'uv_file_size': 1000, 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_Correlated.identifications')} # 1 kB was hardcoded in the Scheduler logger.debug("correlated_uv: {} files {} bytes each".format(result['storage']['output_files']['uv']['nr_of_uv_files'], result['storage']['output_files']['uv']['uv_file_size'])) # count total data size diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py index e3e8e1622c7..0f7359d46d2 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py @@ -26,6 +26,7 @@ from base_resource_estimator import BaseResourceEstimator logger = logging.getLogger(__name__) +DATAPRODUCTS = "Observation.DataProducts." COBALT = "Observation.ObservationControl.OnlineControl.Cobalt." class ObservationResourceEstimator(BaseResourceEstimator): @@ -44,9 +45,13 @@ class ObservationResourceEstimator(BaseResourceEstimator): COBALT + 'BeamFormer.CoherentStokes.timeIntegrationFactor', COBALT + 'BeamFormer.IncoherentStokes.timeIntegrationFactor', 'Observation.VirtualInstrument.stationList', - 'Observation.DataProducts.Output_CoherentStokes.enabled', + DATAPRODUCTS + 'Output_Correlated.enabled', + DATAPRODUCTS + 'Output_Correlated.identifications', + DATAPRODUCTS + 'Output_CoherentStokes.enabled', + DATAPRODUCTS + 'Output_CoherentStokes.identifications', COBALT + 'BeamFormer.CoherentStokes.which', - 'Observation.DataProducts.Output_IncoherentStokes.enabled', + DATAPRODUCTS + 'Output_IncoherentStokes.enabled', + DATAPRODUCTS + 'Output_IncoherentStokes.identifications', COBALT + 'BeamFormer.IncoherentStokes.which' ) @@ -123,10 +128,10 @@ class ObservationResourceEstimator(BaseResourceEstimator): subbandList = parset.getStringVector('Observation.Beam[%d].subbandList' % sap_nr) nr_files = len(subbandList) total_files += nr_files - sap_files[sap_nr]= {'nr_of_uv_files': nr_files} + sap_files[sap_nr] = {'nr_of_uv_files': nr_files} file_size = int((data_size + n_sample_size + size_of_header) * integrated_seconds + size_of_overhead) - output_files = {'nr_of_uv_files': total_files, 'uv_file_size': file_size} + output_files = {'nr_of_uv_files': total_files, 'uv_file_size': file_size, 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_Correlated.identifications')} logger.info("correlated_uv: {} files {} bytes each".format(total_files, file_size)) total_data_size = int(ceil(file_size * total_files)) # bytes @@ -196,7 +201,7 @@ class ObservationResourceEstimator(BaseResourceEstimator): nr_subbands_per_file = min(subbands_per_file, max_nr_subbands) size_per_file = int(nr_subbands_per_file * size_per_subband) - output_files = {'nr_of_cs_files': total_files, 'nr_of_cs_stokes': nr_coherent, 'cs_file_size': size_per_file} + output_files = {'nr_of_cs_files': total_files, 'nr_of_cs_stokes': nr_coherent, 'cs_file_size': size_per_file, 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_CoherentStokes.identifications')} logger.info("coherentstokes: {} files {} bytes each".format(total_files, size_per_file)) total_data_size = int(ceil(total_nr_stokes * max_nr_subbands * size_per_subband)) @@ -251,7 +256,7 @@ class ObservationResourceEstimator(BaseResourceEstimator): size_per_subband = int((samples_per_second * 4) / time_integration_factor / channel_integration_factor * duration) size_per_file = nr_subbands_per_file * size_per_subband - output_files = {'nr_of_is_files': total_files, 'nr_of_is_stokes': nr_incoherent, 'is_file_size': int(size_per_file)} + output_files = {'nr_of_is_files': total_files, 'nr_of_is_stokes': nr_incoherent, 'is_file_size': int(size_per_file), 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_IncoherentStokes.identifications')} logger.info("incoherentstokes: {} files {} bytes each".format(total_files, size_per_file)) total_data_size = int(ceil(total_nr_stokes * max_nr_subbands * size_per_subband)) # bytes diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py index 26471d794ea..a024a9db3c3 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py @@ -40,8 +40,11 @@ class PulsarPipelineResourceEstimator(BaseResourceEstimator): self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_CoherentStokes.enabled', + DATAPRODUCTS + 'Input_CoherentStokes.identifications', DATAPRODUCTS + 'Input_IncoherentStokes.enabled', - DATAPRODUCTS + 'Output_Pulsar.enabled') + DATAPRODUCTS + 'Input_IncoherentStokes.identifications', + DATAPRODUCTS + 'Output_Pulsar.enabled', + DATAPRODUCTS + 'Output_Pulsar.identifications') def _calculate(self, parset, input_files): """ Estimate for Pulsar Pipeline @@ -60,6 +63,10 @@ class PulsarPipelineResourceEstimator(BaseResourceEstimator): logger.debug("start estimate '{}'".format(self.name)) logger.info('parset: %s ' % parset) result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} + input_files = self._filterInputs(input_files, parset.getStringVector(DATAPRODUCTS + 'Input_CoherentStokes.identifications') + + parset.getStringVector(DATAPRODUCTS + 'Input_IncoherentStokes.identifications')) + result['storage']['input_files'] = input_files + duration = self._getDuration(parset.getString('Observation.startTime'), parset.getString('Observation.stopTime')) if not parset.getBool(DATAPRODUCTS + 'Output_Pulsar.enabled'): @@ -85,7 +92,7 @@ class PulsarPipelineResourceEstimator(BaseResourceEstimator): nr_input_files = input_files['is']['nr_of_is_files'] nr_output_files += nr_input_files - result['storage']['output_files']['pulp'] = {'nr_of_pulp_files': nr_output_files, 'pulp_file_size': 1000} # 1 kB was hardcoded in the Scheduler + result['storage']['output_files']['pulp'] = {'nr_of_pulp_files': nr_output_files, 'pulp_file_size': 1000, 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_Pulsar.identifications')} # 1 kB was hardcoded in the Scheduler logger.info(result) logger.info("pulsar_pipeline pulp: {} files {} bytes each".format(result['storage']['output_files']['pulp']['nr_of_pulp_files'], result['storage']['output_files']['pulp']['pulp_file_size'])) -- GitLab From e4db264deb7812c4e196089948fa9d0267ce32c6 Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Wed, 8 Jun 2016 15:10:38 +0000 Subject: [PATCH 296/933] Task #9533: Added a CMake macro lofar_add_sysconf_files to install system configuration files that typically need to be installed in <prefix>/etc. --- CMake/LofarMacros.cmake | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CMake/LofarMacros.cmake b/CMake/LofarMacros.cmake index faeb2187463..78b2fda085d 100644 --- a/CMake/LofarMacros.cmake +++ b/CMake/LofarMacros.cmake @@ -7,6 +7,7 @@ # lofar_add_library(name) # lofar_add_sbin_program(name) # lofar_add_sbin_scripts([name1 [name2 ..]]) +# lofar_add_sysconf_files([name1 [name2 ..]]) # lofar_add_test(name) # lofar_create_target_symlink(target symlink) # lofar_join_arguments(var) @@ -163,6 +164,26 @@ if(NOT DEFINED LOFAR_MACROS_INCLUDED) endmacro(lofar_add_sbin_scripts) + # -------------------------------------------------------------------------- + # lofar_add_sysconf_files([name1 [name2 ..]]) + # + # Add system configuration files (read-only single machine data) that need + # to be installed into the <prefix>/etc directory. Also create a symbolic + # link in <build-dir>/etc to each of these files. The file names may contain + # a relative(!) path. + # -------------------------------------------------------------------------- + macro(lofar_add_sysconf_files) + foreach(_name ${ARGN}) + get_filename_component(_path ${_name} DIRECTORY) + get_filename_component(_abs_name ${_name} ABSOLUTE) + file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/etc/${_path}) + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + ${_abs_name} ${CMAKE_BINARY_DIR}/etc/${_name}) + install(FILES ${_name} DESTINATION etc/${_path}) + endforeach(_name ${ARGN}) + endmacro(lofar_add_sysconf_files) + + # -------------------------------------------------------------------------- # lofar_add_test(name [source ...] [DEPENDS depend ...]) # -- GitLab From bb12d7b08ea3a7350a0c93611e48d93afb11b7c6 Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Wed, 8 Jun 2016 15:39:28 +0000 Subject: [PATCH 297/933] Task 9533: Use new lofar_add_sysconf_files() macro to mark files for installation in <prefix>/etc. --- MAC/GCF/TM/src/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/MAC/GCF/TM/src/CMakeLists.txt b/MAC/GCF/TM/src/CMakeLists.txt index 322a742855d..034ac96f131 100644 --- a/MAC/GCF/TM/src/CMakeLists.txt +++ b/MAC/GCF/TM/src/CMakeLists.txt @@ -35,8 +35,7 @@ lofar_add_library(gcftm lofar_add_bin_program(versiongcftm versiongcftm.cc) -install(FILES +lofar_add_sysconf_files( mac.log_prop mac_debug.log_prop - mac_nopvss.log_prop - DESTINATION etc) + mac_nopvss.log_prop) -- GitLab From dc612eb96e905ec75a7b357a605c9557c2086b10 Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Wed, 8 Jun 2016 16:23:52 +0000 Subject: [PATCH 298/933] Task #9533: Oops! The CMake command get_filename_component() only recently supports DIRECTORY; use PATH instead. --- CMake/LofarMacros.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMake/LofarMacros.cmake b/CMake/LofarMacros.cmake index 78b2fda085d..6a34132d08a 100644 --- a/CMake/LofarMacros.cmake +++ b/CMake/LofarMacros.cmake @@ -174,7 +174,7 @@ if(NOT DEFINED LOFAR_MACROS_INCLUDED) # -------------------------------------------------------------------------- macro(lofar_add_sysconf_files) foreach(_name ${ARGN}) - get_filename_component(_path ${_name} DIRECTORY) + get_filename_component(_path ${_name} PATH) get_filename_component(_abs_name ${_name} ABSOLUTE) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/etc/${_path}) execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink -- GitLab From e00f10fde3a7daa4a3596d75877447a50c1e32b0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 09:49:19 +0000 Subject: [PATCH 299/933] Task #9351: implemented removePath method, which does various sanity checking on the path to prevent accidental deletes --- SAS/DataManagement/CleanupService/config.py | 1 + SAS/DataManagement/CleanupService/rpc.py | 6 +- SAS/DataManagement/CleanupService/service.py | 69 ++++++++++++++--- .../test/test_cleanup_service_and_rpc.py | 76 ++++++++++++++++--- 4 files changed, 128 insertions(+), 24 deletions(-) diff --git a/SAS/DataManagement/CleanupService/config.py b/SAS/DataManagement/CleanupService/config.py index 0e8dafdf6f6..c7dd0f5a3ab 100644 --- a/SAS/DataManagement/CleanupService/config.py +++ b/SAS/DataManagement/CleanupService/config.py @@ -5,3 +5,4 @@ from lofar.messaging import adaptNameToEnvironment DEFAULT_BUSNAME = adaptNameToEnvironment('lofar.dm.command') DEFAULT_SERVICENAME = 'CleanupService' +CEP4_DATA_MOUNTPOINT='/data' diff --git a/SAS/DataManagement/CleanupService/rpc.py b/SAS/DataManagement/CleanupService/rpc.py index 369c01cf4ec..f43cc6b19c8 100644 --- a/SAS/DataManagement/CleanupService/rpc.py +++ b/SAS/DataManagement/CleanupService/rpc.py @@ -12,8 +12,8 @@ class CleanupRPC(RPCWrapper): broker=None): super(CleanupRPC, self).__init__(busname, servicename, broker) - def foo(self): - return self.rpc('foo') + def removePath(self, path): + return self.rpc('RemovePath', path=path) def main(): import sys @@ -35,4 +35,4 @@ def main(): level=logging.INFO if options.verbose else logging.WARN) with CleanupRPC(busname=options.busname, servicename=options.servicename, broker=options.broker) as rpc: - print rpc.foo() + print rpc.removePath() diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index 87b21b9bc4c..e6f118d1bf4 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -19,35 +19,83 @@ with RPC(busname, 'GetProjectDetails') as getProjectDetails: ''' import logging +import os.path +from shutil import rmtree from optparse import OptionParser from lofar.messaging import Service from lofar.messaging import setQpidLogLevel from lofar.messaging.Service import MessageHandlerInterface from lofar.common.util import waitForInterrupt -from lofar.common.util import convertIntKeysToString -from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME +from lofar.common import isProductionEnvironment, isTestEnvironment +from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME, CEP4_DATA_MOUNTPOINT logger = logging.getLogger(__name__) class CleanupHandler(MessageHandlerInterface): def __init__(self, **kwargs): super(CleanupHandler, self).__init__(**kwargs) + self.mountpoint = kwargs.pop("mountpoint", CEP4_DATA_MOUNTPOINT) + self.projects_path = os.path.join(self.mountpoint, 'projects' if isProductionEnvironment() else 'test-projects') - self.service2MethodMap = {'foo': self._foo} + self.service2MethodMap = {'RemovePath': self._removePath} def prepare_loop(self): - pass + logger.info("Cleanup service started with projects_path=%s", self.projects_path) - def _foo(self): - return 'foo' + def _removePath(self, path): + # do various sanity checking to prevent accidental deletes + if not isinstance(path, basestring): + message = "Provided path is not a string" + logger.error(message) + return {'deleted': False, 'message': message} -def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, verbose=False): + if not path: + message = "Empty path provided" + logger.error(message) + return {'deleted': False, 'message': message} + + if '*' in path or '?' in path: + message = "Invalid path '%s': No wildcards allowed" % (path,) + logger.error(message) + return {'deleted': False, 'message': message} + + # remove any trailing slashes + if len(path) > 1: + path = path.rstrip('/') + + if not path.startswith(self.projects_path): + message = "Invalid path '%s': Path does not start with '%s'" % (path, self.projects_path) + logger.error(message) + return {'deleted': False, 'message': message} + + if path[len(self.projects_path)+1:].count('/') == 0: + message = "Invalid path '%s': Path should be a subdir of '%s'" % (path, self.projects_path) + logger.error(message) + return {'deleted': False, 'message': message} + + logger.info("Attempting to delete %s", path) + + failed_paths = set() + def onerror(func, failed_path, excinfo): + logger.info("Failed to delete %s", failed_path) + failed_paths.add(failed_path) + + if rmtree(path, onerror=onerror): + message = "Deleted '%s'" % (path) + logger.info(message) + return {'deleted': True, 'message': message} + + return {'deleted': False, 'message': 'Failed to delete one or more files.', 'failed_paths' : list(failed_paths)} + + +def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, mountpoint=CEP4_DATA_MOUNTPOINT, verbose=False): return Service(servicename, CleanupHandler, busname=busname, broker=broker, use_service_methods=True, - numthreads=2, + numthreads=1, + handler_args={'mountpoint': mountpoint}, verbose=verbose) def main(): @@ -55,8 +103,9 @@ def main(): parser = OptionParser("%prog [options]", description='runs the resourceassignment database service') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') - parser.add_option("-b", "--busname", dest="busname", type="string", default=DEFAULT_BUSNAME, help="Name of the bus exchange on the qpid broker, default: %s" % DEFAULT_BUSNAME) - parser.add_option("-s", "--servicename", dest="servicename", type="string", default=DEFAULT_SERVICENAME, help="Name for this service, default: %s" % DEFAULT_SERVICENAME) + parser.add_option("-b", "--busname", dest="busname", type="string", default=DEFAULT_BUSNAME, help="Name of the bus exchange on the qpid broker, default: %default") + parser.add_option("-s", "--servicename", dest="servicename", type="string", default=DEFAULT_SERVICENAME, help="Name for this service, default: default %default") + parser.add_option("-m", "--mountpoint", dest="mountpoint", type="string", default=CEP4_DATA_MOUNTPOINT, help="path of local cep4 mount point, default: %default") parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() diff --git a/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py index 3d6f441b0ce..6fb8779b520 100755 --- a/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py +++ b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py @@ -5,8 +5,6 @@ import uuid import datetime import logging from lofar.messaging import Service -from lofar.sas.datamanagement.cleanup.service import createService -from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC from qpid.messaging.exceptions import * try: @@ -17,6 +15,14 @@ except ImportError: print 'Please source qpid profile' exit(3) +try: + from mock import MagicMock + from mock import patch +except ImportError: + print 'Cannot run test without python MagicMock' + print 'Please install MagicMock: pip install mock' + exit(3) + connection = None broker = None @@ -32,16 +38,64 @@ try: busname = 'test-lofarbus-%s' % (uuid.uuid1()) broker.addExchange('topic', busname) - class TestCleanupServiceAndRPC(unittest.TestCase): - def test(self): - '''basic test ''' - rpc = CleanupRPC(busname=busname) - self.assertEqual('foo', rpc.foo()) + # the cleanup service uses shutil.rmtree under the hood + # so, mock/patch shutil.rmtree and fake the delete action + # because we do not want to delete any real data in this test + with patch('shutil.rmtree', autospec=True) as mock_rmtree: + mock = mock_rmtree.return_value + mock.return_value = True + + # now that we have a mocked shutil.rmtree, import cleanupservice + from lofar.sas.datamanagement.cleanup.service import createService + from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC + + class TestCleanupServiceAndRPC(unittest.TestCase): + def test(self): + '''basic test ''' + with CleanupRPC(busname=busname) as rpc: + #try some invalid input + self.assertFalse(rpc.removePath(None)['deleted']) + self.assertFalse(rpc.removePath(True)['deleted']) + self.assertFalse(rpc.removePath({'foo':'bar'})['deleted']) + self.assertFalse(rpc.removePath(['foo', 'bar'])['deleted']) + + #try some dangerous paths + #these should not be deleted + result = rpc.removePath('/') + self.assertFalse(result['deleted']) + self.assertTrue('Path does not start with' in result['message']) + + result = rpc.removePath('/foo/*/bar') + self.assertFalse(result['deleted']) + self.assertTrue('No wildcards allowed' in result['message']) + + result = rpc.removePath('/foo/ba?r') + self.assertFalse(result['deleted']) + self.assertTrue('No wildcards allowed' in result['message']) + + result = rpc.removePath('/data') + self.assertFalse(result['deleted']) + self.assertTrue('Path does not start with' in result['message']) + + result = rpc.removePath('/data/test-projects/') + self.assertFalse(result['deleted']) + self.assertTrue('Path should be a subdir of' in result['message']) + + result = rpc.removePath('/data/test-projects/foo') + self.assertFalse(result['deleted']) + self.assertTrue('Path should be a subdir of' in result['message']) + + result = rpc.removePath('/data/test-projects/foo/') + self.assertFalse(result['deleted']) + self.assertTrue('Path should be a subdir of' in result['message']) + + #try an actual delete, should work with mocked shutil.rmtree + self.assertTrue(rpc.removePath('/data/test-projects/foo/bar')['deleted']) - # create and run the service - with createService(busname=busname): - # and run all tests - unittest.main() + # create and run the service + with createService(busname=busname): + # and run all tests + unittest.main() except ConnectError as ce: logger.error(ce) -- GitLab From a642565e6c9b7f99393c3f2b74ad98b58b6d7f0f Mon Sep 17 00:00:00 2001 From: David Rafferty <rafferty@strw.leidenuniv.nl> Date: Thu, 9 Jun 2016 10:58:25 +0000 Subject: [PATCH 300/933] Task #9535: Fix to incorrect ouput images --- CEP/PyBDSM/doc/source/conf.py | 4 ++-- CEP/PyBDSM/doc/source/whats_new.rst | 5 ++++- CEP/PyBDSM/src/python/_version.py | 7 ++++++- CEP/PyBDSM/src/python/functions.py | 4 ++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CEP/PyBDSM/doc/source/conf.py b/CEP/PyBDSM/doc/source/conf.py index a7508205f40..fe682b9ede0 100644 --- a/CEP/PyBDSM/doc/source/conf.py +++ b/CEP/PyBDSM/doc/source/conf.py @@ -50,7 +50,7 @@ copyright = u'2016, David Rafferty and Niruj Mohan' # The short X.Y version. version = '1.8' # The full version, including alpha/beta/rc tags. -release = '1.8.6' +release = '1.8.7' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -248,7 +248,7 @@ texinfo_documents = [ epub_title = u'PyBDSM' epub_author = u'David Rafferty and Niruj Mohan' epub_publisher = u'David Rafferty and Niruj Mohan' -epub_copyright = u'2013, David Rafferty and Niruj Mohan' +epub_copyright = u'2016, David Rafferty and Niruj Mohan' # The language of the text. It defaults to the language option # or en if the language is not set. diff --git a/CEP/PyBDSM/doc/source/whats_new.rst b/CEP/PyBDSM/doc/source/whats_new.rst index 7d3ac6267c0..bd0e282e02c 100644 --- a/CEP/PyBDSM/doc/source/whats_new.rst +++ b/CEP/PyBDSM/doc/source/whats_new.rst @@ -4,13 +4,16 @@ What's New ********** +Version 1.8.7 (2016/06/10) + + * Fix to bug that caused incorrect output images when input image was not square. + Version 1.8.6 (2016/01/20) * Fix to bug that caused incorrect island mask when two islands are very close together. * Fix to bug that caused crash when image is masked and the ``src_ra_dec`` option is used. - Version 1.8.5 (2015/11/30): * Fix to bug in ``export_image`` that resulted in incorrect output image when both ``trim_box`` and ``pad_image`` were used. diff --git a/CEP/PyBDSM/src/python/_version.py b/CEP/PyBDSM/src/python/_version.py index 6b965579414..5b2697fe841 100644 --- a/CEP/PyBDSM/src/python/_version.py +++ b/CEP/PyBDSM/src/python/_version.py @@ -9,7 +9,7 @@ adding to the changelog will naturally do this. """ # Version number -__version__ = '1.8.6' +__version__ = '1.8.7' # Store svn Revision number. For this to work, one also needs to do: # @@ -27,6 +27,11 @@ def changelog(): PyBDSM Changelog. ----------------------------------------------------------------------- + 2016/06/10 - Version 1.8.7 + + 2016/06/10 - Fix to bug that caused incorrect output images when input + image was not square. + 2016/01/20 - Version 1.8.6 2016/01/15 - Fix to bug that caused incorrect island mask when two diff --git a/CEP/PyBDSM/src/python/functions.py b/CEP/PyBDSM/src/python/functions.py index 358f6b7f780..77d2c27028a 100755 --- a/CEP/PyBDSM/src/python/functions.py +++ b/CEP/PyBDSM/src/python/functions.py @@ -1432,8 +1432,8 @@ def write_image_to_file(use, filename, image, img, outdir=None, return temp_im = make_fits_image(N.transpose(image), wcs_obj, img.beam, img.frequency, img.equinox, telescope, xmin=xmin, ymin=ymin, - is_mask=is_mask, shape=(img.shape[1], img.shape[0], image.shape[0], - image.shape[1])) + is_mask=is_mask, shape=(img.shape[1], img.shape[0], image.shape[1], + image.shape[0])) if use == 'rap': outfile = outdir + filename + '.fits' else: -- GitLab From 720611ad25711d5c61a2c942de20ec206a9ebea8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 11:29:45 +0000 Subject: [PATCH 301/933] Task #9351: implemented removeTaskData method for a given otdb_id --- SAS/DataManagement/CleanupService/rpc.py | 3 + SAS/DataManagement/CleanupService/service.py | 71 +++++++++- .../test/test_cleanup_service_and_rpc.py | 132 ++++++++++-------- 3 files changed, 146 insertions(+), 60 deletions(-) diff --git a/SAS/DataManagement/CleanupService/rpc.py b/SAS/DataManagement/CleanupService/rpc.py index f43cc6b19c8..fe17092eac8 100644 --- a/SAS/DataManagement/CleanupService/rpc.py +++ b/SAS/DataManagement/CleanupService/rpc.py @@ -15,6 +15,9 @@ class CleanupRPC(RPCWrapper): def removePath(self, path): return self.rpc('RemovePath', path=path) + def removeTaskData(self, otdb_id): + return self.rpc('RemoveTaskData', otdb_id=otdb_id) + def main(): import sys from optparse import OptionParser diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index e6f118d1bf4..15a7e49e8f2 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -29,6 +29,13 @@ from lofar.common.util import waitForInterrupt from lofar.common import isProductionEnvironment, isTestEnvironment from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME, CEP4_DATA_MOUNTPOINT +from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC as RADBRPC +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME + +from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC +from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME + logger = logging.getLogger(__name__) class CleanupHandler(MessageHandlerInterface): @@ -37,11 +44,49 @@ class CleanupHandler(MessageHandlerInterface): self.mountpoint = kwargs.pop("mountpoint", CEP4_DATA_MOUNTPOINT) self.projects_path = os.path.join(self.mountpoint, 'projects' if isProductionEnvironment() else 'test-projects') - self.service2MethodMap = {'RemovePath': self._removePath} + self.service2MethodMap = {'RemovePath': self._removePath, + 'RemoveTaskData': self._removeTaskData} + + self.radbrpc = RADBRPC(busname=kwargs.pop("radb_busname", RADB_BUSNAME), servicename=kwargs.pop("radb_servicename", RADB_SERVICENAME), broker=kwargs.pop("broker", None)) + self.momrpc = MoMQueryRPC(busname=kwargs.pop("mom_busname", DEFAULT_MOMQUERY_BUSNAME), servicename=kwargs.pop("mom_servicename", DEFAULT_MOMQUERY_SERVICENAME), broker=kwargs.pop("broker", None)) def prepare_loop(self): + self.radbrpc.open() + self.momrpc.open() logger.info("Cleanup service started with projects_path=%s", self.projects_path) + def finalize_loop(self): + self.radbrpc.close() + self.momrpc.close() + + def _removeTaskData(self, otdb_id): + if not isinstance(otdb_id, int): + message = "Provided otdb_id is not an int" + logger.error(message) + return {'deleted': False, 'message': message} + + task = self.radbrpc.getTask(otdb_id=otdb_id) + + if not task: + message = "Could not find task in RADB for otdb_id=%s" % otdb_id + logger.error(message) + return {'deleted': False, 'message': message} + + mom_details = self.momrpc.getProjectDetails(task['mom_id']) + + if not mom_details or str(task['mom_id']) not in mom_details: + message = "Could not find mom project details for otdb_id=%s mom_id=%s radb_id=%s" % (otdb_id, task['mom_id'], task['id']) + logger.error(message) + return {'deleted': False, 'message': message} + + project_name = mom_details[str(task['mom_id'])]['project_name'] + logger.info("Found project '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (project_name, otdb_id, task['mom_id'], task['id'])) + + project_path = os.path.join(self.projects_path, "_".join(project_name.split())) + task_data_path = os.path.join(project_path, 'L%s' % otdb_id) + + return self._removePath(task_data_path) + def _removePath(self, path): # do various sanity checking to prevent accidental deletes if not isinstance(path, basestring): @@ -88,15 +133,23 @@ class CleanupHandler(MessageHandlerInterface): return {'deleted': False, 'message': 'Failed to delete one or more files.', 'failed_paths' : list(failed_paths)} -def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, mountpoint=CEP4_DATA_MOUNTPOINT, verbose=False): +def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, + mountpoint=CEP4_DATA_MOUNTPOINT, verbose=False, + radb_busname=RADB_BUSNAME, radb_servicename=RADB_SERVICENAME, + mom_busname=DEFAULT_MOMQUERY_BUSNAME, mom_servicename=DEFAULT_MOMQUERY_SERVICENAME): return Service(servicename, CleanupHandler, busname=busname, broker=broker, use_service_methods=True, numthreads=1, - handler_args={'mountpoint': mountpoint}, - verbose=verbose) + verbose=verbose, + handler_args={'mountpoint': mountpoint, + 'radb_busname':RADB_BUSNAME, + 'radb_servicename':RADB_SERVICENAME, + 'mom_busname':DEFAULT_MOMQUERY_BUSNAME, + 'mom_servicename':DEFAULT_MOMQUERY_SERVICENAME, + 'broker':broker}) def main(): # Check the invocation arguments @@ -107,6 +160,10 @@ def main(): parser.add_option("-s", "--servicename", dest="servicename", type="string", default=DEFAULT_SERVICENAME, help="Name for this service, default: default %default") parser.add_option("-m", "--mountpoint", dest="mountpoint", type="string", default=CEP4_DATA_MOUNTPOINT, help="path of local cep4 mount point, default: %default") parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') + parser.add_option("--radb_busname", dest="radb_busname", type="string", default=RADB_BUSNAME, help="Name of the bus on which the RADB service listens, default: %default") + parser.add_option("--radb_servicename", dest="radb_servicename", type="string", default=RADB_SERVICENAME, help="Name of the RADB service, default: %default") + parser.add_option("--mom_busname", dest="mom_busname", type="string", default=DEFAULT_MOMQUERY_BUSNAME, help="Name of the bus on which the MoM service listens, default: %default") + parser.add_option("--mom_servicename", dest="mom_servicename", type="string", default=DEFAULT_MOMQUERY_SERVICENAME, help="Name of the MoM service, default: %default") (options, args) = parser.parse_args() setQpidLogLevel(logging.INFO) @@ -116,7 +173,11 @@ def main(): with createService(busname=options.busname, servicename=options.servicename, broker=options.broker, - verbose=options.verbose): + verbose=True, #options.verbose, + radb_busname=options.radb_busname, + radb_servicename=options.radb_servicename, + mom_busname=options.mom_busname, + mom_servicename=options.mom_busname): waitForInterrupt() if __name__ == '__main__': diff --git a/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py index 6fb8779b520..fea2a268a7c 100755 --- a/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py +++ b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py @@ -41,61 +41,83 @@ try: # the cleanup service uses shutil.rmtree under the hood # so, mock/patch shutil.rmtree and fake the delete action # because we do not want to delete any real data in this test - with patch('shutil.rmtree', autospec=True) as mock_rmtree: - mock = mock_rmtree.return_value - mock.return_value = True - - # now that we have a mocked shutil.rmtree, import cleanupservice - from lofar.sas.datamanagement.cleanup.service import createService - from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC - - class TestCleanupServiceAndRPC(unittest.TestCase): - def test(self): - '''basic test ''' - with CleanupRPC(busname=busname) as rpc: - #try some invalid input - self.assertFalse(rpc.removePath(None)['deleted']) - self.assertFalse(rpc.removePath(True)['deleted']) - self.assertFalse(rpc.removePath({'foo':'bar'})['deleted']) - self.assertFalse(rpc.removePath(['foo', 'bar'])['deleted']) - - #try some dangerous paths - #these should not be deleted - result = rpc.removePath('/') - self.assertFalse(result['deleted']) - self.assertTrue('Path does not start with' in result['message']) - - result = rpc.removePath('/foo/*/bar') - self.assertFalse(result['deleted']) - self.assertTrue('No wildcards allowed' in result['message']) - - result = rpc.removePath('/foo/ba?r') - self.assertFalse(result['deleted']) - self.assertTrue('No wildcards allowed' in result['message']) - - result = rpc.removePath('/data') - self.assertFalse(result['deleted']) - self.assertTrue('Path does not start with' in result['message']) - - result = rpc.removePath('/data/test-projects/') - self.assertFalse(result['deleted']) - self.assertTrue('Path should be a subdir of' in result['message']) - - result = rpc.removePath('/data/test-projects/foo') - self.assertFalse(result['deleted']) - self.assertTrue('Path should be a subdir of' in result['message']) - - result = rpc.removePath('/data/test-projects/foo/') - self.assertFalse(result['deleted']) - self.assertTrue('Path should be a subdir of' in result['message']) - - #try an actual delete, should work with mocked shutil.rmtree - self.assertTrue(rpc.removePath('/data/test-projects/foo/bar')['deleted']) - - # create and run the service - with createService(busname=busname): - # and run all tests - unittest.main() + with patch('shutil.rmtree', autospec=True) as patch_rmtree: + mock_rmtree = patch_rmtree.return_value + mock_rmtree.return_value = True + + with patch('lofar.sas.resourceassignment.resourceassignmentservice.rpc.RARPC', autospec=True) as patch_rarpc: + mock_rarpc = patch_rarpc.return_value + mock_rarpc.getTask.side_effect = lambda otdb_id: {'id': 42, 'mom_id': 1000042} if otdb_id == 13 else {'id': 43, 'mom_id': 1000043} if otdb_id == 14 else None + + with patch('lofar.mom.momqueryservice.momqueryrpc.MoMQueryRPC', autospec=True) as patch_momrpc: + mock_momrpc = patch_momrpc.return_value + mock_momrpc.getProjectDetails.return_value = {'1000042': {'project_name': 'my_project'}} + + # now that we have a mocked the external dependencies, import cleanupservice + from lofar.sas.datamanagement.cleanup.service import createService + from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC + + class TestCleanupServiceAndRPC(unittest.TestCase): + def testRemovePath(self): + with CleanupRPC(busname=busname) as rpc: + #try some invalid input + self.assertFalse(rpc.removePath(None)['deleted']) + self.assertFalse(rpc.removePath(True)['deleted']) + self.assertFalse(rpc.removePath({'foo':'bar'})['deleted']) + self.assertFalse(rpc.removePath(['foo', 'bar'])['deleted']) + + #try some dangerous paths + #these should not be deleted + result = rpc.removePath('/') + self.assertFalse(result['deleted']) + self.assertTrue('Path does not start with' in result['message']) + + result = rpc.removePath('/foo/*/bar') + self.assertFalse(result['deleted']) + self.assertTrue('No wildcards allowed' in result['message']) + + result = rpc.removePath('/foo/ba?r') + self.assertFalse(result['deleted']) + self.assertTrue('No wildcards allowed' in result['message']) + + result = rpc.removePath('/data') + self.assertFalse(result['deleted']) + self.assertTrue('Path does not start with' in result['message']) + + result = rpc.removePath('/data/test-projects/') + self.assertFalse(result['deleted']) + self.assertTrue('Path should be a subdir of' in result['message']) + + result = rpc.removePath('/data/test-projects/foo') + self.assertFalse(result['deleted']) + self.assertTrue('Path should be a subdir of' in result['message']) + + result = rpc.removePath('/data/test-projects/foo/') + self.assertFalse(result['deleted']) + self.assertTrue('Path should be a subdir of' in result['message']) + + #try an actual delete, should work with mocked shutil.rmtree + self.assertTrue(rpc.removePath('/data/test-projects/foo/bar')['deleted']) + + def testRemoveTaskData(self): + with CleanupRPC(busname=busname) as rpc: + #try existing otdb_id=13 + self.assertTrue(rpc.removeTaskData(13)['deleted']) + + #try non_existing mom_project for otdb_id=14 + result = rpc.removeTaskData(14) + self.assertFalse(result['deleted']) + self.assertTrue('Could not find mom project details' in result['message']) + + #try non_existing otdb_id=15 + result = rpc.removeTaskData(15) + self.assertFalse(result['deleted']) + self.assertTrue('Could not find task' in result['message']) + + # create and run the service + with createService(busname=busname): + # and run all tests + unittest.main() except ConnectError as ce: logger.error(ce) -- GitLab From a44b4c8b135afd7965f39e9fa01c1d432d773ff1 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 11:58:35 +0000 Subject: [PATCH 302/933] Task #9351: added getPathForOTDBId method. added are you sure to cmdline client --- SAS/DataManagement/CleanupService/rpc.py | 18 +++++++++++++++--- SAS/DataManagement/CleanupService/service.py | 16 ++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/SAS/DataManagement/CleanupService/rpc.py b/SAS/DataManagement/CleanupService/rpc.py index fe17092eac8..c5abd796f85 100644 --- a/SAS/DataManagement/CleanupService/rpc.py +++ b/SAS/DataManagement/CleanupService/rpc.py @@ -12,6 +12,9 @@ class CleanupRPC(RPCWrapper): broker=None): super(CleanupRPC, self).__init__(busname, servicename, broker) + def getPathForOTDBId(self, otdb_id): + return self.rpc('GetPathForOTDBId', otdb_id=otdb_id) + def removePath(self, path): return self.rpc('RemovePath', path=path) @@ -23,7 +26,7 @@ def main(): from optparse import OptionParser # Check the invocation arguments - parser = OptionParser('%prog [options]', + parser = OptionParser('%prog [options] <otdb_id>', description='do cleanup actions on cep4 from the commandline') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') parser.add_option('-b', '--busname', dest='busname', type='string', default=DEFAULT_BUSNAME, help='Name of the bus exchange on the qpid broker, default: %s' % DEFAULT_BUSNAME) @@ -31,11 +34,20 @@ def main(): parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() - if len(sys.argv) == 1: + if len(args) == 0: parser.print_help() + exit(1) logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO if options.verbose else logging.WARN) with CleanupRPC(busname=options.busname, servicename=options.servicename, broker=options.broker) as rpc: - print rpc.removePath() + otdb_id = int(args[0]) + path = rpc.getPathForOTDBId(otdb_id) + print "This will delete everything in '%s'." % path + if raw_input("Are you sure? (y/n) ") == 'y': + result = rpc.removeTaskData(otdb_id) + print result['message'] + exit(0 if result['deleted'] else 1) + else: + print "Nothing deleted" diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index 15a7e49e8f2..fef5704eda6 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -45,6 +45,7 @@ class CleanupHandler(MessageHandlerInterface): self.projects_path = os.path.join(self.mountpoint, 'projects' if isProductionEnvironment() else 'test-projects') self.service2MethodMap = {'RemovePath': self._removePath, + 'GetPathForOTDBId': self._getPathForOTDBId, 'RemoveTaskData': self._removeTaskData} self.radbrpc = RADBRPC(busname=kwargs.pop("radb_busname", RADB_BUSNAME), servicename=kwargs.pop("radb_servicename", RADB_SERVICENAME), broker=kwargs.pop("broker", None)) @@ -59,11 +60,9 @@ class CleanupHandler(MessageHandlerInterface): self.radbrpc.close() self.momrpc.close() - def _removeTaskData(self, otdb_id): + def _getPathForOTDBId(self, otdb_id): if not isinstance(otdb_id, int): - message = "Provided otdb_id is not an int" - logger.error(message) - return {'deleted': False, 'message': message} + raise ValueError("Provided otdb_id is not an int") task = self.radbrpc.getTask(otdb_id=otdb_id) @@ -85,6 +84,15 @@ class CleanupHandler(MessageHandlerInterface): project_path = os.path.join(self.projects_path, "_".join(project_name.split())) task_data_path = os.path.join(project_path, 'L%s' % otdb_id) + return task_data_path + + def _removeTaskData(self, otdb_id): + if not isinstance(otdb_id, int): + message = "Provided otdb_id is not an int" + logger.error(message) + return {'deleted': False, 'message': message} + + task_data_path = self._getPathForOTDBId(otdb_id) return self._removePath(task_data_path) def _removePath(self, path): -- GitLab From dd715693a03a960391518cb6d7eff0821af9e91e Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 9 Jun 2016 12:57:33 +0000 Subject: [PATCH 303/933] Task #8887: Add authentication to nexus pulls in Cobalt_install.sh --- RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh index fa1531c76d5..17f7abfe722 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh @@ -31,7 +31,7 @@ for HOST in ${HOSTS:-cbm001 cbm002 cbm003 cbm004 cbm005 cbm006 cbm007 cbm008 cbm mkdir -p \$dl_dir && cd \$dl_dir || exit 1 # Download archive from NEXUS. -N: clobber existing files - wget -N --tries=3 --no-check-certificate \"${NEXUS_URL}\" || exit 1 + wget -N --tries=3 --no-check-certificate --username=macinstall --password=macinstall \"${NEXUS_URL}\" || exit 1 # The full pathnames are in the tar file, so unpack from root dir. # -m: don't warn on timestamping /localhome -- GitLab From 5af49128689a309075371dbfbbd9f0c5bdf7b74b Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 9 Jun 2016 13:15:57 +0000 Subject: [PATCH 304/933] Task #8887: Add authentication to nexus pulls in Cobalt_install.sh --- RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh index 17f7abfe722..3d246001f8e 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh @@ -31,7 +31,7 @@ for HOST in ${HOSTS:-cbm001 cbm002 cbm003 cbm004 cbm005 cbm006 cbm007 cbm008 cbm mkdir -p \$dl_dir && cd \$dl_dir || exit 1 # Download archive from NEXUS. -N: clobber existing files - wget -N --tries=3 --no-check-certificate --username=macinstall --password=macinstall \"${NEXUS_URL}\" || exit 1 + wget -N --tries=3 --no-check-certificate --user=macinstall --password=macinstall \"${NEXUS_URL}\" || exit 1 # The full pathnames are in the tar file, so unpack from root dir. # -m: don't warn on timestamping /localhome -- GitLab From c51220e1a5e05168e39c84e3c69b8d23394f6640 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 13:16:12 +0000 Subject: [PATCH 305/933] Task #8887: typo's --- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index d0269013145..d12766c78d1 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -134,12 +134,12 @@ class ResourceAssigner(): startTime = maxPredecessorEndTime + timedelta(minutes=1) endTime = startTime + timedelta(hours=1) logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s based on maxPredecessorEndTime (%s)', - startTime, endTime, otdb_id, maxPredecessorEndTime)) + startTime, endTime, otdb_id, maxPredecessorEndTime) else: startTime = datetime.utcnow() + timedelta(hours=1) + timedelta(minutes=1) endTime = startTime + timedelta(hours=1) logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s one hour from now', - startTime, endTime, otdb_id)) + startTime, endTime, otdb_id) # insert new task and specification in the radb # any existing specification and task with same otdb_id will be deleted automatically @@ -352,7 +352,7 @@ class ResourceAssigner(): else: logger.error('claimResources: unknown prop_type:%s' % prop_type_name) - subdicts = (k:v for k,v in needed_claim_for_resource_type.items() if isinstance(v, dict)) + subdicts = {k:v for k,v in needed_claim_for_resource_type.items() if isinstance(v, dict)} for subdict_name, subdict in subdicts.items(): logger.info('claimResources: processing resource_type: %s subdict_name: \'%s\' subdict_contents: %s' % (resource_type_name, subdict_name, subdict)) is_input = 'input' in subdict_name.lower() -- GitLab From d24f14866c0a61d16d8289d975948258d528c96a Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 13:17:23 +0000 Subject: [PATCH 306/933] Task #8887: only propagate CEP4 otdb updated start/endtimes --- .../propagator.py | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index 58b5cb7bcf4..ffa1a3de5ab 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -59,15 +59,23 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): try: radb_task = self.radb.getTask(otdb_id=treeId) if radb_task: - spec = self.otdb.taskGetSpecification(otdb_id=treeId)['specification'] - new_startime = spec['ObsSW.Observation.startTime'] - new_endtime = spec['ObsSW.Observation.stopTime'] - - new_startime = datetime.strptime(new_startime, ('%Y-%m-%d %H:%M:%S.%f' if '.' in new_startime else '%Y-%m-%d %H:%M:%S')) - new_endtime = datetime.strptime(new_endtime, ('%Y-%m-%d %H:%M:%S.%f' if '.' in new_endtime else '%Y-%m-%d %H:%M:%S')) - - logger.info("Updating task (otdb_id=%s, radb_id=%s, status=%s) startime to \'%s\' and endtime to \'%s\'", treeId, radb_task['id'], radb_task['status'], new_startime, new_endtime) - self.radb.updateTaskAndResourceClaims(radb_task['id'], starttime=new_startime, endtime=new_endtime) + # all CEP4 tasks have storage claim(s) + # and we do not want OTDB to update our RA scheduled tasks + # so check if we have storage claims + claims = self.radb.getResourceClaims(task_ids=radb_task['id'], resource_type='storage') + + if not claims: + #this is a CEP2 task, modified by the old scheduler + #update start/stop time + spec = self.otdb.taskGetSpecification(otdb_id=treeId)['specification'] + new_startime = spec['ObsSW.Observation.startTime'] + new_endtime = spec['ObsSW.Observation.stopTime'] + + new_startime = datetime.strptime(new_startime, ('%Y-%m-%d %H:%M:%S.%f' if '.' in new_startime else '%Y-%m-%d %H:%M:%S')) + new_endtime = datetime.strptime(new_endtime, ('%Y-%m-%d %H:%M:%S.%f' if '.' in new_endtime else '%Y-%m-%d %H:%M:%S')) + + logger.info("Updating task (otdb_id=%s, radb_id=%s, status=%s) startime to \'%s\' and endtime to \'%s\'", treeId, radb_task['id'], radb_task['status'], new_startime, new_endtime) + self.radb.updateTaskAndResourceClaims(radb_task['id'], starttime=new_startime, endtime=new_endtime) except Exception as e: logger.error(e) -- GitLab From 7aa27487984e8a0bc8bcbb1a8b956659f9062e6d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 13:51:59 +0000 Subject: [PATCH 307/933] Task #8887: put task to conflict if not all claims could be inserted --- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index d12766c78d1..0a4fb1ca968 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -202,6 +202,10 @@ class ResourceAssigner(): self._sendNotification(task, 'scheduled') self.processPredecessors(specification_tree) + else: + logger.warning('doAssignment: Not all claims could be inserted. Setting task %s status to conflict' % (taskId)) + self.radbrpc.updateTask(taskId, status='conflict') + self._sendNotification(task, 'conflict') def _sendNotification(self, task, status): try: -- GitLab From 87fc8e840b3fdcd1b3e8fd93c266fc3797c761a6 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 13:52:25 +0000 Subject: [PATCH 308/933] Task #8887: rest url for tasks/id/resourceclaims --- .../ResourceAssignmentEditor/lib/webservice.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 221c4a04064..ae017193d75 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -159,6 +159,12 @@ def resourceUsagesForTask(task_id): result = rarpc.getResourceUsages(task_ids=[task_id]) return jsonify({'resourceusages': result}) +@app.route('/rest/tasks/<int:task_id>/resourceclaims', methods=['GET']) +@gzipped +def resourceClaimsForTask(task_id): + result = rarpc.getResourceClaims(task_ids=[task_id], extended=True, include_properties=True) + return jsonify({'resourceclaims': result}) + @app.route('/rest/tasks') def getTasks(): return getTasksFromUntil(None, None) -- GitLab From ba303b5c3b57416751203ff6db74a691f2bb9dbc Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 14:22:02 +0000 Subject: [PATCH 309/933] Task #9351: dependencies --- SAS/ResourceAssignment/ResourceAssignmentEditor/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/CMakeLists.txt index 62f06532429..0ba9df72471 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/CMakeLists.txt @@ -1,6 +1,6 @@ # $Id: CMakeLists.txt 30355 2014-11-04 13:46:05Z loose $ -lofar_package(ResourceAssignmentEditor 0.1) +lofar_package(ResourceAssignmentEditor 0.1 DEPENDS MoMQueryService ResourceAssignmentService PyMessaging CleanupService) include(PythonInstall) set(USE_PYTHON_COMPILATION Off) -- GitLab From c10b5cf9dc72f3103431f427950147efd2bcd960 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 14:23:28 +0000 Subject: [PATCH 310/933] Task #9351: rest url to delete data for task --- .../lib/webservice.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 221c4a04064..d6587d2a064 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -50,6 +50,9 @@ from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAUL from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME from lofar.sas.resourceassignment.resourceassignmenteditor.mom import updateTaskMomDetails +from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC +from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME as DEFAULT_CLEANUP_BUSNAME +from lofar.sas.datamanagement.cleanup.config import DEFAULT_SERVICENAME as DEFAULT_CLEANUP_SERVICENAME #from lofar.sas.resourceassignment.resourceassigner. import updateTaskMomDetails logger = logging.getLogger(__name__) @@ -93,6 +96,7 @@ app.json_encoder = CustomJSONEncoder rarpc = None momrpc = None +curpc = None radbchangeshandler = None @app.route('/') @@ -236,6 +240,20 @@ def putTask(task_id): abort(404) abort(406) +@app.route('/rest/tasks/<int:task_id>/cleanup', methods=['DELETE']) +def cleanupTaskData(task_id): + try: + task = rarpc.getTask(task_id) + + if not task: + abort(404, 'No such task (id=%s)' % task_id) + + result = curpc.removeTaskData(task['otdb_id']) + logger.info(result) + return jsonify(result) + except Exception as e: + abort(500) + @app.route('/rest/tasks/<int:task_id>/resourceclaims') def taskResourceClaims(task_id): return jsonify({'taskResourceClaims': rarpc.getResourceClaims(task_id=task_id, include_properties=True)}) @@ -323,6 +341,8 @@ def main(): parser.add_option('--radb_notification_subjects', dest='radb_notification_subjects', type='string', default=DEFAULT_RADB_CHANGES_SUBJECTS, help='Subject(s) to listen for on the radb notification bus exchange on the qpid broker, default: %default') parser.add_option('--mom_busname', dest='mom_busname', type='string', default=DEFAULT_MOMQUERY_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momservice listens, default: %default') parser.add_option('--mom_servicename', dest='mom_servicename', type='string', default=DEFAULT_MOMQUERY_SERVICENAME, help='Name of the momservice, default: %default') + parser.add_option('--cleanup_busname', dest='cleanup_busname', type='string', default=DEFAULT_CLEANUP_BUSNAME, help='Name of the bus exchange on the qpid broker on which the cleanupservice listens, default: %default') + parser.add_option('--cleanup_servicename', dest='cleanup_servicename', type='string', default=DEFAULT_CLEANUP_SERVICENAME, help='Name of the cleanupservice, default: %default') parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() @@ -330,13 +350,15 @@ def main(): level=logging.DEBUG if options.verbose else logging.INFO) global rarpc - rarpc = RARPC(busname=DEFAULT_RADB_BUSNAME, servicename=DEFAULT_RADB_SERVICENAME, broker=options.broker) + rarpc = RARPC(busname=options.radb_busname, servicename=options.radb_servicename, broker=options.broker) global momrpc - momrpc = MoMQueryRPC(busname=DEFAULT_MOMQUERY_BUSNAME, servicename=DEFAULT_MOMQUERY_SERVICENAME, timeout=2.5, broker=options.broker) + momrpc = MoMQueryRPC(busname=options.mom_busname, servicename=options.mom_servicename, timeout=2.5, broker=options.broker) + global curpc + curpc = CleanupRPC(busname=options.cleanup_busname, servicename=options.cleanup_servicename, broker=options.broker) global radbchangeshandler - radbchangeshandler = RADBChangesHandler(DEFAULT_RADB_CHANGES_BUSNAME, broker=options.broker, momrpc=momrpc) + radbchangeshandler = RADBChangesHandler(options.radb_notification_busname, broker=options.broker, momrpc=momrpc) - with radbchangeshandler, rarpc, momrpc: + with radbchangeshandler, rarpc, curpc, momrpc: '''Start the webserver''' app.run(debug=options.verbose, threaded=True, host='0.0.0.0', port=options.port) -- GitLab From 2d348dd5d282f27b0354fc923a269a24508cc7ec Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 14:43:16 +0000 Subject: [PATCH 311/933] Task #8887: some typo fixes --- .../base_resource_estimator.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py index 20604c62da0..c840d5752c4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py @@ -23,6 +23,7 @@ """ Base class for Resource Estimators """ import logging +import copy from datetime import datetime from lofar.common.datetimeutils import totalSeconds from datetime import datetime, timedelta @@ -47,7 +48,7 @@ class BaseResourceEstimator(object): logger.error("missing keys: %s" % ', '.join(missing_keys)) return False return True - + def _getDateTime(self, date_time): return datetime.strptime(date_time, '%Y-%m-%d %H:%M:%S') @@ -63,12 +64,12 @@ class BaseResourceEstimator(object): def _calculate(self, parset, input_files={}): raise NotImplementedError('calculate() in base class is called. Please implement calculate() in your subclass') - def _filterInputs(input_files, identifications): - """'input_files': - {'uv': {'nr_of_uv_files': 481, 'uv_file_size': 1482951104, + def _filterInputs(self, input_files, identifications): + """'input_files': + {'uv': {'nr_of_uv_files': 481, 'uv_file_size': 1482951104, 'identifications':['mom.G355415.B2.1.C.SAP000.uv.dps','mom.G355415.B2.1.C.SAP001.uv.dps','mom.G355415.B2.1.C.SAP002.uv.dps']}, 'saps': [{'sap_nr': 0, 'properties': {'nr_of_uv_files': 319}}, - {'sap_nr': 1, 'properties': {'nr_of_uv_files': 81}}, + {'sap_nr': 1, 'properties': {'nr_of_uv_files': 81}}, {'sap_nr': 2, 'properties': {'nr_of_uv_files': 81}} ]}}} 'identifications': 'mom.G355415.B2.1.C.SAP000.uv.dps','mom.G355415.B2.1.C.SAP001.uv.dps' @@ -81,7 +82,7 @@ class BaseResourceEstimator(object): if data_type == 'saps': continue if identification in data_properties['identifications']: #This data_type matches an input identification - output_files[data_type] = data_properties.deepcopy() + output_files[data_type] = copy.deepcopy(data_properties) if 'SAP' in identification: #Special case for identifications that contain a SAP number # We would need to make this smarter if we can have the data from multiple SAPs as input. # Or or different input sources of the same data_type @@ -91,14 +92,14 @@ class BaseResourceEstimator(object): sap_nr = int(s[3:]) except: sap_nr = None - for sap in input_files['saps'].items(): + for sap in input_files['saps']: if sap['sap_nr'] == sap_nr: - for sap_data_type, sap_data_value in sap['properties']: + for sap_data_type, sap_data_value in sap['properties'].items(): if sap_data_type in data_properties: # We found this SAP's nr_of_<data_type>_files output_files[data_type][sap_data_type] = sap_data_value # We only count the nr_of_files from this SAP output_files['saps'].append({'sap_nr': sap_nr, 'properties': {sap_data_type:sap_data_value}}) return output_files - + def verify_and_estimate(self, parset, input_files={}): """ Create estimates for a single process based on its parset and input files""" -- GitLab From 328f4eee39a133640a7de0279f29441061ed4d60 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 14:43:41 +0000 Subject: [PATCH 312/933] Task #8887: additional logging --- SAS/ResourceAssignment/ResourceAssignmentService/rpc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py index cff26544ec6..8cc78a6d090 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py @@ -46,8 +46,9 @@ class RARPC(RPCWrapper): extended=extended, include_properties=include_properties) - logger.info("found %s claims" % len(claims)) - + logger.info("found %s claims for claim_ids=%s, lower_bound=%s, upper_bound=%s, task_ids=%s, status=%s, resource_type=%s" % + (len(claims), claim_ids, lower_bound, upper_bound, task_ids, status, resource_type)) + for claim in claims: claim['starttime'] = claim['starttime'].datetime() claim['endtime'] = claim['endtime'].datetime() -- GitLab From 9f1ac18fe713ccb736bfe44c7224769e1d95d031 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 9 Jun 2016 14:52:13 +0000 Subject: [PATCH 313/933] Task #8887: check for key --- .../OTDBtoRATaskStatusPropagator/propagator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index ffa1a3de5ab..9cd4b667a57 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -104,11 +104,11 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): # pipeline control puts tasks in queued state for pipelines, # and gives the pipeline to slurm - # from that moment it is not known exactly the the task will run. + # from that moment it is not known exactly when the task will run. # we do know however that it will run after its predecessors, and after 'now' # reflect that in radb for pipelines task = self.radb.getTask(otdb_id=treeId) - if task and task['type'] == 'pipeline': + if task and task['type'] == 'pipeline' and 'predecessor_ids' in task: predecessor_tasks = [self.radb.getTask(pid) for pid in task['predecessor_ids']] if predecessor_tasks: pred_endtimes = [t['endtime'] for t in predecessor_tasks] -- GitLab From 2066f7f6b602d27fe81b4dbf8935254146771a9a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 9 Jun 2016 15:01:29 +0000 Subject: [PATCH 314/933] Task #8887: Fix user settings for pipeline docker image --- MAC/Services/src/PipelineControl.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index e91f8ea7223..88d7e5a0082 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -361,11 +361,13 @@ class PipelineControl(OTDBBusListener): # call runPipeline.sh in the image on this node "docker run --rm" " --net=host" - " -v /data:/data" - " -u $UID" " -e LOFARENV={lofarenv}" - " -v $HOME/.ssh:/home/lofar/.ssh:ro" + " -u $UID" + " -e USER=$USER" + " -e HOME=$HOME" + " -v $HOME/.ssh:$HOME/.ssh:ro" " -e SLURM_JOB_ID=$SLURM_JOB_ID" + " -v /data:/data" " {image}" " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -B {status_bus}" .format( -- GitLab From 6bbdcf7ccca6b0f1c347b76efe81acaf07b3c7db Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 10 Jun 2016 11:17:23 +0000 Subject: [PATCH 315/933] Task #9537: create task branch -- GitLab From afdcab8f4fc09da531d8d112891d6d8c86173fcf Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 10 Jun 2016 11:22:39 +0000 Subject: [PATCH 316/933] Task #9537: checkin of current status for tec --- .gitattributes | 2 + CEP/DP3/DPPP/include/DPPP/CMakeLists.txt | 2 +- CEP/DP3/DPPP/include/DPPP/GainCal.h | 1 + CEP/DP3/DPPP/include/DPPP/phasefitter.h | 221 +++++++++++++++++++++++ CEP/DP3/DPPP/src/CMakeLists.txt | 1 + CEP/DP3/DPPP/src/GainCal.cc | 81 ++++++++- CEP/DP3/DPPP/src/phasefitter.cc | 111 ++++++++++++ 7 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 CEP/DP3/DPPP/include/DPPP/phasefitter.h create mode 100644 CEP/DP3/DPPP/src/phasefitter.cc diff --git a/.gitattributes b/.gitattributes index 32f2277e1b6..81ff455018f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -854,6 +854,7 @@ CEP/DP3/DPPP/include/DPPP/Stokes.h -text CEP/DP3/DPPP/include/DPPP/SubtractMixed.h -text CEP/DP3/DPPP/include/DPPP/SubtractNew.h -text CEP/DP3/DPPP/include/DPPP/UVWCalculator.h -text +CEP/DP3/DPPP/include/DPPP/phasefitter.h -text CEP/DP3/DPPP/package.dox -text CEP/DP3/DPPP/share/HBAdefault -text CEP/DP3/DPPP/share/LBAdefault -text @@ -883,6 +884,7 @@ CEP/DP3/DPPP/src/Stokes.cc -text CEP/DP3/DPPP/src/SubtractMixed.cc -text CEP/DP3/DPPP/src/SubtractNew.cc -text CEP/DP3/DPPP/src/__init__.py -text +CEP/DP3/DPPP/src/phasefitter.cc -text CEP/DP3/DPPP/src/taqlflagger -text CEP/DP3/DPPP/test/findenv.run_tmpl -text CEP/DP3/DPPP/test/tApplyBeam.run -text diff --git a/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt b/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt index c92e64372df..07b6c83e8f2 100644 --- a/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt +++ b/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt @@ -16,7 +16,7 @@ set(inst_HEADERS DemixerNew.h DemixInfo.h DemixWorker.h ApplyBeam.h ApplyBeam.tcc Predict.h - GainCal.h StefCal.h + GainCal.h StefCal.h phasefitter.h ) # Create symbolic link to include directory. diff --git a/CEP/DP3/DPPP/include/DPPP/GainCal.h b/CEP/DP3/DPPP/include/DPPP/GainCal.h index fb3f72b404b..6181c6f5107 100644 --- a/CEP/DP3/DPPP/include/DPPP/GainCal.h +++ b/CEP/DP3/DPPP/include/DPPP/GainCal.h @@ -29,6 +29,7 @@ #include <DPPP/DPInput.h> #include <DPPP/DPBuffer.h> +#include <DPPP/phasefitter.h> #include <DPPP/StefCal.h> #include <DPPP/Patch.h> #include <DPPP/Predict.h> diff --git a/CEP/DP3/DPPP/include/DPPP/phasefitter.h b/CEP/DP3/DPPP/include/DPPP/phasefitter.h new file mode 100644 index 00000000000..0f3aec2e164 --- /dev/null +++ b/CEP/DP3/DPPP/include/DPPP/phasefitter.h @@ -0,0 +1,221 @@ +/** + * @file phasefitter.h Implements TEC model phase filter @ref PhaseFitter. + * @author André Offringa + * @date 2016-04-06 + */ + +#ifndef PHASE_FITTER_H +#define PHASE_FITTER_H + +#include <vector> +#include <cmath> +#include <cstring> + +/** + * Phase fitter that can force phase solutions over frequency onto a TEC model. + * To use: + * - Construct and set the frequencies (with @ref FrequencyData() ) and if already possible + * the weights (@ref WeightData()). + * - Perform a calibration iteration. + * - Set the phase values (@ref PhaseData()) and, if not yet done, the weights (@ref WeightData()) + * to the current phase solutions / weights of a single antenna. + * - Call @ref FitDataToTECModel(). + * - Read the new phase values. + * - When fitting multiple polarizations, the above steps should be done twice for each + * diagonal value of the Jones matrix. Also repeat for all antennae. + * - Continue the iteration with the new phase values. + */ +class PhaseFitter +{ + public: + /** + * Construct a phase fitter for the given number of channels. + * Weights are initialized to unity. The fitting accuracy is + * initialized to 1e-6. + * @param channelCount number of channels. + */ + PhaseFitter(size_t channelCount) : + _phases(channelCount, 0.0), + _frequencies(channelCount, 0.0), + _weights(channelCount, 1.0), + _fittingAccuracy(1e-6) + { } + + ~PhaseFitter() = default; + PhaseFitter(const PhaseFitter&) = delete; + PhaseFitter& operator=(const PhaseFitter&) = delete; + + /** + * Fits the given phase values to a TEC model. This function is + * robust even when phase wrapping occurs. + * After this call, the @ref PhaseData() satisfy the TEC model. + * The TEC model has two parameters and are fitted as described in + * @ref FitTECModelParameters(). + * @param alpha Found value for the alpha parameter. + * @param beta Found value for the beta parameter. + * + * @returns Cost of the found solution. + */ + double FitDataToTECModel(double& alpha, double& beta); + + /** + * Fits the given phase values to a TEC model using prior estimates of the + * model parameters. This method is similar to @ref FitDataToTECModel(), + * except that it will use the provided initial values of alpha and + * beta to speed up the solution. When the provided initial values + * are not accurate, the fit might not be accurate. + * + * @todo No fast method has been implemented -- instead it will perform a full parameter search. + * @param alpha Estimate of alpha parameter on input, found value on output. + * @param beta Estimate of beta parameter on input, found value on output. + * + * @returns Cost of the found solution. + */ + double FitDataToTECModelWithInitialValues(double& alpha, double& beta) + { + return FitDataToTECModel(alpha, beta); + } + + /** + * Fit the data and get the best fitting parameters. The model + * used is f(nu) = alpha/nu + beta, with possible 2pi wrappings in the + * data. + * The phase data is not changed. + * The alpha parameter is linearly related to the TEC. The beta parameter + * is a constant phase offset, given in radians. + * The fitting algorithm uses a combination of brute force searching and + * binary-like searching (ternary search). + * @param alpha Will be set to the fitted value for the alpha parameter + * (value on input is not used). + * @param beta Will be set to the fitted value for the beta parameter + * (value on input is not used). + */ + void FitTECModelParameters(double& alpha, double& beta) const; + + /** + * Get a pointer to the array of phase values. This array should be filled with the + * phases of solutions before calling one of the fit methods. @ref FitDataToTECModel() + * sets this array to the fitted phases. + * Normally, these values should be set to std::arg(z) or atan2(z.imag(), z.real()), where + * z is a complex solution for one polarizations. All phases should correspond to the same + * polarizations, i.e., different polarizations (xx/yy/ll/rr, etc.) should be independently + * fitted. + * @returns Array of @ref Size() doubles with the phases. + */ + double* PhaseData() { return _phases.data(); } + + /** + * Get a constant pointer to the array of values. + * @returns Constant array of @ref Size() doubles with the phases. + */ + const double* PhaseData() const { return _phases.data(); } + + /** + * Get a pointer to the array of frequency values. This array should be set to the + * frequency values of the channels, such that FrequencyData()[ch] is the frequency + * corresponding to the phase value PhaseData()[ch]. The fitter will not change this + * array. + * @returns Array of @ref Size() doubles with the frequencies in Hz. + */ + double* FrequencyData() { return _frequencies.data(); } + + /** + * Constant frequency data. + * @returns Constant array of @ref Size() doubles with the frequencies in Hz. + */ + const double* FrequencyData() const { return _frequencies.data(); } + + /** + * This array should be filled with the weights + * of the channel phase solutions. If the solver supports weights during solving, this value should + * be set to the sum of weights of all visibilities that are used for the solution of + * this channel. If the solver does not support weights, it should be set to the number + * of unflagged visibilities used by the solver to generate the corresponding phase. Another + * way of saying this, is that the weights should be set to the esimated inverse variance of the phase + * solutions. + * + * The use of weights will make sure that noisy channels do not bias the result. Weighting is + * for example helpful to avoid that the few remaining samples in a badly RFI contaminated + * channels cause the fit to be inaccurate. + * + * While the weights could be different for each antenna solution, generally the weight of a channel + * is constant over the antennas. The latter implies that the weights can be set once before + * the solution starts, and only the @ref PhaseData() need to be changed within solution + * iterations. + * + * The weights are initially set to one. + * + * @returns Array of @ref Size() doubles with the weights. + */ + double* WeightData() { return _weights.data(); } + + /** + * Constant array of weights, as described above. + * @returns Constant array of @ref Size() doubles with weights. + */ + const double* WeightData() const { return _weights.data(); } + + /** + * Number of channels used for the fitter. + * @returns Number of channels. + */ + size_t Size() const { return _phases.size(); } + + /** + * Get the fitting accuracy. The fitter will stop once this accuracy is approximately reached. + * The default value is 1e-6. + * @returns Fitting accuracy. + */ + double FittingAccuracy() const { return _fittingAccuracy; } + + /** + * Change the fitting accuracy. See @ref FittingAccuracy(). + * @param newAccuracy New accuracy. + */ + void SetFittingAccuracy(double newAccuracy) { _fittingAccuracy = newAccuracy; } + + /** + * Evaluate the cost function for given TEC model parameters. The higher the + * cost, the worser the data fit the given parameters. + * @param alpha TEC parameter + * @param beta Phase offset parameter + * @returns sum of | nu_i / alpha + beta - theta_i |, and each sum term is + * phase unwrapped. + */ + double TECModelCost(double alpha, double beta) const; + + /** + * Like @ref TECModelFunc(), but 2-pi wrapped. + * @param nu Frequency in Hz + * @param alpha TEC parameter (in undefined units) + * @param beta Phase offset parameter (in radians) + * @returns | nu_i / alpha + beta | % 2pi + */ + static double TECModelFunc(double nu, double alpha, double beta) + { + return alpha / nu + beta; + } + + /** + * Like @ref TECModelFunc(), but 2-pi wrapped. + * @param nu Frequency in Hz + * @param alpha TEC parameter (in undefined units) + * @param beta Phase offset parameter (in radians) + * @returns | nu_i / alpha + beta | % 2pi + */ + static double TECModelFuncWrapped(double nu, double alpha, double beta) + { + return fmod(alpha / nu + beta, 2*M_PI); + } + private: + std::vector<double> _phases, _frequencies, _weights; + double _fittingAccuracy; + + double fitTECModelBeta(double alpha, double betaEstimate) const; + void bruteForceSearchTECModel(double& lowerAlpha, double& upperAlpha, double& beta) const; + double ternarySearchTECModelAlpha(double startAlpha, double endAlpha, double& beta) const; + void fillDataWithTECModel(double alpha, double beta); + +}; + +#endif diff --git a/CEP/DP3/DPPP/src/CMakeLists.txt b/CEP/DP3/DPPP/src/CMakeLists.txt index 51598d1c8b1..0c4077b777b 100644 --- a/CEP/DP3/DPPP/src/CMakeLists.txt +++ b/CEP/DP3/DPPP/src/CMakeLists.txt @@ -20,6 +20,7 @@ lofar_add_library(dppp DemixerNew.cc DemixInfo.cc DemixWorker.cc Predict.cc ApplyBeam.cc + phasefitter.cc ) lofar_add_bin_program(NDPPP NDPPP.cc) diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index 20280fada09..9454139959f 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -25,6 +25,7 @@ #include <DPPP/GainCal.h> #include <DPPP/Simulate.h> #include <DPPP/ApplyCal.h> +#include <DPPP/phasefitter.h> #include <DPPP/CursorUtilCasa.h> #include <DPPP/DPBuffer.h> #include <DPPP/DPInfo.h> @@ -120,7 +121,8 @@ namespace LOFAR { ASSERT(itsMode=="diagonal" || itsMode=="phaseonly" || itsMode=="fulljones" || itsMode=="scalarphase" || - itsMode=="amplitudeonly" || itsMode=="scalaramplitude"); + itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || + itsMode=="tec"); } GainCal::~GainCal() @@ -486,6 +488,23 @@ namespace LOFAR { uint iter=0; + PhaseFitter fitter(itsNFreqCells); + + if (itsMode=="tec") { + // Set frequency data for TEC fitter + double* nu = fitter.FrequencyData(); + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + double meanfreq=0; + uint chmin=itsNChan*freqCell; + uint chmax=min(info().nchan(), chmin+itsNChan); + + meanfreq = std::accumulate(info().chanFreqs().data()+chmin, + info().chanFreqs().data()+chmax, 0.0); + + nu[freqCell] = meanfreq / (chmax-chmin); + } + } + std::vector<StefCal::Status> converged(itsNFreqCells,StefCal::NOTCONVERGED); for (;iter<itsMaxIter;++iter) { bool allConverged=true; @@ -502,6 +521,66 @@ namespace LOFAR { } } + double alpha=0., beta=0.; + if (itsMode=="tec") { + casa::Matrix<casa::DComplex> tecsol(itsNFreqCells, info().antennaNames().size()); + + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + casa::Matrix<casa::DComplex> sol = iS[freqCell].getSolution(false); + for (uint ant=0; ant<info().antennaNames().size(); ++ant) { + tecsol(freqCell, ant) = sol(ant, 0)/sol(0, 0); + } + } + + uint nSt = info().antennaNames().size(); + for (uint ant=0; ant<nSt; ++ant) { + uint numpoints=0; + double cost=0; + double* phases = fitter.PhaseData(); + double* weights = fitter.WeightData(); + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + if (iS[freqCell].getStationFlagged()[ant%nSt]) { + phases[freqCell] = 0; + weights[freqCell] = 0; + } else { + phases[freqCell] = arg(tecsol(freqCell, ant)); + weights[freqCell] = 1; + numpoints++; + } + } + + if (itsDebugLevel>0 && ant==1) { + //cout<<endl<<"phases["<<ant<<"]=["; + cout<<"unfitted["<<iter<<"]=["; + uint freqCell=0; + for (; freqCell<itsNFreqCells-1; ++freqCell) { + cout<<phases[freqCell]<<","; + } + cout<<phases[freqCell]<<"];"<<endl; + } + if (numpoints>1) { + cost=fitter.FitDataToTECModel(alpha, beta); + // Update solution in stefcal + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + iS[freqCell].getSolution(false)(ant, 0) = polar(1., phases[freqCell]); + } + } else if (itsDebugLevel>5) { + cout<<"Not enough points ("<<numpoints<<") for antenna "<<ant<<endl; + } + if (itsDebugLevel>1 && ant==1) { + cout<<"fitted["<<iter<<"]=["; + uint freqCell=0; + for (; freqCell<itsNFreqCells-1; ++freqCell) { + cout<<phases[freqCell]<<","; + } + cout<<phases[freqCell]<<"]"<<endl; + } + if (itsDebugLevel>0 && ant==1) { + cout << "fitdata["<<iter<<"]=[" << alpha << ", " << beta << ", " << cost << "];" << endl; + } + } + } + if (allConverged) { break; } diff --git a/CEP/DP3/DPPP/src/phasefitter.cc b/CEP/DP3/DPPP/src/phasefitter.cc new file mode 100644 index 00000000000..ccc034f1788 --- /dev/null +++ b/CEP/DP3/DPPP/src/phasefitter.cc @@ -0,0 +1,111 @@ +#include <lofar_config.h> +#include "DPPP/phasefitter.h" + +#include <limits> + +double PhaseFitter::TECModelCost(double alpha, double beta) const +{ + double costVal = 0.0; + for(int i=0; i!=Size(); ++i) { + double estphase = TECModelFuncWrapped(_frequencies[i], alpha, beta); + double dCost = fmod(std::fabs(estphase - _phases[i]), 2.0*M_PI); + if(dCost > M_PI) dCost = 2.0*M_PI - dCost; + dCost *= _weights[i]; + costVal += dCost; + } + return costVal; +} + +double PhaseFitter::fitTECModelBeta(double alpha, double betaEstimate) const { + double weightSum = 0.0; + for(size_t iter=0; iter!=3; ++iter) { + double sum = 0.0; + for(size_t i=0; i!=Size(); ++i) { + double p = _phases[i], e = TECModelFunc(_frequencies[i], alpha, betaEstimate); + double dist = fmod(p - e, 2.0*M_PI); + if(dist < -M_PI) + dist += 2.0*M_PI; + else if(dist > M_PI) + dist -= 2.0*M_PI; + sum += dist * _weights[i]; + weightSum += _weights[i]; + } + betaEstimate = betaEstimate + sum / weightSum; + } + return fmod(betaEstimate, 2.0*M_PI); +} + +void PhaseFitter::bruteForceSearchTECModel(double& lowerAlpha, double& upperAlpha, double& beta) const +{ + double minCost = std::numeric_limits<double>::max(); + double alphaOversampling = 256; + //size_t betaOversampling = 16; + double dAlpha = upperAlpha - lowerAlpha; + const double bandw = _frequencies.back() - _frequencies.front(); + int alphaIndex = 0; + for(int i=0; i!=alphaOversampling; ++i) { + // make r between [0, 1] + double r = double(i)/alphaOversampling; + double alpha = lowerAlpha + r*dAlpha; + double curBeta = fitTECModelBeta(alpha, beta); + double costVal = TECModelCost(alpha, curBeta); + if(costVal < minCost) { + beta = curBeta; + minCost = costVal; + alphaIndex = i; + } + } + double newLowerAlpha = double(alphaIndex-1)/alphaOversampling*dAlpha + lowerAlpha; + upperAlpha = double(alphaIndex+1)/alphaOversampling*dAlpha + lowerAlpha; + lowerAlpha = newLowerAlpha; +} + +double PhaseFitter::ternarySearchTECModelAlpha(double startAlpha, double endAlpha, double& beta) const +{ + size_t iter = 0; + double dCost, lAlpha, rAlpha; + do { + lAlpha = startAlpha + (endAlpha - startAlpha) * (1.0/3.0); + rAlpha = startAlpha + (endAlpha - startAlpha) * (2.0/3.0); + double lBeta = fitTECModelBeta(lAlpha, beta); + double rBeta = fitTECModelBeta(rAlpha, beta); + double lCost = TECModelCost(lAlpha, lBeta); + double rCost = TECModelCost(rAlpha, rBeta); + if(lCost < rCost) { + endAlpha = rAlpha; + beta = lBeta; + } else { + startAlpha = lAlpha; + beta = rBeta; + } + dCost = std::fabs(lCost - rCost); + ++iter; + } while(dCost > _fittingAccuracy && iter < 100); + double finalAlpha = (lAlpha + rAlpha) * 0.5; + beta = fitTECModelBeta(finalAlpha, beta); + return finalAlpha; +} + +void PhaseFitter::fillDataWithTECModel(double alpha, double beta) +{ + for(size_t ch=0; ch!=Size(); ++ch) + _phases[ch] = TECModelFunc(_frequencies[ch], alpha, beta); +} + +void PhaseFitter::FitTECModelParameters(double& alpha, double& beta) const +{ + double lowerAlpha = -40000.0e6, upperAlpha = 40000.0e6; + bruteForceSearchTECModel(lowerAlpha, upperAlpha, beta); + alpha = (lowerAlpha + upperAlpha) * 0.5; + //beta = fitBeta(alpha, beta); + alpha = ternarySearchTECModelAlpha(lowerAlpha, upperAlpha, beta); +} + +double PhaseFitter::FitDataToTECModel(double& alpha, double& beta) +{ + FitTECModelParameters(alpha, beta); + double cost = TECModelCost(alpha, beta); + fillDataWithTECModel(alpha, beta); + return cost; +} + -- GitLab From 29d53cc63a4fe5294c86ca2419b466f2c77aad87 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 10 Jun 2016 12:27:29 +0000 Subject: [PATCH 317/933] Task #9541: applycal can now apply commonscalaramplitude --- CEP/DP3/DPPP/src/ApplyCal.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CEP/DP3/DPPP/src/ApplyCal.cc b/CEP/DP3/DPPP/src/ApplyCal.cc index 402dbac3abb..0e4f28cdca3 100644 --- a/CEP/DP3/DPPP/src/ApplyCal.cc +++ b/CEP/DP3/DPPP/src/ApplyCal.cc @@ -178,6 +178,8 @@ namespace LOFAR { itsParmExprs.push_back("CommonScalarPhase"); } else if (itsCorrectType == "rotationmeasure") { itsParmExprs.push_back("RotationMeasure"); + } else if (itsCorrectType == "commonscalaramplitude") { + itsParmExprs.push_back("CommonScalarAmplitude"); } else { THROW (Exception, "Correction type " + itsCorrectType + @@ -468,6 +470,10 @@ namespace LOFAR { itsParms(0, ant, tf) = polar(1., parmvalues[0][ant][tf]); itsParms(1, ant, tf) = polar(1., parmvalues[0][ant][tf]); } + else if (itsCorrectType=="commonscalaramplitude") { + itsParms(0, ant, tf) = parmvalues[0][ant][tf]; + itsParms(1, ant, tf) = parmvalues[0][ant][tf]; + } // Invert (rotationmeasure and commonrotationangle are already inverted) if (itsInvert && itsParms.shape()[0]==2) { -- GitLab From 42be963f3f754ca91ad4fdeaa973c5b0bd7c2696 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 10 Jun 2016 13:25:11 +0000 Subject: [PATCH 318/933] Task #8887: Allocate 20 cores/node/job, cancel abort-trigger job as well on received status changes, prevent dependencies on subjobs --- MAC/Services/src/PipelineControl.py | 18 +++++++++++++++--- MAC/Services/test/tPipelineControl.py | 11 +++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 88d7e5a0082..31ce1b09762 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -202,8 +202,12 @@ class Slurm(object): lines = stdout.split("\n") + # We cannot rely on subjobs, so filter them out + # by only accepting numbers, not dots ("136.0") or strings ("136.batch"). + lines = [l in lines if l.isdigit()] + if len(lines) > 1: - logger.warning("Duplicate job name: %s" % (jobname,)) + logger.warning("Duplicate job name: %s matches jobs [%s]" % (jobname,", ".join(lines))) # Use last occurance if there are multiple return lines[-1] @@ -270,8 +274,13 @@ class PipelineControl(OTDBBusListener): # Cancel corresponding SLURM job, causing any successors # to be cancelled as well. jobName = parset.slurmJobName() - logger.info("Cancelling job %s", jobName) - self.slurm.cancel(jobName) + + def cancel(jobName): + logger.info("Cancelling job %s", jobName) + self.slurm.cancel(jobName) + + cancel("%s-abort-trigger" % jobName) + cancel(jobName) """ More statusses we want to abort on. @@ -336,6 +345,9 @@ class PipelineControl(OTDBBusListener): # TODO: Compute nr nodes "--nodes=24", + + # TODO: Compute nr cores/node + "--cpus-per-task=20", # Define better places to write the output os.path.expandvars("--output=/data/log/runPipeline-%s.log" % (otdbId,)), diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index 03283a0b385..927a2507c93 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -69,6 +69,13 @@ class TestSlurmJobs(unittest.TestCase): self.assertEqual(Slurm().jobid("foo"), "119") + def test_one_job_with_subjobs(self): + """ Test 'scontrol show job' output for a single job that has subjobs. """ + with patch('lofar.mac.PipelineControl.Slurm._runCommand') as MockRunSlurmCommand: + MockRunSlurmCommand.return_value = """119\n119.0\n119.batch""" + + self.assertEqual(Slurm().jobid("foo"), "119") + class TestPipelineControlClassMethods(unittest.TestCase): def test_shouldHandle(self): """ Test whether we filter the right OTDB trees. """ @@ -227,7 +234,7 @@ class TestPipelineControl(unittest.TestCase): # Check if job was scheduled self.assertIn("3", pipelineControl.slurm.scheduled_jobs) - self.assertIn("3-aborted", pipelineControl.slurm.scheduled_jobs) + self.assertIn("3-abort-trigger", pipelineControl.slurm.scheduled_jobs) def testPredecessors(self): """ @@ -249,7 +256,7 @@ class TestPipelineControl(unittest.TestCase): # Check if job was scheduled self.assertIn("1", pipelineControl.slurm.scheduled_jobs) - self.assertIn("1-aborted", pipelineControl.slurm.scheduled_jobs) + self.assertIn("1-abort-trigger", pipelineControl.slurm.scheduled_jobs) # Earliest start of this job > stop time of observation for p in pipelineControl.slurm.scheduled_jobs["1"][1]["sbatch_params"]: -- GitLab From 250765271fb72c8f9bdc01e739edd5390329c841 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 10 Jun 2016 13:26:24 +0000 Subject: [PATCH 319/933] Task #8887: Log at INFO by default and at DEBUG when verbose --- MAC/Services/src/pipelinecontrol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/src/pipelinecontrol b/MAC/Services/src/pipelinecontrol index 9d03027965f..610180e3e5a 100644 --- a/MAC/Services/src/pipelinecontrol +++ b/MAC/Services/src/pipelinecontrol @@ -45,7 +45,7 @@ if __name__ == "__main__": sys.exit(1) logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', - level=logging.INFO if options.verbose else logging.WARN) + level=logging.DEBUG if options.verbose else logging.INFO) with PipelineControl(otdb_notification_busname=options.otdb_notification_busname, otdb_service_busname=options.otdb_service_busname) as pipelineControl: waitForInterrupt() -- GitLab From 6daf443a7e417b1610674db263ed9c950fd75521 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 10 Jun 2016 13:40:47 +0000 Subject: [PATCH 320/933] Task #8887: Fixed typo --- MAC/Services/src/PipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 31ce1b09762..008d8c927f0 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -204,7 +204,7 @@ class Slurm(object): # We cannot rely on subjobs, so filter them out # by only accepting numbers, not dots ("136.0") or strings ("136.batch"). - lines = [l in lines if l.isdigit()] + lines = [l for l in lines if l.isdigit()] if len(lines) > 1: logger.warning("Duplicate job name: %s matches jobs [%s]" % (jobname,", ".join(lines))) -- GitLab From 963b731eaceb31285e850b2489abf825e33b6e9b Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Fri, 10 Jun 2016 15:56:25 +0000 Subject: [PATCH 321/933] Task #9192: Changes to filter input in translator --- .../lib/translator.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 1d617569ba0..22c2fa14917 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -210,15 +210,20 @@ class RAtoOTDBTranslator(): """ result = {} if 'saps' in storage_claim: - result['saps'] = [] + saps = [] for s in storage_claim['saps']: properties = {} for p in s['properties']: - properties[p['type_name']] = p['value'] - result['saps'].append({'sap_nr' : s['sap_nr'], 'properties': properties}) + if p['io_type_name'] == 'output': + properties[p['type_name']] = p['value'] + if properties: + saps.append({'sap_nr' : s['sap_nr'], 'properties': properties}) + if saps: + result['saps'] = saps if 'properties' in storage_claim: for p in storage_claim['properties']: - result[p['type_name']] = p['value'] + if p['io_type_name'] == 'output': + result[p['type_name']] = p['value'] logging.info(result) return result -- GitLab From 4079bf9bb9c9301433e40e131f8a99aa5abaf8d1 Mon Sep 17 00:00:00 2001 From: Ger van Diepen <diepen@astron.nl> Date: Mon, 13 Jun 2016 11:43:20 +0000 Subject: [PATCH 322/933] Task #8887 Fixed error message for baseline selection CS*,RS*& if no remote stations; Also introduced possibility to use lists instead of subtable --- CEP/MS/include/MS/BaselineSelect.h | 47 +++++++++++- CEP/MS/include/MS/MSCreate.h | 4 + CEP/MS/src/BaselineSelect.cc | 117 ++++++++++++++++++++++++----- CEP/MS/src/MSCreate.cc | 36 +++++++-- CEP/MS/src/makems.cc | 5 ++ CEP/MS/src/makems.cfg | 6 +- CEP/MS/src/msoverview.cc | 3 +- CEP/MS/test/tBaselineSelect.cc | 43 ++++++++++- 8 files changed, 232 insertions(+), 29 deletions(-) diff --git a/CEP/MS/include/MS/BaselineSelect.h b/CEP/MS/include/MS/BaselineSelect.h index 80daa057871..540ab928f70 100644 --- a/CEP/MS/include/MS/BaselineSelect.h +++ b/CEP/MS/include/MS/BaselineSelect.h @@ -29,11 +29,18 @@ //# Includes #include <Common/lofar_string.h> +#include <ms/MSSel/MSSelectionErrorHandler.h> +#include <ostream> + //# Forward Declarations namespace casa { template<class T> class Matrix; + template<class T> class Vector; + class Table; + class TableExprNode; + class MPosition; } namespace LOFAR @@ -51,10 +58,48 @@ class BaselineSelect public: // Parse the MSSelection baseline string and create a Matrix telling // which baselines are selected. + // Possible messages from the parser are written to the ostream. static casa::Matrix<bool> convert (const string& msName, - const string& baselineSelection); + const string& baselineSelection, + std::ostream&); + + // Parse the MSSelection baseline string and create a Matrix telling + // which baselines are selected. + // The input is a vector of station names and positions. + // Possible messages from the parser are written to the ostream. + static casa::Matrix<bool> convert (const casa::Vector<casa::String>& names, + const std::vector<casa::MPosition>& pos, + const casa::Vector<casa::Int>& ant1, + const casa::Vector<casa::Int>& ant2, + const string& baselineSelection, + std::ostream&); + +private: + static casa::Matrix<bool> convert (casa::Table& anttab, + casa::TableExprNode& a1, + casa::TableExprNode& a2, + const string& baselineSelection, + std::ostream& os); + }; + + +// This class handles an error from the Casacore's MSAntennaParse. +// It adds the message to the message list of the parent BaselineSelect. +class BaselineSelectErrorHandler : public casa::MSSelectionErrorHandler +{ +public: + BaselineSelectErrorHandler (std::ostream& os) + : itsStream (os) + {} + virtual ~BaselineSelectErrorHandler(); + virtual void reportError (const char *token, const casa::String message); +private: + std::ostream& itsStream; +}; + + // @} } // end namespace diff --git a/CEP/MS/include/MS/MSCreate.h b/CEP/MS/include/MS/MSCreate.h index e91a1f665e5..f35ce3dda2a 100644 --- a/CEP/MS/include/MS/MSCreate.h +++ b/CEP/MS/include/MS/MSCreate.h @@ -84,6 +84,10 @@ public: // Destructor ~MSCreate(); + // Close all subtables to limit the nr of open files. + // This can be done once all subtables have been written. + void closeSubTables(); + // Add the extra columns needed for lwimager for every column not existing. // These are CORRECTED_DATA, MODEL_DATA, and IMAGING_WEIGHT. // Furthermore it sets the CHANNEL_SELECTION keyword for casa::VisSet. diff --git a/CEP/MS/src/BaselineSelect.cc b/CEP/MS/src/BaselineSelect.cc index 8015a4de683..12308534734 100644 --- a/CEP/MS/src/BaselineSelect.cc +++ b/CEP/MS/src/BaselineSelect.cc @@ -22,23 +22,38 @@ //# Includes #include <MS/BaselineSelect.h> +#include <Common/LofarLogger.h> + #include <ms/MeasurementSets/MeasurementSet.h> +#include <ms/MeasurementSets/MSAntenna.h> +#include <ms/MeasurementSets/MSAntennaColumns.h> #if defined(casacore) #include <ms/MSSel/MSSelection.h> +#include <ms/MSSel/MSAntennaParse.h> +#include <ms/MSSel/MSAntennaGram.h> #else #include <ms/MeasurementSets/MSSelection.h> +#include <ms/MeasurementSets/MSAntennaParse.h> +#include <ms/MeasurementSets/MSAntennaGram.h> #endif +#include <tables/Tables/Table.h> +#include <tables/Tables/SetupNewTab.h> +#include <tables/Tables/TableRecord.h> #include <tables/Tables/TableParse.h> #include <tables/Tables/ScalarColumn.h> +#include <tables/Tables/ScaColDesc.h> +#include <measures/Measures/MPosition.h> #include <casa/Arrays/Matrix.h> #include <casa/Arrays/Vector.h> + using namespace casa; namespace LOFAR { Matrix<bool> BaselineSelect::convert (const string& msName, - const string& baselineSelection) + const string& baselineSelection, + std::ostream& os) { // Find the unique baselines in the MS. // Do not use unique sort, because that is slow for a large MS. @@ -60,23 +75,91 @@ namespace LOFAR { } bltab = tab(Vector<uInt>(rows)); } - MeasurementSet ms(bltab); - MSSelection select; - // Set given selection strings. - select.setAntennaExpr (baselineSelection); - // Create a table expression over a MS representing the selection - TableExprNode node = select.toTableExprNode (&ms); - Table seltab = ms(node); - // Get the antenna numbers. - Vector<Int> a1 = ROScalarColumn<Int>(seltab, "ANTENNA1").getColumn(); - Vector<Int> a2 = ROScalarColumn<Int>(seltab, "ANTENNA2").getColumn(); - int nant = ms.antenna().nrow(); - Matrix<bool> bl(nant, nant, false); - for (uint i=0; i<a1.size(); ++i) { - bl(a1[i], a2[i]) = true; - bl(a2[i], a1[i]) = true; + TableExprNode a1 (bltab.col("ANTENNA1")); + TableExprNode a2 (bltab.col("ANTENNA2")); + Table anttab (bltab.keywordSet().asTable("ANTENNA")); + return convert (anttab, a1, a2, baselineSelection, os); + } + + casa::Matrix<bool> BaselineSelect::convert (const Vector<String>& names, + const vector<MPosition>& pos, + const Vector<Int>& ant1, + const Vector<Int>& ant2, + const string& baselineSelection, + std::ostream& os) + { + ASSERT (names.size() == pos.size()); + // Create a temporary MSAntenna table in memory for parsing purposes. + SetupNewTable antNew(String(), MSAntenna::requiredTableDesc(), + Table::New); + Table anttab(antNew, Table::Memory, names.size()); + MSAntenna msant(anttab); + MSAntennaColumns antcol(msant); + antcol.name().putColumn (names); + for (size_t i=0; i<pos.size(); ++i) { + antcol.positionMeas().put (i, pos[i]); } - return bl; + // Create a temporary table holding the antenna numbers of the baselines. + TableDesc td; + td.addColumn (ScalarColumnDesc<Int>("ANTENNA1")); + td.addColumn (ScalarColumnDesc<Int>("ANTENNA2")); + SetupNewTable tabNew(String(), td, Table::New); + Table tab(tabNew, Table::Memory, ant1.size()); + ScalarColumn<Int> ac1(tab, "ANTENNA1"); + ScalarColumn<Int> ac2(tab, "ANTENNA2"); + ac1.putColumn (ant1); + ac2.putColumn (ant2); + // Do the selection using the temporary tables. + TableExprNode a1 (tab.col("ANTENNA1")); + TableExprNode a2 (tab.col("ANTENNA2")); + return convert (anttab, a1, a2, baselineSelection, os); + } + + Matrix<bool> BaselineSelect::convert (Table& anttab, + TableExprNode& a1, + TableExprNode& a2, + const string& baselineSelection, + std::ostream& os) + { + // Overwrite the error handler to ignore errors for unknown antennas. + // First construct MSSelection, because it resets the error handler. + Vector<Int> selectedAnts1; + Vector<Int> selectedAnts2; + Matrix<Int> selectedBaselines; + MSSelectionErrorHandler* curHandler = MSAntennaParse::thisMSAErrorHandler; + BaselineSelectErrorHandler errorHandler (os); + MSAntennaParse::thisMSAErrorHandler = &errorHandler; + try { + // Create a table expression representing the selection. + TableExprNode node = msAntennaGramParseCommand + (anttab, a1, a2, baselineSelection, + selectedAnts1, selectedAnts2, selectedBaselines); + // Get the antenna numbers. + Table seltab = node.table()(node); + Vector<Int> a1 = ROScalarColumn<Int>(seltab, "ANTENNA1").getColumn(); + Vector<Int> a2 = ROScalarColumn<Int>(seltab, "ANTENNA2").getColumn(); + int nant = anttab.nrow(); + Matrix<bool> bl(nant, nant, false); + for (uint i=0; i<a1.size(); ++i) { + bl(a1[i], a2[i]) = true; + bl(a2[i], a1[i]) = true; + } + MSAntennaParse::thisMSAErrorHandler = curHandler; + return bl; + } catch (const std::exception&) { + MSAntennaParse::thisMSAErrorHandler = curHandler; + throw; + } + } + + + BaselineSelectErrorHandler::~BaselineSelectErrorHandler() + {} + + void BaselineSelectErrorHandler::reportError (const char* token, + const String message) + { + itsStream << message << token << endl; } } // end namespace diff --git a/CEP/MS/src/MSCreate.cc b/CEP/MS/src/MSCreate.cc index 95028ba30e6..b0aca89eb9f 100644 --- a/CEP/MS/src/MSCreate.cc +++ b/CEP/MS/src/MSCreate.cc @@ -263,6 +263,28 @@ void MSCreate::createMS (const String& msName, } } +void MSCreate::closeSubTables() +{ + itsMS->antenna() = MSAntenna(); + itsMS->dataDescription() = MSDataDescription(); + itsMS->doppler() = MSDoppler(); + itsMS->feed() = MSFeed(); + itsMS->field() = MSField(); + itsMS->flagCmd() = MSFlagCmd(); + itsMS->freqOffset() = MSFreqOffset(); + itsMS->history() = MSHistory(); + itsMS->observation() = MSObservation(); + itsMS->pointing() = MSPointing(); + itsMS->polarization() = MSPolarization(); + itsMS->processor() = MSProcessor(); + itsMS->source() = MSSource(); + itsMS->spectralWindow() = MSSpectralWindow(); + itsMS->state() = MSState(); + itsMS->sysCal() = MSSysCal(); + itsMS->weather() = MSWeather(); + itsMS->keywordSet().closeTables(); +} + int MSCreate::addBand (int npolarizations, int nchannels, double refFreq, double chanWidth) { @@ -305,7 +327,7 @@ int MSCreate::addBand (int npolarizations, int nchannels, if (polnr < 0) { polnr = addPolarization (npolarizations); } - // Add a row to the DATADESCRIPTION subtable. + // Add a row to the DATA_DESCRIPTION subtable. MSDataDescription msdd = itsMS->dataDescription(); MSDataDescColumns msddCol(msdd); uInt rownr = msdd.nrow(); @@ -313,7 +335,7 @@ int MSCreate::addBand (int npolarizations, int nchannels, msddCol.spectralWindowId().put (rownr, rownr); msddCol.polarizationId().put (rownr, polnr); msddCol.flagRow().put (rownr, False); - // Add a row to the SPECTRALWINDOW subtable. + // Add a row to the SPECTRAL_WINDOW subtable. // Find the total bandwidth from the minimum and maximum. Vector<double> stFreqs = chanFreqs - chanWidths/2.; Vector<double> endFreqs = chanFreqs + chanWidths/2.; @@ -588,7 +610,7 @@ void MSCreate::updateTimes() Double midTime = (itsStartTime + endTime) / 2; // Update all rows in FEED subtable. { - MSFeed mssub = itsMS->feed(); + MSFeed mssub (itsMS->keywordSet().asTable("FEED")); MSFeedColumns mssubCol(mssub); Vector<Double> val(mssub.nrow()); val = midTime; @@ -598,7 +620,7 @@ void MSCreate::updateTimes() } // Update all rows in POINTING subtable. { - MSPointing mssub = itsMS->pointing(); + MSPointing mssub (itsMS->keywordSet().asTable("POINTING")); MSPointingColumns mssubCol(mssub); Vector<Double> val(mssub.nrow()); val = midTime; @@ -608,7 +630,7 @@ void MSCreate::updateTimes() } // Update all rows in OBSERVATION subtable. { - MSObservation msobs = itsMS->observation(); + MSObservation msobs (itsMS->keywordSet().asTable("OBSERVATION")); MSObservationColumns msobsCol(msobs); Vector<Double> timeRange(2); timeRange(0) = itsStartTime; @@ -843,6 +865,10 @@ void MSCreate::addImagerColumns (MeasurementSet& ms) ms.addColumn (td, stMan); // Set MODEL_DATA keyword for casa::VisSet. // Sort out the channel selection. + if (ms.spectralWindow().isNull()) { + ms.spectralWindow() = + MSSpectralWindow(ms.keywordSet().asTable("SPECTRAL_WINDOW")); + } MSSpWindowColumns msSpW(ms.spectralWindow()); Matrix<Int> selection(2, msSpW.nrow()); // Fill in default selection (all bands and channels). diff --git a/CEP/MS/src/makems.cc b/CEP/MS/src/makems.cc index 7f9b48afa3c..2989ace240f 100644 --- a/CEP/MS/src/makems.cc +++ b/CEP/MS/src/makems.cc @@ -37,6 +37,7 @@ #include <casa/Arrays/Matrix.h> #include <casa/Arrays/Vector.h> #include <casa/OS/Path.h> +#include <casa/OS/Timer.h> #include <tables/Tables/Table.h> #include <tables/Tables/ScalarColumn.h> #include <tables/Tables/ArrayColumn.h> @@ -173,6 +174,7 @@ void readParms (const string& parset) void createMS (int nband, int bandnr, const string& msName) { + Timer timer; int nfpb = itsNFreq/itsNBand; MSCreate msmaker(msName, itsStartTime, itsStepTime, nfpb, itsNCorr, itsAntPos, itsAntennaTableName, itsWriteAutoCorr, @@ -187,6 +189,9 @@ void createMS (int nband, int bandnr, const string& msName) for (uint i=0; i<itsRa.size(); ++i) { msmaker.addField (itsRa[i], itsDec[i]); } + // Close all subtables to reduce nr of open files. + msmaker.closeSubTables(); + timer.show ("Create MS"); for (int i=0; i<itsNTime; ++i) { msmaker.writeTimeStep(); } diff --git a/CEP/MS/src/makems.cfg b/CEP/MS/src/makems.cfg index 7330fa8aae8..8cd40b02cf9 100644 --- a/CEP/MS/src/makems.cfg +++ b/CEP/MS/src/makems.cfg @@ -7,13 +7,13 @@ Declination=62.34.44.313606568 NBands=4 NFrequencies=64 NTimes=14 -NParts=2 +NParts=4 Dirs=["node1:abc", 'node1:def'] TileSizeFreq=8 TileSizeRest=10 WriteAutoCorr=T -AntennaTableName=~/WSRT_ANTENNA -MSName=tst/test.MS +AntennaTableName=~/data/WSRT_ANTENNA +MSName=test.MS VDSPath=. FlagColumn=LOFAR_FLAG NFlagBits=8 diff --git a/CEP/MS/src/msoverview.cc b/CEP/MS/src/msoverview.cc index d4d110fdf01..8d2e3df428d 100644 --- a/CEP/MS/src/msoverview.cc +++ b/CEP/MS/src/msoverview.cc @@ -112,7 +112,8 @@ int main (int argc, char* argv[]) logio << LogIO::NORMAL << endl << LogIO::POST; summ.listField (logio, False); logio << LogIO::NORMAL << endl << LogIO::POST; - summ.listSpectralAndPolInfo (logio, verbose); + ///summ.listSpectralAndPolInfo (logio, verbose, False); // new version + summ.listSpectralAndPolInfo (logio, verbose); // old version if (verbose) { logio << LogIO::NORMAL << endl << LogIO::POST; summ.listAntenna (logio, True); diff --git a/CEP/MS/test/tBaselineSelect.cc b/CEP/MS/test/tBaselineSelect.cc index b335d469613..29e6089085b 100644 --- a/CEP/MS/test/tBaselineSelect.cc +++ b/CEP/MS/test/tBaselineSelect.cc @@ -22,21 +22,60 @@ //# Includes #include <MS/BaselineSelect.h> +#include <Common/LofarLogger.h> + +#include <measures/Measures/MPosition.h> +#include <measures/Measures/MeasTable.h> #include <casa/Arrays/ArrayIO.h> +#include <casa/BasicSL/STLIO.h> #include <iostream> using namespace LOFAR; using namespace casa; using namespace std; +void testTemp() +{ + Vector<String> names(5); + names[0] = "CS013HBA0"; + names[1] = "CS013HBA1"; + names[2] = "CS014HBA0"; + names[3] = "RS015"; + names[4] = "DE013"; + Vector<Int> a1(15); + Vector<Int> a2(15); + int inx=0; + for (int i=0; i<5; ++i) { + for (int j=i; j<5; ++j) { + a1[inx] = i; + a2[inx] = j; + inx++; + } + } + vector<MPosition> pos(5); + ASSERT (MeasTable::Observatory (pos[0], "WSRT")); + ASSERT (MeasTable::Observatory (pos[1], "WSRT")); + ASSERT (MeasTable::Observatory (pos[2], "LOFAR")); + ASSERT (MeasTable::Observatory (pos[3], "WSRT")); + ASSERT (MeasTable::Observatory (pos[4], "LOFAR")); + // Try a few selection strings. + cout << BaselineSelect::convert (names, pos, a1, a2, "CS013HBA[01]", cout); + cout << BaselineSelect::convert (names, pos, a1, a2, "CS013HBA[23]", cout); + cout << BaselineSelect::convert (names, pos, a1, a2, "CS*&&[CR]S*", cout); + cout << BaselineSelect::convert (names, pos, a1, a2, "<10000", cout); +} + int main(int argc, char* argv[]) { try { - if (argc < 3) { + if (argc == 1) { + testTemp(); + } else if (argc < 3) { cout << "Run as: tBaselineSelect msname expr" << endl; return 0; + } else { + cout << BaselineSelect::convert (argv[1], argv[2], cout); } - cout << BaselineSelect::convert (argv[1], argv[2]); } catch (exception& x) { cout << "Unexpected expection: " << x.what() << endl; return 1; -- GitLab From 54f549821c396fb61c2d68d46bde2f4060c441e3 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 13 Jun 2016 13:07:28 +0000 Subject: [PATCH 323/933] Task #8887: added two methods to browse the tasks/claims in html tables. --- .../lib/webservice.py | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index ae017193d75..8ea09345b4c 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -313,6 +313,137 @@ def getUpdateEvents(): def getLofarTime(): return jsonify({'lofarTime': asIsoFormat(datetime.utcnow())}) + +#ugly method to generate html tables for all tasks +@app.route('/tasks.html') +def getTasksHtml(): + tasks = rarpc.getTasks() + if not tasks: + abort(404) + + updateTaskMomDetails(tasks, momrpc) + + html = '<!DOCTYPE html><html><head><title>Tasks</title><style>table, th, td {border: 1px solid black; border-collapse: collapse; padding: 4px;}</style></head><body><table style="width:100%">\n' + + props = sorted(tasks[0].keys()) + html += '<tr>%s</tr>\n' % ''.join('<th>%s</th>' % prop for prop in props) + + for task in tasks: + html += '<tr>' + for prop in props: + if prop in task: + if prop == 'id': + html += '<td><a href="/rest/tasks/%s.html">%s</a></td> ' % (task[prop], task[prop]) + else: + html += '<td>%s</td> ' % task[prop] + html += '</tr>\n' + + html += '</table></body></html>\n' + + return html + +#ugly method to generate html tables for the task and it's claims +@app.route('/tasks/<int:task_id>.html', methods=['GET']) +def getTaskHtml(task_id): + task = rarpc.getTask(task_id) + + if not task: + abort(404, 'No such task %s' % task_id) + + task['name'] = 'Task %d' % task['id'] + updateTaskMomDetails(task, momrpc) + + html = '<!DOCTYPE html><html><head><title>Tasks</title><style>table, th, td {border: 1px solid black; border-collapse: collapse; padding: 4px;}</style></head><body><table style="">\n' + + html += '<h1>Task %s</h1>' % task_id + + props = sorted(task.keys()) + html += '<tr><th>key</th><th>value</th></tr>\n' + + for prop in props: + html += '<tr><td>%s</td>' % prop + + if prop == 'id': + html += '<td><a href="/rest/tasks/%s.html">%s</a></td> ' % (task[prop], task[prop]) + elif prop == 'predecessor_ids' or prop == 'successor_ids': + ids = task[prop] + if ids: + html += '<td>%s</td> ' % ', '.join('<a href="/rest/tasks/%s.html">%s</a>' % (id, id) for id in ids) + else: + html += '<td></td> ' + else: + html += '<td>%s</td> ' % task[prop] + + html += '</tr>' + + html += '</table>\n<br>' + + claims = rarpc.getResourceClaims(task_ids=[task_id], extended=True, include_properties=True) + + if claims: + html += '<h1>Claims</h1>' + + for claim in claims: + html += '<table>' + for claim_key,claim_value in claim.items(): + if claim_key == 'properties': + html += '<tr><td>properties</td><td><table>' + if claim_value: + propnames = sorted(claim_value[0].keys()) + html += '<tr>%s</tr>\n' % ''.join('<th>%s</th>' % propname for propname in propnames) + for prop in claim_value: + html += '<tr>%s</tr>\n' % ''.join('<td>%s</td>' % prop[propname] for propname in propnames) + html += '</table></td></tr>' + elif claim_key == 'saps': + html += '<tr><td>saps</td><td><table>' + saps = claim_value + if saps: + sap_keys = ['sap_nr', 'properties'] + html += '<tr>%s</tr>\n' % ''.join('<th>%s</th>' % sap_key for sap_key in sap_keys) + for sap in saps: + html += '<tr>' + for sap_key in sap_keys: + if sap_key == 'properties': + html += '<td><table>' + sap_props = sap[sap_key] + if sap_props: + propnames = sorted(sap_props[0].keys()) + html += '<tr>%s</tr>\n' % ''.join('<th>%s</th>' % propname for propname in propnames) + for prop in sap_props: + html += '<tr>%s</tr>\n' % ''.join('<td>%s</td>' % prop[propname] for propname in propnames) + html += '</table></td>' + else: + html += '<td>%s</td>' % (sap[sap_key]) + html += '</tr>' + html += '</table></td></tr>' + else: + html += '<tr><td>%s</td><td>%s</td></tr>' % (claim_key,claim_value) + html += '</table>' + html += '<br>' + + html += '</body></html>\n' + + return html + +@app.route('/rest/tasks/<int:task_id>/resourceclaims.html', methods=['GET']) +@gzipped +def resourceClaimsForTaskHtml(task_id): + claims = rarpc.getResourceClaims(task_ids=[task_id], extended=True, include_properties=True) + + if not claims: + abort(404, 'No resource claims for task %s' % task_id) + + html = '<!DOCTYPE html><html><head><title>Tasks</title><style>table, th, td {border: 1px solid black; border-collapse: collapse; padding: 4px;}</style></head><body><table style="">\n' + + for claim in claims: + html += '<tr><td>%s</td>' % claim + + html += '</table></body></html>\n' + + return html + + + def main(): # make sure we run in UTC timezone import os -- GitLab From 24f6eba6ee702b09ae69d4e4157cb6ab29070e5f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 13 Jun 2016 13:55:01 +0000 Subject: [PATCH 324/933] Task #9351: proper error message in case path for otdb_id not found --- SAS/DataManagement/CleanupService/rpc.py | 19 ++++++++++++------- SAS/DataManagement/CleanupService/service.py | 6 +++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/SAS/DataManagement/CleanupService/rpc.py b/SAS/DataManagement/CleanupService/rpc.py index c5abd796f85..7235f194137 100644 --- a/SAS/DataManagement/CleanupService/rpc.py +++ b/SAS/DataManagement/CleanupService/rpc.py @@ -43,11 +43,16 @@ def main(): with CleanupRPC(busname=options.busname, servicename=options.servicename, broker=options.broker) as rpc: otdb_id = int(args[0]) - path = rpc.getPathForOTDBId(otdb_id) - print "This will delete everything in '%s'." % path - if raw_input("Are you sure? (y/n) ") == 'y': - result = rpc.removeTaskData(otdb_id) - print result['message'] - exit(0 if result['deleted'] else 1) + path_result = rpc.getPathForOTDBId(otdb_id) + if path_result['found']: + path = path_result['path'] + print "This will delete everything in '%s'." % path + if raw_input("Are you sure? (y/n) ") == 'y': + result = rpc.removeTaskData(otdb_id) + print result['message'] + exit(0 if result['deleted'] else 1) + else: + print "Nothing deleted" else: - print "Nothing deleted" + print path_result['message'] + exit(1) diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index fef5704eda6..2af3e28592a 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -69,14 +69,14 @@ class CleanupHandler(MessageHandlerInterface): if not task: message = "Could not find task in RADB for otdb_id=%s" % otdb_id logger.error(message) - return {'deleted': False, 'message': message} + return {'found': False, 'message': message, 'path': None} mom_details = self.momrpc.getProjectDetails(task['mom_id']) if not mom_details or str(task['mom_id']) not in mom_details: message = "Could not find mom project details for otdb_id=%s mom_id=%s radb_id=%s" % (otdb_id, task['mom_id'], task['id']) logger.error(message) - return {'deleted': False, 'message': message} + return {'found': False, 'message': message, 'path': None} project_name = mom_details[str(task['mom_id'])]['project_name'] logger.info("Found project '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (project_name, otdb_id, task['mom_id'], task['id'])) @@ -84,7 +84,7 @@ class CleanupHandler(MessageHandlerInterface): project_path = os.path.join(self.projects_path, "_".join(project_name.split())) task_data_path = os.path.join(project_path, 'L%s' % otdb_id) - return task_data_path + return {'found': True, 'message': '', 'path': task_data_path} def _removeTaskData(self, otdb_id): if not isinstance(otdb_id, int): -- GitLab From dca4e99394eec20d082f4ef4ff5bf231e42af119 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 13 Jun 2016 14:07:38 +0000 Subject: [PATCH 325/933] Task #9351: added getTaskDataPath --- .../ResourceAssignmentEditor/lib/webservice.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index d6587d2a064..74c97521871 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -254,6 +254,22 @@ def cleanupTaskData(task_id): except Exception as e: abort(500) +@app.route('/rest/tasks/<int:task_id>/datapath', methods=['GET']) +def getTaskDataPath(task_id): + try: + task = rarpc.getTask(task_id) + + if not task: + abort(404, 'No such task (id=%s)' % task_id) + + result = curpc.getPathForOTDBId(task['otdb_id']) + except Exception as e: + abort(500, str(e)) + + if result['found']: + return jsonify({'datapath': result['path']}) + abort(404, result['message']) + @app.route('/rest/tasks/<int:task_id>/resourceclaims') def taskResourceClaims(task_id): return jsonify({'taskResourceClaims': rarpc.getResourceClaims(task_id=task_id, include_properties=True)}) -- GitLab From ee639a335202b6b7094091f9eae11f0533a9d3fc Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 14 Jun 2016 06:13:08 +0000 Subject: [PATCH 326/933] Task #8887 #9519: Use casacore 2.1.0 to be able to read tables without TabRefCodes --- Docker/lofar-base/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 5eff1ea9549..aef0133c364 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -18,7 +18,7 @@ ENV DEBIAN_FRONTEND=noninteractive \ # # versions # -ENV CASACORE_VERSION=2.0.3 \ +ENV CASACORE_VERSION=2.1.0 \ CASAREST_VERSION=1.4.1 \ PYTHON_CASACORE_VERSION=2.0.1 \ BOOST_VERSION=1.54 -- GitLab From 48be9901eb370f95b589bb4044ef9d09b7e60302 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 14 Jun 2016 08:11:34 +0000 Subject: [PATCH 327/933] Task #9351: merged branch Webscheduler-Task9349 (c34667) via trunk into this branch because I need the angular contextmenu which was developed there --- .gitattributes | 3 + LCS/Messaging/python/messaging/RPC.py | 10 +- SAS/DataManagement/CleanupService/service.py | 10 +- SAS/MoM/MoMQueryService/CMakeLists.txt | 3 +- SAS/MoM/MoMQueryService/config.py | 3 + SAS/MoM/MoMQueryService/momcopytask | 11 +++ SAS/MoM/MoMQueryService/momrpc.py | 46 +++++++++ .../ResourceAssigner/lib/assignment.py | 3 +- .../ResourceAssignmentDatabase/radb.py | 34 +++++-- .../sql/create_database.sql | 4 +- .../lib/CMakeLists.txt | 1 + .../lib/radbchangeshandler.py | 6 +- .../static/app/controllers/datacontroller.js | 23 ++++- .../app/controllers/ganttprojectcontroller.js | 56 +++++++++-- .../controllers/ganttresourcecontroller.js | 52 ++++++++-- .../static/app/controllers/gridcontroller.js | 80 +++++++++++++++- .../angular-gantt-contextmenu-plugin.js | 96 +++++++++++++++++++ .../lib/templates/index.html | 4 +- .../lib/webservice.py | 63 ++++++++++-- 19 files changed, 461 insertions(+), 47 deletions(-) create mode 100644 SAS/MoM/MoMQueryService/momcopytask create mode 100644 SAS/MoM/MoMQueryService/momrpc.py create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js diff --git a/.gitattributes b/.gitattributes index e23842dbd59..8550832d67f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4753,11 +4753,13 @@ SAS/MoM/CMakeLists.txt -text SAS/MoM/MoMQueryService/CMakeLists.txt -text SAS/MoM/MoMQueryService/__init__.py -text SAS/MoM/MoMQueryService/config.py -text +SAS/MoM/MoMQueryService/momcopytask -text SAS/MoM/MoMQueryService/momquery -text SAS/MoM/MoMQueryService/momqueryrpc.py -text SAS/MoM/MoMQueryService/momqueryservice -text SAS/MoM/MoMQueryService/momqueryservice.ini -text SAS/MoM/MoMQueryService/momqueryservice.py -text +SAS/MoM/MoMQueryService/momrpc.py -text SAS/MoM/MoMQueryService/test/CMakeLists.txt -text SAS/MoM/MoMQueryService/test/test_momqueryservice.py -text SAS/MoM/MoMQueryService/test/test_momqueryservice.run -text @@ -5091,6 +5093,7 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datac SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/bootstrap.min.css -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/favicon.ico -text diff --git a/LCS/Messaging/python/messaging/RPC.py b/LCS/Messaging/python/messaging/RPC.py index 69935df5b42..81a94cc05bc 100644 --- a/LCS/Messaging/python/messaging/RPC.py +++ b/LCS/Messaging/python/messaging/RPC.py @@ -155,6 +155,9 @@ class RPC(): Reply = FromBus("%s ; %s" %(ReplyAddress,str(options)), broker=self.broker) else: Reply = FromBus("%s/%s" % (self.BusName, ReplyAddress), broker=self.broker) + # supply fully specified reply address including '{node:{type:topic}}' specification so handlers like JMS can handle reply address + ReplyAddress = "%s/%s ;{node:{type:topic}}" % (self.BusName, ReplyAddress) + with Reply: MyMsg = RequestMessage(content=Content, reply_to=ReplyAddress, has_args=HasArgs, has_kwargs=HasKwArgs) MyMsg.ttl = timeout @@ -286,11 +289,13 @@ class RPCWrapper(object): def __init__(self, busname=None, servicename=None, broker=None, - timeout=10): + timeout=10, + verbose=False): self.busname = busname self.servicename = servicename self.broker = broker self.timeout = timeout + self.verbose = verbose self._serviceRPCs = {} #cache of rpc's for each service @@ -317,7 +322,8 @@ class RPCWrapper(object): '''execute the rpc call on the <bus>/<service>.<method> and return the result''' try: if self.timeout: - rpckwargs = {'timeout': self.timeout} + rpckwargs = {'timeout': self.timeout, + 'Verbose': self.verbose} service_method = (self.servicename + '.' + method) if self.servicename and method \ else self.servicename if self.servicename else method diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index 2af3e28592a..9b4a228e7e1 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -61,6 +61,8 @@ class CleanupHandler(MessageHandlerInterface): self.momrpc.close() def _getPathForOTDBId(self, otdb_id): + logger.info("Get path for otdb_id %s" % (otdb_id,)) + if not isinstance(otdb_id, int): raise ValueError("Provided otdb_id is not an int") @@ -71,6 +73,8 @@ class CleanupHandler(MessageHandlerInterface): logger.error(message) return {'found': False, 'message': message, 'path': None} + logger.info("Get path for otdb_id %s, found radb task with mom_id=%s and radb_id=%s" % (otdb_id, task['mom_id'], task['id'])) + mom_details = self.momrpc.getProjectDetails(task['mom_id']) if not mom_details or str(task['mom_id']) not in mom_details: @@ -87,6 +91,8 @@ class CleanupHandler(MessageHandlerInterface): return {'found': True, 'message': '', 'path': task_data_path} def _removeTaskData(self, otdb_id): + logger.info("Remove task data for otdb_id %s" % (otdb_id,)) + if not isinstance(otdb_id, int): message = "Provided otdb_id is not an int" logger.error(message) @@ -96,6 +102,8 @@ class CleanupHandler(MessageHandlerInterface): return self._removePath(task_data_path) def _removePath(self, path): + logger.info("Remove path: %s" % (path,)) + # do various sanity checking to prevent accidental deletes if not isinstance(path, basestring): message = "Provided path is not a string" @@ -181,7 +189,7 @@ def main(): with createService(busname=options.busname, servicename=options.servicename, broker=options.broker, - verbose=True, #options.verbose, + verbose=options.verbose, radb_busname=options.radb_busname, radb_servicename=options.radb_servicename, mom_busname=options.mom_busname, diff --git a/SAS/MoM/MoMQueryService/CMakeLists.txt b/SAS/MoM/MoMQueryService/CMakeLists.txt index bb355d1c6eb..b16906069d3 100644 --- a/SAS/MoM/MoMQueryService/CMakeLists.txt +++ b/SAS/MoM/MoMQueryService/CMakeLists.txt @@ -9,11 +9,12 @@ set(_py_files config.py momqueryservice.py momqueryrpc.py + momrpc.py ) python_install(${_py_files} DESTINATION lofar/mom/momqueryservice) -lofar_add_bin_scripts(momqueryservice momquery) +lofar_add_bin_scripts(momqueryservice momquery momcopytask) # supervisord config files install(FILES diff --git a/SAS/MoM/MoMQueryService/config.py b/SAS/MoM/MoMQueryService/config.py index a5b66e022d8..7067fe98593 100644 --- a/SAS/MoM/MoMQueryService/config.py +++ b/SAS/MoM/MoMQueryService/config.py @@ -5,3 +5,6 @@ from lofar.messaging import adaptNameToEnvironment DEFAULT_MOMQUERY_BUSNAME = adaptNameToEnvironment('lofar.ra.command') DEFAULT_MOMQUERY_SERVICENAME = 'momqueryservice' + +DEFAULT_MOM_BUSNAME = adaptNameToEnvironment('lofar.mom.command') +DEFAULT_MOM_SERVICENAME = '' diff --git a/SAS/MoM/MoMQueryService/momcopytask b/SAS/MoM/MoMQueryService/momcopytask new file mode 100644 index 00000000000..74208d5e5b9 --- /dev/null +++ b/SAS/MoM/MoMQueryService/momcopytask @@ -0,0 +1,11 @@ +#!/usr/bin/python +# $Id: $ + +''' +performs an rpc call to the mom copy task service +''' + +from lofar.mom.momqueryservice.momrpc import main + +if __name__ == '__main__': + main() diff --git a/SAS/MoM/MoMQueryService/momrpc.py b/SAS/MoM/MoMQueryService/momrpc.py new file mode 100644 index 00000000000..52d59c4cf8e --- /dev/null +++ b/SAS/MoM/MoMQueryService/momrpc.py @@ -0,0 +1,46 @@ +#!/usr/bin/python + +import sys +import logging +from optparse import OptionParser +from lofar.messaging.RPC import RPC, RPCException, RPCWrapper +from lofar.messaging import setQpidLogLevel +from lofar.mom.momqueryservice.config import DEFAULT_MOM_BUSNAME, DEFAULT_MOM_SERVICENAME + +logger = logging.getLogger(__file__) + +class MoMRPC(RPCWrapper): + def __init__(self, busname=DEFAULT_MOM_BUSNAME, servicename=DEFAULT_MOM_SERVICENAME, broker=None, timeout=10, verbose=False): + super(MoMRPC, self).__init__(busname=busname, servicename=servicename, broker=broker, timeout=timeout, verbose=verbose) + + def copyTask(self, mom2id, newTaskName=None, newTaskDescription=None): + logger.info("calling copyTask rpc for mom2id %s" % (mom2id)) + new_task_mom2id = self.rpc('TaskCopy', mom2Id=mom2id) #, newTaskName=newTaskName, newTaskDescription=newTaskDescription) + logger.info("mom2id of copied task = %s" % (new_task_mom2id)) + return new_task_mom2id + +def main(): + # Check the invocation arguments + parser = OptionParser('%prog [options]', + description='do rpc calls to the momservice from the commandline') + parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') + parser.add_option('-b', '--busname', dest='busname', type='string', default=DEFAULT_MOM_BUSNAME, help='Name of the bus exchange on the qpid broker [default: %default]') + parser.add_option('--mom2id', dest='mom2id_to_copy', type='int', help='[REQUIRED] mom2id of the task to copy.') + parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') + (options, args) = parser.parse_args() + + if options.mom2id_to_copy == None: + parser.print_help() + parser.error('Missing required option mom2id') + + verbose = bool(options.verbose) + + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.DEBUG if verbose else logging.INFO) + setQpidLogLevel(logging.WARN) + + with MoMRPC(busname=options.busname, broker=options.broker, verbose=verbose) as rpc: + print rpc.copyTask(options.mom2id_to_copy) + +if __name__ == '__main__': + main() diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 86e334e2f86..ccdd26ca6c3 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -116,7 +116,8 @@ class ResourceAssigner(): startTime = datetime.strptime(mainParset.getString('Observation.startTime'), '%Y-%m-%d %H:%M:%S') endTime = datetime.strptime(mainParset.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') except ValueError: - logger.warning('cannot parse for start/end time from specification for otdb_id=%s', (otdb_id, )) + logger.warning('cannot parse for start/end time from specification for otdb_id=%s. skipping specification.', (otdb_id, )) + return # insert new task and specification in the radb # any existing specification and task with same otdb_id will be deleted automatically diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index e1d0196bafa..669fed24139 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -221,6 +221,12 @@ class RADatabase: return task_status, task_type def insertTask(self, mom_id, otdb_id, task_status, task_type, specification_id, commit=True): + if isinstance(mom_id, int) and mom_id <= 0: + mom_id = None + + if isinstance(otdb_id, int) and otdb_id <= 0: + otdb_id = None + logger.info('insertTask mom_id=%s, otdb_id=%s, task_status=%s, task_type=%s, specification_id=%s' % (mom_id, otdb_id, task_status, task_type, specification_id)) task_status, task_type = self._convertTaskTypeAndStatusToIds(task_status, task_type) @@ -1057,14 +1063,15 @@ class RADatabase: # update the claims as well # updateResourceClaims also validates the updated claims claim_ids = [c['id'] for c in claimsBeforeUpdate] - updated &= self.updateResourceClaims(claim_ids, - starttime=starttime, - endtime=endtime, - status=claim_status, - session_id=session_id, - username=username, user_id=user_id, - validate=True, - commit=False) + if claim_ids: + updated &= self.updateResourceClaims(claim_ids, + starttime=starttime, + endtime=endtime, + status=claim_status, + session_id=session_id, + username=username, user_id=user_id, + validate=True, + commit=False) # because we moved or changed the status of these claims, # validate the claims 'underneath' which may have been in conflict @@ -1177,10 +1184,17 @@ class RADatabase: ''' Insert a new specification and task in one transaction. Removes existing task with same otdb_id if present in the same transaction. + Removes existing task with same mom_id if present in the same transaction. ''' try: task = self.getTask(otdb_id=otdb_id) + if task: + # delete old specification, task, and resource claims using cascaded delete + self.deleteSpecification(task['specification_id'], False) + + task = self.getTask(mom_id=mom_id) + if task: # delete old specification, task, and resource claims using cascaded delete self.deleteSpecification(task['specification_id'], False) @@ -1403,6 +1417,10 @@ if __name__ == '__main__': for t in db.getTasks(): print db.getTask(t['id']) + print db.getTask(db.insertTask(12340, 56780, 600, 0, t['specification_id'])) + print db.getTask(db.insertTask(0, 567801, 600, 0, t['specification_id'])) + print db.getTask(db.insertTask(123401, 0, 600, 0, t['specification_id'])) + exit() #print db.getResourceClaims(task_id=440) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql index 4fd10e07875..985594488bf 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql @@ -141,8 +141,8 @@ ALTER TABLE resource_allocation.specification CREATE TABLE resource_allocation.task ( id serial NOT NULL, - mom_id integer, - otdb_id integer, + mom_id integer UNIQUE, + otdb_id integer UNIQUE, status_id integer NOT NULL REFERENCES resource_allocation.task_status DEFERRABLE INITIALLY IMMEDIATE, type_id integer NOT NULL REFERENCES resource_allocation.task_type DEFERRABLE INITIALLY IMMEDIATE, specification_id integer NOT NULL REFERENCES resource_allocation.specification ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE, diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt index 0e9c1f37a64..ffbddce6f5c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt @@ -35,6 +35,7 @@ set(app_files static/app/controllers/ganttresourcecontroller.js static/app/controllers/chartresourceusagecontroller.js static/app/controllers/ganttprojectcontroller.js + static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js static/css/main.css templates/index.html) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py index f1d9cf31031..c16481807e6 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py @@ -44,7 +44,7 @@ CHANGE_INSERT_TYPE = 'insert' CHANGE_DELETE_TYPE = 'delete' class RADBChangesHandler(RADBBusListener): - def __init__(self, busname=DEFAULT_NOTIFICATION_BUSNAME, subjects=DEFAULT_NOTIFICATION_SUBJECTS, broker=None, momrpc=None, **kwargs): + def __init__(self, busname=DEFAULT_NOTIFICATION_BUSNAME, subjects=DEFAULT_NOTIFICATION_SUBJECTS, broker=None, momqueryrpc=None, **kwargs): """ RADBChangesHandler listens on the lofar notification message bus and keeps track of all the change notifications. :param broker: valid Qpid broker host (default: None, which means localhost) @@ -60,7 +60,7 @@ class RADBChangesHandler(RADBBusListener): self._lock = Lock() self._changedCondition = Condition() self._changeNumber = 0L - self._momrpc = momrpc + self._momqueryrpc = momqueryrpc def _handleChange(self, change): '''_handleChange appends a change in the changes list and calls the onChangedCallback. @@ -89,7 +89,7 @@ class RADBChangesHandler(RADBBusListener): :param task: dictionary with the inserted task''' task['starttime'] = task['starttime'].datetime() task['endtime'] = task['endtime'].datetime() - updateTaskMomDetails(task, self._momrpc) + updateTaskMomDetails(task, self._momqueryrpc) task_change = {'changeType':CHANGE_INSERT_TYPE, 'objectType':'task', 'value':task} self._handleChange(task_change) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index e524f91b4e2..1406d27d525 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -29,6 +29,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.taskTimes = {}; self.resourceClaimTimes = {}; + self.config = {}; + self.selected_resource_id; self.selected_resourceGroup_id; self.selected_task_id; @@ -283,6 +285,13 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }) }; + self.copyTask = function(task) { + $http.put('/rest/tasks/' + task.id + '/copy').error(function(result) { + console.log("Error. Could not copy task. " + result); + alert("Error: Could not copy task with mom id " + task.mom_id); + }) + }; + self.computeMinMaxTaskTimes = function() { var starttimes = self.filteredTasks.map(function(t) { return t.starttime;}); var endtimes = self.filteredTasks.map(function(t) { return t.endtime;}); @@ -478,6 +487,17 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }); }; + self.getConfig = function() { + var defer = $q.defer(); + $http.get('/rest/config').success(function(result) { + self.config = result.config; + defer.resolve(); + }); + + return defer.promise; + }; + + //start with local client time //lofarTime will be synced with server, //because local machine might have incorrect clock @@ -504,7 +524,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.lastUpdateChangeNumber = result.mostRecentChangeNumber; } - var nrOfItemsToLoad = 6; + var nrOfItemsToLoad = 7; var nrOfItemsLoaded = 0; var checkInitialLoadCompleteness = function() { nrOfItemsLoaded += 1; @@ -513,6 +533,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } }; + self.getConfig().then(checkInitialLoadCompleteness); self.getMoMProjects().then(checkInitialLoadCompleteness); self.getTaskTypes().then(checkInitialLoadCompleteness); self.getTaskStatusTypes().then(checkInitialLoadCompleteness); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index 4ad95c83910..85782ee0c7e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -13,7 +13,8 @@ var ganttProjectControllerMod = angular.module('GanttProjectControllerMod', [ 'gantt.groups', 'gantt.dependencies', 'gantt.overlap', - 'gantt.resizeSensor']).config(['$compileProvider', function($compileProvider) { + 'gantt.resizeSensor', + 'gantt.contextmenu']).config(['$compileProvider', function($compileProvider) { $compileProvider.debugInfoEnabled(false); // Remove debug info (angularJS >= 1.3) }]); @@ -48,34 +49,73 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS } ); - api.directives.on.new($scope, function(directiveName, directiveScope, element) { + api.directives.on.new($scope, function(directiveName, directiveScope, directiveElement) { if (directiveName === 'ganttRow' || directiveName === 'ganttRowLabel' ) { - element.bind('click', function(event) { + directiveElement.bind('click', function(event) { if(directiveScope.row.model.project) { $scope.dataService.selected_project_id = directiveScope.row.model.project.id; } }); } else if (directiveName === 'ganttTask') { - element.bind('click', function(event) { + directiveElement.bind('click', function(event) { if(directiveScope.task.model.raTask) { $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; } }); - element.bind('dblclick', function(event) { + directiveElement.bind('dblclick', function(event) { if(directiveScope.task.model.raTask) { $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; $scope.jumpToSelectedTasks(); } }); +// directiveElement.bind('contextmenu', function(event) { +// if(directiveScope.task.model.raTask) { +// $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; +// } +// +// //search for already existing contextmenu element +// if(directiveElement.find('#gantt-project-context-menu').length) { +// //found, remove it, so we can create a fresh one +// directiveElement.find('#gantt-project-context-menu')[0].remove(); +// } +// +// //create contextmenu element +// //with list of menu items, +// //each with it's own action +// var contextmenuElement = angular.element('<div id="gantt-project-context-menu"></div>'); +// ulElement = angular.element('<ul style="z-index:10000; position:fixed; top:initial; left:initial; display:block;" role="menu" class="dropdown-menu"></ul>'); +// contextmenuElement.append(ulElement); +// liElement = angular.element('<li><a href="#">Copy Task</a></li>'); +// ulElement.append(liElement); +// liElement.on('click', function() { +// $scope.dataService.copyTask(directiveScope.task.model.raTask); +// closeContextMenu(); +// }); +// +// var closeContextMenu = function() { +// contextmenuElement.remove(); +// angular.element(document).unbind('click', closeContextMenu); +// }; +// +// //click anywhere to remove the contextmenu +// angular.element(document).bind('click', closeContextMenu); +// +// //add contextmenu to clicked element +// directiveElement.append(contextmenuElement); +// +// //prevent bubbling event upwards +// return false; +// }); } }); - api.directives.on.destroy($scope, function(directiveName, directiveScope, element) { + api.directives.on.destroy($scope, function(directiveName, directiveScope, directiveElement) { if (directiveName === 'ganttRow' || directiveName === 'ganttRowLabel' || directiveName === 'ganttTask') { - element.unbind('click'); + directiveElement.unbind('click'); } if (directiveName === 'ganttTask') { - element.unbind('dblclick'); + directiveElement.unbind('dblclick'); +// directiveElement.unbind('contextmenu'); } }); } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js index 312e7885528..1dcc1fcd7f2 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js @@ -11,7 +11,8 @@ var ganttResourceControllerMod = angular.module('GanttResourceControllerMod', [ 'gantt.tree', 'gantt.groups', 'gantt.overlap', - 'gantt.resizeSensor']).config(['$compileProvider', function($compileProvider) { + 'gantt.resizeSensor', + 'gantt.contextmenu']).config(['$compileProvider', function($compileProvider) { $compileProvider.debugInfoEnabled(false); // Remove debug info (angularJS >= 1.3) }]); @@ -45,9 +46,9 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat api.tasks.on.resizeEnd($scope, moveHandler); }); - api.directives.on.new($scope, function(directiveName, directiveScope, element) { + api.directives.on.new($scope, function(directiveName, directiveScope, directiveElement) { if (directiveName === 'ganttRow' || directiveName === 'ganttRowLabel' ) { - element.bind('click', function(event) { + directiveElement.bind('click', function(event) { if(directiveScope.row.model.resource) { $scope.dataService.selected_resource_id = directiveScope.row.model.resource.id; } else if(directiveScope.row.model.resourceGroup) { @@ -55,7 +56,7 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat } }); } else if (directiveName === 'ganttTask') { - element.bind('click', function(event) { + directiveElement.bind('click', function(event) { if(directiveScope.task.model.raTask) { $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; } @@ -63,12 +64,51 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat $scope.dataService.selected_resourceClaim_id = directiveScope.task.model.claim.id; } }); + directiveElement.bind('contextmenu', function(event) { + if(directiveScope.task.model.raTask) { + $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; + } + + //search for already existing contextmenu element + if(directiveElement.find('#gantt-resource-context-menu').length) { + //found, remove it, so we can create a fresh one + directiveElement.find('#gantt-resource-context-menu')[0].remove(); + } + + //create contextmenu element + //with list of menu items, + //each with it's own action + var contextmenuElement = angular.element('<div id="gantt-resource-context-menu"></div>'); + ulElement = angular.element('<ul style="z-index:10000; position:fixed; top:initial; left:initial; display:block;" role="menu" class="dropdown-menu"></ul>'); + contextmenuElement.append(ulElement); + liElement = angular.element('<li><a href="#">Copy Task</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + $scope.dataService.copyTask(directiveScope.task.model.raTask); + closeContextMenu(); + }); + + var closeContextMenu = function() { + contextmenuElement.remove(); + angular.element(document).unbind('click', closeContextMenu); + }; + + //click anywhere to remove the contextmenu + angular.element(document).bind('click', closeContextMenu); + + //add contextmenu to clicked element + directiveElement.append(contextmenuElement); + + //prevent bubbling event upwards + return false; + }); } }); - api.directives.on.destroy($scope, function(directiveName, directiveScope, element) { + api.directives.on.destroy($scope, function(directiveName, directiveScope, directiveElement) { if (directiveName === 'ganttRow' || directiveName === 'ganttRowLabel' || directiveName === 'ganttTask') { - element.unbind('click'); + directiveElement.unbind('click'); + directiveElement.unbind('contextmenu'); } }); } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index ae7f5cf8513..081ea2bd50c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -11,7 +11,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid $scope.dataService = dataService; - $scope.columns = [ +$scope.columns = [ { field: 'name', enableCellEdit: false, width: '15%' @@ -95,6 +95,8 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid gridMenuShowHideColumns: false, columnDefs: $scope.columns, data: [], +// rowTemplate: "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.uid\" ui-grid-one-bind-id-grid=\"rowRenderIndex + '-' + col.uid + '-cell'\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" role=\"{{col.isRowHeader ? 'rowheader' : 'gridcell'}}\" ui-grid-cell></div>" + rowTemplate: "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.uid\" ui-grid-one-bind-id-grid=\"rowRenderIndex + '-' + col.uid + '-cell'\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" role=\"{{col.isRowHeader ? 'rowheader' : 'gridcell'}}\" ui-grid-cell context-menu>", onRegisterApi: function(gridApi){ $scope.gridApi = gridApi; @@ -229,6 +231,78 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid }; $scope.$watch('dataService.momProjectsDict', fillProjectsColumFilterSelectOptions); - $scope.$watch('dataService.selected_task_id', jumpToSelectedTaskRow); -} + $scope.$watch('dataService.selected_task_id', jumpToSelectedTaskRow);} ]); + +gridControllerMod.directive('contextMenu', ['$document', function($document) { + return { + restrict: 'A', + scope: { + }, + link: function($scope, $element, $attrs) { + function handleContextMenuEvent(event) { + //pragmatic 'hard-coded' way of getting the dataService and the rowEntity via scope tree. + var dataService = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.dataService; + var rowEntity = $scope.$parent.$parent.$parent.row.entity; + + if(!dataService || !rowEntity) + return true; + + var taskId = rowEntity.id; + var task = dataService.taskDict[taskId]; + dataService.selected_task_id = taskId; + + var docElement = angular.element($document); + + //search for already existing contextmenu element + while($document.find('#grid-context-menu').length) { + //found, remove it, so we can create a fresh one + $document.find('#grid-context-menu')[0].remove(); + + //unbind document close event handlers + docElement.unbind('click', closeContextMenu); + docElement.unbind('contextmenu', closeContextMenu); + } + + //create contextmenu element + //with list of menu items, + //each with it's own action + var contextmenuElement = angular.element('<div id="grid-context-menu"></div>'); + var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>'); + contextmenuElement.append(ulElement); + var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + dataService.copyTask(task); + closeContextMenu(); + }); + + var closeContextMenu = function(cme) { + contextmenuElement.remove(); + + //unbind document close event handlers + docElement.unbind('click', closeContextMenu); + docElement.unbind('contextmenu', closeContextMenu); + }; + + //click anywhere to remove the contextmenu + docElement.bind('click', closeContextMenu); + docElement.bind('contextmenu', closeContextMenu); + + //add contextmenu to body + var body = $document.find('body'); + body.append(contextmenuElement); + + //prevent bubbling event upwards + return false; + } + + $element.bind('contextmenu', handleContextMenuEvent); + + $scope.$on('$destroy', function() { + console.log('destroy'); + $element.unbind('contextmenu', handleContextMenuEvent); + }); + } + }; + }]); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js new file mode 100644 index 00000000000..18c7a7e29b6 --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -0,0 +1,96 @@ +(function(){ + 'use strict'; + angular.module('gantt.contextmenu', ['gantt', 'gantt.contextmenu.templates']).directive('ganttContextmenu', ['$compile', '$document', function($compile, $document) { + return { + restrict: 'E', + require: '^gantt', + scope: { + enabled: '=?' + }, + link: function(scope, element, attrs, ganttCtrl) { + var api = ganttCtrl.gantt.api; + + // Load options from global options attribute. + if (scope.options && typeof(scope.options.contextmenu) === 'object') { + for (var option in scope.options.contextmenu) { + scope[option] = scope.options[option]; + } + } + + if (scope.enabled === undefined) { + scope.enabled = true; + } + + api.directives.on.new(scope, function(dName, dScope, dElement, dAttrs, dController) { + //for each new ganttTask + if (dName === 'ganttTask') { + dElement.bind('contextmenu', function(event) { + //TODO: remove link to dataService in this generic plugin + var dataService = dScope.scope.dataService; + var docElement = angular.element($document); + + if(dScope.task.model.raTask) { + dataService.selected_task_id = dScope.task.model.raTask.id; + } + + //search for already existing contextmenu element + while($document.find('#gantt-context-menu').length) { + //found, remove it, so we can create a fresh one + $document.find('#gantt-context-menu')[0].remove(); + + //unbind document close event handlers + docElement.unbind('click', closeContextMenu); + docElement.unbind('contextmenu', closeContextMenu); + } + + //create contextmenu element + //with list of menu items, + //each with it's own action + var contextmenuElement = angular.element('<div id="gantt-context-menu"></div>'); + var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>'); + contextmenuElement.append(ulElement); + var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + //TODO: remove link to dataService in this generic plugin + dataService.copyTask(dScope.task.model.raTask); + closeContextMenu(); + }); + + var closeContextMenu = function() { + contextmenuElement.remove(); + + //unbind document close event handlers + docElement.unbind('click', closeContextMenu); + docElement.unbind('contextmenu', closeContextMenu); + }; + + //click anywhere to remove the contextmenu + docElement.bind('click', closeContextMenu); + docElement.bind('contextmenu', closeContextMenu); + + //add contextmenu to body + var body = $document.find('body'); + body.append(contextmenuElement); + + //prevent bubbling event upwards + return false; + }); + } + }); + + api.directives.on.destroy(scope, function(dName, dScope, dElement, dAttrs, dController) { + //for each destroyed ganttTask + if (dName === 'ganttTask') { + dElement.unbind('contextmenu'); + } + }); + } + }; + }]); +}()); + +angular.module('gantt.contextmenu.templates', []).run(['$templateCache', function($templateCache) { + +}]); + diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index 0818ef43f8f..a6b8ac4d993 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -40,6 +40,7 @@ <script src="/static/app/controllers/gridcontroller.js"></script> <script src="/static/app/controllers/ganttresourcecontroller.js"></script> <script src="/static/app/controllers/ganttprojectcontroller.js"></script> + <script src="/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js"></script> <script src="/static/app/controllers/chartresourceusagecontroller.js"></script> </head> <body style="overflow:hidden;"> @@ -88,7 +89,6 @@ </div> </div> - <div class="top-stretch" ui-layout options="{flow: 'column'}"> <div ng-controller="GridController as gridCtrl" style="margin-right: 4px;" > <div id="grid" @@ -115,6 +115,7 @@ </gantt-movable> <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> <gantt-dependencies enabled="true"></gantt-dependencies> + <gantt-contextmenu enabled="true"></gantt-contextmenu> </div> </div> @@ -138,6 +139,7 @@ allow-resizing="true" allow-row-switching="false"></gantt-movable> <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> + <gantt-contextmenu enabled="true"></gantt-contextmenu> </div> </div> diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 74c97521871..40d6c0d87dc 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -49,10 +49,13 @@ from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAUL from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as DEFAULT_RADB_SERVICENAME from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME +from lofar.mom.momqueryservice.momrpc import MoMRPC +from lofar.mom.momqueryservice.config import DEFAULT_MOM_BUSNAME, DEFAULT_MOM_SERVICENAME from lofar.sas.resourceassignment.resourceassignmenteditor.mom import updateTaskMomDetails from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME as DEFAULT_CLEANUP_BUSNAME from lofar.sas.datamanagement.cleanup.config import DEFAULT_SERVICENAME as DEFAULT_CLEANUP_SERVICENAME +from lofar.common import isProductionEnvironment, isTestEnvironment #from lofar.sas.resourceassignment.resourceassigner. import updateTaskMomDetails logger = logging.getLogger(__name__) @@ -97,6 +100,7 @@ app.json_encoder = CustomJSONEncoder rarpc = None momrpc = None curpc = None +momqueryrpc = None radbchangeshandler = None @app.route('/') @@ -106,6 +110,18 @@ def index(): '''Serves the ResourceAssignmentEditor's index page''' return render_template('index.html', title='Scheduler') +@app.route('/rest/config') +@gzipped +def config(): + config = {'mom_base_url':''} + + if isProductionEnvironment(): + config['mom_base_url'] = 'https://lofar.astron.nl/mom3' + elif isTestEnvironment(): + config['mom_base_url'] = 'http://lofartest.control.lofar:8080/mom3' + + return jsonify({'config': config}) + @app.route('/rest/resources') @gzipped def resources(): @@ -185,7 +201,7 @@ def getTasksFromUntil(fromTimestamp=None, untilTimestamp=None): # there are no task names in the database yet. # will they come from spec/MoM? # add Task <id> as name for now - updateTaskMomDetails(tasks, momrpc) + updateTaskMomDetails(tasks, momqueryrpc) return jsonify({'tasks': tasks}) @@ -198,7 +214,7 @@ def getTask(task_id): abort(404) task['name'] = 'Task %d' % task['id'] - updateTaskMomDetails(task, momrpc) + updateTaskMomDetails(task, momqueryrpc) return jsonify({'task': task}) except Exception as e: abort(404) @@ -207,7 +223,8 @@ def getTask(task_id): @app.route('/rest/tasks/<int:task_id>', methods=['PUT']) def putTask(task_id): - abort(403, 'Editing of tasks is by users is not yet approved') + if isProductionEnvironment(): + abort(403, 'Editing of tasks is by users is not yet approved') if 'Content-Type' in request.headers and \ request.headers['Content-Type'].startswith('application/json'): @@ -270,6 +287,27 @@ def getTaskDataPath(task_id): return jsonify({'datapath': result['path']}) abort(404, result['message']) +@app.route('/rest/tasks/<int:task_id>/copy', methods=['PUT']) +def copyTask(task_id): + if isProductionEnvironment(): + abort(403, 'Copying of tasks is by users is not yet approved') + + try: + task = rarpc.getTask(task_id) + + if not task: + logger.error('Could not find task %s' % task_id) + abort(404, 'Could not find task %s' % task_id) + + mom2id = task['mom_id'] + new_task_mom2id = momrpc.copyTask(mom2id=mom2id) + return jsonify({'copied':True, 'new_task_mom2id':new_task_mom2id, 'old_task_mom2id':mom2id}) + except Exception as e: + logger.error(e) + abort(404, str(e)) + + return jsonify({'copied':False}) + @app.route('/rest/tasks/<int:task_id>/resourceclaims') def taskResourceClaims(task_id): return jsonify({'taskResourceClaims': rarpc.getResourceClaims(task_id=task_id, include_properties=True)}) @@ -302,7 +340,7 @@ def resourceclaimpropertytypes(): def getMoMProjects(): projects = [] try: - projects = momrpc.getProjects() + projects = momqueryrpc.getProjects() projects = [x for x in projects if x['status_id'] in [1, 7]] for project in projects: project['mom_id'] = project.pop('mom2id') @@ -315,7 +353,7 @@ def getMoMProjects(): @app.route('/rest/momobjectdetails/<int:mom2id>') def getMoMObjectDetails(mom2id): - details = momrpc.getProjectDetails(mom2id) + details = momqueryrpc.getProjectDetails(mom2id) details = details.values()[0] if details else None if details: details['project_mom_id'] = details.pop('project_mom2id') @@ -355,8 +393,11 @@ def main(): parser.add_option('--radb_servicename', dest='radb_servicename', type='string', default=DEFAULT_RADB_SERVICENAME, help='Name of the radbservice, default: %default') parser.add_option('--radb_notification_busname', dest='radb_notification_busname', type='string', default=DEFAULT_RADB_CHANGES_BUSNAME, help='Name of the notification bus exchange on the qpid broker on which the radb notifications are published, default: %default') parser.add_option('--radb_notification_subjects', dest='radb_notification_subjects', type='string', default=DEFAULT_RADB_CHANGES_SUBJECTS, help='Subject(s) to listen for on the radb notification bus exchange on the qpid broker, default: %default') - parser.add_option('--mom_busname', dest='mom_busname', type='string', default=DEFAULT_MOMQUERY_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momservice listens, default: %default') - parser.add_option('--mom_servicename', dest='mom_servicename', type='string', default=DEFAULT_MOMQUERY_SERVICENAME, help='Name of the momservice, default: %default') + parser.add_option('--mom_busname', dest='mom_busname', type='string', default=DEFAULT_MOM_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momservice listens, default: %default') + parser.add_option('--mom_servicename', dest='mom_servicename', type='string', default=DEFAULT_MOM_SERVICENAME, help='Name of the momservice, default: %default') + parser.add_option('--mom_broker', dest='mom_broker', type='string', default=None, help='Address of the qpid broker for the mom service, default: localhost') + parser.add_option('--mom_query_busname', dest='mom_query_busname', type='string', default=DEFAULT_MOMQUERY_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momqueryservice listens, default: %default') + parser.add_option('--mom_query_servicename', dest='mom_query_servicename', type='string', default=DEFAULT_MOMQUERY_SERVICENAME, help='Name of the momqueryservice, default: %default') parser.add_option('--cleanup_busname', dest='cleanup_busname', type='string', default=DEFAULT_CLEANUP_BUSNAME, help='Name of the bus exchange on the qpid broker on which the cleanupservice listens, default: %default') parser.add_option('--cleanup_servicename', dest='cleanup_servicename', type='string', default=DEFAULT_CLEANUP_SERVICENAME, help='Name of the cleanupservice, default: %default') parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') @@ -368,13 +409,15 @@ def main(): global rarpc rarpc = RARPC(busname=options.radb_busname, servicename=options.radb_servicename, broker=options.broker) global momrpc - momrpc = MoMQueryRPC(busname=options.mom_busname, servicename=options.mom_servicename, timeout=2.5, broker=options.broker) + momrpc = MoMRPC(busname=options.mom_busname, servicename=options.mom_servicename, timeout=2.5, broker=options.broker) global curpc curpc = CleanupRPC(busname=options.cleanup_busname, servicename=options.cleanup_servicename, broker=options.broker) + global momqueryrpc + momqueryrpc = MoMQueryRPC(busname=options.mom_query_busname, servicename=options.mom_query_servicename, timeout=2.5, broker=options.broker) global radbchangeshandler - radbchangeshandler = RADBChangesHandler(options.radb_notification_busname, broker=options.broker, momrpc=momrpc) + radbchangeshandler = RADBChangesHandler(options.radb_notification_busname, subjects=options.radb_notification_subjects, broker=options.broker, momqueryrpc=momqueryrpc) - with radbchangeshandler, rarpc, curpc, momrpc: + with radbchangeshandler, rarpc, curpc, momrpc, momqueryrpc: '''Start the webserver''' app.run(debug=options.verbose, threaded=True, host='0.0.0.0', port=options.port) -- GitLab From d7d7eba3d120e3a0de29518eac61eb11a5b6add4 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Tue, 14 Jun 2016 09:14:27 +0000 Subject: [PATCH 328/933] Task #9373: Apply COBALT tied-array beamformer corrections (fine delay compensation, bandpass correction, (coherent) beamforming) at 256 ch/sb (up from 64). This restores the BF spectroscopy science case for Raymond Oonk without negatively affecting pulsar or other BF science. Change has been extensively discussed. --- .../etc/parset-additions.d/default/NoCascadingFFT.parset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/NoCascadingFFT.parset b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/NoCascadingFFT.parset index ac1c9480b8a..bc5839123d7 100644 --- a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/NoCascadingFFT.parset +++ b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/NoCascadingFFT.parset @@ -1,5 +1,5 @@ # $Id$ # Define the sizes of both FFTs in the BF pipeline, # to prevent a cascade of FFTs -Cobalt.BeamFormer.nrDelayCompensationChannels = 64 -Cobalt.BeamFormer.nrHighResolutionChannels = 64 +Cobalt.BeamFormer.nrDelayCompensationChannels = 256 +Cobalt.BeamFormer.nrHighResolutionChannels = 256 -- GitLab From 83dd913ba823d4315649ea26bd0a20a52e27ccd6 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 14 Jun 2016 09:46:07 +0000 Subject: [PATCH 329/933] Task #8887: Propagate nr of allocated cores to OpenMP node recipes --- CEP/Pipeline/recipes/sip/master/executable_args.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CEP/Pipeline/recipes/sip/master/executable_args.py b/CEP/Pipeline/recipes/sip/master/executable_args.py index 2bec87956bb..e418b7e399c 100644 --- a/CEP/Pipeline/recipes/sip/master/executable_args.py +++ b/CEP/Pipeline/recipes/sip/master/executable_args.py @@ -195,6 +195,9 @@ class executable_args(BaseRecipe, RemoteCommandRecipeMixIn): if 'executable' in self.inputs: executable = self.inputs['executable'] + if self.inputs['nthreads']: + self.environment["OMP_NUM_THREADS"] = str(self.inputs['nthreads']) + if 'environment' in self.inputs: self.environment.update(self.inputs['environment']) -- GitLab From a3988cf4c91251145e77e5fac5b1bd50f42d3d73 Mon Sep 17 00:00:00 2001 From: Ger van Diepen <diepen@astron.nl> Date: Tue, 14 Jun 2016 10:34:28 +0000 Subject: [PATCH 330/933] Task #8887: Improved tests and adapted DPPP as needed --- .gitattributes | 2 ++ CEP/DP3/DPPP/src/BaselineSelection.cc | 11 ++++++++++- CEP/DP3/DPPP/src/DPRun.cc | 4 ++-- CEP/DP3/DPPP/src/NDPPP.cc | 7 ++++--- CEP/MS/test/tBaselineSelect.sh | 2 ++ CEP/MS/test/tBaselineSelect.stdout | 26 ++++++++++++++++++++++++++ 6 files changed, 46 insertions(+), 6 deletions(-) create mode 100755 CEP/MS/test/tBaselineSelect.sh create mode 100644 CEP/MS/test/tBaselineSelect.stdout diff --git a/.gitattributes b/.gitattributes index fd72c21e29a..29c565268dd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1312,6 +1312,8 @@ CEP/MS/src/ls_nostderr -text CEP/MS/src/makemsdistr -text CEP/MS/src/makemsdistr-part -text CEP/MS/src/movemss -text +CEP/MS/test/tBaselineSelect.sh -text +CEP/MS/test/tBaselineSelect.stdout -text CEP/MS/test/tcombinevds.in_vds1 -text CEP/MS/test/tcombinevds.in_vds2 -text CEP/MS/test/tmakems.in_antenna.tgz -text svneol=unset#application/x-compressed-tar diff --git a/CEP/DP3/DPPP/src/BaselineSelection.cc b/CEP/DP3/DPPP/src/BaselineSelection.cc index fd6fcb4d25b..e09fc904674 100644 --- a/CEP/DP3/DPPP/src/BaselineSelection.cc +++ b/CEP/DP3/DPPP/src/BaselineSelection.cc @@ -28,6 +28,7 @@ #include <Common/ParameterSet.h> #include <Common/ParameterValue.h> #include <Common/LofarLogger.h> +#include <Common/StringUtil.h> #include <Common/StreamUtil.h> using namespace casa; @@ -138,7 +139,15 @@ namespace LOFAR { // Specified in casacore's MSSelection format. string msName = info.msName(); ASSERT (! msName.empty()); - Matrix<bool> sel(BaselineSelect::convert (msName, itsStrBL)); + std::ostringstream os; + Matrix<bool> sel(BaselineSelect::convert (msName, itsStrBL, os)); + // Show possible messages about unknown stations. + if (! os.str().empty()) { + vector<string> messages = StringUtil::split (os.str(), '\n'); + for (size_t i=0; i<messages.size(); ++i) { + DPLOG_WARN_STR (messages[i]); + } + } // The resulting matrix can be smaller because new stations might have // been added that are not present in the MS's ANTENNA table. if (sel.nrow() == selectBL.nrow()) { diff --git a/CEP/DP3/DPPP/src/DPRun.cc b/CEP/DP3/DPPP/src/DPRun.cc index 599a298c40b..1babeee7186 100644 --- a/CEP/DP3/DPPP/src/DPRun.cc +++ b/CEP/DP3/DPPP/src/DPRun.cc @@ -103,8 +103,8 @@ namespace LOFAR { NSTimer nstimer; nstimer.start(); ParameterSet parset; - if (parsetName!="") { - parset.adoptFile(parsetName); + if (! parsetName.empty()) { + parset.adoptFile (parsetName); } // Adopt possible parameters given at the command line. parset.adoptArgv (argc, argv); //# works fine if argc==0 and argv==0 diff --git a/CEP/DP3/DPPP/src/NDPPP.cc b/CEP/DP3/DPPP/src/NDPPP.cc index 5652c6c38ab..f668199eedb 100644 --- a/CEP/DP3/DPPP/src/NDPPP.cc +++ b/CEP/DP3/DPPP/src/NDPPP.cc @@ -68,11 +68,12 @@ int main(int argc, char *argv[]) return 0; } - string parsetName(""); - if (argc > 1 && string(argv[1]).find('=')==string::npos) { + string parsetName; + if (argc > 1 && string(argv[1]).find('=') == string::npos) { // First argument is parset name (except if it's a key-value pair) parsetName = argv[1]; - } else if (argc==1) { // If no arguments given: try to load [N]DPPP.parset + } else if (argc==1) { + // No arguments given: try to load [N]DPPP.parset if (casa::File("NDPPP.parset").exists()) { parsetName="NDPPP.parset"; } else if (casa::File("DPPP.parset").exists()) { diff --git a/CEP/MS/test/tBaselineSelect.sh b/CEP/MS/test/tBaselineSelect.sh new file mode 100755 index 00000000000..7b258eed4f2 --- /dev/null +++ b/CEP/MS/test/tBaselineSelect.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh tBaselineSelect diff --git a/CEP/MS/test/tBaselineSelect.stdout b/CEP/MS/test/tBaselineSelect.stdout new file mode 100644 index 00000000000..1db78f080d6 --- /dev/null +++ b/CEP/MS/test/tBaselineSelect.stdout @@ -0,0 +1,26 @@ +Axis Lengths: [5, 5] (NB: Matrix in Row/Column order) +[0, 1, 1, 1, 1 + 1, 0, 1, 1, 1 + 1, 1, 0, 0, 0 + 1, 1, 0, 0, 0 + 1, 1, 0, 0, 0] +Antenna Expression: No match found for token(s) "CS013HBA[23]" +No match found for the antenna specificion [ID(s): []] +Axis Lengths: [5, 5] (NB: Matrix in Row/Column order) +[0, 0, 0, 0, 0 + 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0] +Axis Lengths: [5, 5] (NB: Matrix in Row/Column order) +[1, 1, 1, 1, 0 + 1, 1, 1, 1, 0 + 1, 1, 1, 1, 0 + 1, 1, 1, 0, 0 + 0, 0, 0, 0, 0] +Axis Lengths: [5, 5] (NB: Matrix in Row/Column order) +[1, 1, 0, 1, 0 + 1, 1, 0, 1, 0 + 0, 0, 1, 0, 1 + 1, 1, 0, 1, 0 + 0, 0, 1, 0, 1] -- GitLab From f07d6ffa56747ade48d06b99b72edbd5364b1e30 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 14 Jun 2016 14:17:45 +0000 Subject: [PATCH 331/933] Task #9351: log db credentials at startup --- SAS/MoM/MoMQueryService/momqueryservice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index 5893a21bc8b..7ea59821aae 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -225,6 +225,8 @@ def main(): dbcreds = dbcredentials.parse_options(options) + logger.info("Using dbcreds: %s" % dbcreds.stringWithHiddenPassword()) + # start the service and listen. with createService(busname=options.busname, servicename=options.servicename, -- GitLab From b690f43e5bff2530f3a8cd0bbaa465eaa760e273 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 14 Jun 2016 14:18:13 +0000 Subject: [PATCH 332/933] Task #9351: minor fixes --- SAS/DataManagement/CleanupService/service.py | 24 +++++--------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index 9b4a228e7e1..e35936d7263 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -2,21 +2,6 @@ # $Id$ ''' -Simple Service listening on momqueryservice.GetProjectDetails -which gives the project details for each requested mom object id - -Example usage: -service side: just run this service somewhere where it can access the momdb and -a qpid broker. -Make sure the bus exists: qpid-config add exchange topic <busname> - -client side: do a RPC call to the <busname>.GetProjectDetails with a -comma seperated string of mom2object id's as argument. -You get a dict of mom2id to project-details-dict back. - -with RPC(busname, 'GetProjectDetails') as getProjectDetails: - res, status = getProjectDetails(ids_string) - ''' import logging import os.path @@ -98,8 +83,11 @@ class CleanupHandler(MessageHandlerInterface): logger.error(message) return {'deleted': False, 'message': message} - task_data_path = self._getPathForOTDBId(otdb_id) - return self._removePath(task_data_path) + result = self._getPathForOTDBId(otdb_id) + if result['found']: + return self._removePath(result['path']) + + return {'deleted': False, 'message': result['message']} def _removePath(self, path): logger.info("Remove path: %s" % (path,)) @@ -138,7 +126,7 @@ class CleanupHandler(MessageHandlerInterface): failed_paths = set() def onerror(func, failed_path, excinfo): - logger.info("Failed to delete %s", failed_path) + logger.warning("Failed to delete %s", failed_path) failed_paths.add(failed_path) if rmtree(path, onerror=onerror): -- GitLab From 0c992402554202748a1acf9876e3f03ed56f544e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 14 Jun 2016 14:23:04 +0000 Subject: [PATCH 333/933] Task #9351: use cleanup rest url in webscheduler UI via angular cleanupcontroller --- .gitattributes | 1 + .../lib/static/app/app.js | 3 +- .../app/controllers/cleanupcontroller.js | 64 ++++++ .../static/app/controllers/gridcontroller.js | 143 +++++++------- .../angular-gantt-contextmenu-plugin.js | 9 + .../lib/templates/index.html | 183 +++++++++--------- 6 files changed, 245 insertions(+), 158 deletions(-) create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js diff --git a/.gitattributes b/.gitattributes index 8550832d67f..03607e7d1d8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5089,6 +5089,7 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/chartresourceusagecontroller.js -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js -text diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js index 1c33462df9b..7b4e06b1712 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js @@ -1,7 +1,8 @@ // $Id$ var app = angular.module('raeApp', - ['DataControllerMod', + ['CleanupControllerMod', + 'DataControllerMod', 'GanttResourceControllerMod', 'GanttProjectControllerMod', 'ChartResourceUsageControllerMod', diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js new file mode 100644 index 00000000000..50af9f2dac5 --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -0,0 +1,64 @@ +// $Id: controller.js 32761 2015-11-02 11:50:21Z schaap $ + +var cleanupControllerMod = angular.module('CleanupControllerMod', ['ui.bootstrap']); + +cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$http', '$q', function($scope, $uibModal, $http, $q) { + var self = this; + + self.getTaskDataPath = function(task) { + var defer = $q.defer(); + + $http.get('/rest/tasks/' + task.id + '/datapath').success(function(result) { + defer.resolve(result); + }).error(function(result) { + console.log("Error. Could get data path for task " + task.id + ", " + result); + alert(result); + }); + + return defer.promise; + }; + + self.deleteTaskDataWithConfirmation = function(task) { + self.getTaskDataPath(task).then(function(result) { + openDeleteConfirmationDialog(task, result.datapath); + }); + }; + + function deleteTaskData(task) { + $http.delete('/rest/tasks/' + task.id + '/cleanup').error(function(result) { + console.log("Error. Could cleanup data for task " + task.id + ", " + result); + }).success(function(result) { + if(!result.deleted) { + alert(result.message); + } + }); + }; + + function openDeleteConfirmationDialog(task, path) { + var modalInstance = $uibModal.open({ + animation: false, + template: '<div class="modal-header">\ + <h3 class="modal-title">Are you sure?</h3>\ + </div>\ + <div class="modal-body">\ + <p>This will delete all data in ' + path + '<br>\ + Are you sure?</p>\ + </div>\ + <div class="modal-footer">\ + <button class="btn btn-primary" type="button" ng-click="ok()">OK</button>\ + <button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>\ + </div>', + controller: function ($scope, $uibModalInstance) { + $scope.ok = function () { + $uibModalInstance.close(); + deleteTaskData(task); + }; + + $scope.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + } + }); + }; +}]); + diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 081ea2bd50c..dc6a2b40a69 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -11,75 +11,76 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid $scope.dataService = dataService; -$scope.columns = [ - { field: 'name', - enableCellEdit: false, - width: '15%' - }, - { field: 'project_name', - displayName:'Project', - enableCellEdit: false, - cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}">{{row.entity[col.field]}}</a>', - width: '15%', - filter: { - type: uiGridConstants.filter.SELECT, - selectOptions: [] - } - }, - { field: 'starttime', - displayName: 'Start', - width: '14%', - type: 'date', - enableCellEdit: false, - enableCellEditOnFocus: false, - cellTemplate:'<div style=\'text-align:left\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>', - sort: { direction: uiGridConstants.ASC } - }, - { field: 'endtime', - displayName: 'End', - width: '14%', - type: 'date', - enableCellEdit: false, - enableCellEditOnFocus: false, - cellTemplate:'<div style=\'text-align:left\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>' - }, - { field: 'duration', - displayName: 'Duration', - width: '8%', - enableFiltering: false, - enableCellEdit: false, - enableCellEditOnFocus: false, - cellTemplate:'<div style=\'text-align:left\'>{{row.entity[col.field] | secondsToHHmmss}}</div>' - }, - { field: 'status', - enableCellEdit: true, - width: '8%', - filter: { - type: uiGridConstants.filter.SELECT, - selectOptions: [] + $scope.columns = [ + { field: 'name', + enableCellEdit: false, + width: '15%' }, - editableCellTemplate: 'ui-grid/dropdownEditor', - editDropdownOptionsArray: [] - }, - { field: 'type', - enableCellEdit: false, - width: '8%', - filter: { - type: uiGridConstants.filter.SELECT, - selectOptions: [] + { field: 'project_name', + displayName:'Project', + enableCellEdit: false, + cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}">{{row.entity[col.field]}}</a>', + width: '15%', + filter: { + type: uiGridConstants.filter.SELECT, + selectOptions: [] + } + }, + { field: 'starttime', + displayName: 'Start', + width: '14%', + type: 'date', + enableCellEdit: false, + enableCellEditOnFocus: false, + cellTemplate:'<div style=\'text-align:left\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>', + sort: { direction: uiGridConstants.ASC } + }, + { field: 'endtime', + displayName: 'End', + width: '14%', + type: 'date', + enableCellEdit: false, + enableCellEditOnFocus: false, + cellTemplate:'<div style=\'text-align:left\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>' + }, + { field: 'duration', + displayName: 'Duration', + width: '8%', + enableFiltering: false, + enableCellEdit: false, + enableCellEditOnFocus: false, + cellTemplate:'<div style=\'text-align:left\'>{{row.entity[col.field] | secondsToHHmmss}}</div>' + }, + { field: 'status', + enableCellEdit: true, + width: '8%', + filter: { + type: uiGridConstants.filter.SELECT, + selectOptions: [] + }, + editableCellTemplate: 'ui-grid/dropdownEditor', + editDropdownOptionsArray: [] + }, + { field: 'type', + enableCellEdit: false, + width: '8%', + filter: { + type: uiGridConstants.filter.SELECT, + selectOptions: [] + } + }, + { field: 'mom_id', + displayName: 'MoM ID', + enableCellEdit: false, + cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}">{{row.entity[col.field]}}</a>', + width: '8%' + }, + { field: 'otdb_id', + displayName: 'SAS ID', + enableCellEdit: false, + width: '8%' } - }, - { field: 'mom_id', - displayName: 'MoM ID', - enableCellEdit: false, - cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}">{{row.entity[col.field]}}</a>', - width: '8%' - }, - { field: 'otdb_id', - displayName: 'SAS ID', - enableCellEdit: false, - width: '8%' - }]; + ]; $scope.gridOptions = { enableGridMenu: false, enableSorting: true, @@ -243,6 +244,7 @@ gridControllerMod.directive('contextMenu', ['$document', function($document) { function handleContextMenuEvent(event) { //pragmatic 'hard-coded' way of getting the dataService and the rowEntity via scope tree. var dataService = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.dataService; + var cleanupCtrl = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.cleanupCtrl; var rowEntity = $scope.$parent.$parent.$parent.row.entity; if(!dataService || !rowEntity) @@ -273,8 +275,15 @@ gridControllerMod.directive('contextMenu', ['$document', function($document) { var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); ulElement.append(liElement); liElement.on('click', function() { + closeContextMenu(); dataService.copyTask(task); + }); + + var liElement = angular.element('<li><a href="#">Delete data</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { closeContextMenu(); + cleanupCtrl.deleteTaskDataWithConfirmation(task); }); var closeContextMenu = function(cme) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index 18c7a7e29b6..ed879020df5 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -27,6 +27,7 @@ dElement.bind('contextmenu', function(event) { //TODO: remove link to dataService in this generic plugin var dataService = dScope.scope.dataService; + var cleanupCtrl = dScope.scope.$parent.cleanupCtrl; var docElement = angular.element($document); if(dScope.task.model.raTask) { @@ -49,12 +50,20 @@ var contextmenuElement = angular.element('<div id="gantt-context-menu"></div>'); var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>'); contextmenuElement.append(ulElement); + var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); ulElement.append(liElement); liElement.on('click', function() { + closeContextMenu(); //TODO: remove link to dataService in this generic plugin dataService.copyTask(dScope.task.model.raTask); + }); + + var liElement = angular.element('<li><a href="#">Delete data</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { closeContextMenu(); + cleanupCtrl.deleteTaskDataWithConfirmation(dScope.task.model.raTask); }); var closeContextMenu = function() { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index a6b8ac4d993..b1a7d72e720 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -37,6 +37,7 @@ <script type="text/javascript" src="/static/js/highcharts/highcharts-ng.js"></script> <script src="/static/app/app.js"></script> <script src="/static/app/controllers/datacontroller.js"></script> + <script src="/static/app/controllers/cleanupcontroller.js"></script> <script src="/static/app/controllers/gridcontroller.js"></script> <script src="/static/app/controllers/ganttresourcecontroller.js"></script> <script src="/static/app/controllers/ganttprojectcontroller.js"></script> @@ -46,103 +47,105 @@ <body style="overflow:hidden;"> {% raw %} <div ng-controller="DataController as dataCtrl" class="container-fluid"> - <div class="row"> - <div class="col-md-2"> - <label>Time (UTC):</label> - <p> - <strong style="font-size:16px">{{dataService.lofarTime | date }}</strong> - </p> - </div> - <div class="col-md-3"> - <label>From:</label> - <p class="input-group"> - <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="dataService.viewTimeSpan.from" is-open="viewFromDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> - <span class="input-group-btn"> - <button type="button" class="btn btn-default" ng-click="openViewFromDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> - </span> - <uib-timepicker ng-model="dataService.viewTimeSpan.from" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> - </p> - </div> - <div class="col-md-3"> - <label>To:</label> - <p class="input-group"> - <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="dataService.viewTimeSpan.to" is-open="viewToDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> - <span class="input-group-btn"> - <button type="button" class="btn btn-default" ng-click="openViewToDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> - </span> - <uib-timepicker ng-model="dataService.viewTimeSpan.to" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> - </p> - </div> - <div class="col-md-3"> - <label>Jump:</label> - <p class="input-group"> - <span class="input-group-btn"> - <button type="button" class="btn btn-default" ng-click="jumpToSelectedTasks()" title="Jump to selected Task(s)">Task</button> - </span> - <span class="input-group-btn" style="width:10px;"></span> - <span class="input-group-btn"> - <button type="button" class="btn btn-default" ng-click="jumpToNow()" title="Jump to Now">Now</button> - </span> - <select class="form-control" ng-model=jumpTimespanWidth ng-options="option.name for option in jumpTimespanWidths track by option.value" ng-change="onJumpTimespanWidthChanged(span)"> - </select> - </p> + <div ng-controller="CleanupController as cleanupCtrl" class="container-fluid"> + <div class="row"> + <div class="col-md-2"> + <label>Time (UTC):</label> + <p> + <strong style="font-size:16px">{{dataService.lofarTime | date }}</strong> + </p> + </div> + <div class="col-md-3"> + <label>From:</label> + <p class="input-group"> + <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="dataService.viewTimeSpan.from" is-open="viewFromDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> + <span class="input-group-btn"> + <button type="button" class="btn btn-default" ng-click="openViewFromDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> + </span> + <uib-timepicker ng-model="dataService.viewTimeSpan.from" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> + </p> + </div> + <div class="col-md-3"> + <label>To:</label> + <p class="input-group"> + <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="dataService.viewTimeSpan.to" is-open="viewToDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> + <span class="input-group-btn"> + <button type="button" class="btn btn-default" ng-click="openViewToDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> + </span> + <uib-timepicker ng-model="dataService.viewTimeSpan.to" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> + </p> + </div> + <div class="col-md-3"> + <label>Jump:</label> + <p class="input-group"> + <span class="input-group-btn"> + <button type="button" class="btn btn-default" ng-click="jumpToSelectedTasks()" title="Jump to selected Task(s)">Task</button> + </span> + <span class="input-group-btn" style="width:10px;"></span> + <span class="input-group-btn"> + <button type="button" class="btn btn-default" ng-click="jumpToNow()" title="Jump to Now">Now</button> + </span> + <select class="form-control" ng-model=jumpTimespanWidth ng-options="option.name for option in jumpTimespanWidths track by option.value" ng-change="onJumpTimespanWidthChanged(span)"> + </select> + </p> + </div> </div> - </div> - <div class="top-stretch" ui-layout options="{flow: 'column'}"> - <div ng-controller="GridController as gridCtrl" style="margin-right: 4px;" > - <div id="grid" - ui-grid="gridOptions" - ui-grid-edit ui-grid-selection ui-grid-cellNav ui-grid-resize-columns ui-grid-auto-resize - class="grid"></div> - </div> - <div ui-layout options="{flow: 'row'}"> - <div ng-controller="GanttProjectController as ganttProjectCtrl" style="overflow:auto; margin-left:12px; margin-bottom:4px; "> - <div gantt data=ganttData - api=options.api - show-side='true' - view-scale="options.viewScale" - from-date="options.fromDate" - to-date="options.toDate" - current-date="options.currentDate" - current-date-value="options.currentDateValue" - column-magnet="options.columnMagnet"> - <gantt-tree enabled="true"></gantt-tree> - <gantt-movable enabled="true" - allow-moving="true" - allow-resizing="true" - allow-row-switching="false"> - </gantt-movable> - <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> - <gantt-dependencies enabled="true"></gantt-dependencies> - <gantt-contextmenu enabled="true"></gantt-contextmenu> - </div> + <div class="top-stretch" ui-layout options="{flow: 'column'}"> + <div ng-controller="GridController as gridCtrl" style="margin-right: 4px;" > + <div id="grid" + ui-grid="gridOptions" + ui-grid-edit ui-grid-selection ui-grid-cellNav ui-grid-resize-columns ui-grid-auto-resize + class="grid"></div> </div> + <div ui-layout options="{flow: 'row'}"> + <div ng-controller="GanttProjectController as ganttProjectCtrl" style="overflow:auto; margin-left:12px; margin-bottom:4px; "> + <div gantt data=ganttData + api=options.api + show-side='true' + view-scale="options.viewScale" + from-date="options.fromDate" + to-date="options.toDate" + current-date="options.currentDate" + current-date-value="options.currentDateValue" + column-magnet="options.columnMagnet"> + <gantt-tree enabled="true"></gantt-tree> + <gantt-movable enabled="true" + allow-moving="true" + allow-resizing="true" + allow-row-switching="false"> + </gantt-movable> + <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> + <gantt-dependencies enabled="true"></gantt-dependencies> + <gantt-contextmenu enabled="true"></gantt-contextmenu> + </div> + </div> - <div ng-controller="ChartResourceUsageController as chartResourceUsageCtrl"> - <highchart id="chart_resource_usage" config="chartConfig" style="width: 96%; height: 100%; margin: 12px;" ></highchart> - </div> + <div ng-controller="ChartResourceUsageController as chartResourceUsageCtrl"> + <highchart id="chart_resource_usage" config="chartConfig" style="width: 96%; height: 100%; margin: 12px;" ></highchart> + </div> - <div ng-controller="GanttResourceController as ganttResourceCtrl" style="overflow:auto; margin-left:12px; margin-top:12px"> - <div gantt data=ganttData - api=options.api - show-side='true' - view-scale="options.viewScale" - from-date="options.fromDate" - to-date="options.toDate" - current-date="options.currentDate" - current-date-value="options.currentDateValue" - column-magnet="options.columnMagnet"> - <gantt-tree enabled="true"></gantt-tree> - <gantt-movable enabled="true" - allow-moving="true" - allow-resizing="true" - allow-row-switching="false"></gantt-movable> - <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> - <gantt-contextmenu enabled="true"></gantt-contextmenu> + <div ng-controller="GanttResourceController as ganttResourceCtrl" style="overflow:auto; margin-left:12px; margin-top:12px"> + <div gantt data=ganttData + api=options.api + show-side='true' + view-scale="options.viewScale" + from-date="options.fromDate" + to-date="options.toDate" + current-date="options.currentDate" + current-date-value="options.currentDateValue" + column-magnet="options.columnMagnet"> + <gantt-tree enabled="true"></gantt-tree> + <gantt-movable enabled="true" + allow-moving="true" + allow-resizing="true" + allow-row-switching="false"></gantt-movable> + <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> + <gantt-contextmenu enabled="true"></gantt-contextmenu> + </div> </div> - </div> + </div> </div> </div> </div> -- GitLab From 4d930cb1740054035b4d8df04fd845bc1045cb5c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 15 Jun 2016 09:46:49 +0000 Subject: [PATCH 334/933] Task #8887: Do not cancel docker jobs if they fail or exit quickly on a node --- MAC/Services/src/PipelineControl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 008d8c927f0..89708ce7519 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -366,9 +366,11 @@ class PipelineControl(OTDBBusListener): slurm_job_id = self.slurm.submit(parset.slurmJobName(), # pull docker image from repository on all nodes "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull" + " --no-kill" " docker pull {repository}/{image}\n" # put a local tag on the pulled image "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-tag" + " --no-kill" " docker tag -f {repository}/{image} {image}\n" # call runPipeline.sh in the image on this node "docker run --rm" -- GitLab From 5bed8c127a73757b0ccd55945c8ab1f2a1202099 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 15 Jun 2016 10:14:27 +0000 Subject: [PATCH 335/933] Task #8887: Ignore pipelines to CEP4 in production --- MAC/APL/MainCU/src/MACScheduler/MACScheduler.conf.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/APL/MainCU/src/MACScheduler/MACScheduler.conf.in b/MAC/APL/MainCU/src/MACScheduler/MACScheduler.conf.in index ed9c486a187..fd6dd597002 100644 --- a/MAC/APL/MainCU/src/MACScheduler/MACScheduler.conf.in +++ b/MAC/APL/MainCU/src/MACScheduler/MACScheduler.conf.in @@ -33,4 +33,4 @@ ParsetQueuename = lofar.task.specification.system # Pipelines on cluster X can be ignored by the MACScheduler with this key. # use e.g. 'CEP2' or 'CEP4' -excludePipelinesOnThisCluster = '' +excludePipelinesOnThisCluster = 'CEP4' -- GitLab From 0bbcd924c39fee390884437cff32dfdace7d785d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 15 Jun 2016 11:07:07 +0000 Subject: [PATCH 336/933] Task #8887: black font for queued status --- .../ResourceAssignmentEditor/lib/static/css/main.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index 46a6dac3cce..ac3b5cd0789 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -83,11 +83,11 @@ div.gantt-task span { padding: 0px 10px; } -div.gantt-task.task-status-on_hold span, div.gantt-task.task-status-prescheduled span, div.gantt-task.task-status-scheduled span, div.gantt-task.task-status-queued span, div.gantt-task.task-status-aborted span, div.gantt-task.task-status-error span { +div.gantt-task.task-status-on_hold span, div.gantt-task.task-status-prescheduled span, div.gantt-task.task-status-scheduled span, div.gantt-task.task-status-aborted span, div.gantt-task.task-status-error span { color: #ffffff; } -div.gantt-task.claim-task-status-on_hold span, div.gantt-task.claim-task-status-prescheduled span, div.gantt-task.claim-task-status-scheduled span, div.gantt-task.claim-task-status-queued span, div.gantt-task.claim-task-status-aborted span, div.gantt-task.claim-task-status-error span { +div.gantt-task.claim-task-status-on_hold span, div.gantt-task.claim-task-status-prescheduled span, div.gantt-task.claim-task-status-scheduled span, div.gantt-task.claim-task-status-aborted span, div.gantt-task.claim-task-status-error span { color: #ffffff; } @@ -185,7 +185,6 @@ div.gantt-task.claim-task-status-suspended span { .grid-status-queued { background-color: #ccffff !important; - color: #ffffff; } .grid-status-active { -- GitLab From 2f4bdcf3f3f9027366aee29a7baf01cabf50d1b0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 15 Jun 2016 11:07:33 +0000 Subject: [PATCH 337/933] Task #8887: only update stop times from otdb for pipelines --- .../OTDBtoRATaskStatusPropagator/propagator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index 9cd4b667a57..711f4721680 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -139,7 +139,7 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): def _updateStopTime(self, treeId): radb_task = self.radb.getTask(otdb_id=treeId) - if radb_task: + if radb_task and radb_task['type'] == 'pipeline': otdb_task = self.otdb.taskGetTreeInfo(otdb_id=treeId) if otdb_task and (otdb_task['starttime'] != radb_task['starttime'] or otdb_task['stoptime'] != radb_task['endtime']): new_endtime = otdb_task['stoptime'] -- GitLab From 526475d0d6c0932e250f61e0472b676c58c4f866 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 15 Jun 2016 11:08:09 +0000 Subject: [PATCH 338/933] Task #8887: delete existing task in case mom_id known --- .../ResourceAssignmentDatabase/radb.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 908d6a59b4d..f9a4ee19ef9 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -953,7 +953,7 @@ class RADatabase: return None # gather all properties for all claims - # store them as list of (claim_id, prop_type, prop_value, sap_nr) tuples + # store them as list of (claim_id, prop_type, prop_value, io_type, sap_nr) tuples properties = [] for claim_id, claim in zip(claimIds, claims): if 'properties' in claim and len(claim['properties']) > 0: @@ -1203,10 +1203,15 @@ class RADatabase: ''' Insert a new specification and task in one transaction. Removes existing task with same otdb_id if present in the same transaction. + Removes existing task with same mom_id if present in the same transaction. ''' try: task = self.getTask(otdb_id=otdb_id) + if task: + # delete old specification, task, and resource claims using cascaded delete + self.deleteSpecification(task['specification_id'], False) + task = self.getTask(mom_id=mom_id) if task: # delete old specification, task, and resource claims using cascaded delete self.deleteSpecification(task['specification_id'], False) @@ -1444,7 +1449,15 @@ if __name__ == '__main__': #print db.getTaskSuccessorIds() #resultPrint(db.getSpecifications) #resultPrint(db.getResourceClaims) - resultPrint(db.getResourceClaimProperties) + #resultPrint(db.getResourceClaimProperties) + + task = db.getTask(otdb_id=521446) + print task + claims = db.getResourceClaims(task_ids=task['id'], extended=True, include_properties=True) + print claims + + + exit(0) resources = db.getResources() -- GitLab From 7ffc720548cacd8c7359b0ae69f0c41217acbd61 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 15 Jun 2016 11:08:51 +0000 Subject: [PATCH 339/933] Task #8887: bug fix, scope of sap_nr --- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 0a4fb1ca968..a272b2f529d 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -365,7 +365,7 @@ class ResourceAssigner(): for sap_dict in needed_prop_group: processProperties(sap_dict['properties'], sap_dict['sap_nr'], is_input) else: - processProperties(needed_prop_group, is_input) + processProperties(needed_prop_group, None, is_input) logger.info('claimResources: created claim:%s' % claim) claims.append(claim) -- GitLab From ebf09d501ef7aaba99e38cdfe611786d0a090806 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 15 Jun 2016 13:15:25 +0000 Subject: [PATCH 340/933] Task #9351: typo's --- SAS/DataManagement/CleanupService/cleanupservice.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/DataManagement/CleanupService/cleanupservice.ini b/SAS/DataManagement/CleanupService/cleanupservice.ini index 95b1a0b7521..0e705c1191c 100644 --- a/SAS/DataManagement/CleanupService/cleanupservice.ini +++ b/SAS/DataManagement/CleanupService/cleanupservice.ini @@ -1,5 +1,5 @@ -[program:radbservice] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec radbservice --log-queries' +[program:cleanupservice] +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec cleanupservice --log-queries' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals -- GitLab From e3cb8888d529f55704b70b4608506233be14213a Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 15 Jun 2016 13:18:33 +0000 Subject: [PATCH 341/933] Task #9353: added skeleton code for StorageQueryService --- .gitattributes | 14 +++++ CMake/LofarPackageList.cmake | 4 +- SAS/DataManagement/CMakeLists.txt | 1 + .../StorageQueryService/CMakeLists.txt | 24 ++++++++ .../StorageQueryService/__init__.py | 1 + .../StorageQueryService/config.py | 7 +++ SAS/DataManagement/StorageQueryService/rpc.py | 35 ++++++++++++ .../StorageQueryService/service.py | 57 +++++++++++++++++++ .../StorageQueryService/storagequery | 9 +++ .../StorageQueryService/storagequeryservice | 10 ++++ .../storagequeryservice.ini | 8 +++ .../StorageQueryService/test/CMakeLists.txt | 5 ++ .../test/test_storagequery_service_and_rpc.py | 54 ++++++++++++++++++ .../test_storagequery_service_and_rpc.run | 6 ++ .../test/test_storagequery_service_and_rpc.sh | 3 + SubSystems/CMakeLists.txt | 1 + SubSystems/DataManagement/CMakeLists.txt | 10 ++++ SubSystems/DataManagement/DataManagement.ini | 3 + 18 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 SAS/DataManagement/StorageQueryService/CMakeLists.txt create mode 100644 SAS/DataManagement/StorageQueryService/__init__.py create mode 100644 SAS/DataManagement/StorageQueryService/config.py create mode 100644 SAS/DataManagement/StorageQueryService/rpc.py create mode 100644 SAS/DataManagement/StorageQueryService/service.py create mode 100644 SAS/DataManagement/StorageQueryService/storagequery create mode 100644 SAS/DataManagement/StorageQueryService/storagequeryservice create mode 100644 SAS/DataManagement/StorageQueryService/storagequeryservice.ini create mode 100644 SAS/DataManagement/StorageQueryService/test/CMakeLists.txt create mode 100755 SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.py create mode 100755 SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.run create mode 100755 SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.sh create mode 100644 SubSystems/DataManagement/CMakeLists.txt create mode 100644 SubSystems/DataManagement/DataManagement.ini diff --git a/.gitattributes b/.gitattributes index 03607e7d1d8..a42a20a7cff 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4741,6 +4741,18 @@ SAS/DataManagement/CleanupService/test/CMakeLists.txt -text SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py -text SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.run -text SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.sh -text +SAS/DataManagement/StorageQueryService/CMakeLists.txt -text +SAS/DataManagement/StorageQueryService/__init__.py -text +SAS/DataManagement/StorageQueryService/config.py -text +SAS/DataManagement/StorageQueryService/rpc.py -text +SAS/DataManagement/StorageQueryService/service.py -text +SAS/DataManagement/StorageQueryService/storagequery -text +SAS/DataManagement/StorageQueryService/storagequeryservice -text +SAS/DataManagement/StorageQueryService/storagequeryservice.ini -text +SAS/DataManagement/StorageQueryService/test/CMakeLists.txt -text +SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.py -text +SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.run -text +SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.sh -text SAS/Feedback_Service/CMakeLists.txt -text SAS/Feedback_Service/package.dox -text SAS/Feedback_Service/src/CMakeLists.txt -text @@ -5436,6 +5448,8 @@ SAS/XML_generator/test/test_regression.in_data/xml/test_input_cep4.xml -text SAS/XML_generator/test/test_regression.in_data/xml/test_input_long_baseline_pipeline.xml -text SAS/XML_generator/test/test_regression.py -text SAS/XML_generator/test/test_regression.sh -text +SubSystems/DataManagement/CMakeLists.txt -text +SubSystems/DataManagement/DataManagement.ini -text SubSystems/LAPS_CEP/test/startPythonFromMsg.py eol=lf SubSystems/LAPS_CEP/test/startPythonFromMsg.run eol=lf SubSystems/LAPS_CEP/test/startPythonFromMsg.sh eol=lf diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index 6dd5fc4396e..61816d21df8 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -1,7 +1,7 @@ # - Create for each LOFAR package a variable containing the absolute path to # its source directory. # -# Generated by gen_LofarPackageList_cmake.sh at wo 8 jun 2016 12:02:27 CEST +# Generated by gen_LofarPackageList_cmake.sh at wo 15 jun 2016 15:03:55 CEST # # ---- DO NOT EDIT ---- # @@ -146,6 +146,7 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(OTDB_Services_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/OTDB_Services) set(XML_generator_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/XML_generator) set(CleanupService_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/DataManagement/CleanupService) + set(StorageQueryService_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/DataManagement/StorageQueryService) set(MoMQueryService_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/MoM/MoMQueryService) set(jOTDB3_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/OTB/jOTDB3) set(OTB-Java_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/OTB/OTB) @@ -172,4 +173,5 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(PVSS_DB_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SubSystems/PVSS_DB) set(LAPS_CEP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SubSystems/LAPS_CEP) set(RAServices_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SubSystems/RAServices) + set(DataManagement_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SubSystems/DataManagement) endif(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) diff --git a/SAS/DataManagement/CMakeLists.txt b/SAS/DataManagement/CMakeLists.txt index 455c2be7bd0..d9d1f0ff4f3 100644 --- a/SAS/DataManagement/CMakeLists.txt +++ b/SAS/DataManagement/CMakeLists.txt @@ -1,3 +1,4 @@ # $Id: CMakeLists.txt 34529 2016-05-24 17:00:41Z mol $ lofar_add_package(CleanupService) +lofar_add_package(StorageQueryService) diff --git a/SAS/DataManagement/StorageQueryService/CMakeLists.txt b/SAS/DataManagement/StorageQueryService/CMakeLists.txt new file mode 100644 index 00000000000..088a8038032 --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/CMakeLists.txt @@ -0,0 +1,24 @@ +# $Id$ + +lofar_package(StorageQueryService 1.0 DEPENDS PyMessaging MoMQueryService) + +lofar_find_package(Python 2.6 REQUIRED) +include(PythonInstall) + +set(_py_files + __init__.py + config.py + rpc.py + service.py +) + +python_install(${_py_files} DESTINATION lofar/sas/datamanagement/storagequery) + +lofar_add_bin_scripts(storagequery storagequeryservice) + +# supervisord config files +install(FILES + storagequeryservice.ini + DESTINATION etc/supervisord.d) + +add_subdirectory(test) diff --git a/SAS/DataManagement/StorageQueryService/__init__.py b/SAS/DataManagement/StorageQueryService/__init__.py new file mode 100644 index 00000000000..fbbab2d1199 --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/__init__.py @@ -0,0 +1 @@ +# $Id$ diff --git a/SAS/DataManagement/StorageQueryService/config.py b/SAS/DataManagement/StorageQueryService/config.py new file mode 100644 index 00000000000..8e00e695b47 --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/config.py @@ -0,0 +1,7 @@ +#!/usr/bin/python +# $Id$ + +from lofar.messaging import adaptNameToEnvironment + +DEFAULT_BUSNAME = adaptNameToEnvironment('lofar.dm.command') +DEFAULT_SERVICENAME = 'StorageQueryService' diff --git a/SAS/DataManagement/StorageQueryService/rpc.py b/SAS/DataManagement/StorageQueryService/rpc.py new file mode 100644 index 00000000000..ba2d3e7717b --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/rpc.py @@ -0,0 +1,35 @@ +#!/usr/bin/python + +import logging +from lofar.messaging.RPC import RPC, RPCException, RPCWrapper +from lofar.sas.datamanagement.storagequery.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME + +logger = logging.getLogger(__name__) + +class StorageQueryRPC(RPCWrapper): + def __init__(self, busname=DEFAULT_BUSNAME, + servicename=DEFAULT_SERVICENAME, + broker=None): + super(StorageQueryRPC, self).__init__(busname, servicename, broker) + + def foo(self): + return self.rpc('foo') + +def main(): + import sys + from optparse import OptionParser + + # Check the invocation arguments + parser = OptionParser('%prog [options]', + description='do storage queries (on cep4) from the commandline') + parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') + parser.add_option('-b', '--busname', dest='busname', type='string', default=DEFAULT_BUSNAME, help='Name of the bus exchange on the qpid broker, default: %s' % DEFAULT_BUSNAME) + parser.add_option('-s', '--servicename', dest='servicename', type='string', default=DEFAULT_SERVICENAME, help='Name for this service, default: %s' % DEFAULT_SERVICENAME) + parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') + (options, args) = parser.parse_args() + + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.INFO if options.verbose else logging.WARN) + + with StorageQueryRPC(busname=options.busname, servicename=options.servicename, broker=options.broker) as rpc: + print rpc.foo() diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py new file mode 100644 index 00000000000..5ea343f93fe --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/service.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# $Id$ + +import logging +from optparse import OptionParser +from lofar.messaging import Service +from lofar.messaging import setQpidLogLevel +from lofar.messaging.Service import MessageHandlerInterface +from lofar.common.util import waitForInterrupt +from lofar.common.util import convertIntKeysToString +from lofar.sas.datamanagement.storagequery.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME + +logger = logging.getLogger(__name__) + +class StorageQueryHandler(MessageHandlerInterface): + def __init__(self, **kwargs): + super(StorageQueryHandler, self).__init__(**kwargs) + + self.service2MethodMap = {'foo': self._foo} + + def prepare_loop(self): + pass + + def _foo(self): + return 'foo' + +def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, verbose=False): + return Service(servicename, + StorageQueryHandler, + busname=busname, + broker=broker, + use_service_methods=True, + numthreads=2, + verbose=verbose) + +def main(): + # Check the invocation arguments + parser = OptionParser("%prog [options]", + description='runs the resourceassignment database service') + parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') + parser.add_option("-b", "--busname", dest="busname", type="string", default=DEFAULT_BUSNAME, help="Name of the bus exchange on the qpid broker, default: %s" % DEFAULT_BUSNAME) + parser.add_option("-s", "--servicename", dest="servicename", type="string", default=DEFAULT_SERVICENAME, help="Name for this service, default: %s" % DEFAULT_SERVICENAME) + parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') + (options, args) = parser.parse_args() + + setQpidLogLevel(logging.INFO) + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.DEBUG if options.verbose else logging.INFO) + + with createService(busname=options.busname, + servicename=options.servicename, + broker=options.broker, + verbose=options.verbose): + waitForInterrupt() + +if __name__ == '__main__': + main() diff --git a/SAS/DataManagement/StorageQueryService/storagequery b/SAS/DataManagement/StorageQueryService/storagequery new file mode 100644 index 00000000000..6eb5904ac62 --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/storagequery @@ -0,0 +1,9 @@ +#!/usr/bin/python + +''' +do storage queries (on cep4) from the commandline +''' + +if __name__ == '__main__': + from lofar.sas.datamanagement.storagequery.rpc import main + main() diff --git a/SAS/DataManagement/StorageQueryService/storagequeryservice b/SAS/DataManagement/StorageQueryService/storagequeryservice new file mode 100644 index 00000000000..d33f5fee03b --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/storagequeryservice @@ -0,0 +1,10 @@ +#!/usr/bin/python +# $Id: radbservice 33373 2016-01-22 11:01:15Z schaap $ + +''' +runs the storagequery service +''' + +if __name__ == '__main__': + from lofar.sas.datamanagement.storagequery.service import main + main() diff --git a/SAS/DataManagement/StorageQueryService/storagequeryservice.ini b/SAS/DataManagement/StorageQueryService/storagequeryservice.ini new file mode 100644 index 00000000000..45bcc52ad0c --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/storagequeryservice.ini @@ -0,0 +1,8 @@ +[program:storagequeryservice] +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec storagequeryservice --log-queries' +user=lofarsys +stopsignal=INT ; KeyboardInterrupt +stopasgroup=true ; bash does not propagate signals +stdout_logfile=%(program_name)s.log +redirect_stderr=true +stderr_logfile=NONE diff --git a/SAS/DataManagement/StorageQueryService/test/CMakeLists.txt b/SAS/DataManagement/StorageQueryService/test/CMakeLists.txt new file mode 100644 index 00000000000..2e74c54f0c7 --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/test/CMakeLists.txt @@ -0,0 +1,5 @@ +# $Id$ +include(LofarCTest) + +lofar_add_test(test_storagequery_service_and_rpc) + diff --git a/SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.py b/SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.py new file mode 100755 index 00000000000..0e79fa13c3b --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +import unittest +import uuid +import datetime +import logging +from lofar.messaging import Service +from lofar.sas.datamanagement.storagequery.service import createService +from lofar.sas.datamanagement.storagequery.rpc import StorageQueryRPC +from qpid.messaging.exceptions import * + +try: + from qpid.messaging import Connection + from qpidtoollibs import BrokerAgent +except ImportError: + print 'Cannot run test without qpid tools' + print 'Please source qpid profile' + exit(3) + +connection = None +broker = None + +try: + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO) + logger = logging.getLogger(__name__) + + # setup broker connection + connection = Connection.establish('127.0.0.1') + broker = BrokerAgent(connection) + + # add test service busname + busname = 'test-lofarbus-%s' % (uuid.uuid1()) + broker.addExchange('topic', busname) + + class TestCleanupServiceAndRPC(unittest.TestCase): + def test(self): + '''basic test ''' + rpc = StorageQueryRPC(busname=busname) + self.assertEqual('foo', rpc.foo()) + + # create and run the service + with createService(busname=busname): + # and run all tests + unittest.main() + +except ConnectError as ce: + logger.error(ce) + exit(3) +finally: + # cleanup test bus and exit + if broker: + broker.delExchange(busname) + if connection: + connection.close() diff --git a/SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.run b/SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.run new file mode 100755 index 00000000000..10b5e4b507c --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.run @@ -0,0 +1,6 @@ +#!/bin/bash + +# Run the unit test +source python-coverage.sh +python_coverage_test "storagequery*" test_storagequery_service_and_rpc.py + diff --git a/SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.sh b/SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.sh new file mode 100755 index 00000000000..86a85a94fab --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/test/test_storagequery_service_and_rpc.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +./runctest.sh test_storagequery_service_and_rpc diff --git a/SubSystems/CMakeLists.txt b/SubSystems/CMakeLists.txt index e3ed89ab0de..98ec1dfc379 100644 --- a/SubSystems/CMakeLists.txt +++ b/SubSystems/CMakeLists.txt @@ -13,3 +13,4 @@ lofar_add_package(SAS_Tools) lofar_add_package(PVSS_DB) lofar_add_package(LAPS_CEP) lofar_add_package(RAServices) +lofar_add_package(DataManagement) diff --git a/SubSystems/DataManagement/CMakeLists.txt b/SubSystems/DataManagement/CMakeLists.txt new file mode 100644 index 00000000000..99e70b6f669 --- /dev/null +++ b/SubSystems/DataManagement/CMakeLists.txt @@ -0,0 +1,10 @@ +# $Id: CMakeLists.txt 20934 2012-05-15 09:26:48Z schoenmakers $ + +lofar_package(DataManagement + DEPENDS CleanupService + StorageQueryService) + +# supervisord config files +install(FILES + DataManagement.ini + DESTINATION etc/supervisord.d) diff --git a/SubSystems/DataManagement/DataManagement.ini b/SubSystems/DataManagement/DataManagement.ini new file mode 100644 index 00000000000..9de2323567f --- /dev/null +++ b/SubSystems/DataManagement/DataManagement.ini @@ -0,0 +1,3 @@ +[group:DataManagementServices] +programs=cleanupservice,storagequeryservice +priority=200 -- GitLab From 8de01b04c26222a18b11e6e7ddf63f49eb45cc88 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Wed, 15 Jun 2016 23:41:25 +0000 Subject: [PATCH 342/933] Task #7519: move NetFuncs.h from src/ to include/Stream/ --- LCS/Stream/include/Stream/CMakeLists.txt | 1 + LCS/Stream/{src => include/Stream}/NetFuncs.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename LCS/Stream/{src => include/Stream}/NetFuncs.h (98%) diff --git a/LCS/Stream/include/Stream/CMakeLists.txt b/LCS/Stream/include/Stream/CMakeLists.txt index 854586b7522..701f08b79b0 100644 --- a/LCS/Stream/include/Stream/CMakeLists.txt +++ b/LCS/Stream/include/Stream/CMakeLists.txt @@ -3,6 +3,7 @@ set (inst_HEADERS FileDescriptorBasedStream.h FileStream.h NamedPipeStream.h + NetFuncs.h NullStream.h PortBroker.h SharedMemoryStream.h diff --git a/LCS/Stream/src/NetFuncs.h b/LCS/Stream/include/Stream/NetFuncs.h similarity index 98% rename from LCS/Stream/src/NetFuncs.h rename to LCS/Stream/include/Stream/NetFuncs.h index 5c5aa59dee4..a71639f80b7 100644 --- a/LCS/Stream/src/NetFuncs.h +++ b/LCS/Stream/include/Stream/NetFuncs.h @@ -1,6 +1,6 @@ //# NetFuncs.h: //# -//# Copyright (C) 2008 +//# Copyright (C) 2015 //# ASTRON (Netherlands Institute for Radio Astronomy) //# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands //# -- GitLab From b701b05bd6a6189190f7340ce6ea9578f41466aa Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Wed, 15 Jun 2016 23:44:26 +0000 Subject: [PATCH 343/933] Task #7519: ammend prev commit: fix include path --- LCS/Stream/src/NetFuncs.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LCS/Stream/src/NetFuncs.cc b/LCS/Stream/src/NetFuncs.cc index d0c6179a188..a44bdf7e029 100644 --- a/LCS/Stream/src/NetFuncs.cc +++ b/LCS/Stream/src/NetFuncs.cc @@ -19,9 +19,10 @@ //# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. //# //# $Id$ + #include <lofar_config.h> -#include "NetFuncs.h" +#include <Stream/NetFuncs.h> #include <Common/Thread/Mutex.h> #include <Common/LofarLogger.h> #include <Common/SystemCallException.h> -- GitLab From e232e99f845713cf588d67580e435e4ba0b37671 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Wed, 15 Jun 2016 23:46:10 +0000 Subject: [PATCH 344/933] Task #7519: ammend prev commit: fix more include paths to NetFuncs.h --- LCS/Stream/src/SocketStream.cc | 2 +- LCS/Stream/test/tNetFuncs.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LCS/Stream/src/SocketStream.cc b/LCS/Stream/src/SocketStream.cc index eacf2dd1560..24cc49dde4d 100644 --- a/LCS/Stream/src/SocketStream.cc +++ b/LCS/Stream/src/SocketStream.cc @@ -45,7 +45,7 @@ #include <Common/Thread/Cancellation.h> #include <Common/LofarLogger.h> -#include "NetFuncs.h" +#include <Stream/NetFuncs.h> //# AI_NUMERICSERV is not defined on OS-X #ifndef AI_NUMERICSERV diff --git a/LCS/Stream/test/tNetFuncs.cc b/LCS/Stream/test/tNetFuncs.cc index ba334cb0b6f..89f1a27e48f 100644 --- a/LCS/Stream/test/tNetFuncs.cc +++ b/LCS/Stream/test/tNetFuncs.cc @@ -24,7 +24,7 @@ #include <lofar_config.h> //# Includes -#include "../src/NetFuncs.h" +#include <Stream/NetFuncs.h> #include <Common/LofarLogger.h> -- GitLab From acb2f77725358d5139d05e1f4e7847e83d13b328 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 16 Jun 2016 07:57:22 +0000 Subject: [PATCH 345/933] Task #8887: use evalAsync to use angulars queue to keep the ui responsive --- .../lib/static/app/controllers/chartresourceusagecontroller.js | 2 +- .../lib/static/app/controllers/datacontroller.js | 3 +++ .../lib/static/app/controllers/ganttprojectcontroller.js | 2 +- .../lib/static/app/controllers/ganttresourcecontroller.js | 2 +- .../lib/static/app/controllers/gridcontroller.js | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/chartresourceusagecontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/chartresourceusagecontroller.js index f503b02ad28..2164ee9da8a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/chartresourceusagecontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/chartresourceusagecontroller.js @@ -239,7 +239,7 @@ chartResourceUsageControllerMod.controller('ChartResourceUsageController', ['$sc $scope.$watch('dataService.selected_resource_id', updateChartData); $scope.$watch('dataService.resources', updateChartData, true); - $scope.$watch('dataService.resourceUsagesDict', updateChartData, true); + $scope.$watch('dataService.resourceUsagesChangeCntr', function() { $scope.$evalAsync(updateChartData); }); $scope.$watch('dataService.viewTimeSpan', updateChartData, true); } ]); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 622cb56b710..83c2e494087 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -38,6 +38,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.initialLoadComplete = false; self.taskChangeCntr = 0; self.claimChangeCntr = 0; + self.resourceUsagesChangeCntr = 0; self.loadedHours = {}; @@ -335,6 +336,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.resourceUsagesDict[result.resourceusages[i].resource_id] = result.resourceusages[i]; } + self.resourceUsagesChangeCntr++; + defer.resolve(); }); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index 4ad95c83910..8fb1940a1aa 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -231,7 +231,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS $scope.$watch('dataService.filteredTaskDict', updateGanttData); $scope.$watch('dataService.momProjectsDict', updateGanttData); $scope.$watch('dataService.viewTimeSpan', updateGanttData, true); - $scope.$watch('dataService.taskChangeCntr', updateGanttData); + $scope.$watch('dataService.taskChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); $scope.$watch('dataService.lofarTime', function() { if($scope.dataService.lofarTime.getSeconds() % 5 == 0) { $scope.options.currentDateValue= $scope.dataService.lofarTime;}}); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js index 312e7885528..da1d2eee5dc 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js @@ -437,7 +437,7 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat $scope.$watch('dataService.resourceGroupMemberships', updateGanttData); $scope.$watch('dataService.filteredTaskDict', updateGanttData); $scope.$watch('dataService.viewTimeSpan', updateGanttData, true); - $scope.$watch('dataService.claimChangeCntr', updateGanttData); + $scope.$watch('dataService.claimChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); $scope.$watch('dataService.lofarTime', function() { if($scope.dataService.lofarTime.getSeconds() % 5 == 0) { $scope.options.currentDateValue= $scope.dataService.lofarTime;}}); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index b70aacf2b59..e835f38de72 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -195,7 +195,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid } }; - $scope.$watch('dataService.taskChangeCntr', populateList); + $scope.$watch('dataService.taskChangeCntr', function() { $scope.$evalAsync(populateList); }); $scope.$watch('dataService.viewTimeSpan', function() { populateList(); setTimeout(jumpToSelectedTaskRow, 250); -- GitLab From a6ddcafd1621dfe72d578032b553f9aabbe2cb12 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 16 Jun 2016 08:12:23 +0000 Subject: [PATCH 346/933] Task #8887: scroll back/forward in time --- .../lib/static/app/controllers/datacontroller.js | 16 ++++++++++++++++ .../lib/templates/index.html | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 83c2e494087..c9f0073a69d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -707,6 +707,22 @@ dataControllerMod.controller('DataController', }; }; + $scope.scrollBack = function() { + var viewTimeSpanInmsec = dataService.viewTimeSpan.to.getTime() - dataService.viewTimeSpan.from.getTime(); + dataService.viewTimeSpan = { + from: dataService.floorDate(new Date(dataService.viewTimeSpan.from.getTime() - 0.25*viewTimeSpanInmsec), 1, 5), + to: dataService.floorDate(new Date(dataService.viewTimeSpan.to.getTime() - 0.25*viewTimeSpanInmsec), 1, 5) + }; + }; + + $scope.scrollForward = function() { + var viewTimeSpanInmsec = dataService.viewTimeSpan.to.getTime() - dataService.viewTimeSpan.from.getTime(); + dataService.viewTimeSpan = { + from: dataService.floorDate(new Date(dataService.viewTimeSpan.from.getTime() + 0.25*viewTimeSpanInmsec), 1, 5), + to: dataService.floorDate(new Date(dataService.viewTimeSpan.to.getTime() + 0.25*viewTimeSpanInmsec), 1, 5) + }; + }; + $scope.onJumpTimespanWidthChanged = function(span) { var focusTime = dataService.floorDate(dataService.lofarTime, 1, 5); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index 0818ef43f8f..73510d643ec 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -72,6 +72,13 @@ <uib-timepicker ng-model="dataService.viewTimeSpan.to" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> </p> </div> + <div class="col-md-1"> + <label>Scroll:</label> + <p class="input-group"> + <button type="button" class="btn btn-default" ng-click="scrollBack()"><i class="glyphicon glyphicon-step-backward"></i></button> + <button type="button" class="btn btn-default" ng-click="scrollForward()"><i class="glyphicon glyphicon-step-forward"></i></button> + </p> + </div> <div class="col-md-3"> <label>Jump:</label> <p class="input-group"> -- GitLab From 4a6ae8ed0dc8dd2c24c258a9b465ed4f0d1ffae5 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 16 Jun 2016 09:49:33 +0000 Subject: [PATCH 347/933] Task #8887: getTasks by task_ids --- .../ResourceAssignmentDatabase/radb.py | 12 ++++++++++-- .../ResourceAssignmentService/rpc.py | 4 ++-- .../ResourceAssignmentService/service.py | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index f9a4ee19ef9..13537b6c6c4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -155,7 +155,7 @@ class RADatabase: raise KeyError('No such status: %s. Valid values are: %s' % (status_name, ', '.join(self.getResourceClaimStatusNames()))) - def getTasks(self, lower_bound=None, upper_bound=None): + def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None): query = '''SELECT * from resource_allocation.task_view''' conditions = [] @@ -169,6 +169,14 @@ class RADatabase: conditions.append('starttime <= %s') qargs.append(upper_bound) + if task_ids is not None: + if isinstance(task_ids, int): # just a single id + conditions.append('task_id = %s') + qargs.append(task_ids) + elif len(task_ids) > 0: # list of id's + conditions.append('task_id in %s') + qargs.append(tuple(task_ids)) + if conditions: query += ' WHERE ' + ' AND '.join(conditions) @@ -1131,9 +1139,9 @@ class RADatabase: claimDict = {c['id']:c for c in claims} resource_ids = list(set([c['resource_id'] for c in claims])) - task_ids = list(set(c['task_id'] for c in claims)) min_starttime = min(c['starttime'] for c in claims) max_endtime = min(c['endtime'] for c in claims) + task_ids = list(set(c['task_id'] for c in claims)) logger.info("validating status of %d resource claim(s) for task_id(s) %s" % (len(claims), to_csv_string(task_ids))) diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py index 8cc78a6d090..0a3edb4b68f 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py @@ -178,8 +178,8 @@ class RARPC(RPCWrapper): otdb_id=otdb_id, status=status) - def getTasks(self, lower_bound=None, upper_bound=None): - tasks = self.rpc('GetTasks', lower_bound=lower_bound, upper_bound=upper_bound) + def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None): + tasks = self.rpc('GetTasks', lower_bound=lower_bound, upper_bound=upper_bound, task_ids=task_ids) for task in tasks: task['starttime'] = task['starttime'].datetime() task['endtime'] = task['endtime'].datetime() diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/service.py b/SAS/ResourceAssignment/ResourceAssignmentService/service.py index c48ae9867ec..4b4128f5b7b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/service.py @@ -220,7 +220,8 @@ class RADBHandler(MessageHandlerInterface): def _getTasks(self, **kwargs): logger.info('GetTasks: %s' % dict({k:v for k,v in kwargs.items() if v != None})) return self.radb.getTasks(lower_bound=kwargs.get('lower_bound').datetime() if kwargs.get('lower_bound') else None, - upper_bound=kwargs.get('upper_bound').datetime() if kwargs.get('upper_bound') else None) + upper_bound=kwargs.get('upper_bound').datetime() if kwargs.get('upper_bound') else None, + task_ids=kwargs.get('task_ids')) def _getTask(self, **kwargs): logger.info('GetTask: %s' % dict({k:v for k,v in kwargs.items() if v != None})) -- GitLab From b850aefd702bc709a9a4f6be46318b3c2b64377f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 16 Jun 2016 11:45:18 +0000 Subject: [PATCH 348/933] Task #8887: bandwith values in bps --- .../sql/add_virtual_instrument.sql | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_virtual_instrument.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_virtual_instrument.sql index 45e9a05943d..bceb310b153 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_virtual_instrument.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_virtual_instrument.sql @@ -16,7 +16,7 @@ DELETE FROM virtual_instrument.unit CASCADE; -- end of initial cleanup INSERT INTO virtual_instrument.unit VALUES -(0, 'rsp_channel_bit'),(1, 'bytes'),(2, 'rcu_board'),(3, 'bytes/second'),(4, 'cores'); +(0, 'rsp_channel_bit'),(1, 'bytes'),(2, 'rcu_board'),(3, 'bits/second'),(4, 'cores'); INSERT INTO virtual_instrument.resource_type VALUES (0, 'rsp', 0), (1, 'tbb', 1), (2, 'rcu', 2), (3, 'bandwidth', 3), (4, 'processor', 4), (5, 'storage', 1); INSERT INTO virtual_instrument.resource_group_type VALUES @@ -83,7 +83,9 @@ INSERT INTO virtual_instrument.resource_group VALUES (62, 'cbt008', 5); INSERT INTO virtual_instrument.resource VALUES (0, 'bandwidth', 3), (1, 'processor', 4), (2, 'bandwidth', 3), (3, 'processor', 4), (4, 'bandwidth', 3), (5, 'processor', 4), (6, 'bandwidth', 3), (7, 'processor', 4), (8, 'bandwidth', 3), (9, 'processor', 4), (10, 'bandwidth', 3), (11, 'processor', 4), (12, 'bandwidth', 3), (13, 'processor', 4), (14, 'bandwidth', 3), (15, 'processor', 4), (16, 'bandwidth', 3), (17, 'processor', 4), (18, 'bandwidth', 3), (19, 'processor', 4), (20, 'bandwidth', 3), (21, 'processor', 4), (22, 'bandwidth', 3), (23, 'processor', 4), (24, 'bandwidth', 3), (25, 'processor', 4), (26, 'bandwidth', 3), (27, 'processor', 4), (28, 'bandwidth', 3), (29, 'processor', 4), (30, 'bandwidth', 3), (31, 'processor', 4), (32, 'bandwidth', 3), (33, 'processor', 4), (34, 'bandwidth', 3), (35, 'processor', 4), (36, 'bandwidth', 3), (37, 'processor', 4), (38, 'bandwidth', 3), (39, 'processor', 4), (40, 'bandwidth', 3), (41, 'processor', 4), (42, 'bandwidth', 3), (43, 'processor', 4), (44, 'bandwidth', 3), (45, 'processor', 4), (46, 'bandwidth', 3), (47, 'processor', 4), (48, 'bandwidth', 3), (49, 'processor', 4), (50, 'bandwidth', 3), (51, 'processor', 4), (52, 'bandwidth', 3), (53, 'processor', 4), (54, 'bandwidth', 3), (55, 'processor', 4), (56, 'bandwidth', 3), (57, 'processor', 4), (58, 'bandwidth', 3), (59, 'processor', 4), (60, 'bandwidth', 3), (61, 'processor', 4), (62, 'bandwidth', 3), (63, 'processor', 4), (64, 'bandwidth', 3), (65, 'processor', 4), (66, 'bandwidth', 3), (67, 'processor', 4), (68, 'bandwidth', 3), (69, 'processor', 4), (70, 'bandwidth', 3), (71, 'processor', 4), (72, 'bandwidth', 3), (73, 'processor', 4), (74, 'bandwidth', 3), (75, 'processor', 4), (76, 'bandwidth', 3), (77, 'processor', 4), (78, 'bandwidth', 3), (79, 'processor', 4), (80, 'bandwidth', 3), (81, 'processor', 4), (82, 'bandwidth', 3), (83, 'processor', 4), (84, 'bandwidth', 3), (85, 'processor', 4), (86, 'bandwidth', 3), (87, 'processor', 4), (88, 'bandwidth', 3), (89, 'processor', 4), (90, 'bandwidth', 3), (91, 'processor', 4), (92, 'bandwidth', 3), (93, 'processor', 4), (94, 'bandwidth', 3), (95, 'processor', 4), (96, 'bandwidth', 3), (97, 'processor', 4), (98, 'bandwidth', 3), (99, 'processor', 4), (100, 'bandwidth', 3), (101, 'processor', 4), (102, 'bandwidth', 3), (103, 'processor', 4), (104, 'bandwidth', 3), (105, 'processor', 4), (106, 'bandwidth', 3), (107, 'processor', 4), (108, 'bandwidth', 3), (109, 'processor', 4), (110, 'bandwidth', 3), (111, 'processor', 4), (112, 'bandwidth', 3), (113, 'processor', 4), (114, 'bandwidth', 3), (115, 'processor', 4), (116, 'cep4bandwidth', 3), (117, 'cep4storage', 5); INSERT INTO virtual_instrument.resource_to_resource_group VALUES (DEFAULT, 0, 5), (DEFAULT, 1, 5), (DEFAULT, 2, 6), (DEFAULT, 3, 6), (DEFAULT, 4, 7), (DEFAULT, 5, 7), (DEFAULT, 6, 8), (DEFAULT, 7, 8), (DEFAULT, 8, 9), (DEFAULT, 9, 9), (DEFAULT, 10, 10), (DEFAULT, 11, 10), (DEFAULT, 12, 11), (DEFAULT, 13, 11), (DEFAULT, 14, 12), (DEFAULT, 15, 12), (DEFAULT, 16, 13), (DEFAULT, 17, 13), (DEFAULT, 18, 14), (DEFAULT, 19, 14), (DEFAULT, 20, 15), (DEFAULT, 21, 15), (DEFAULT, 22, 16), (DEFAULT, 23, 16), (DEFAULT, 24, 17), (DEFAULT, 25, 17), (DEFAULT, 26, 18), (DEFAULT, 27, 18), (DEFAULT, 28, 19), (DEFAULT, 29, 19), (DEFAULT, 30, 20), (DEFAULT, 31, 20), (DEFAULT, 32, 21), (DEFAULT, 33, 21), (DEFAULT, 34, 22), (DEFAULT, 35, 22), (DEFAULT, 36, 23), (DEFAULT, 37, 23), (DEFAULT, 38, 24), (DEFAULT, 39, 24), (DEFAULT, 40, 25), (DEFAULT, 41, 25), (DEFAULT, 42, 26), (DEFAULT, 43, 26), (DEFAULT, 44, 27), (DEFAULT, 45, 27), (DEFAULT, 46, 28), (DEFAULT, 47, 28), (DEFAULT, 48, 29), (DEFAULT, 49, 29), (DEFAULT, 50, 30), (DEFAULT, 51, 30), (DEFAULT, 52, 31), (DEFAULT, 53, 31), (DEFAULT, 54, 32), (DEFAULT, 55, 32), (DEFAULT, 56, 33), (DEFAULT, 57, 33), (DEFAULT, 58, 34), (DEFAULT, 59, 34), (DEFAULT, 60, 35), (DEFAULT, 61, 35), (DEFAULT, 62, 36), (DEFAULT, 63, 36), (DEFAULT, 64, 37), (DEFAULT, 65, 37), (DEFAULT, 66, 38), (DEFAULT, 67, 38), (DEFAULT, 68, 39), (DEFAULT, 69, 39), (DEFAULT, 70, 40), (DEFAULT, 71, 40), (DEFAULT, 72, 41), (DEFAULT, 73, 41), (DEFAULT, 74, 42), (DEFAULT, 75, 42), (DEFAULT, 76, 43), (DEFAULT, 77, 43), (DEFAULT, 78, 44), (DEFAULT, 79, 44), (DEFAULT, 80, 45), (DEFAULT, 81, 45), (DEFAULT, 82, 46), (DEFAULT, 83, 46), (DEFAULT, 84, 47), (DEFAULT, 85, 47), (DEFAULT, 86, 48), (DEFAULT, 87, 48), (DEFAULT, 88, 49), (DEFAULT, 89, 49), (DEFAULT, 90, 50), (DEFAULT, 91, 50), (DEFAULT, 92, 51), (DEFAULT, 93, 51), (DEFAULT, 94, 52), (DEFAULT, 95, 52), (DEFAULT, 96, 53), (DEFAULT, 97, 53), (DEFAULT, 98, 54), (DEFAULT, 99, 54), (DEFAULT, 100, 55), (DEFAULT, 101, 55), (DEFAULT, 102, 56), (DEFAULT, 103, 56), (DEFAULT, 104, 57), (DEFAULT, 105, 57), (DEFAULT, 106, 58), (DEFAULT, 107, 58), (DEFAULT, 108, 59), (DEFAULT, 109, 59), (DEFAULT, 110, 60), (DEFAULT, 111, 60), (DEFAULT, 112, 61), (DEFAULT, 113, 61), (DEFAULT, 114, 62), (DEFAULT, 115, 62), (DEFAULT, 116, 1), (DEFAULT, 117, 1); -INSERT INTO resource_monitoring.resource_capacity VALUES (DEFAULT, 0, 53687091200, 53687091200), (DEFAULT, 1, 24, 24), (DEFAULT, 2, 53687091200, 53687091200), (DEFAULT, 3, 24, 24), (DEFAULT, 4, 53687091200, 53687091200), (DEFAULT, 5, 24, 24), (DEFAULT, 6, 53687091200, 53687091200), (DEFAULT, 7, 24, 24), (DEFAULT, 8, 53687091200, 53687091200), (DEFAULT, 9, 24, 24), (DEFAULT, 10, 53687091200, 53687091200), (DEFAULT, 11, 24, 24), (DEFAULT, 12, 53687091200, 53687091200), (DEFAULT, 13, 24, 24), (DEFAULT, 14, 53687091200, 53687091200), (DEFAULT, 15, 24, 24), (DEFAULT, 16, 53687091200, 53687091200), (DEFAULT, 17, 24, 24), (DEFAULT, 18, 53687091200, 53687091200), (DEFAULT, 19, 24, 24), (DEFAULT, 20, 53687091200, 53687091200), (DEFAULT, 21, 24, 24), (DEFAULT, 22, 53687091200, 53687091200), (DEFAULT, 23, 24, 24), (DEFAULT, 24, 53687091200, 53687091200), (DEFAULT, 25, 24, 24), (DEFAULT, 26, 53687091200, 53687091200), (DEFAULT, 27, 24, 24), (DEFAULT, 28, 53687091200, 53687091200), (DEFAULT, 29, 24, 24), (DEFAULT, 30, 53687091200, 53687091200), (DEFAULT, 31, 24, 24), (DEFAULT, 32, 53687091200, 53687091200), (DEFAULT, 33, 24, 24), (DEFAULT, 34, 53687091200, 53687091200), (DEFAULT, 35, 24, 24), (DEFAULT, 36, 53687091200, 53687091200), (DEFAULT, 37, 24, 24), (DEFAULT, 38, 53687091200, 53687091200), (DEFAULT, 39, 24, 24), (DEFAULT, 40, 53687091200, 53687091200), (DEFAULT, 41, 24, 24), (DEFAULT, 42, 53687091200, 53687091200), (DEFAULT, 43, 24, 24), (DEFAULT, 44, 53687091200, 53687091200), (DEFAULT, 45, 24, 24), (DEFAULT, 46, 53687091200, 53687091200), (DEFAULT, 47, 24, 24), (DEFAULT, 48, 53687091200, 53687091200), (DEFAULT, 49, 24, 24), (DEFAULT, 50, 53687091200, 53687091200), (DEFAULT, 51, 24, 24), (DEFAULT, 52, 53687091200, 53687091200), (DEFAULT, 53, 24, 24), (DEFAULT, 54, 53687091200, 53687091200), (DEFAULT, 55, 24, 24), (DEFAULT, 56, 53687091200, 53687091200), (DEFAULT, 57, 24, 24), (DEFAULT, 58, 53687091200, 53687091200), (DEFAULT, 59, 24, 24), (DEFAULT, 60, 53687091200, 53687091200), (DEFAULT, 61, 24, 24), (DEFAULT, 62, 53687091200, 53687091200), (DEFAULT, 63, 24, 24), (DEFAULT, 64, 53687091200, 53687091200), (DEFAULT, 65, 24, 24), (DEFAULT, 66, 53687091200, 53687091200), (DEFAULT, 67, 24, 24), (DEFAULT, 68, 53687091200, 53687091200), (DEFAULT, 69, 24, 24), (DEFAULT, 70, 53687091200, 53687091200), (DEFAULT, 71, 24, 24), (DEFAULT, 72, 53687091200, 53687091200), (DEFAULT, 73, 24, 24), (DEFAULT, 74, 53687091200, 53687091200), (DEFAULT, 75, 24, 24), (DEFAULT, 76, 53687091200, 53687091200), (DEFAULT, 77, 24, 24), (DEFAULT, 78, 53687091200, 53687091200), (DEFAULT, 79, 24, 24), (DEFAULT, 80, 53687091200, 53687091200), (DEFAULT, 81, 24, 24), (DEFAULT, 82, 53687091200, 53687091200), (DEFAULT, 83, 24, 24), (DEFAULT, 84, 53687091200, 53687091200), (DEFAULT, 85, 24, 24), (DEFAULT, 86, 53687091200, 53687091200), (DEFAULT, 87, 24, 24), (DEFAULT, 88, 53687091200, 53687091200), (DEFAULT, 89, 24, 24), (DEFAULT, 90, 53687091200, 53687091200), (DEFAULT, 91, 24, 24), (DEFAULT, 92, 53687091200, 53687091200), (DEFAULT, 93, 24, 24), (DEFAULT, 94, 53687091200, 53687091200), (DEFAULT, 95, 24, 24), (DEFAULT, 96, 53687091200, 53687091200), (DEFAULT, 97, 24, 24), (DEFAULT, 98, 53687091200, 53687091200), (DEFAULT, 99, 24, 24), (DEFAULT, 100, 53687091200, 53687091200), (DEFAULT, 101, 24, 24), (DEFAULT, 102, 53687091200, 53687091200), (DEFAULT, 103, 24, 24), (DEFAULT, 104, 53687091200, 53687091200), (DEFAULT, 105, 24, 24), (DEFAULT, 106, 53687091200, 53687091200), (DEFAULT, 107, 24, 24), (DEFAULT, 108, 53687091200, 53687091200), (DEFAULT, 109, 24, 24), (DEFAULT, 110, 53687091200, 53687091200), (DEFAULT, 111, 24, 24), (DEFAULT, 112, 53687091200, 53687091200), (DEFAULT, 113, 24, 24), (DEFAULT, 114, 53687091200, 53687091200), (DEFAULT, 115, 24, 24), (DEFAULT, 116, 85899345920, 85899345920), (DEFAULT, 117, 2254857830400, 2254857830400); +INSERT INTO resource_monitoring.resource_capacity VALUES (DEFAULT, 0, 50000000000, 50000000000), (DEFAULT, 1, 24, 24), (DEFAULT, 2, 50000000000, 50000000000), (DEFAULT, 3, 24, 24), (DEFAULT, 4, 50000000000, 50000000000), (DEFAULT, 5, 24, 24), (DEFAULT, 6, 50000000000, 50000000000), (DEFAULT, 7, 24, 24), (DEFAULT, 8, 50000000000, 50000000000), (DEFAULT, 9, 24, 24), (DEFAULT, 10, 50000000000, 50000000000), (DEFAULT, 11, 24, 24), (DEFAULT, 12, 50000000000, 50000000000), (DEFAULT, 13, 24, 24), (DEFAULT, 14, 50000000000, 50000000000), (DEFAULT, 15, 24, 24), (DEFAULT, 16, 50000000000, 50000000000), (DEFAULT, 17, 24, 24), (DEFAULT, 18, 50000000000, 50000000000), (DEFAULT, 19, 24, 24), (DEFAULT, 20, 50000000000, 50000000000), (DEFAULT, 21, 24, 24), (DEFAULT, 22, 50000000000, 50000000000), (DEFAULT, 23, 24, 24), (DEFAULT, 24, 50000000000, 50000000000), (DEFAULT, 25, 24, 24), (DEFAULT, 26, 50000000000, 50000000000), (DEFAULT, 27, 24, 24), (DEFAULT, 28, 50000000000, 50000000000), (DEFAULT, 29, 24, 24), (DEFAULT, 30, 50000000000, 50000000000), (DEFAULT, 31, 24, 24), (DEFAULT, 32, 50000000000, 50000000000), (DEFAULT, 33, 24, 24), (DEFAULT, 34, 50000000000, 50000000000), (DEFAULT, 35, 24, 24), (DEFAULT, 36, 50000000000, 50000000000), (DEFAULT, 37, 24, 24), (DEFAULT, 38, 50000000000, 50000000000), (DEFAULT, 39, 24, 24), (DEFAULT, 40, 50000000000, 50000000000), (DEFAULT, 41, 24, 24), (DEFAULT, 42, 50000000000, 50000000000), (DEFAULT, 43, 24, 24), (DEFAULT, 44, 50000000000, 50000000000), (DEFAULT, 45, 24, 24), (DEFAULT, 46, 50000000000, 50000000000), (DEFAULT, 47, 24, 24), (DEFAULT, 48, 50000000000, 50000000000), (DEFAULT, 49, 24, 24), (DEFAULT, 50, 50000000000, 50000000000), (DEFAULT, 51, 24, 24), (DEFAULT, 52, 50000000000, 50000000000), (DEFAULT, 53, 24, 24), (DEFAULT, 54, 50000000000, 50000000000), (DEFAULT, 55, 24, 24), (DEFAULT, 56, 50000000000, 50000000000), (DEFAULT, 57, 24, 24), (DEFAULT, 58, 50000000000, 50000000000), (DEFAULT, 59, 24, 24), (DEFAULT, 60, 50000000000, 50000000000), (DEFAULT, 61, 24, 24), (DEFAULT, 62, 50000000000, 50000000000), (DEFAULT, 63, 24, 24), (DEFAULT, 64, 50000000000, 50000000000), (DEFAULT, 65, 24, 24), (DEFAULT, 66, 50000000000, 50000000000), (DEFAULT, 67, 24, 24), (DEFAULT, 68, 50000000000, 50000000000), (DEFAULT, 69, 24, 24), (DEFAULT, 70, 50000000000, 50000000000), (DEFAULT, 71, 24, 24), (DEFAULT, 72, 50000000000, 50000000000), (DEFAULT, 73, 24, 24), (DEFAULT, 74, 50000000000, 50000000000), (DEFAULT, 75, 24, 24), (DEFAULT, 76, 50000000000, 50000000000), (DEFAULT, 77, 24, 24), (DEFAULT, 78, 50000000000, 50000000000), (DEFAULT, 79, 24, 24), (DEFAULT, 80, 50000000000, 50000000000), (DEFAULT, 81, 24, 24), (DEFAULT, 82, 50000000000, 50000000000), (DEFAULT, 83, 24, 24), (DEFAULT, 84, 50000000000, 50000000000), (DEFAULT, 85, 24, 24), (DEFAULT, 86, 50000000000, 50000000000), (DEFAULT, 87, 24, 24), (DEFAULT, 88, 50000000000, 50000000000), (DEFAULT, 89, 24, 24), (DEFAULT, 90, 50000000000, 50000000000), (DEFAULT, 91, 24, 24), (DEFAULT, 92, 50000000000, 50000000000), (DEFAULT, 93, 24, 24), (DEFAULT, 94, 50000000000, 50000000000), (DEFAULT, 95, 24, 24), (DEFAULT, 96, 50000000000, 50000000000), (DEFAULT, 97, 24, 24), (DEFAULT, 98, 50000000000, 50000000000), (DEFAULT, 99, 24, 24), (DEFAULT, 100, 50000000000, 50000000000), (DEFAULT, 101, 24, 24), (DEFAULT, 102, 50000000000, 50000000000), (DEFAULT, 103, 24, 24), (DEFAULT, 104, 50000000000, 50000000000), (DEFAULT, 105, 24, 24), (DEFAULT, 106, 50000000000, 50000000000), (DEFAULT, 107, 24, 24), (DEFAULT, 108, 50000000000, 50000000000), (DEFAULT, 109, 24, 24), (DEFAULT, 110, 50000000000, 50000000000), (DEFAULT, 111, 24, 24), (DEFAULT, 112, 50000000000, 50000000000), (DEFAULT, 113, 24, 24), (DEFAULT, 114, 50000000000, 50000000000), (DEFAULT, 115, 24, 24), +(DEFAULT, 116, 640000000000, 640000000000), --cep4bandwidth, guestimated from cobalt->outputproc-nodes 8*2*40Gbps +(DEFAULT, 117, 2254857830400, 2254857830400); INSERT INTO resource_monitoring.resource_availability VALUES (DEFAULT, 0, TRUE), (DEFAULT, 1, TRUE), (DEFAULT, 2, TRUE), (DEFAULT, 3, TRUE), (DEFAULT, 4, TRUE), (DEFAULT, 5, TRUE), (DEFAULT, 6, TRUE), (DEFAULT, 7, TRUE), (DEFAULT, 8, TRUE), (DEFAULT, 9, TRUE), (DEFAULT, 10, TRUE), (DEFAULT, 11, TRUE), (DEFAULT, 12, TRUE), (DEFAULT, 13, TRUE), (DEFAULT, 14, TRUE), (DEFAULT, 15, TRUE), (DEFAULT, 16, TRUE), (DEFAULT, 17, TRUE), (DEFAULT, 18, TRUE), (DEFAULT, 19, TRUE), (DEFAULT, 20, TRUE), (DEFAULT, 21, TRUE), (DEFAULT, 22, TRUE), (DEFAULT, 23, TRUE), (DEFAULT, 24, TRUE), (DEFAULT, 25, TRUE), (DEFAULT, 26, TRUE), (DEFAULT, 27, TRUE), (DEFAULT, 28, TRUE), (DEFAULT, 29, TRUE), (DEFAULT, 30, TRUE), (DEFAULT, 31, TRUE), (DEFAULT, 32, TRUE), (DEFAULT, 33, TRUE), (DEFAULT, 34, TRUE), (DEFAULT, 35, TRUE), (DEFAULT, 36, TRUE), (DEFAULT, 37, TRUE), (DEFAULT, 38, TRUE), (DEFAULT, 39, TRUE), (DEFAULT, 40, TRUE), (DEFAULT, 41, TRUE), (DEFAULT, 42, TRUE), (DEFAULT, 43, TRUE), (DEFAULT, 44, TRUE), (DEFAULT, 45, TRUE), (DEFAULT, 46, TRUE), (DEFAULT, 47, TRUE), (DEFAULT, 48, TRUE), (DEFAULT, 49, TRUE), (DEFAULT, 50, TRUE), (DEFAULT, 51, TRUE), (DEFAULT, 52, TRUE), (DEFAULT, 53, TRUE), (DEFAULT, 54, TRUE), (DEFAULT, 55, TRUE), (DEFAULT, 56, TRUE), (DEFAULT, 57, TRUE), (DEFAULT, 58, TRUE), (DEFAULT, 59, TRUE), (DEFAULT, 60, TRUE), (DEFAULT, 61, TRUE), (DEFAULT, 62, TRUE), (DEFAULT, 63, TRUE), (DEFAULT, 64, TRUE), (DEFAULT, 65, TRUE), (DEFAULT, 66, TRUE), (DEFAULT, 67, TRUE), (DEFAULT, 68, TRUE), (DEFAULT, 69, TRUE), (DEFAULT, 70, TRUE), (DEFAULT, 71, TRUE), (DEFAULT, 72, TRUE), (DEFAULT, 73, TRUE), (DEFAULT, 74, TRUE), (DEFAULT, 75, TRUE), (DEFAULT, 76, TRUE), (DEFAULT, 77, TRUE), (DEFAULT, 78, TRUE), (DEFAULT, 79, TRUE), (DEFAULT, 80, TRUE), (DEFAULT, 81, TRUE), (DEFAULT, 82, TRUE), (DEFAULT, 83, TRUE), (DEFAULT, 84, TRUE), (DEFAULT, 85, TRUE), (DEFAULT, 86, TRUE), (DEFAULT, 87, TRUE), (DEFAULT, 88, TRUE), (DEFAULT, 89, TRUE), (DEFAULT, 90, TRUE), (DEFAULT, 91, TRUE), (DEFAULT, 92, TRUE), (DEFAULT, 93, TRUE), (DEFAULT, 94, TRUE), (DEFAULT, 95, TRUE), (DEFAULT, 96, TRUE), (DEFAULT, 97, TRUE), (DEFAULT, 98, TRUE), (DEFAULT, 99, TRUE), (DEFAULT, 100, TRUE), (DEFAULT, 101, TRUE), (DEFAULT, 102, TRUE), (DEFAULT, 103, TRUE), (DEFAULT, 104, TRUE), (DEFAULT, 105, TRUE), (DEFAULT, 106, TRUE), (DEFAULT, 107, TRUE), (DEFAULT, 108, TRUE), (DEFAULT, 109, TRUE), (DEFAULT, 110, TRUE), (DEFAULT, 111, TRUE), (DEFAULT, 112, TRUE), (DEFAULT, 113, TRUE), (DEFAULT, 114, TRUE), (DEFAULT, 115, TRUE), (DEFAULT, 116, TRUE), (DEFAULT, 117, TRUE); INSERT INTO virtual_instrument.resource_group_to_resource_group VALUES (DEFAULT, 0, NULL), (DEFAULT, 3, 1), (DEFAULT, 5, 3), (DEFAULT, 6, 3), (DEFAULT, 7, 3), (DEFAULT, 8, 3), (DEFAULT, 9, 3), (DEFAULT, 10, 3), (DEFAULT, 11, 3), (DEFAULT, 12, 3), (DEFAULT, 13, 3), (DEFAULT, 14, 3), (DEFAULT, 15, 3), (DEFAULT, 16, 3), (DEFAULT, 17, 3), (DEFAULT, 18, 3), (DEFAULT, 19, 3), (DEFAULT, 20, 3), (DEFAULT, 21, 3), (DEFAULT, 22, 3), (DEFAULT, 23, 3), (DEFAULT, 24, 3), (DEFAULT, 25, 3), (DEFAULT, 26, 3), (DEFAULT, 27, 3), (DEFAULT, 28, 3), (DEFAULT, 29, 3), (DEFAULT, 30, 3), (DEFAULT, 31, 3), (DEFAULT, 32, 3), (DEFAULT, 33, 3), (DEFAULT, 34, 3), (DEFAULT, 35, 3), (DEFAULT, 36, 3), (DEFAULT, 37, 3), (DEFAULT, 38, 3), (DEFAULT, 39, 3), (DEFAULT, 40, 3), (DEFAULT, 41, 3), (DEFAULT, 42, 3), (DEFAULT, 43, 3), (DEFAULT, 44, 3), (DEFAULT, 45, 3), (DEFAULT, 46, 3), (DEFAULT, 47, 3), (DEFAULT, 48, 3), (DEFAULT, 49, 3), (DEFAULT, 50, 3), (DEFAULT, 51, 3), (DEFAULT, 52, 3), (DEFAULT, 53, 3), (DEFAULT, 54, 3), (DEFAULT, 55, 2), (DEFAULT, 56, 2), (DEFAULT, 57, 2), (DEFAULT, 58, 2), (DEFAULT, 59, 2), (DEFAULT, 60, 2), (DEFAULT, 61, 2), (DEFAULT, 62, 2), (DEFAULT, 1, 0), (DEFAULT, 2, 0); COMMIT; -- GitLab From fe6e3de4e8f79ad73a72368efb6fce3c2821458b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 16 Jun 2016 11:58:38 +0000 Subject: [PATCH 349/933] Task #8887: font --- .../ResourceAssignmentEditor/lib/static/css/main.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index ac3b5cd0789..8498165d1cc 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -1,5 +1,10 @@ /* $Id: app.js 32724 2015-10-28 13:24:49Z schaap $ */ +body { + font-family: DejaVu Sans Monospace; + font-size: 12px; +} + .grid { min-height: 400px; min-width: 500px; -- GitLab From 789ccc9991016d70185cd7c586c0dec58f4a0d5c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 16 Jun 2016 11:59:06 +0000 Subject: [PATCH 350/933] Task #8887: column for radb id with link to table browser --- .../lib/static/app/controllers/gridcontroller.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index e835f38de72..8671ca77386 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -77,12 +77,18 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid displayName: 'MoM ID', enableCellEdit: false, cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}">{{row.entity[col.field]}}</a>', - width: '8%' + width: '6%' }, { field: 'otdb_id', displayName: 'SAS ID', enableCellEdit: false, - width: '8%' + width: '6%' + }, + { field: 'id', + displayName: 'RADB ID', + enableCellEdit: false, + cellTemplate:'<a target="_blank" href="tasks/{{row.entity.id}}.html">{{row.entity[col.field]}}</a>', + width: '6%' }]; $scope.gridOptions = { enableGridMenu: false, -- GitLab From 02ffd8a3d05b1b569deec0d45e88676308926e4f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 16 Jun 2016 14:42:18 +0000 Subject: [PATCH 351/933] Task #8887: auto follow now --- .../lib/static/app/controllers/datacontroller.js | 9 +++++++++ .../ResourceAssignmentEditor/lib/templates/index.html | 9 +++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index c9f0073a69d..49617e2f99e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -43,6 +43,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.loadedHours = {}; self.viewTimeSpan = {from: new Date(), to: new Date() }; + self.autoFollowNow = true; self.floorDate = function(date, hourMod=1, minMod=1) { var min = date.getMinutes(); @@ -708,6 +709,7 @@ dataControllerMod.controller('DataController', }; $scope.scrollBack = function() { + dataService.autoFollowNow = false; var viewTimeSpanInmsec = dataService.viewTimeSpan.to.getTime() - dataService.viewTimeSpan.from.getTime(); dataService.viewTimeSpan = { from: dataService.floorDate(new Date(dataService.viewTimeSpan.from.getTime() - 0.25*viewTimeSpanInmsec), 1, 5), @@ -716,6 +718,7 @@ dataControllerMod.controller('DataController', }; $scope.scrollForward = function() { + dataService.autoFollowNow = false; var viewTimeSpanInmsec = dataService.viewTimeSpan.to.getTime() - dataService.viewTimeSpan.from.getTime(); dataService.viewTimeSpan = { from: dataService.floorDate(new Date(dataService.viewTimeSpan.from.getTime() + 0.25*viewTimeSpanInmsec), 1, 5), @@ -759,6 +762,12 @@ dataControllerMod.controller('DataController', $scope.$watch('dataService.filteredTasks', dataService.computeMinMaxTaskTimes); + $scope.$watch('dataService.lofarTime', function() { + if(dataService.autoFollowNow && (Math.round(dataService.lofarTime.getTime()/1000))%5==0) { + $scope.jumpToNow(); + } + }); + dataService.initialLoad(); //clock ticking every second diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index 73510d643ec..4bcd9771886 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -72,14 +72,15 @@ <uib-timepicker ng-model="dataService.viewTimeSpan.to" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> </p> </div> - <div class="col-md-1"> + <div class="col-md-2"> <label>Scroll:</label> <p class="input-group"> - <button type="button" class="btn btn-default" ng-click="scrollBack()"><i class="glyphicon glyphicon-step-backward"></i></button> - <button type="button" class="btn btn-default" ng-click="scrollForward()"><i class="glyphicon glyphicon-step-forward"></i></button> + <label title="Follow 'now' automatically" style="padding-right: 4px; vertical-align: top;">auto<input type="checkbox" ng-model="dataService.autoFollowNow"></label> + <button title="Scroll back in time" type="button" class="btn btn-default" ng-click="scrollBack()"><i class="glyphicon glyphicon-step-backward"></i></button> + <button title="Scroll forward in time"type="button" class="btn btn-default" ng-click="scrollForward()"><i class="glyphicon glyphicon-step-forward"></i></button> </p> </div> - <div class="col-md-3"> + <div class="col-md-2"> <label>Jump:</label> <p class="input-group"> <span class="input-group-btn"> -- GitLab From 1417fcfdfeafbc2212a5d6ce4eff508ff107971d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 16 Jun 2016 14:59:51 +0000 Subject: [PATCH 352/933] Task #8887: typo --- .../ResourceAssignmentDatabase/radb.py | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 13537b6c6c4..4fa4eb2b942 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -171,10 +171,10 @@ class RADatabase: if task_ids is not None: if isinstance(task_ids, int): # just a single id - conditions.append('task_id = %s') + conditions.append('id = %s') qargs.append(task_ids) elif len(task_ids) > 0: # list of id's - conditions.append('task_id in %s') + conditions.append('id in %s') qargs.append(tuple(task_ids)) if conditions: @@ -1171,23 +1171,8 @@ class RADatabase: resource2otherClaims[claim['resource_id']].append(claim) for claim_id, claim in claimDict.items(): - claimSize = claim['claim_size'] - resource_id = claim['resource_id'] - resource = resources[resource_id] - resourceOtherClaims = resource2otherClaims[resource_id] - totalOtherClaimSize = sum(c['claim_size'] for c in resourceOtherClaims) - - logger.info('resource_id=%s claimSize=%s totalOtherClaimSize=%s total=%s available_capacity=%s' % - (resource_id, - claimSize, - totalOtherClaimSize, - totalOtherClaimSize + claimSize, - resource['available_capacity'])) - - if totalOtherClaimSize + claimSize >= resource['available_capacity']: - newClaimStatuses[conflistStatusId].append(claim_id) - elif claim['status_id'] != allocatedStatusId: - newClaimStatuses[claimedStatusId].append(claim_id) + + if newClaimStatuses: for status_id, claim_ids in newClaimStatuses.items(): -- GitLab From a91d61042bcce94d3cdc4b67315694690651d1e3 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 16 Jun 2016 15:01:26 +0000 Subject: [PATCH 353/933] Task #8887: only change claim status of tasks in 'prepared', 'approved', 'on_hold', 'conflict', 'prescheduled' state --- .../ResourceAssignmentDatabase/radb.py | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 4fa4eb2b942..8c55a6cb86e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -1142,6 +1142,7 @@ class RADatabase: min_starttime = min(c['starttime'] for c in claims) max_endtime = min(c['endtime'] for c in claims) task_ids = list(set(c['task_id'] for c in claims)) + taskDict = {t['id']:t for t in self.getTasks(task_ids=task_ids)} logger.info("validating status of %d resource claim(s) for task_id(s) %s" % (len(claims), to_csv_string(task_ids))) @@ -1171,8 +1172,28 @@ class RADatabase: resource2otherClaims[claim['resource_id']].append(claim) for claim_id, claim in claimDict.items(): - - + task_id = claim['task_id'] + task_status = taskDict[task_id]['status'] + if task_status in ['prepared', 'approved', 'on_hold', 'conflict', 'prescheduled']: + claimSize = claim['claim_size'] + resource_id = claim['resource_id'] + resource = resources[resource_id] + resourceOtherClaims = resource2otherClaims[resource_id] + totalOtherClaimSize = sum(c['claim_size'] for c in resourceOtherClaims) + + logger.info('resource_id=%s claimSize=%s totalOtherClaimSize=%s total=%s available_capacity=%s' % + (resource_id, + claimSize, + totalOtherClaimSize, + totalOtherClaimSize + claimSize, + resource['available_capacity'])) + + if totalOtherClaimSize + claimSize >= resource['available_capacity']: + logger.info("totalOtherClaimSize (%s) + claimSize (%s) >= resource_available_capacity %s for claim %s on resource %s %s for task %s", + totalOtherClaimSize, claimSize, resource['available_capacity'], claim_id, resource_id, resource['name'], task_id) + newClaimStatuses[conflistStatusId].append(claim_id) + elif claim['status_id'] != allocatedStatusId: + newClaimStatuses[claimedStatusId].append(claim_id) if newClaimStatuses: for status_id, claim_ids in newClaimStatuses.items(): -- GitLab From effb45a0c4ee076ddfd96762b6d1bb6666c4aa34 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 16 Jun 2016 21:03:27 +0000 Subject: [PATCH 354/933] Task #9555: Query RADB for predecessors/successors to manage the pipeline dependencies, instead of letting SLURM handle the dependencies. The latter is prone to inconsistencies and trigger-happy behaviour. --- MAC/Services/CMakeLists.txt | 2 +- MAC/Services/src/PipelineControl.py | 272 +++++++++++++------------- MAC/Services/test/tPipelineControl.py | 238 +++++++++++++++------- 3 files changed, 308 insertions(+), 204 deletions(-) diff --git a/MAC/Services/CMakeLists.txt b/MAC/Services/CMakeLists.txt index d721a170318..303a12a5b2e 100644 --- a/MAC/Services/CMakeLists.txt +++ b/MAC/Services/CMakeLists.txt @@ -1,6 +1,6 @@ # $Id$ -lofar_package(MAC_Services 1.0 DEPENDS PyMessaging OTDB_Services pyparameterset Docker) +lofar_package(MAC_Services 1.0 DEPENDS PyMessaging OTDB_Services pyparameterset Docker ResourceAssignmentService) add_subdirectory(src) add_subdirectory(test) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 89708ce7519..d13a516e0b3 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -67,6 +67,8 @@ from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTD from lofar.sas.otdb.otdbrpc import OTDBRPC from lofar.common.util import waitForInterrupt from lofar.messaging.RPC import RPCTimeoutException +from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as DEFAULT_RAS_SERVICE_BUSNAME import subprocess import datetime @@ -139,9 +141,6 @@ class Parset(dict): return (self[PARSET_PREFIX + "Observation.ObservationControl.PythonControl.softwareVersion"] or self.defaultDockerImage()) - def slurmJobName(self): - return str(self.otdbId()) - def otdbId(self): return int(self[PARSET_PREFIX + "Observation.otdbID"]) @@ -176,65 +175,93 @@ class Slurm(object): def cancel(self, jobName): self._runCommand("scancel --jobname %s" % (jobName,)) - def jobs(self, maxage=datetime.timedelta(365)): - starttime = (datetime.datetime.utcnow() - maxage).strftime("%FT%T") + def isQueuedOrRunning(self, jobName): + stdout = self._runCommand("sacct --starttime=2016-01-01 --noheader --parsable2 --format=jobid --name=%s --state=PENDING,CONFIGURING,RUNNING,RESIZING,COMPLETING,SUSPENDED" % (jobname,)) - stdout = self._runCommand("sacct --starttime=%s --noheader --parsable2 --format=jobid,jobname" % (starttime,)) + return stdout != "" - jobs = {} - for l in stdout.split("\n"): - # One line is one job - jobid, jobname = l.split("|") - jobs_properties = { "JobId": jobid, "JobName": jobname } +class PipelineDependencies(object): + def __init__(self, ra_service_busname=DEFAULT_RAS_SERVICE_BUSNAME): + self.rarpc = RARPC(busname=ra_service_busname) - # warn of duplicate names - if jobname in jobs: - logger.warning("Duplicate job name: %s" % (jobname,)) - jobs[jobname] = job_properties + def open(self): + self.rarpc.open() - return jobs + def close(self): + self.rarpc.close() - def jobid(self, jobname): - stdout = self._runCommand("sacct --starttime=2016-01-01 --noheader --parsable2 --format=jobid --name=%s" % (jobname,)) + def __enter__(self): + self.open() + return self - if stdout == "": - return None + def __exit__(self, type, value, tb): + self.close() + + def getState(self, otdb_id): + """ + Return the status of a single `otdb_id'. + """ - lines = stdout.split("\n") + radb_task = self.rarpc.getTask(otdb_id=otdb_id) + return radb_task["status"] - # We cannot rely on subjobs, so filter them out - # by only accepting numbers, not dots ("136.0") or strings ("136.batch"). - lines = [l for l in lines if l.isdigit()] + def getPredecessorStates(self, otdb_id): + """ + Return a dict of {"sasid":"status"} pairs of all the predecessors of `otdb_id'. + """ + radb_task = self.rarpc.getTask(otdb_id=otdb_id) - if len(lines) > 1: - logger.warning("Duplicate job name: %s matches jobs [%s]" % (jobname,", ".join(lines))) + predecessor_ids = radb_task['predecessor_ids'] + predecessor_tasks = self.rarpc.getTasks(task_ids=predecessor_ids) - # Use last occurance if there are multiple - return lines[-1] + return {t["otdb_id"]: t["status"] for t in predecessor_tasks} + + def getSuccessorIds(self, otdb_id): + """ + Return a list of all the successors of `otdb_id'. + """ + radb_task = self.rarpc.getTask(otdb_id=otdb_id) + + successor_ids = radb_task['successor_ids'] + successor_tasks = self.rarpc.getTasks(task_ids=successor_ids) if successor_ids else [] + + return [t["otdb_id"] for t in successor_tasks] + + def canStart(self, otdbId): + """ + Return whether `otdbId' can start, according to the status of the predecessors + and its own status. + """ + return ( + self.getState(otdbId) == "scheduled" and + all([x == "finished" for x in self.getPredecessorStates(otdbId).values()]) + ) class PipelineControl(OTDBBusListener): - def __init__(self, otdb_notification_busname=DEFAULT_OTDB_NOTIFICATION_BUSNAME, otdb_service_busname=DEFAULT_OTDB_SERVICE_BUSNAME, **kwargs): + def __init__(self, otdb_notification_busname=DEFAULT_OTDB_NOTIFICATION_BUSNAME, otdb_service_busname=DEFAULT_OTDB_SERVICE_BUSNAME, ra_service_busname=DEFAULT_RAS_SERVICE_BUSNAME, **kwargs): super(PipelineControl, self).__init__(busname=otdb_notification_busname, **kwargs) self.otdb_service_busname = otdb_service_busname self.otdbrpc = OTDBRPC(busname=otdb_service_busname) + self.dependencies = PipelineDependencies(ra_service_busname=ra_service_busname) self.slurm = Slurm() - def _setStatus(self, obsid, status): - try: - self.otdbrpc.taskSetStatus(otdb_id=obsid, new_status=status) - except RPCTimeoutException, e: - # We use a queue, so delivery is guaranteed. We don't care about the answer. - pass + def _setStatus(self, otdb_id, status): + self.otdbrpc.taskSetStatus(otdb_id=otdb_id, new_status=status) + + def _getParset(self, otdbId): + return Parset(self.otdbrpc.taskGetSpecification(otdb_id=otdbId)["specification"]) def start_listening(self, **kwargs): self.otdbrpc.open() + self.dependencies.open() super(PipelineControl, self).start_listening(**kwargs) def stop_listening(self, **kwargs): super(PipelineControl, self).stop_listening(**kwargs) + self.dependencies.close() self.otdbrpc.close() @staticmethod @@ -249,86 +276,22 @@ class PipelineControl(OTDBBusListener): return True - def _slurmJobIds(self, parsets): - return [self.slurm.jobid(p.slurmJobName()) for p in parsets] - - def _getParset(self, otdbId): - return Parset(self.otdbrpc.taskGetSpecification(otdb_id=otdbId)["specification"]) - - def _getPredecessorParsets(self, parset): - otdbIds = parset.predecessors() - - logger.info("Obtaining predecessor parsets %s", otdbIds) - - return [self._getParset(otdbId) for otdbId in otdbIds] - - def onObservationAborted(self, otdbId, modificationTime): - logger.info("***** STOP Otdb ID %s *****", otdbId) - - # Request the parset - parset = self._getParset(otdbId) - - if not self._shouldHandle(parset): - return - - # Cancel corresponding SLURM job, causing any successors - # to be cancelled as well. - jobName = parset.slurmJobName() - - def cancel(jobName): - logger.info("Cancelling job %s", jobName) - self.slurm.cancel(jobName) - - cancel("%s-abort-trigger" % jobName) - cancel(jobName) - - """ - More statusses we want to abort on. - """ - onObservationDescribed = onObservationAborted - onObservationPrepared = onObservationAborted - onObservationApproved = onObservationAborted - onObservationPrescheduled = onObservationAborted - onObservationConflict = onObservationAborted - onObservationHold = onObservationAborted - - @classmethod - def _minStartTime(self, preparsets, margin=datetime.timedelta(0, 60, 0)): - result = None - - for p in preparsets: - processType = p[PARSET_PREFIX + "Observation.processType"] - - # If we depend on an observation, start 1 minute after it - obs_endtime = datetime.datetime.strptime(p[PARSET_PREFIX + "Observation.stopTime"], "%Y-%m-%d %H:%M:%S") - min_starttime = obs_endtime + margin - - result = max(result, min_starttime) if result else min_starttime - - return result - - def onObservationScheduled(self, otdbId, modificationTime): - logger.info("***** QUEUE Otdb ID %s *****", otdbId) - - # Request the parset - parset = self._getParset(otdbId) - - if not self._shouldHandle(parset): - return - - """ - Collect predecessor information. - """ - - # Collect the parsets of predecessors - logger.info("Obtaining predecessor parsets") - preparsets = self._getPredecessorParsets(parset) + @staticmethod + def _jobName(otdbId): + return str(otdbId) + def _startPipeline(self, otdbId, parset): """ Schedule "docker-runPipeline.sh", which will fetch the parset and run the pipeline within a SLURM job. """ + # Avoid race conditions by checking whether we haven't already sent the job + # to SLURM. Our QUEUED status update may still be being processed. + if self.slurm.isQueuedOrRunning(otdbId): + logger.info("Pipeline %s is already queued or running in SLURM.", otdbId) + return + # Determine SLURM parameters sbatch_params = [ # Only run job if all nodes are ready @@ -353,17 +316,9 @@ class PipelineControl(OTDBBusListener): os.path.expandvars("--output=/data/log/runPipeline-%s.log" % (otdbId,)), ] - min_starttime = self._minStartTime([x for x in preparsets if x.isObservation()]) - if min_starttime: - sbatch_params.append("--begin=%s" % (min_starttime.strftime("%FT%T"),)) - - predecessor_jobs = self._slurmJobIds([x for x in preparsets if x.isPipeline()]) - if predecessor_jobs: - sbatch_params.append("--dependency=%s" % (",".join(("afterok:%s" % x for x in predecessor_jobs)),)) - # Schedule runPipeline.sh logger.info("Scheduling SLURM job for runPipeline.sh") - slurm_job_id = self.slurm.submit(parset.slurmJobName(), + slurm_job_id = self.slurm.submit(self._jobName(otdbId), # pull docker image from repository on all nodes "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull" " --no-kill" @@ -395,12 +350,11 @@ class PipelineControl(OTDBBusListener): sbatch_params=sbatch_params ) - logger.info("Scheduled SLURM job %s" % (slurm_job_id,)) + logger.info("Scheduled SLURM job %s", slurm_job_id) # Schedule pipelineAborted.sh logger.info("Scheduling SLURM job for pipelineAborted.sh") - slurm_cancel_job_id = self.slurm.submit("%s-abort-trigger" % parset.slurmJobName(), - + slurm_cancel_job_id = self.slurm.submit("%s-abort-trigger" % self._jobName(otdbId), "ssh {myhostname} '" "source {lofarroot}/lofarinit.sh && " "setOTDBTreeStatus -o {obsid} -s aborted -B {status_bus}" @@ -421,18 +375,74 @@ class PipelineControl(OTDBBusListener): "--output=/data/log/abort-trigger-%s.log" % (otdbId,), ] ) - logger.info("Scheduled SLURM job %s" % (slurm_cancel_job_id,)) + logger.info("Scheduled SLURM job %s", slurm_cancel_job_id) - """ - Update OTDB status. Note the possible race condition - as the SLURM jobs will set the status too. - """ - - # Set OTDB status to QUEUED - # TODO: How to avoid race condition with runPipeline.sh setting the status to STARTED - # when the SLURM job starts running? logger.info("Setting status to QUEUED") self._setStatus(otdbId, "queued") - logger.info("Pipeline processed.") + def _stopPipeline(self, otdbId): + # Cancel corresponding SLURM job, but first the abort-trigger + # to avoid setting ABORTED as a side effect. + # to be cancelled as well. + + if not self.slurm.isQueuedOrRunning(otdbId): + logger.info("_stopPipeline: Job %s not running") + return + + def cancel(jobName): + logger.info("Cancelling job %s", jobName) + self.slurm.cancel(jobName) + + jobName = self._jobName(otdbId) + cancel("%s-abort-trigger" % jobName) + cancel(jobName) + + def _startSuccessors(self, otdbId): + for s in self.dependencies.getSuccessorIds(otdbId): + parset = self._getParset(s) + if not self._shouldHandle(parset): + continue + + if self.dependencies.canStart(s): + logger.info("***** START Otdb ID %s *****", otdbId) + self._startPipeline(s, parset) + else: + logger.info("Job %s still cannot start yet.", otdbId) + + def onObservationScheduled(self, otdbId, modificationTime): + parset = self._getParset(otdbId) + if not self._shouldHandle(parset): + return + + # Maybe the pipeline can start already + if self.dependencies.canStart(otdbId): + logger.info("***** START Otdb ID %s *****", otdbId) + self._startPipeline(otdbId, parset) + else: + logger.info("Job %s was set to scheduled, but cannot start yet.", otdbId) + + def onObservationFinished(self, otdbId, modificationTime): + """ Check if any successors can now start. """ + + logger.info("Considering to start successors of %s", otdbId) + + self._startSuccessors(otdbId) + + def onObservationAborted(self, otdbId, modificationTime): + parset = self._getParset(otdbId) + if not self._shouldHandle(parset): + return + + logger.info("***** STOP Otdb ID %s *****", otdbId) + self._stop(otdbId) + + """ + More statusses we want to abort on. + """ + onObservationDescribed = onObservationAborted + onObservationPrepared = onObservationAborted + onObservationApproved = onObservationAborted + onObservationPrescheduled = onObservationAborted + onObservationConflict = onObservationAborted + onObservationHold = onObservationAborted diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index 927a2507c93..50b03020dde 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -5,6 +5,7 @@ import sys from lofar.mac.PipelineControl import * from lofar.sas.otdb.OTDBBusListener import OTDBBusListener from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_SUBJECT, DEFAULT_OTDB_SERVICENAME +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as DEFAULT_RAS_SERVICENAME from lofar.messaging import ToBus, Service, EventMessage, MessageHandlerInterface from lofar.common.methodtrigger import MethodTrigger @@ -15,7 +16,7 @@ import uuid import datetime import logging -logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +logging.basicConfig(stream=sys.stdout, level=logging.INFO) try: from mock import patch @@ -54,28 +55,6 @@ class TestRunCommand(unittest.TestCase): output = runCommand("cat -", "yes") self.assertEqual(output, "yes") -class TestSlurmJobs(unittest.TestCase): - def test_no_jobs(self): - """ Test 'scontrol show job' output if there are no jobs. """ - with patch('lofar.mac.PipelineControl.Slurm._runCommand') as MockRunSlurmCommand: - MockRunSlurmCommand.return_value = "" - - self.assertEqual(Slurm().jobid("foo"), None) - - def test_one_job(self): - """ Test 'scontrol show job' output for a single job. """ - with patch('lofar.mac.PipelineControl.Slurm._runCommand') as MockRunSlurmCommand: - MockRunSlurmCommand.return_value = """119""" - - self.assertEqual(Slurm().jobid("foo"), "119") - - def test_one_job_with_subjobs(self): - """ Test 'scontrol show job' output for a single job that has subjobs. """ - with patch('lofar.mac.PipelineControl.Slurm._runCommand') as MockRunSlurmCommand: - MockRunSlurmCommand.return_value = """119\n119.0\n119.batch""" - - self.assertEqual(Slurm().jobid("foo"), "119") - class TestPipelineControlClassMethods(unittest.TestCase): def test_shouldHandle(self): """ Test whether we filter the right OTDB trees. """ @@ -95,6 +74,113 @@ class TestPipelineControlClassMethods(unittest.TestCase): "ObsSW.Observation.Cluster.ProcessingCluster.clusterName": t["cluster"] } self.assertEqual(PipelineControl._shouldHandle(Parset(parset)), t["shouldHandle"]) +class MockRAService(MessageHandlerInterface): + """ + Fakes RAService calls. + + For each job, radb_id = otdb_id + 1000 to detect misplaced ids. + """ + def __init__(self, predecessors, status): + super(MockRAService, self).__init__() + + self.service2MethodMap = { + "GetTask": self.GetTask, + "GetTasks": self.GetTasks, + } + + self.predecessors = predecessors + self.successors = {x: [s for s in predecessors if x in predecessors[s]] for x in predecessors} + self.status = status + + def GetTask(self, id, mom_id, otdb_id): + print "***** GetTask(%s) *****" % (otdb_id,) + + return { + 'status': self.status[otdb_id], + + 'predecessor_ids': [1000 + x for x in self.predecessors[otdb_id]], + 'successor_ids': [1000 + x for x in self.successors[otdb_id]], + + 'starttime': datetime.datetime.utcnow(), + 'endtime': datetime.datetime.utcnow(), + } + + def GetTasks(self, lower_bound, upper_bound, task_ids): + print "***** GetTasks(%s) *****" % (task_ids,) + + return [{ + 'otdb_id': t - 1000, + 'status': self.status[t - 1000], + + 'starttime': datetime.datetime.utcnow(), + 'endtime': datetime.datetime.utcnow(), + } for t in task_ids] + +class TestPipelineDependencies(unittest.TestCase): + def setUp(self): + # Create a random bus + self.busname = "%s-%s" % (sys.argv[0], str(uuid.uuid4())[:8]) + self.bus = ToBus(self.busname, { "create": "always", "delete": "always", "node": { "type": "topic" } }) + self.bus.open() + self.addCleanup(self.bus.close) + + # ================================ + # Global state to manipulate + # ================================ + + predecessors = { + 1: [2,3,4], + 2: [3], + 3: [], + 4: [], + } + + status = { + 1: "scheduled", # cannot start, since predecessor 2 hasn't finished + 2: "scheduled", # can start, since predecessor 3 has finished + 3: "finished", + 4: "scheduled", # can start, because no predecessors + } + + # ================================ + # Setup mock ra service + # + # Note that RA IDs are the same as + # OTDB IDs + 1000 in this test. + # ================================ + + service = Service(DEFAULT_RAS_SERVICENAME, + MockRAService, + busname=self.busname, + use_service_methods=True, + handler_args={"predecessors": predecessors, "status": status}) + service.start_listening() + self.addCleanup(service.stop_listening) + + def testGetState(self): + with PipelineDependencies(ra_service_busname=self.busname) as pipelineDependencies: + self.assertEqual(pipelineDependencies.getState(1), "scheduled") + self.assertEqual(pipelineDependencies.getState(2), "scheduled") + self.assertEqual(pipelineDependencies.getState(3), "finished") + self.assertEqual(pipelineDependencies.getState(4), "scheduled") + + def testPredecessorStates(self): + with PipelineDependencies(ra_service_busname=self.busname) as pipelineDependencies: + self.assertEqual(pipelineDependencies.getPredecessorStates(1), {2: "scheduled", 3: "finished", 4: "scheduled"}) + self.assertEqual(pipelineDependencies.getPredecessorStates(3), {}) + + def testSuccessorIds(self): + with PipelineDependencies(ra_service_busname=self.busname) as pipelineDependencies: + self.assertEqual(pipelineDependencies.getSuccessorIds(1), []) + self.assertEqual(pipelineDependencies.getSuccessorIds(3), [1,2]) + + def testCanStart(self): + with PipelineDependencies(ra_service_busname=self.busname) as pipelineDependencies: + self.assertEqual(pipelineDependencies.canStart(1), False) + self.assertEqual(pipelineDependencies.canStart(2), True) + self.assertEqual(pipelineDependencies.canStart(3), False) + self.assertEqual(pipelineDependencies.canStart(4), True) + class TestPipelineControl(unittest.TestCase): def setUp(self): # Create a random bus @@ -113,15 +199,11 @@ class TestPipelineControl(unittest.TestCase): self.scheduled_jobs[jobName] = (args, kwargs) - # Return job ID + # Return fake job ID return "42" - def jobid(self, jobname): - if jobname in ["1", "2", "3"]: - return jobname - - # "4" is an observation, so no SLURM job - return None + def isQueuedOrRunning(self, otdbId): + return str(otdbId) in self.scheduled_jobs patcher = patch('lofar.mac.PipelineControl.Slurm') patcher.start().side_effect = MockSlurm @@ -132,6 +214,24 @@ class TestPipelineControl(unittest.TestCase): patcher.start().return_value = "lofar-pipeline:trunk" self.addCleanup(patcher.stop) + # ================================ + # Global state to manipulate + # ================================ + + predecessors = { + 1: [2,3,4], + 2: [3], + 3: [], + 4: [], + } + + status = { + 1: "prescheduled", + 2: "prescheduled", + 3: "prescheduled", + 4: "prescheduled", + } + # ================================ # Setup mock otdb service # ================================ @@ -153,35 +253,18 @@ class TestPipelineControl(unittest.TestCase): def TaskGetSpecification(self, OtdbID): print "***** TaskGetSpecification(%s) *****" % (OtdbID,) - if OtdbID == 1: - predecessors = "[2,3,4]" - elif OtdbID == 2: - predecessors = "[3]" - elif OtdbID == 3: - predecessors = "[]" - elif OtdbID == 4: - return { "TaskSpecification": { - "Version.number": "1", - PARSET_PREFIX + "Observation.otdbID": str(OtdbID), - PARSET_PREFIX + "Observation.Scheduler.predecessors": "[]", - PARSET_PREFIX + "Observation.processType": "Observation", - PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", - PARSET_PREFIX + "Observation.stopTime": "2016-01-01 01:00:00", - } } - else: - raise Exception("Invalid OtdbID: %s" % OtdbID) - return { "TaskSpecification": { - "Version.number": "1", + "Version.number": "1", PARSET_PREFIX + "Observation.otdbID": str(OtdbID), - PARSET_PREFIX + "Observation.Scheduler.predecessors": predecessors, - PARSET_PREFIX + "Observation.processType": "Pipeline", + PARSET_PREFIX + "Observation.processType": ("Observation" if OtdbID == 4 else "Pipeline"), PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", } } def TaskSetStatus(self, OtdbID, NewStatus, UpdateTimestamps): print "***** TaskSetStatus(%s,%s) *****" % (OtdbID, NewStatus) + status[OtdbID] = NewStatus + # Broadcast the state change content = { "treeID" : OtdbID, "state" : NewStatus, "time_of_change" : datetime.datetime.utcnow() } msg = EventMessage(context=DEFAULT_OTDB_NOTIFICATION_SUBJECT, content=content) @@ -197,6 +280,18 @@ class TestPipelineControl(unittest.TestCase): service.start_listening() self.addCleanup(service.stop_listening) + # ================================ + # Setup mock ra service + # ================================ + + service = Service(DEFAULT_RAS_SERVICENAME, + MockRAService, + busname=self.busname, + use_service_methods=True, + handler_args={"predecessors": predecessors, "status": status}) + service.start_listening() + self.addCleanup(service.stop_listening) + # ================================ # Setup listener to catch result # of our service @@ -206,15 +301,15 @@ class TestPipelineControl(unittest.TestCase): listener.start_listening() self.addCleanup(listener.stop_listening) - self.trigger = MethodTrigger(listener, "onObservationQueued") + self.queued_trigger = MethodTrigger(listener, "onObservationQueued") def test_setStatus(self): - with PipelineControl(otdb_notification_busname=self.busname, otdb_service_busname=self.busname) as pipelineControl: + with PipelineControl(otdb_notification_busname=self.busname, otdb_service_busname=self.busname, ra_service_busname=self.busname) as pipelineControl: pipelineControl._setStatus(12345, "queued") # Wait for the status to propagate - self.assertTrue(self.trigger.wait()) - self.assertEqual(self.trigger.args[0], 12345) + self.assertTrue(self.queued_trigger.wait()) + self.assertEqual(self.queued_trigger.args[0], 12345) def testNoPredecessors(self): """ @@ -222,15 +317,15 @@ class TestPipelineControl(unittest.TestCase): 3 requires nothing """ - with PipelineControl(otdb_notification_busname=self.busname, otdb_service_busname=self.busname) as pipelineControl: + with PipelineControl(otdb_notification_busname=self.busname, otdb_service_busname=self.busname, ra_service_busname=self.busname) as pipelineControl: # Send fake status update pipelineControl._setStatus(3, "scheduled") - # Wait for message to arrive - self.assertTrue(self.trigger.wait()) + # Wait for pipeline to be queued + self.assertTrue(self.queued_trigger.wait()) # Verify message - self.assertEqual(self.trigger.args[0], 3) # otdbId + self.assertEqual(self.queued_trigger.args[0], 3) # otdbId # Check if job was scheduled self.assertIn("3", pipelineControl.slurm.scheduled_jobs) @@ -244,29 +339,28 @@ class TestPipelineControl(unittest.TestCase): 2 requires 3 4 is an observation """ - with PipelineControl(otdb_notification_busname=self.busname, otdb_service_busname=self.busname) as pipelineControl: + with PipelineControl(otdb_notification_busname=self.busname, otdb_service_busname=self.busname, ra_service_busname=self.busname) as pipelineControl: # Send fake status update pipelineControl._setStatus(1, "scheduled") - # Wait for message to arrive - self.assertTrue(self.trigger.wait()) + # Message should not arrive, as predecessors havent finished + self.assertFalse(self.queued_trigger.wait()) + + # Finish predecessors + pipelineControl._setStatus(2, "finished") + pipelineControl._setStatus(3, "finished") + pipelineControl._setStatus(4, "finished") + + # Wait for pipeline to be queued + self.assertTrue(self.queued_trigger.wait()) # Verify message - self.assertEqual(self.trigger.args[0], 1) # otdbId + self.assertEqual(self.queued_trigger.args[0], 1) # otdbId # Check if job was scheduled self.assertIn("1", pipelineControl.slurm.scheduled_jobs) self.assertIn("1-abort-trigger", pipelineControl.slurm.scheduled_jobs) - # Earliest start of this job > stop time of observation - for p in pipelineControl.slurm.scheduled_jobs["1"][1]["sbatch_params"]: - if p.startswith("--begin="): - begin = datetime.datetime.strptime(p, "--begin=%Y-%m-%dT%H:%M:%S") - self.assertGreater(begin, datetime.datetime(2016, 1, 1, 1, 0, 0)) - break - else: - self.assertTrue(False, "--begin parameter not given to SLURM job") - def main(argv): unittest.main() -- GitLab From 1a135ebf97f920ee4aff9cdfd7b51b9f1119c306 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 16 Jun 2016 21:44:09 +0000 Subject: [PATCH 355/933] Task #9373: Port 256 ch/sb delay compensation/bandpass/BF to 2.16 --- .../etc/parset-additions.d/default/NoCascadingFFT.parset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/NoCascadingFFT.parset b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/NoCascadingFFT.parset index ac1c9480b8a..bc5839123d7 100644 --- a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/NoCascadingFFT.parset +++ b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/NoCascadingFFT.parset @@ -1,5 +1,5 @@ # $Id$ # Define the sizes of both FFTs in the BF pipeline, # to prevent a cascade of FFTs -Cobalt.BeamFormer.nrDelayCompensationChannels = 64 -Cobalt.BeamFormer.nrHighResolutionChannels = 64 +Cobalt.BeamFormer.nrDelayCompensationChannels = 256 +Cobalt.BeamFormer.nrHighResolutionChannels = 256 -- GitLab From 4e182af45c50edb3dc98df1ce79aa439adadbbb3 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 17 Jun 2016 06:06:10 +0000 Subject: [PATCH 356/933] Task #8887: Use more ProcessingCluster keys --- MAC/Services/src/PipelineControl.py | 20 ++++++++++++++------ MAC/Services/test/tPipelineControl.py | 5 ++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index d13a516e0b3..07ff9a86ed0 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -128,6 +128,15 @@ class Parset(dict): def processingCluster(self): return self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] or "CEP2" + def processingPartition(self): + return self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterPartition"] or "cpu" + + def processingNumberOfCoresPerTask(self): + return int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfCoresPerTask"]) or "20" + + def processingNumberOfTasks(self): + return int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfTasks"]) or "24" + @staticmethod def dockerRepository(): return "nexus.cep4.control.lofar:18080" @@ -305,12 +314,10 @@ class PipelineControl(OTDBBusListener): # Maximum run time for job (31 days) "--time=31-0", - - # TODO: Compute nr nodes - "--nodes=24", - - # TODO: Compute nr cores/node - "--cpus-per-task=20", + + "--partition=%s" % parset.processingPartition(), + "--nodes=%s" % parset.processingNumberOfTasks(), + "--cpus-per-task=%s" % parset.processingNumberOfCoresPerTask(), # Define better places to write the output os.path.expandvars("--output=/data/log/runPipeline-%s.log" % (otdbId,)), @@ -367,6 +374,7 @@ class PipelineControl(OTDBBusListener): ), sbatch_params=[ + "--partition=%s" % parset.processingPartition(), "--cpus-per-task=1", "--ntasks=1", "--dependency=afternotok:%s" % slurm_job_id, diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index 50b03020dde..58599fa5c06 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -257,7 +257,10 @@ class TestPipelineControl(unittest.TestCase): "Version.number": "1", PARSET_PREFIX + "Observation.otdbID": str(OtdbID), PARSET_PREFIX + "Observation.processType": ("Observation" if OtdbID == 4 else "Pipeline"), - PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", + PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName": "CEP4", + PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterPartition": "cpu", + PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfCoresPerTask": "20", + PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfTasks": "24", } } def TaskSetStatus(self, OtdbID, NewStatus, UpdateTimestamps): -- GitLab From 972be70e255244964c1fad3645c63d04cfbe1d6c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 17 Jun 2016 08:33:13 +0000 Subject: [PATCH 357/933] Task #8887: various time scroll and zoom enhancements --- .../static/app/controllers/datacontroller.js | 82 +++++++++++++------ .../app/controllers/ganttprojectcontroller.js | 2 +- .../static/app/controllers/gridcontroller.js | 4 +- .../lib/templates/index.html | 14 +--- 4 files changed, 65 insertions(+), 37 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 49617e2f99e..567bf54947c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -662,26 +662,26 @@ dataControllerMod.controller('DataController', $scope.openViewFromDatePopup = function() { $scope.viewFromDatePopupOpened = true; }; $scope.openViewToDatePopup = function() { $scope.viewToDatePopupOpened = true; }; - $scope.jumpTimespanWidths = [{value:30, name:'30 Minutes'}, {value:60, name:'1 Hour'}, {value:3*60, name:'3 Hours'}, {value:6*60, name:'6 Hours'}, {value:12*60, name:'12 Hours'}, {value:24*60, name:'1 Day'}, {value:2*24*60, name:'2 Days'}, {value:3*24*60, name:'3 Days'}, {value:5*24*60, name:'5 Days'}, {value:7*24*60, name:'1 Week'}, {value:14*24*60, name:'2 Weeks'}, {value:28*24*60, name:'4 Weeks'}]; - $scope.jumpTimespanWidth = $scope.jumpTimespanWidths[7]; + $scope.zoomTimespans = [{value:30, name:'30 Minutes'}, {value:60, name:'1 Hour'}, {value:3*60, name:'3 Hours'}, {value:6*60, name:'6 Hours'}, {value:12*60, name:'12 Hours'}, {value:24*60, name:'1 Day'}, {value:2*24*60, name:'2 Days'}, {value:3*24*60, name:'3 Days'}, {value:5*24*60, name:'5 Days'}, {value:7*24*60, name:'1 Week'}, {value:14*24*60, name:'2 Weeks'}, {value:28*24*60, name:'4 Weeks'}, {value:1, name:'Custom (1 min)'}]; + $scope.zoomTimespan = $scope.zoomTimespans[5]; $scope.jumpToNow = function() { var floorLofarTime = dataService.floorDate(dataService.lofarTime, 1, 5); dataService.viewTimeSpan = { - from: dataService.floorDate(new Date(floorLofarTime.getTime() - 0.33*$scope.jumpTimespanWidth.value*60*1000), 1, 5), - to: dataService.ceilDate(new Date(floorLofarTime.getTime() + 0.67*$scope.jumpTimespanWidth.value*60*1000), 1, 5) + from: dataService.floorDate(new Date(floorLofarTime.getTime() - 0.4*$scope.zoomTimespan.value*60*1000), 1, 5), + to: dataService.floorDate(new Date(floorLofarTime.getTime() + 0.6*$scope.zoomTimespan.value*60*1000), 1, 5) }; + }; + + $scope.jumpToNow(); - //automatically select current task + $scope.selectCurrentTask = function() { var currentTasks = dataService.tasks.filter(function(t) { return t.starttime <= dataService.viewTimeSpan.to && t.endime >= dataService.viewTimeSpan.from; }); if(currentTasks.lenght > 0) { dataService.selected_task_id = currentTasks[0].id; } }; - //initialize are now - $scope.jumpToNow(); - - $scope.jumpToSelectedTasks = function() { + $scope.jumpToSelectedTask = function() { if(dataService.selected_task_id == undefined) return; @@ -694,17 +694,20 @@ dataControllerMod.controller('DataController', var taskDurationInMinutes = taskDurationInmsec/60000; var viewSpanInMinutes = taskDurationInMinutes; - var fittingSpans = $scope.jumpTimespanWidths.filter(function(w) { return w.value >= taskDurationInMinutes; }); + var fittingSpans = $scope.zoomTimespans.filter(function(w) { return w.value >= taskDurationInMinutes; }); if(fittingSpans.length > 0) { - $scope.jumpTimespanWidth = fittingSpans[0]; - viewSpanInMinutes = $scope.jumpTimespanWidth.value; + $scope.zoomTimespan = fittingSpans[0]; + //select one span larger if possible + if(fittingSpans.length > 1) + $scope.zoomTimespan = fittingSpans[1]; + viewSpanInMinutes = $scope.zoomTimespan.value; } var focusTime = new Date(task.starttime.getTime() + 0.5*taskDurationInmsec); dataService.viewTimeSpan = { - from: dataService.floorDate(new Date(focusTime.getTime() - 0.33*viewSpanInMinutes*60*1000), 1, 5), - to: dataService.ceilDate(new Date(focusTime.getTime() + 0.67*viewSpanInMinutes*60*1000), 1, 5) + from: dataService.floorDate(new Date(focusTime.getTime() - 0.4*viewSpanInMinutes*60*1000), 1, 5), + to: dataService.floorDate(new Date(focusTime.getTime() + 0.6*viewSpanInMinutes*60*1000), 1, 5) }; }; @@ -726,10 +729,13 @@ dataControllerMod.controller('DataController', }; }; - $scope.onJumpTimespanWidthChanged = function(span) { - var focusTime = dataService.floorDate(dataService.lofarTime, 1, 5); + $scope.onZoomTimespanChanged = function(span) { + var viewTimeSpanInmsec = dataService.viewTimeSpan.to.getTime() - dataService.viewTimeSpan.from.getTime(); + var focusTime = new Date(dataService.viewTimeSpan.from + 0.5*viewTimeSpanInmsec); - if(dataService.selected_task_id != undefined) { + if(dataService.autoFollowNow) { + focusTime = dataService.floorDate(dataService.lofarTime, 1, 5); + } else if(dataService.selected_task_id != undefined) { var task = dataService.taskDict[dataService.selected_task_id]; if(task) { @@ -738,26 +744,44 @@ dataControllerMod.controller('DataController', } dataService.viewTimeSpan = { - from: dataService.floorDate(new Date(focusTime.getTime() - 0.33*$scope.jumpTimespanWidth.value*60*1000)), - to: dataService.ceilDate(new Date(focusTime.getTime() + 0.67*$scope.jumpTimespanWidth.value*60*1000)) + from: dataService.floorDate(new Date(focusTime.getTime() - 0.4*$scope.zoomTimespan.value*60*1000)), + to: dataService.floorDate(new Date(focusTime.getTime() + 0.6*$scope.zoomTimespan.value*60*1000)) }; }; + $scope.selectZoomTimespan = function() { + var viewTimeSpanInmsec = dataService.viewTimeSpan.to.getTime() - dataService.viewTimeSpan.from.getTime(); + var viewTimeSpanInMinutes = Math.round(viewTimeSpanInmsec/60000); + + var foundZoomTimespan = $scope.zoomTimespans.find(function(zts) { return zts.value == viewTimeSpanInMinutes; }); + + if(foundZoomTimespan) { + $scope.zoomTimespan = foundZoomTimespan; + } else { + var customZoomTimespan = $scope.zoomTimespans.find(function(zts) { return zts.name.startsWith('Custom'); }); + customZoomTimespan.value = viewTimeSpanInMinutes; + customZoomTimespan.name = 'Custom (' + viewTimeSpanInMinutes + ' min)'; + $scope.zoomTimespan = customZoomTimespan; + } + }; + $scope.$watch('dataService.viewTimeSpan.from', function() { if(dataService.viewTimeSpan.from >= dataService.viewTimeSpan.to) { - dataService.viewTimeSpan.to = dataService.ceilDate(new Date(dataService.viewTimeSpan.from.getTime() + 60*60*1000), 1, 5); + dataService.viewTimeSpan.from = dataService.floorDate(new Date(dataService.viewTimeSpan.to.getTime() - 5*60*1000), 1, 5); } }); $scope.$watch('dataService.viewTimeSpan.to', function() { if(dataService.viewTimeSpan.to <= dataService.viewTimeSpan.from) { - dataService.viewTimeSpan.from = dataService.floorDate(new Date(dataService.viewTimeSpan.to.getTime() - 60*60*1000), 1, 5); + dataService.viewTimeSpan.to = dataService.floorDate(new Date(dataService.viewTimeSpan.from.getTime() + 5*60*1000), 1, 5); } }); $scope.$watch('dataService.viewTimeSpan', function() { - dataService.clearTasksAndClaimsOutsideViewSpan(); - dataService.getTasksAndClaimsForViewSpan(); + $scope.selectZoomTimespan(); + + $scope.$evalAsync(function() { dataService.clearTasksAndClaimsOutsideViewSpan(); }); + $scope.$evalAsync(function() { dataService.getTasksAndClaimsForViewSpan(); }); }, true); $scope.$watch('dataService.filteredTasks', dataService.computeMinMaxTaskTimes); @@ -768,6 +792,18 @@ dataControllerMod.controller('DataController', } }); + $scope.$watch('dataService.lofarTime', function() { + if(dataService.autoFollowNow && (Math.round(dataService.lofarTime.getTime()/1000))%5==0) { + $scope.jumpToNow(); + } + }); + + $scope.$watch('dataService.autoFollowNow', function() { + if(dataService.autoFollowNow) { + $scope.jumpToNow(); + } + }); + dataService.initialLoad(); //clock ticking every second diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index 8fb1940a1aa..456830fe0d5 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -64,7 +64,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS element.bind('dblclick', function(event) { if(directiveScope.task.model.raTask) { $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; - $scope.jumpToSelectedTasks(); + $scope.jumpToSelectedTask(); } }); } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 8671ca77386..a3bc405df79 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -45,7 +45,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid }, { field: 'duration', displayName: 'Duration', - width: '8%', + width: '7%', enableFiltering: false, enableCellEdit: false, enableCellEditOnFocus: false, @@ -53,7 +53,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid }, { field: 'status', enableCellEdit: true, - width: '8%', + width: '7%', filter: { type: uiGridConstants.filter.SELECT, selectOptions: [] diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index 4bcd9771886..5f4036320a2 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -75,23 +75,15 @@ <div class="col-md-2"> <label>Scroll:</label> <p class="input-group"> - <label title="Follow 'now' automatically" style="padding-right: 4px; vertical-align: top;">auto<input type="checkbox" ng-model="dataService.autoFollowNow"></label> + <label title="Automatically scroll 'From' and 'To' to watch live events" style="padding-right: 4px; vertical-align: top;">Live <input type="checkbox" ng-model="dataService.autoFollowNow"></label> <button title="Scroll back in time" type="button" class="btn btn-default" ng-click="scrollBack()"><i class="glyphicon glyphicon-step-backward"></i></button> <button title="Scroll forward in time"type="button" class="btn btn-default" ng-click="scrollForward()"><i class="glyphicon glyphicon-step-forward"></i></button> </p> </div> <div class="col-md-2"> - <label>Jump:</label> + <label>Zoom:</label> <p class="input-group"> - <span class="input-group-btn"> - <button type="button" class="btn btn-default" ng-click="jumpToSelectedTasks()" title="Jump to selected Task(s)">Task</button> - </span> - <span class="input-group-btn" style="width:10px;"></span> - <span class="input-group-btn"> - <button type="button" class="btn btn-default" ng-click="jumpToNow()" title="Jump to Now">Now</button> - </span> - <select class="form-control" ng-model=jumpTimespanWidth ng-options="option.name for option in jumpTimespanWidths track by option.value" ng-change="onJumpTimespanWidthChanged(span)"> - </select> + <select class="form-control" ng-model=zoomTimespan ng-options="option.name for option in zoomTimespans track by option.value" ng-change="onZoomTimespanChanged(span)"></select> </p> </div> </div> -- GitLab From 340aa592b68d4d4978a6300c796a4f9bc980d312 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 17 Jun 2016 09:18:14 +0000 Subject: [PATCH 358/933] Task #5390: Use credentials for pulling from Nexus --- RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh index fa1531c76d5..3d246001f8e 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh @@ -31,7 +31,7 @@ for HOST in ${HOSTS:-cbm001 cbm002 cbm003 cbm004 cbm005 cbm006 cbm007 cbm008 cbm mkdir -p \$dl_dir && cd \$dl_dir || exit 1 # Download archive from NEXUS. -N: clobber existing files - wget -N --tries=3 --no-check-certificate \"${NEXUS_URL}\" || exit 1 + wget -N --tries=3 --no-check-certificate --user=macinstall --password=macinstall \"${NEXUS_URL}\" || exit 1 # The full pathnames are in the tar file, so unpack from root dir. # -m: don't warn on timestamping /localhome -- GitLab From a09ee390a690cdd0854e94eb986331647f1e2f86 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 17 Jun 2016 09:20:15 +0000 Subject: [PATCH 359/933] Task #5390: Ported use of Nexus credentials in Cobalt_install.sh --- RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh index fa1531c76d5..3d246001f8e 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh @@ -31,7 +31,7 @@ for HOST in ${HOSTS:-cbm001 cbm002 cbm003 cbm004 cbm005 cbm006 cbm007 cbm008 cbm mkdir -p \$dl_dir && cd \$dl_dir || exit 1 # Download archive from NEXUS. -N: clobber existing files - wget -N --tries=3 --no-check-certificate \"${NEXUS_URL}\" || exit 1 + wget -N --tries=3 --no-check-certificate --user=macinstall --password=macinstall \"${NEXUS_URL}\" || exit 1 # The full pathnames are in the tar file, so unpack from root dir. # -m: don't warn on timestamping /localhome -- GitLab From ff44e24fe8e89255ccfbbe09842c7932190c4da7 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 17 Jun 2016 12:10:42 +0000 Subject: [PATCH 360/933] Task #8887: various time scroll and zoom enhancements --- SAS/MoM/MoMQueryService/momqueryservice.py | 5 +++-- .../ResourceAssignmentEditor/lib/mom.py | 2 ++ .../static/app/controllers/datacontroller.js | 1 - .../static/app/controllers/gridcontroller.js | 21 +++++++++++++------ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index 5893a21bc8b..384376a06cf 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -92,7 +92,7 @@ class MoMDatabaseWrapper: def getProjectDetails(self, mom_ids): ''' get the project details (project_mom2id, project_name, project_description, object_mom2id, object_name, object_description, - object_type, object_group_id) for given mom object mom_ids + object_type, object_group_id, object_group_name) for given mom object mom_ids :param mixed mom_ids comma seperated string of mom2object id's, or list of ints :rtype list of dict's key value pairs with the project details ''' @@ -114,9 +114,10 @@ class MoMDatabaseWrapper: # TODO: make a view for this query in momdb! query = '''SELECT project.mom2id as project_mom2id, project.id as project_mom2objectid, project.name as project_name, project.description as project_description, - object.mom2id as object_mom2id, object.id as object_mom2objectid, object.name as object_name, object.description as object_description, object.mom2objecttype as object_type, object.group_id as object_group_id + object.mom2id as object_mom2id, object.id as object_mom2objectid, object.name as object_name, object.description as object_description, object.mom2objecttype as object_type, object.group_id as object_group_id, grp.id as object_group_mom2objectid, grp.name as object_group_name FROM mom2object as object left join mom2object as project on project.id = object.ownerprojectid + left join mom2object as grp on grp.id = object.group_id where object.mom2id in (%s) order by project_mom2id ''' % (ids_str,) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py index 1390d521c88..b3bd59f0bc4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py @@ -65,6 +65,8 @@ def updateTaskMomDetails(task, momrpc): t['project_mom2object_id'] = m['project_mom2objectid'] t['mom2object_id'] = m['object_mom2objectid'] t['mom_object_group_id'] = m['object_group_id'] + t['mom_object_group_name'] = m.get('object_group_name', 'foo') + t['mom_object_group_mom2object_id'] = m.get('mom_object_group_mom2objectid', 331516) else: t['project_name'] = 'OTDB Only' t['project_mom_id'] = -98 diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 567bf54947c..4b408d5b78a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -222,7 +222,6 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, var task = result.tasks[i]; task.starttime = convertDatestringToLocalUTCDate(task.starttime); task.endtime = convertDatestringToLocalUTCDate(task.endtime); - } var initialTaskLoad = self.tasks.length == 0; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index a3bc405df79..8fba4c7f062 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -28,7 +28,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid }, { field: 'starttime', displayName: 'Start', - width: '14%', + width: '13%', type: 'date', enableCellEdit: false, enableCellEditOnFocus: false, @@ -37,7 +37,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid }, { field: 'endtime', displayName: 'End', - width: '14%', + width: '13%', type: 'date', enableCellEdit: false, enableCellEditOnFocus: false, @@ -45,7 +45,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid }, { field: 'duration', displayName: 'Duration', - width: '7%', + width: '6%', enableFiltering: false, enableCellEdit: false, enableCellEditOnFocus: false, @@ -53,7 +53,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid }, { field: 'status', enableCellEdit: true, - width: '7%', + width: '6%', filter: { type: uiGridConstants.filter.SELECT, selectOptions: [] @@ -67,12 +67,18 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid }, { field: 'type', enableCellEdit: false, - width: '8%', + width: '6%', filter: { type: uiGridConstants.filter.SELECT, selectOptions: [] } }, + { field: 'mom_object_group_name', + displayName: 'Group', + enableCellEdit: false, + cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity[col.field]}}</a>', + width: '6%' + }, { field: 'mom_id', displayName: 'MoM ID', enableCellEdit: false, @@ -179,7 +185,10 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid status: task.status, type: task.type, project_mom2object_id: task.project_mom2object_id, - mom2object_id: task.mom2object_id + mom2object_id: task.mom2object_id, + mom_object_group_id: task.mom_object_group_id, + mom_object_group_name: task.mom_object_group_name, + mom_object_group_mom2object_id: task.mom_object_group_mom2object_id }; tasks.push(gridTask); } -- GitLab From 7b91ba8b5cc7d9a563c81331ec5be6246fb68535 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 17 Jun 2016 12:16:46 +0000 Subject: [PATCH 361/933] Task #8887: in r34709 and this changeset, added group name and object_id in momqueryservice. Show group name and link to mom in webscheduler list --- SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py index b3bd59f0bc4..e097c71de92 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py @@ -65,8 +65,8 @@ def updateTaskMomDetails(task, momrpc): t['project_mom2object_id'] = m['project_mom2objectid'] t['mom2object_id'] = m['object_mom2objectid'] t['mom_object_group_id'] = m['object_group_id'] - t['mom_object_group_name'] = m.get('object_group_name', 'foo') - t['mom_object_group_mom2object_id'] = m.get('mom_object_group_mom2objectid', 331516) + t['mom_object_group_name'] = m.get('object_group_name') + t['mom_object_group_mom2object_id'] = m.get('mom_object_group_mom2objectid') else: t['project_name'] = 'OTDB Only' t['project_mom_id'] = -98 -- GitLab From 5c026cb8b06d8da80c6782bbd1edfa34e94f3059 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 17 Jun 2016 13:03:38 +0000 Subject: [PATCH 362/933] Task #8887: typo's, and dropdown filter for group in webscheduler list --- SAS/MoM/MoMQueryService/momqueryservice.py | 2 +- .../ResourceAssignmentEditor/lib/mom.py | 2 +- .../static/app/controllers/gridcontroller.js | 25 +++++++++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index 384376a06cf..9db6eb84d64 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -117,7 +117,7 @@ class MoMDatabaseWrapper: object.mom2id as object_mom2id, object.id as object_mom2objectid, object.name as object_name, object.description as object_description, object.mom2objecttype as object_type, object.group_id as object_group_id, grp.id as object_group_mom2objectid, grp.name as object_group_name FROM mom2object as object left join mom2object as project on project.id = object.ownerprojectid - left join mom2object as grp on grp.id = object.group_id + left join mom2object as grp on grp.mom2id = object.group_id where object.mom2id in (%s) order by project_mom2id ''' % (ids_str,) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py index e097c71de92..1534babfdc9 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py @@ -66,7 +66,7 @@ def updateTaskMomDetails(task, momrpc): t['mom2object_id'] = m['object_mom2objectid'] t['mom_object_group_id'] = m['object_group_id'] t['mom_object_group_name'] = m.get('object_group_name') - t['mom_object_group_mom2object_id'] = m.get('mom_object_group_mom2objectid') + t['mom_object_group_mom2object_id'] = m.get('object_group_mom2objectid') else: t['project_name'] = 'OTDB Only' t['project_mom_id'] = -98 diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 8fba4c7f062..1c808f7b830 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -20,7 +20,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid displayName:'Project', enableCellEdit: false, cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}">{{row.entity[col.field]}}</a>', - width: '15%', + width: '12%', filter: { type: uiGridConstants.filter.SELECT, selectOptions: [] @@ -28,7 +28,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid }, { field: 'starttime', displayName: 'Start', - width: '13%', + width: '11%', type: 'date', enableCellEdit: false, enableCellEditOnFocus: false, @@ -37,7 +37,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid }, { field: 'endtime', displayName: 'End', - width: '13%', + width: '11%', type: 'date', enableCellEdit: false, enableCellEditOnFocus: false, @@ -77,7 +77,11 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid displayName: 'Group', enableCellEdit: false, cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity[col.field]}}</a>', - width: '6%' + width: '13%', + filter: { + type: uiGridConstants.filter.SELECT, + selectOptions: [] + } }, { field: 'mom_id', displayName: 'MoM ID', @@ -198,7 +202,8 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid } else $scope.gridOptions.data = [] - fillProjectsColumFilterSelectOptions(); + $scope.$evalAsync(fillProjectsColumFilterSelectOptions) + $scope.$evalAsync(fillGroupsColumFilterSelectOptions); }; function jumpToSelectedTaskRow() { @@ -247,6 +252,16 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid fillColumFilterSelectOptions(projectNames, $scope.columns[1]); }; + function fillGroupsColumFilterSelectOptions() { + var tasks = $scope.dataService.tasks; + //get unique groupNames from tasks + var groupNames = tasks.map(function(t) { return t.mom_object_group_name; }); + groupNames = groupNames.filter(function(value, index, arr) { return arr.indexOf(value) == index;}) + groupNames.sort(); + + fillColumFilterSelectOptions(groupNames, $scope.columns[7]); + }; + $scope.$watch('dataService.momProjectsDict', fillProjectsColumFilterSelectOptions); $scope.$watch('dataService.selected_task_id', jumpToSelectedTaskRow); } -- GitLab From b8d40545e9af5d6e45378502aad295327a83df03 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 17 Jun 2016 13:48:21 +0000 Subject: [PATCH 363/933] Task #8887: typo's in task html urls --- .../ResourceAssignmentEditor/lib/webservice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 8ea09345b4c..739ea298293 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -364,11 +364,11 @@ def getTaskHtml(task_id): html += '<tr><td>%s</td>' % prop if prop == 'id': - html += '<td><a href="/rest/tasks/%s.html">%s</a></td> ' % (task[prop], task[prop]) + html += '<td><a href="/tasks/%s.html">%s</a></td> ' % (task[prop], task[prop]) elif prop == 'predecessor_ids' or prop == 'successor_ids': ids = task[prop] if ids: - html += '<td>%s</td> ' % ', '.join('<a href="/rest/tasks/%s.html">%s</a>' % (id, id) for id in ids) + html += '<td>%s</td> ' % ', '.join('<a href="/tasks/%s.html">%s</a>' % (id, id) for id in ids) else: html += '<td></td> ' else: -- GitLab From b5c6ac21ec4d5a27e0a863c6f8ce7f387b61570a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 17 Jun 2016 14:40:46 +0000 Subject: [PATCH 364/933] Task #8887: Upgraded LOFAR images to Ubuntu 16.04 and python-casacore 2.1.2 --- Docker/lofar-base/Dockerfile.tmpl | 12 ++++++------ Docker/lofar-outputproc/Dockerfile.tmpl | 2 +- Docker/lofar-pipeline/Dockerfile.tmpl | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index aef0133c364..1f718d68812 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -1,7 +1,7 @@ # # base # -FROM ubuntu:14.04 +FROM ubuntu:16.04 # # common-environment @@ -20,8 +20,8 @@ ENV DEBIAN_FRONTEND=noninteractive \ # ENV CASACORE_VERSION=2.1.0 \ CASAREST_VERSION=1.4.1 \ - PYTHON_CASACORE_VERSION=2.0.1 \ - BOOST_VERSION=1.54 + PYTHON_CASACORE_VERSION=2.1.2 \ + BOOST_VERSION=1.58 # # set-uid @@ -39,7 +39,7 @@ ENV J=6 #RUN sed -i 's/archive.ubuntu.com/osmirror.rug.nl/' /etc/apt/sources.list RUN apt-get update && \ apt-get install -y python2.7 libpython2.7 && \ - apt-get install -y libblas3 liblapacke python-numpy libcfitsio3 libwcs4 libfftw3-bin libhdf5-7 libboost-python${BOOST_VERSION}.0 && \ + apt-get install -y libblas3 liblapacke python-numpy libcfitsio-bin libwcs5 libfftw3-bin libhdf5-10 libboost-python${BOOST_VERSION}.0 && \ apt-get install -y nano # @@ -59,7 +59,7 @@ RUN mkdir -p ${INSTALLDIR} # Casacore # ******************* # -RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/casacore/build && \ mkdir -p ${INSTALLDIR}/casacore/data && \ cd ${INSTALLDIR}/casacore && git clone https://github.com/casacore/casacore.git src && \ @@ -71,7 +71,7 @@ RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison cd ${INSTALLDIR}/casacore/build && make install && \ bash -c "strip ${INSTALLDIR}/casacore/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casacore/{build,src}" && \ - apt-get purge -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ + apt-get purge -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ apt-get autoremove -y # diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index eb5ef0c866b..7f05fa2a5b0 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -10,7 +10,7 @@ FROM lofar-base:${LOFAR_TAG} # # Run-time dependencies -RUN apt-get update && apt-get install -y liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libboost-regex${BOOST_VERSION}.0 +RUN apt-get update && apt-get install -y liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libboost-regex${BOOST_VERSION}.0 # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index eae03529529..80569d2d9f0 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -6,7 +6,7 @@ FROM lofar-base:${LOFAR_TAG} ENV AOFLAGGER_VERSION=2.7.1 # Run-time dependencies -RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ +RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ apt-get -y install python-pip python-dev && \ pip install pyfits pywcs python-monetdb && \ apt-get -y purge python-pip python-dev && \ @@ -43,7 +43,7 @@ ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ LOFAR_BUILDVARIANT=gnu_optarch # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ @@ -56,6 +56,6 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "chmod a+rwx ${INSTALLDIR}/lofar/var/{log,run}" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ - apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ + apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ apt-get autoremove -y -- GitLab From a461602a11b1ffe173e97d479679e7becf0fcac2 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 17 Jun 2016 14:46:31 +0000 Subject: [PATCH 365/933] Task #8887: Fixed typo --- MAC/Services/src/PipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 07ff9a86ed0..e250756b94e 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -185,7 +185,7 @@ class Slurm(object): self._runCommand("scancel --jobname %s" % (jobName,)) def isQueuedOrRunning(self, jobName): - stdout = self._runCommand("sacct --starttime=2016-01-01 --noheader --parsable2 --format=jobid --name=%s --state=PENDING,CONFIGURING,RUNNING,RESIZING,COMPLETING,SUSPENDED" % (jobname,)) + stdout = self._runCommand("sacct --starttime=2016-01-01 --noheader --parsable2 --format=jobid --name=%s --state=PENDING,CONFIGURING,RUNNING,RESIZING,COMPLETING,SUSPENDED" % (jobName,)) return stdout != "" -- GitLab From 28b933a2baa4692aca30b964ff791ef383980399 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 17 Jun 2016 14:47:57 +0000 Subject: [PATCH 366/933] Task #8887: Disable table locking in casacore (we do not open MSes in parallel) --- Docker/lofar-base/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 1f718d68812..72e31dfad6a 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -66,7 +66,7 @@ RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison if [ "${CASACORE_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casacore/src && git checkout tags/v${CASACORE_VERSION}; fi && \ cd ${INSTALLDIR}/casacore/data && wget --retry-connrefused ftp://ftp.astron.nl/outgoing/Measures/WSRT_Measures.ztar && \ cd ${INSTALLDIR}/casacore/data && tar xf WSRT_Measures.ztar && rm -f WSRT_Measures.ztar && \ - cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DUSE_OPENMP=True -DUSE_FFTW3=TRUE -DUSE_HDF5=ON ../src/ && \ + cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DENABLE_TABLELOCKING=OFF -DUSE_OPENMP=ON -DUSE_FFTW3=TRUE -DUSE_HDF5=ON ../src/ && \ cd ${INSTALLDIR}/casacore/build && make -j ${J} && \ cd ${INSTALLDIR}/casacore/build && make install && \ bash -c "strip ${INSTALLDIR}/casacore/{lib,bin}/* || true" && \ -- GitLab From 9b95d46991364f3d3819dc4ddc6e085aef1acf3f Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Fri, 17 Jun 2016 14:55:24 +0000 Subject: [PATCH 367/933] Task #9556: Added setting input files in parset --- .../lib/propagator.py | 55 ++++++- .../lib/rotspservice.py | 4 +- .../lib/translator.py | 135 ++++++++---------- .../sql/add_resource_allocation_statics.sql | 2 +- .../base_resource_estimator.py | 2 + .../calibration_pipeline.py | 10 +- .../longbaseline_pipeline.py | 5 +- .../resource_estimators/observation.py | 2 +- 8 files changed, 129 insertions(+), 86 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py index cf44678c2ff..66afebe4a20 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py @@ -143,6 +143,56 @@ class RAtoOTDBPropagator(): logger.error(e) self.doTaskConflict(otdb_id) + def parseStorageProperties(self, storage_claim): + """input something like: + {u'username':u'anonymous', u'status': u'allocated', u'resource_name': + u'cep4storage', u'user_id': -1, u'resource_type_id': 5, u'task_id': 6349, + u'status_id': 1, u'resource_id': 117, u'session_id': 1, u'id': 339, + u'claim_size': 24000, u'starttime': datetime.datetime(2016, 6, 10, 16, 8, 15), + u'resource_type_name': u'storage', u'endtime': datetime.datetime(2016, 7, 11, + 16, 33, 15), u'properties': [{u'io_type_name': u'output', u'type_name': + u'img_file_size', u'value': 1000, u'io_type_id': 0, u'type_id': 12, u'id': 808}, + {u'io_type_name': u'output', u'type_name': u'nr_of_img_files', u'value': 24, + u'io_type_id': 0, u'type_id': 4, u'id': 809}, {u'io_type_name': u'input', + u'type_name': u'nr_of_uv_files', u'value': 240, u'io_type_id': 1, u'type_id': 2, + u'id': 810}, {u'io_type_name': u'input', u'type_name': u'uv_file_size', + u'value': 43957416, u'io_type_id': 1, u'type_id': 10, u'id': 811}]} + output something like: + {'output_files': {u'nr_of_im_files': 488, u'nr_of_uv_files': 488, u'im_file_size': 1000, u'uv_file_size': 32500565}} + """ + result = {'input_files': {}, 'output_files': {}} + # FIXME This is very fragile code, mainly because we don't know if 'saps' are part of the input or output. + # This should probably be redesigned, but might require changes in how RADB works. + if 'saps' in storage_claim: + input = False + output = False + saps = [] + for s in storage_claim['saps']: + properties = {} + for p in s['properties']: + if p['io_type_name'] == 'output': + properties[p['type_name']] = p['value'] + output = True + if p['io_type_name'] == 'input': + properties[p['type_name']] = p['value'] + input = True + if input or output: + saps.append({'sap_nr' : s['sap_nr'], 'properties': properties}) + if input: + if saps: + result['input_files']['saps'] = saps + if output: + if saps: + result['output_files']['saps'] = saps + if 'properties' in storage_claim: + for p in storage_claim['properties']: + if p['io_type_name'] == 'output': + result['output_files'][p['type_name']] = p['value'] + if p['io_type_name'] == 'input': + result['input_files'][p['type_name']] = p['value'] + logging.info(result) + return result + def getRAinfo(self, ra_id): info = {} info["storage"] = {} @@ -150,11 +200,12 @@ class RAtoOTDBPropagator(): claims = self.radbrpc.getResourceClaims(task_ids=ra_id, extended=True, include_properties=True) for claim in claims: logger.debug("Processing claim: %s" % claim) - if claim['resource_type_name'] == 'storage': - info['storage'] = claim + if claim['resource_type_name'] == 'storage': ## TODO we will need to check for different storage names/types in the future + info['storage'] = parseStorageProperties(claim) info["starttime"] = task["starttime"] info["endtime"] = task["endtime"] info["status"] = task["status"] + info["type"] = task["type"] return info def setOTDBinfo(self, otdb_id, otdb_info, otdb_status): diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py index 1a942ead365..bc3359afa3d 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py @@ -74,11 +74,11 @@ class RATaskStatusChangedListener(RABusListener): def onTaskConflict(self, task_ids): radb_id = task_ids.get('radb_id') - otdb_id = task_ids.get('otdb_id') + otdb_id = task_ids.get('otdb_id') #Does this work if one of the Id's is not set? mom_id = task_ids.get('mom_id') logger.info('onTaskConflict: radb_id=%s otdb_id=%s mom_id=%s', radb_id, otdb_id, mom_id) - self.propagator.doTaskConflict(radb_id, otdb_id, mom_id) + self.propagator.doTaskConflict(otdb_id) __all__ = ["RATaskStatusChangedListener"] diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 22c2fa14917..989c85241ea 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -59,8 +59,7 @@ class RAtoOTDBTranslator(): '''returns the full path to the data dir on CEP4 for give project and otdb_id, depending on environment''' return "%s/%s/L%d" % (self.cep4DataPath(), project_name, otdb_id) - def CreateCorrelated(self, otdb_id, storage_properties, project_name): - sb_nr = 0 + def CreateCorrelated(self, otdb_id, storage_properties, project_name, io_type, sb_nr = 0): locations = [] filenames = [] skip = [] @@ -69,23 +68,27 @@ class RAtoOTDBTranslator(): for sap in storage_properties["saps"]: ##We might need to sort saps? logging.debug('processing sap: %s' % sap) if "nr_of_uv_files" in sap['properties']: + if 'start_sb_nr' in sap['properties']: + sb_nr = sap['properties']['start_sb_nr'] for _ in xrange(sap['properties']['nr_of_uv_files']): - locations.append(self.locationPath(project_name, otdb_id)) + locations.append(self.locationPath(project_name, otdb_id) + '/uv') filenames.append("L%d_SAP%03d_SB%03d_uv.MS" % (otdb_id, sap['sap_nr'], sb_nr)) skip.append("0") sb_nr += 1 else: ## It's a pipeline (no SAPs) + if 'start_sb_nr' in storage_properties: + sb_nr = storage_properties['start_sb_nr'] for _ in xrange(storage_properties['nr_of_uv_files']): - locations.append(self.locationPath(project_name, otdb_id)) + locations.append(self.locationPath(project_name, otdb_id) + '/uv') filenames.append("L%d_SB%03d_uv.MS" % (otdb_id, sb_nr)) skip.append("0") sb_nr += 1 - result[PREFIX + 'DataProducts.Output_Correlated.locations'] = '[' + to_csv_string(locations) + ']' - result[PREFIX + 'DataProducts.Output_Correlated.filenames'] = '[' + to_csv_string(filenames) + ']' - result[PREFIX + 'DataProducts.Output_Correlated.skip'] = '[' + to_csv_string(skip) + ']' + result[PREFIX + 'DataProducts.%s_Correlated.locations' % (io_type)] = '[' + to_csv_string(locations) + ']' + result[PREFIX + 'DataProducts.%s_Correlated.filenames' % (io_type)] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.%s_Correlated.skip' % (io_type)] = '[' + to_csv_string(skip) + ']' return result - def CreateCoherentStokes(self, otdb_id, storage_properties, project_name): + def CreateCoherentStokes(self, otdb_id, storage_properties, project_name, io_type): locations = [] filenames = [] skip = [] @@ -99,15 +102,15 @@ class RAtoOTDBTranslator(): for tab in xrange(nr_tabs): for stokes in xrange(nr_stokes): for part in xrange(nr_parts): - locations.append(self.locationPath(project_name, otdb_id)) + locations.append(self.locationPath(project_name, otdb_id) + '/cs') filenames.append("L%d_SAP%03d_B%03d_S%d_P%03d_bf.h5" % (otdb_id, sap['sap_nr'], tab, stokes, part)) skip.append("0") - result[PREFIX + 'DataProducts.Output_CoherentStokes.locations'] = '[' + to_csv_string(locations) + ']' - result[PREFIX + 'DataProducts.Output_CoherentStokes.filenames'] = '[' + to_csv_string(filenames) + ']' - result[PREFIX + 'DataProducts.Output_CoherentStokes.skip'] = '[' + to_csv_string(skip) + ']' + result[PREFIX + 'DataProducts.%s_CoherentStokes.locations' % (io_type)] = '[' + to_csv_string(locations) + ']' + result[PREFIX + 'DataProducts.%s_CoherentStokes.filenames' % (io_type)] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.%s_CoherentStokes.skip' % (io_type)] = '[' + to_csv_string(skip) + ']' return result - def CreateIncoherentStokes(self, otdb_id, storage_properties, project_name): + def CreateIncoherentStokes(self, otdb_id, storage_properties, project_name, io_type): locations = [] filenames = [] skip = [] @@ -121,114 +124,89 @@ class RAtoOTDBTranslator(): for tab in xrange(nr_tabs): for stokes in xrange(nr_stokes): for part in xrange(nr_parts): - locations.append(self.locationPath(project_name, otdb_id)) + locations.append(self.locationPath(project_name, otdb_id) + '/is') filenames.append("L%d_SAP%03d_B%03d_S%d_P%03d_bf.h5" % (otdb_id, sap['sap_nr'], tab, stokes, part)) skip.append("0") - result[PREFIX + 'DataProducts.Output_IncoherentStokes.locations'] = '[' + to_csv_string(locations) + ']' - result[PREFIX + 'DataProducts.Output_IncoherentStokes.filenames'] = '[' + to_csv_string(filenames) + ']' - result[PREFIX + 'DataProducts.Output_IncoherentStokes.skip'] = '[' + to_csv_string(skip) + ']' + result[PREFIX + 'DataProducts.%s_IncoherentStokes.locations' % (io_type)] = '[' + to_csv_string(locations) + ']' + result[PREFIX + 'DataProducts.%s_IncoherentStokes.filenames' % (io_type)] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.%s_IncoherentStokes.skip' % (io_type)] = '[' + to_csv_string(skip) + ']' return result - def CreateInstrumentModel(self, otdb_id, storage_properties, project_name): - sb_nr = 0 + def CreateInstrumentModel(self, otdb_id, storage_properties, project_name, io_type, sb_nr = 0): locations = [] filenames = [] skip = [] result = {} + if 'start_sb_nr' in storage_properties: + sb_nr = storage_properties['start_sb_nr'] for _ in xrange(storage_properties['nr_of_im_files']): - locations.append(self.locationPath(project_name, otdb_id)) + locations.append(self.locationPath(project_name, otdb_id) + '/im') filenames.append("L%d_SB%03d_inst.INST" % (otdb_id, sb_nr)) skip.append("0") sb_nr += 1 - result[PREFIX + 'DataProducts.Output_InstrumentModel.locations'] = '[' + to_csv_string(locations) + ']' - result[PREFIX + 'DataProducts.Output_InstrumentModel.filenames'] = '[' + to_csv_string(filenames) + ']' - result[PREFIX + 'DataProducts.Output_InstrumentModel.skip'] = '[' + to_csv_string(skip) + ']' + result[PREFIX + 'DataProducts.%s_InstrumentModel.locations' % (io_type)] = '[' + to_csv_string(locations) + ']' + result[PREFIX + 'DataProducts.%s_InstrumentModel.filenames' % (io_type)] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.%s_InstrumentModel.skip' % (io_type)] = '[' + to_csv_string(skip) + ']' return result - def CreateSkyImage(self, otdb_id, storage_properties, project_name): + def CreateSkyImage(self, otdb_id, storage_properties, project_name, io_type): sbg_nr = 0 locations = [] filenames = [] skip = [] result = {} for _ in xrange(storage_properties['nr_of_img_files']): - locations.append(self.locationPath(project_name, otdb_id)) + locations.append(self.locationPath(project_name, otdb_id) + '/img') filenames.append("L%d_SBG%03d_sky.IM" % (otdb_id, sbg_nr)) skip.append("0") sbg_nr += 1 - result[PREFIX + 'DataProducts.Output_SkyImage.locations'] = '[' + to_csv_string(locations) + ']' - result[PREFIX + 'DataProducts.Output_SkyImage.filenames'] = '[' + to_csv_string(filenames) + ']' - result[PREFIX + 'DataProducts.Output_SkyImage.skip'] = '[' + to_csv_string(skip) + ']' + result[PREFIX + 'DataProducts.%s_SkyImage.locations' % (io_type)] = '[' + to_csv_string(locations) + ']' + result[PREFIX + 'DataProducts.%s_SkyImage.filenames' % (io_type)] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.%s_SkyImage.skip' % (io_type)] = '[' + to_csv_string(skip) + ']' return result - def CreatePulsarPipeline(self, otdb_id, storage_properties, project_name): + def CreatePulsarPipeline(self, otdb_id, storage_properties, project_name, io_type): p_nr = 0 locations = [] filenames = [] skip = [] result = {} for _ in range(storage_properties['nr_of_pulp_files']): - locations.append(self.locationPath(project_name, otdb_id)) + locations.append(self.locationPath(project_name, otdb_id) + '/pulp') filenames.append("L%d_P%03d_pulp.tgz" % (otdb_id, p_nr)) skip.append("0") p_nr += 1 - result[PREFIX + 'DataProducts.Output_Pulsar.locations'] = '[' + to_csv_string(locations) + ']' - result[PREFIX + 'DataProducts.Output_Pulsar.filenames'] = '[' + to_csv_string(filenames) + ']' - result[PREFIX + 'DataProducts.Output_Pulsar.skip'] = '[' + to_csv_string(skip) + ']' + result[PREFIX + 'DataProducts.%s_Pulsar.locations' % (io_type)] = '[' + to_csv_string(locations) + ']' + result[PREFIX + 'DataProducts.%s_Pulsar.filenames' % (io_type)] = '[' + to_csv_string(filenames) + ']' + result[PREFIX + 'DataProducts.%s_Pulsar.skip' % (io_type)] = '[' + to_csv_string(skip) + ']' return result - - def CreateStorageKeys(self, otdb_id, storage_properties, project_name): - logging.debug(otdb_id, storage_properties) - result = {} + def CreateStorageKeys(self, otdb_id, storage_properties, project_name, io_type): if 'nr_of_uv_files' in storage_properties: - result.update(self.CreateCorrelated(otdb_id, storage_properties, project_name)) + result.update(self.CreateCorrelated(otdb_id, storage_properties, project_name, io_type)) if 'nr_of_cs_files' in storage_properties: - result.update(self.CreateCoherentStokes(otdb_id, storage_properties, project_name)) + result.update(self.CreateCoherentStokes(otdb_id, storage_properties, project_name, io_type)) if 'nr_of_is_files' in storage_properties: - result.update(self.CreateIncoherentStokes(otdb_id, storage_properties, project_name)) + result.update(self.CreateIncoherentStokes(otdb_id, storage_properties, project_name, io_type)) if 'nr_of_im_files' in storage_properties: - result.update(self.CreateInstrumentModel(otdb_id, storage_properties, project_name)) + result.update(self.CreateInstrumentModel(otdb_id, storage_properties, project_name, io_type)) if 'nr_of_img_files' in storage_properties: - result.update(self.CreateSkyImage(otdb_id, storage_properties, project_name)) + result.update(self.CreateSkyImage(otdb_id, storage_properties, project_name, io_type)) if 'nr_of_pulp_files' in storage_properties: - result.update(self.CreatePulsarPipeline(otdb_id, storage_properties, project_name)) + result.update(self.CreatePulsarPipeline(otdb_id, storage_properties, project_name, io_type)) return result - def parseStorageProperties(self, storage_claim): - """input something like: - {u'username': u'anonymous', u'status': u'allocated', u'resource_name': u'cep4storage', u'user_id': -1, - u'resource_type_id': 5, u'task_id': 6030, u'status_id': 1, u'resource_id': 117, u'session_id': 1, - u'id': 118, u'claim_size': 15860763720, u'starttime': datetime.datetime(2016, 6, 4, 12, 38, 6), - u'resource_type_name': u'storage', u'endtime': datetime.datetime(2016, 7, 5, 12, 53, 6), - u'properties': [{u'type_name': u'nr_of_uv_files', u'id': 347, u'value': 488, u'type_id': 2}, - {u'type_name': u'uv_file_size', u'id': 348, u'value': 32500565, u'type_id': 10}, - {u'type_name': u'im_file_size', u'id': 349, u'value': 1000, u'type_id': 11}, - {u'type_name': u'nr_of_im_files', u'id': 350, u'value': 488, u'type_id': 3}]} - output something like: - {u'nr_of_im_files': 488, u'nr_of_uv_files': 488, u'im_file_size': 1000, u'uv_file_size': 32500565} - """ + def ProcessStorageInfo(self, otdb_id, storage_info, project_name): + logging.debug('processing the storage for %i with ' % (otdb_id) + str(storage_info)) result = {} - if 'saps' in storage_claim: - saps = [] - for s in storage_claim['saps']: - properties = {} - for p in s['properties']: - if p['io_type_name'] == 'output': - properties[p['type_name']] = p['value'] - if properties: - saps.append({'sap_nr' : s['sap_nr'], 'properties': properties}) - if saps: - result['saps'] = saps - if 'properties' in storage_claim: - for p in storage_claim['properties']: - if p['io_type_name'] == 'output': - result[p['type_name']] = p['value'] - logging.info(result) + if 'input_files' in storage_info: + result.update(self.CreateStorageKeys(otdb_id, storage_info['input_files'], project_name, "Input")) + if 'output_files' in storage_info: + result.update(self.CreateStorageKeys(otdb_id, storage_info['output_files'], project_name, "Output")) return result def CreateParset(self, otdb_id, ra_info, project_name): - logger.info('CreateParset: start=%s, end=%s' % (ra_info['starttime'], ra_info['endtime'])) + logger.info('CreateParset for %s with start=%s, end=%s' % (otdb_id, ra_info['starttime'], ra_info['endtime'])) parset = {} #parset[PREFIX+'momID'] = str(mom_id) @@ -236,12 +214,15 @@ class RAtoOTDBTranslator(): parset[PREFIX+'stopTime'] = ra_info['endtime'].strftime('%Y-%m-%d %H:%M:%S') if 'storage' in ra_info: - logging.info(ra_info['storage']) - parset.update(self.CreateStorageKeys(otdb_id, self.parseStorageProperties(ra_info['storage']), project_name)) + logging.info("Adding storage claims to parset: " + str(ra_info['storage'])) + parset.update(self.ProcessStorageInfo(otdb_id, ra_info['storage'], project_name)) if 'stations' in ra_info: + logging.info("Adding stations to parset: " + str(ra_info["stations"])) parset[PREFIX+'VirtualInstrument.stationList'] = ra_info["stations"] -# parset[PREFIX+'ObservationControl.OnlineControl.inspectionHost'] = 'head01.cep4.control.lofar' -# parset[PREFIX+'ObservationControl.OnlineControl.inspectionProgram'] = 'inspection-plots-observation.sh' + if ra_info['type'] == 'observation': + logging.info("Adding inspection plot commands to parset") + parset[PREFIX+'ObservationControl.OnlineControl.inspectionHost'] = 'head01.cep4.control.lofar' + parset[PREFIX+'ObservationControl.OnlineControl.inspectionProgram'] = 'inspection-plots-observation.sh' return parset diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql index 8c2ba9cfcaf..5aee075a79a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql @@ -7,7 +7,7 @@ INSERT INTO resource_allocation.task_status VALUES (200, 'prepared'), (300, 'app (1150, 'error'), (1200, 'obsolete'); -- This is the list from OTDB, we'll need to merge it with the list from MoM in the future, might use different indexes? INSERT INTO resource_allocation.task_type VALUES (0, 'observation'),(1, 'pipeline'); -- We'll need more types INSERT INTO resource_allocation.resource_claim_status VALUES (0, 'claimed'), (1, 'allocated'), (2, 'conflict'); -INSERT INTO resource_allocation.resource_claim_property_type VALUES (0, 'nr_of_is_files'),(1, 'nr_of_cs_files'),(2, 'nr_of_uv_files'),(3, 'nr_of_im_files'),(4, 'nr_of_img_files'),(5, 'nr_of_pulp_files'),(6, 'nr_of_cs_stokes'),(7, 'nr_of_is_stokes'),(8, 'is_file_size'),(9, 'cs_file_size'),(10, 'uv_file_size'),(11, 'im_file_size'),(12, 'img_file_size'),(13, 'nr_of_pulp_files'),(14, 'nr_of_tabs'); +INSERT INTO resource_allocation.resource_claim_property_type VALUES (0, 'nr_of_is_files'),(1, 'nr_of_cs_files'),(2, 'nr_of_uv_files'),(3, 'nr_of_im_files'),(4, 'nr_of_img_files'),(5, 'nr_of_pulp_files'),(6, 'nr_of_cs_stokes'),(7, 'nr_of_is_stokes'),(8, 'is_file_size'),(9, 'cs_file_size'),(10, 'uv_file_size'),(11, 'im_file_size'),(12, 'img_file_size'),(13, 'nr_of_pulp_files'),(14, 'nr_of_tabs'),(15, 'start_sb_nr'); INSERT INTO resource_allocation.resource_claim_property_io_type VALUES (0, 'output'),(1, 'input'); INSERT INTO resource_allocation.config VALUES (0, 'max_fill_percentage_cep4', '85.00'), (1, 'claim_timeout', '172800'), (2, 'min_inter_task_delay', '60'); -- Just some values 172800 is two days in seconds INSERT INTO resource_allocation.conflict_reason diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py index c840d5752c4..6701cae2344 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py @@ -98,6 +98,8 @@ class BaseResourceEstimator(object): if sap_data_type in data_properties: # We found this SAP's nr_of_<data_type>_files output_files[data_type][sap_data_type] = sap_data_value # We only count the nr_of_files from this SAP output_files['saps'].append({'sap_nr': sap_nr, 'properties': {sap_data_type:sap_data_value}}) + if sap_data_type == 'start_sb_nr': + output_files[data_type]['start_sb_nr'] = sap_data_value return output_files diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py index dcbbc995506..5d7cb3a07ee 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py @@ -92,12 +92,18 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): output_file_size = 0.0 new_size = input_file_size / float(reduction_factor) output_file_size = new_size + new_size / 64.0 * (1.0 + reduction_factor) + new_size / 2.0 - result['storage']['output_files']['uv'] = {'nr_of_uv_files': input_files['uv']['nr_of_uv_files'], 'uv_file_size': int(output_file_size), 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_Correlated.identifications')} + result['storage']['output_files']['uv'] = {'nr_of_uv_files': input_files['uv']['nr_of_uv_files'], + 'uv_file_size': int(output_file_size), + 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_Correlated.identifications'), + 'start_sb_nr': input_files['uv']['start_sb_nr']} logger.info("correlated_uv: {} files {} bytes each".format(result['storage']['output_files']['uv']['nr_of_uv_files'], result['storage']['output_files']['uv']['uv_file_size'])) if parset.getBool(DATAPRODUCTS + 'Output_InstrumentModel.enabled'): logger.info("calculate instrument-model data size") - result['storage']['output_files']['im'] = {'nr_of_im_files': input_files['uv']['nr_of_uv_files'], 'im_file_size': 1000, 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_InstrumentModel.identifications')} # 1 kB was hardcoded in the Scheduler + result['storage']['output_files']['im'] = {'nr_of_im_files': input_files['uv']['nr_of_uv_files'], + 'im_file_size': 1000, + 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_InstrumentModel.identifications'), + 'start_sb_nr': input_files['uv']['start_sb_nr']} # 1 kB was hardcoded in the Scheduler logger.info("correlated_im: {} files {} bytes each".format(result['storage']['output_files']['im']['nr_of_im_files'], result['storage']['output_files']['im']['im_file_size'])) # count total data size diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py index 7aa75217988..6014e46e9fc 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py @@ -90,7 +90,10 @@ class LongBaselinePipelineResourceEstimator(BaseResourceEstimator): logger.debug("calculate correlated data size") result['storage']['output_files'] = {} nr_output_files = nr_input_files / (subbands_per_subbandgroup * subbandgroups_per_ms) - result['storage']['output_files']['uv'] = {'nr_of_uv_files': nr_output_files, 'uv_file_size': 1000, 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_Correlated.identifications')} # 1 kB was hardcoded in the Scheduler + result['storage']['output_files']['uv'] = {'nr_of_uv_files': nr_output_files, + 'uv_file_size': 1000, + 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_Correlated.identifications'), + 'start_sb_nr': input_files['uv']['start_sb_nr']} # 1 kB was hardcoded in the Scheduler logger.debug("correlated_uv: {} files {} bytes each".format(result['storage']['output_files']['uv']['nr_of_uv_files'], result['storage']['output_files']['uv']['uv_file_size'])) # count total data size diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py index 0f7359d46d2..f94857845ae 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py @@ -127,8 +127,8 @@ class ObservationResourceEstimator(BaseResourceEstimator): for sap_nr in xrange(parset.getInt('Observation.nrBeams')): subbandList = parset.getStringVector('Observation.Beam[%d].subbandList' % sap_nr) nr_files = len(subbandList) + sap_files[sap_nr] = {'nr_of_uv_files': nr_files, 'start_sb_nr': total_files} total_files += nr_files - sap_files[sap_nr] = {'nr_of_uv_files': nr_files} file_size = int((data_size + n_sample_size + size_of_header) * integrated_seconds + size_of_overhead) output_files = {'nr_of_uv_files': total_files, 'uv_file_size': file_size, 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_Correlated.identifications')} -- GitLab From 0f8dc35c91c3021b47627a8fc393911ae9fbc12c Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Fri, 17 Jun 2016 15:29:11 +0000 Subject: [PATCH 368/933] Task #9556: Added setting input files in parset --- .../RAtoOTDBTaskSpecificationPropagator/lib/propagator.py | 4 ++-- .../RAtoOTDBTaskSpecificationPropagator/lib/translator.py | 1 + .../resource_estimators/base_resource_estimator.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py index 66afebe4a20..8d6f8e6b6fa 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py @@ -143,7 +143,7 @@ class RAtoOTDBPropagator(): logger.error(e) self.doTaskConflict(otdb_id) - def parseStorageProperties(self, storage_claim): + def ParseStorageProperties(self, storage_claim): """input something like: {u'username':u'anonymous', u'status': u'allocated', u'resource_name': u'cep4storage', u'user_id': -1, u'resource_type_id': 5, u'task_id': 6349, @@ -201,7 +201,7 @@ class RAtoOTDBPropagator(): for claim in claims: logger.debug("Processing claim: %s" % claim) if claim['resource_type_name'] == 'storage': ## TODO we will need to check for different storage names/types in the future - info['storage'] = parseStorageProperties(claim) + info['storage'] = self.ParseStorageProperties(claim) info["starttime"] = task["starttime"] info["endtime"] = task["endtime"] info["status"] = task["status"] diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 989c85241ea..be12371ee9b 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -182,6 +182,7 @@ class RAtoOTDBTranslator(): return result def CreateStorageKeys(self, otdb_id, storage_properties, project_name, io_type): + result = {} if 'nr_of_uv_files' in storage_properties: result.update(self.CreateCorrelated(otdb_id, storage_properties, project_name, io_type)) if 'nr_of_cs_files' in storage_properties: diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py index 6701cae2344..adc887b7751 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py @@ -97,7 +97,7 @@ class BaseResourceEstimator(object): for sap_data_type, sap_data_value in sap['properties'].items(): if sap_data_type in data_properties: # We found this SAP's nr_of_<data_type>_files output_files[data_type][sap_data_type] = sap_data_value # We only count the nr_of_files from this SAP - output_files['saps'].append({'sap_nr': sap_nr, 'properties': {sap_data_type:sap_data_value}}) + output_files['saps'].append({'sap_nr': sap_nr, 'properties': sap['properties']}) if sap_data_type == 'start_sb_nr': output_files[data_type]['start_sb_nr'] = sap_data_value return output_files -- GitLab From ae2336a87b3a17e8e57d411d74ab6807a5e06a61 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Fri, 17 Jun 2016 18:15:35 +0000 Subject: [PATCH 369/933] Task #7519: LCS/Stream: add getReadBufferSize() in addition to small fix to setReadBufferSize() (SO_RCVBUF getter/setter). Change needed for APERTIF. --- LCS/Stream/include/Stream/SocketStream.h | 4 +++- LCS/Stream/src/SocketStream.cc | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/LCS/Stream/include/Stream/SocketStream.h b/LCS/Stream/include/Stream/SocketStream.h index 5af1ad02bd8..d26495c1337 100644 --- a/LCS/Stream/include/Stream/SocketStream.h +++ b/LCS/Stream/include/Stream/SocketStream.h @@ -52,7 +52,9 @@ class SocketStream : public FileDescriptorBasedStream FileDescriptorBasedStream *detach(); void reaccept(time_t deadline = 0); // only for TCP server socket - void setReadBufferSize(size_t size); + + size_t getReadBufferSize() const; + void setReadBufferSize(size_t size) const; const Protocol protocol; const Mode mode; diff --git a/LCS/Stream/src/SocketStream.cc b/LCS/Stream/src/SocketStream.cc index 24cc49dde4d..8afb6968e39 100644 --- a/LCS/Stream/src/SocketStream.cc +++ b/LCS/Stream/src/SocketStream.cc @@ -243,9 +243,24 @@ void SocketStream::accept(time_t deadline) } -void SocketStream::setReadBufferSize(size_t size) +size_t SocketStream::getReadBufferSize() const { - if (fd >= 0 && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof size) < 0) + size_t size; + socklen_t sizeSize = sizeof size; + if (::getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, &sizeSize) != 0) { + THROW_SYSCALL("getsockopt(SO_RCVBUF)"); + } + return size; +} + + +void SocketStream::setReadBufferSize(size_t size) const +{ +#ifdef __linux__ + // Linux default and max at: /proc/sys/net/core/rmem_{default,max} + size /= 2; // Linux doubles it; getsockopt() returns the doubled value. +#endif + if (::setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof size) < 0) THROW_SYSCALL("setsockopt(SO_RCVBUF)"); } -- GitLab From 5a2a1456e18e814a2aa7ee068a213bf24e504752 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 08:55:54 +0000 Subject: [PATCH 370/933] Task #8887: Add blas/lapack headers for casarest compile --- Docker/lofar-base/Dockerfile.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 72e31dfad6a..fd131c0e5e5 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -79,7 +79,7 @@ RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison # Casarest # ******************* # -RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libblas-dev liblapacke-dev && \ mkdir -p ${INSTALLDIR}/casarest/build && \ cd ${INSTALLDIR}/casarest && git clone https://github.com/casacore/casarest.git src && \ if [ "${CASAREST_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casarest/src && git checkout tags/v${CASAREST_VERSION}; fi && \ @@ -88,7 +88,7 @@ RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system- cd ${INSTALLDIR}/casarest/build && make install && \ bash -c "strip ${INSTALLDIR}/casarest/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casarest/{build,src}" && \ - apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ + apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libblas-dev liblapacke-dev && \ apt-get autoremove -y # -- GitLab From 11e3443b0cf8630c10a48f544681a397cb8f41cb Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 09:11:43 +0000 Subject: [PATCH 371/933] Task #8887: Optimise casacore/casarest builds --- Docker/lofar-base/Dockerfile.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index fd131c0e5e5..e8b4a812f7a 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -66,7 +66,7 @@ RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison if [ "${CASACORE_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casacore/src && git checkout tags/v${CASACORE_VERSION}; fi && \ cd ${INSTALLDIR}/casacore/data && wget --retry-connrefused ftp://ftp.astron.nl/outgoing/Measures/WSRT_Measures.ztar && \ cd ${INSTALLDIR}/casacore/data && tar xf WSRT_Measures.ztar && rm -f WSRT_Measures.ztar && \ - cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DENABLE_TABLELOCKING=OFF -DUSE_OPENMP=ON -DUSE_FFTW3=TRUE -DUSE_HDF5=ON ../src/ && \ + cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DENABLE_TABLELOCKING=OFF -DUSE_OPENMP=ON -DUSE_FFTW3=TRUE -DUSE_HDF5=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-fsigned-char -O2 -DNDEBUG -march=native" ../src/ && \ cd ${INSTALLDIR}/casacore/build && make -j ${J} && \ cd ${INSTALLDIR}/casacore/build && make install && \ bash -c "strip ${INSTALLDIR}/casacore/{lib,bin}/* || true" && \ @@ -83,7 +83,7 @@ RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system- mkdir -p ${INSTALLDIR}/casarest/build && \ cd ${INSTALLDIR}/casarest && git clone https://github.com/casacore/casarest.git src && \ if [ "${CASAREST_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casarest/src && git checkout tags/v${CASAREST_VERSION}; fi && \ - cd ${INSTALLDIR}/casarest/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casarest -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore ../src/ && \ + cd ${INSTALLDIR}/casarest/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casarest -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore -DCMAKE_BUILD_TYPE=Release ../src/ && \ cd ${INSTALLDIR}/casarest/build && make -j ${J} && \ cd ${INSTALLDIR}/casarest/build && make install && \ bash -c "strip ${INSTALLDIR}/casarest/{lib,bin}/* || true" && \ -- GitLab From c28abde66bf32e65b086938f42a9e4e32d3528de Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 10:10:49 +0000 Subject: [PATCH 372/933] Task #8887: Improved build_qpid script and upgraded to qpid 0.32 --- LCS/MessageBus/qpid/local/sbin/build_qpid | 28 +++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/LCS/MessageBus/qpid/local/sbin/build_qpid b/LCS/MessageBus/qpid/local/sbin/build_qpid index fc344947c88..86c3269fc65 100755 --- a/LCS/MessageBus/qpid/local/sbin/build_qpid +++ b/LCS/MessageBus/qpid/local/sbin/build_qpid @@ -1,16 +1,19 @@ -#!/bin/bash -QPIDINSTALLDIR=/opt/qpid +#!/bin/bash -eu + +# set default configuation +: ${QPIDINSTALLDIR:=/opt/qpid} +: ${PROTONVERSION:=0.8} +: ${QPIDVERSION:=0.32} # checkout sources for apache qpid and apache proton if [[ -x ~/sources/proton ]] then echo Skipping svn download because source directory exists else - cd ~ - mkdir -p sources + mkdir -p ~/sources cd ~/sources - svn co http://svn.apache.org/repos/asf/qpid/proton/tags/0.8/ proton - svn co http://svn.apache.org/repos/asf/qpid/tags/0.30/qpid/ qpid-0.30 + svn export http://svn.apache.org/repos/asf/qpid/proton/tags/$PROTONVERSION/ proton + svn export http://svn.apache.org/repos/asf/qpid/tags/$QPIDVERSION/qpid/ qpid-$QPIDVERSION fi # build and install proton libraries cd ~/sources/proton/ @@ -33,7 +36,7 @@ fi PROTONDIR=$QPIDINSTALLDIR/lib/cmake/Proton # build and install QPID C++ broker and libraries -cd ~/sources/qpid-0.30/cpp +cd ~/sources/qpid-$QPIDVERSION/cpp rm -Rf ./BUILD mkdir BUILD cd BUILD @@ -46,7 +49,8 @@ make -j4 make install # setup config with 256MB storage per queue max. -cat >> $QPIDINSTALLDIR/etc/qpid/qpidd.conf << \EOF +mkdir -p $QPIDINSTALLDIR/etc/qpid +cat > $QPIDINSTALLDIR/etc/qpid/qpidd.conf << \EOF # max 256MB per queue persistent buffering num-jfiles=32 @@ -55,19 +59,19 @@ jfile-size-pgs=128 EOF # build and install QPID python generic libs -cd ~/sources/qpid-0.30/python +cd ~/sources/qpid-$QPIDVERSION/python ./setup.py build ./setup.py install --home=$QPIDINSTALLDIR cd .. # build and install QPID QMF python libraries -cd ~/sources/qpid-0.30/extras/qmf +cd ~/sources/qpid-$QPIDVERSION/extras/qmf ./setup.py build ./setup.py install --home=$QPIDINSTALLDIR cd ../.. # build and install QPID tools -cd ~/sources/qpid-0.30/tools +cd ~/sources/qpid-$QPIDVERSION/tools ./setup.py build ./setup.py install --home=$QPIDINSTALLDIR cd .. @@ -89,7 +93,7 @@ then echo "Applying patch on $QPIDINSTALLDIR/lib/python/qpid/messaging/driver.py for Python 2.4.x" echo "--- $QPIDINSTALLDIR/lib/python/qpid/messaging/driver.py 2015-02-06 14:40:42.000000000 +0000" > /tmp/patch_qpid_driver_python2.4 echo "+++ $QPIDINSTALLDIR/lib/python/qpid/messaging/driver.py 2015-02-06 15:37:54.000000000 +0000" >> /tmp/patch_qpid_driver_python2.4 -cat >> /tmp/patch_qpid_driver_python2.4 << \EOF +cat > /tmp/patch_qpid_driver_python2.4 << \EOF @@ -1050,10 +1050,16 @@ declare = props.get("x-declare", {}) -- GitLab From 627c94ea0f134bc2ce9c7da3cb7c740c05f7bf3d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 11:05:32 +0000 Subject: [PATCH 373/933] Task #8887: Upgraded to qpid 0.34 --- LCS/MessageBus/qpid/local/sbin/build_qpid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LCS/MessageBus/qpid/local/sbin/build_qpid b/LCS/MessageBus/qpid/local/sbin/build_qpid index 86c3269fc65..1c3970fea3a 100755 --- a/LCS/MessageBus/qpid/local/sbin/build_qpid +++ b/LCS/MessageBus/qpid/local/sbin/build_qpid @@ -3,7 +3,7 @@ # set default configuation : ${QPIDINSTALLDIR:=/opt/qpid} : ${PROTONVERSION:=0.8} -: ${QPIDVERSION:=0.32} +: ${QPIDVERSION:=0.34} # checkout sources for apache qpid and apache proton if [[ -x ~/sources/proton ]] -- GitLab From c0dcd37c88e020a4d8382c5442e939cdd2f2461c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 12:01:19 +0000 Subject: [PATCH 374/933] Task #8887: Fixed checkout for qpid >= 0.34 --- LCS/MessageBus/qpid/local/sbin/build_qpid | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/LCS/MessageBus/qpid/local/sbin/build_qpid b/LCS/MessageBus/qpid/local/sbin/build_qpid index 1c3970fea3a..32dc413f12e 100755 --- a/LCS/MessageBus/qpid/local/sbin/build_qpid +++ b/LCS/MessageBus/qpid/local/sbin/build_qpid @@ -13,7 +13,12 @@ else mkdir -p ~/sources cd ~/sources svn export http://svn.apache.org/repos/asf/qpid/proton/tags/$PROTONVERSION/ proton - svn export http://svn.apache.org/repos/asf/qpid/tags/$QPIDVERSION/qpid/ qpid-$QPIDVERSION + if test ${QPIDVERSION} ">" "0.32"; then + QPIDTAG=qpid-cpp-$QPIDVERSION + else + QPIDTAG=$QPIDVERSION + fi + svn export http://svn.apache.org/repos/asf/qpid/tags/$QPIDTAG/qpid/ qpid-$QPIDVERSION fi # build and install proton libraries cd ~/sources/proton/ -- GitLab From 8aefe5199084b5952231eb61ba253e030ffc4172 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 12:23:59 +0000 Subject: [PATCH 375/933] Task #8887: Fix build dependencies for lofar-pipeline for Ubuntu 16.04 base --- Docker/lofar-pipeline/Dockerfile.tmpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 80569d2d9f0..b2f85e69964 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -18,7 +18,7 @@ RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cp # ******************* # -RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ +RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libblas-dev liblapacke-dev && \ mkdir -p ${INSTALLDIR}/aoflagger/build && \ bash -c "cd ${INSTALLDIR}/aoflagger && wget --retry-connrefused http://downloads.sourceforge.net/project/aoflagger/aoflagger-${AOFLAGGER_VERSION%%.?}.0/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ cd ${INSTALLDIR}/aoflagger && tar xf aoflagger-${AOFLAGGER_VERSION}.tar.bz2 && \ @@ -28,7 +28,7 @@ RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12 bash -c "strip ${INSTALLDIR}/aoflagger/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/{build,aoflagger-${AOFLAGGER_VERSION}}" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ - apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ + apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libblas-dev liblapacke-dev && \ apt-get -y autoremove # @@ -43,7 +43,7 @@ ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ LOFAR_BUILDVARIANT=gnu_optarch # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev libblas-dev liblapacke-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ @@ -56,6 +56,6 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "chmod a+rwx ${INSTALLDIR}/lofar/var/{log,run}" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ - apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ + apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libblas-dev liblapacke-dev && \ apt-get autoremove -y -- GitLab From 03dbd8cec3f51e388a04c74c9a6c0272503b0354 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 12:52:13 +0000 Subject: [PATCH 376/933] Task #8887: Pull numpy from pip to get the multiarray library under Ubuntu 16.04 --- Docker/lofar-base/Dockerfile.tmpl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index e8b4a812f7a..48151c3b872 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -39,7 +39,11 @@ ENV J=6 #RUN sed -i 's/archive.ubuntu.com/osmirror.rug.nl/' /etc/apt/sources.list RUN apt-get update && \ apt-get install -y python2.7 libpython2.7 && \ - apt-get install -y libblas3 liblapacke python-numpy libcfitsio-bin libwcs5 libfftw3-bin libhdf5-10 libboost-python${BOOST_VERSION}.0 && \ + apt-get install -y libblas3 liblapacke libcfitsio-bin libwcs5 libfftw3-bin libhdf5-10 libboost-python${BOOST_VERSION}.0 && \ + apt-get install -y python-pip && \ + pip install numpy && \ + apt-get purge -y python-pip && \ + apt-get autoremove && \ apt-get install -y nano # -- GitLab From 14de85150ddacc88523d533f9d434e12832b64a9 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 13:09:09 +0000 Subject: [PATCH 377/933] Task #8887: Added missing -y --- Docker/lofar-base/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 48151c3b872..d117010a845 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -43,7 +43,7 @@ RUN apt-get update && \ apt-get install -y python-pip && \ pip install numpy && \ apt-get purge -y python-pip && \ - apt-get autoremove && \ + apt-get autoremove -y && \ apt-get install -y nano # -- GitLab From 3a2a7e248eee835a4829b02fa339acc4c8c1ce92 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 15:04:15 +0000 Subject: [PATCH 378/933] Task #8887: Added optarch to GNUCXX11 build variant --- CMake/variants/GNUCXX11.cmake | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/CMake/variants/GNUCXX11.cmake b/CMake/variants/GNUCXX11.cmake index c2736c2e2df..427fd5e01d4 100644 --- a/CMake/variants/GNUCXX11.cmake +++ b/CMake/variants/GNUCXX11.cmake @@ -7,34 +7,41 @@ set(LOFAR_COMPILER_SUITES GNUCXX11) # Build variants -set(LOFAR_BUILD_VARIANTS DEBUG OPT OPT3) +set(LOFAR_BUILD_VARIANTS DEBUG OPT OPT3 OPTARCH) -# GNUCXX11 compiler suite +# GNU compiler suite set(GNUCXX11_COMPILERS GNUCXX11_C GNUCXX11_CXX GNUCXX11_Fortran GNUCXX11_ASM) -set(GNUCXX11_C /usr/bin/gcc) # GNUCXX11 C compiler -set(GNUCXX11_CXX /usr/bin/g++) # GNUCXX11 C++ compiler -set(GNUCXX11_Fortran /usr/bin/gfortran) # GNUCXX11 Fortran compiler -set(GNUCXX11_ASM /usr/bin/gcc) # GNUCXX11 assembler +set(GNUCXX11_C /usr/bin/gcc) # GNU C compiler +set(GNUCXX11_CXX /usr/bin/g++) # GNU C++ compiler +set(GNUCXX11_Fortran /usr/bin/gfortran) # GNU Fortran compiler +set(GNUCXX11_ASM /usr/bin/gcc) # GNU assembler set(GNUCXX11_C_FLAGS "-W -Wall -Wno-unknown-pragmas") set(GNUCXX11_C_FLAGS_DEBUG "-g") set(GNUCXX11_C_FLAGS_OPT "-g -O2") set(GNUCXX11_C_FLAGS_OPT3 "-g -O3") +set(GNUCXX11_C_FLAGS_OPTARCH "-g -O3 -march=native") set(GNUCXX11_CXX_FLAGS "-std=c++11 -W -Wall -Woverloaded-virtual -Wno-unknown-pragmas") set(GNUCXX11_CXX_FLAGS_DEBUG "-g") set(GNUCXX11_CXX_FLAGS_OPT "-g -O2") set(GNUCXX11_CXX_FLAGS_OPT3 "-g -O3") +set(GNUCXX11_CXX_FLAGS_OPTARCH "-g -O3 -march=native") set(GNUCXX11_EXE_LINKER_FLAGS) set(GNUCXX11_EXE_LINKER_FLAGS_DEBUG) set(GNUCXX11_EXE_LINKER_FLAGS_OPT) set(GNUCXX11_EXE_LINKER_FLAGS_OPT3) +set(GNUCXX11_EXE_LINKER_FLAGS_OPTARCH) set(GNUCXX11_SHARED_LINKER_FLAGS) set(GNUCXX11_SHARED_LINKER_FLAGS_DEBUG) set(GNUCXX11_SHARED_LINKER_FLAGS_OPT) set(GNUCXX11_SHARED_LINKER_FLAGS_OPT3) +set(GNUCXX11_SHARED_LINKER_FLAGS_OPTARCH3) set(GNUCXX11_COMPILE_DEFINITIONS) set(GNUCXX11_COMPILE_DEFINITIONS_DEBUG "-DLOFAR_DEBUG -DENABLE_DBGASSERT -DENABLE_TRACER") set(GNUCXX11_COMPILE_DEFINITIONS_OPT) set(GNUCXX11_COMPILE_DEFINITIONS_OPT3 "-DNDEBUG -DDISABLE_DEBUG_OUTPUT") +set(GNUCXX11_COMPILE_DEFINITIONS_OPTARCH + "-DNDEBUG -DDISABLE_DEBUG_OUTPUT") + -- GitLab From 7a6f65208aba87d40edd930f51d25623d857ec0a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 15:10:59 +0000 Subject: [PATCH 379/933] Task #8993: In C++11, cast from basic_istream to bool is explicit --- LCS/Common/src/ReadLine.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LCS/Common/src/ReadLine.cc b/LCS/Common/src/ReadLine.cc index 752f9325d25..75de0414c48 100644 --- a/LCS/Common/src/ReadLine.cc +++ b/LCS/Common/src/ReadLine.cc @@ -46,7 +46,7 @@ namespace LOFAR { if (!prompt.empty()) cerr << prompt; getline (cin, line); - return cin; + return (bool)cin; } #endif -- GitLab From 6d36926a8ee8c9ca2b2517c38be6cb296be638ac Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 15:15:09 +0000 Subject: [PATCH 380/933] Task #8993: C++11 fixes for AddressTranslator.cc --- LCS/Common/src/AddressTranslator.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/LCS/Common/src/AddressTranslator.cc b/LCS/Common/src/AddressTranslator.cc index 0c916d141f5..01b33449e4c 100644 --- a/LCS/Common/src/AddressTranslator.cc +++ b/LCS/Common/src/AddressTranslator.cc @@ -46,8 +46,8 @@ namespace LOFAR { // Map of symbol tables. // Use the load address of the shared object or executable as key. - typedef std::map< bfd_vma, boost::shared_ptr<SymbolTable> > SymbolTableMap; - + typedef boost::shared_ptr<SymbolTable> SymbolTablePtr; + typedef std::map<bfd_vma, SymbolTablePtr> SymbolTableMap; // The map of symbol tables is implemented as a Meyers singleton. // Use a lock to make access thread-safe. SymbolTableMap& theSymbolTableMap() @@ -88,8 +88,9 @@ namespace LOFAR // if the symbol table of #bfdFile is not yet present in the map. SymbolTableMap::iterator it = theSymbolTableMap().find(base_addr); if(it == theSymbolTableMap().end()) { - it = theSymbolTableMap().insert - (std::make_pair(base_addr, new SymbolTable(bfdFile))).first; + it = theSymbolTableMap().insert( + std::make_pair(base_addr, + SymbolTablePtr(new SymbolTable(bfdFile)))).first; } // Get the BFD-handle of the matching SymbolTable object. -- GitLab From c85e1aeebc61a2a5edd8fc4c607557bc9dae1ff9 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 15:20:38 +0000 Subject: [PATCH 381/933] Task #8993: Use -fext-numeric-literals to get tSaxPy to compile --- CMake/variants/GNUCXX11.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMake/variants/GNUCXX11.cmake b/CMake/variants/GNUCXX11.cmake index 427fd5e01d4..a87d417a256 100644 --- a/CMake/variants/GNUCXX11.cmake +++ b/CMake/variants/GNUCXX11.cmake @@ -21,7 +21,7 @@ set(GNUCXX11_C_FLAGS_DEBUG "-g") set(GNUCXX11_C_FLAGS_OPT "-g -O2") set(GNUCXX11_C_FLAGS_OPT3 "-g -O3") set(GNUCXX11_C_FLAGS_OPTARCH "-g -O3 -march=native") -set(GNUCXX11_CXX_FLAGS "-std=c++11 -W -Wall -Woverloaded-virtual -Wno-unknown-pragmas") +set(GNUCXX11_CXX_FLAGS "-std=c++11 -fext-numeric-literals -W -Wall -Woverloaded-virtual -Wno-unknown-pragmas") set(GNUCXX11_CXX_FLAGS_DEBUG "-g") set(GNUCXX11_CXX_FLAGS_OPT "-g -O2") set(GNUCXX11_CXX_FLAGS_OPT3 "-g -O3") -- GitLab From eccfca7db18c2436c128f798bd4932631fcd23c3 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 19:00:08 +0000 Subject: [PATCH 382/933] Task #8887: Unbreak 2.4 patch --- LCS/MessageBus/qpid/local/sbin/build_qpid | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/LCS/MessageBus/qpid/local/sbin/build_qpid b/LCS/MessageBus/qpid/local/sbin/build_qpid index 32dc413f12e..1ae7b21324b 100755 --- a/LCS/MessageBus/qpid/local/sbin/build_qpid +++ b/LCS/MessageBus/qpid/local/sbin/build_qpid @@ -56,7 +56,6 @@ make install # setup config with 256MB storage per queue max. mkdir -p $QPIDINSTALLDIR/etc/qpid cat > $QPIDINSTALLDIR/etc/qpid/qpidd.conf << \EOF - # max 256MB per queue persistent buffering num-jfiles=32 jfile-size-pgs=128 @@ -98,7 +97,7 @@ then echo "Applying patch on $QPIDINSTALLDIR/lib/python/qpid/messaging/driver.py for Python 2.4.x" echo "--- $QPIDINSTALLDIR/lib/python/qpid/messaging/driver.py 2015-02-06 14:40:42.000000000 +0000" > /tmp/patch_qpid_driver_python2.4 echo "+++ $QPIDINSTALLDIR/lib/python/qpid/messaging/driver.py 2015-02-06 15:37:54.000000000 +0000" >> /tmp/patch_qpid_driver_python2.4 -cat > /tmp/patch_qpid_driver_python2.4 << \EOF +cat >> /tmp/patch_qpid_driver_python2.4 << \EOF @@ -1050,10 +1050,16 @@ declare = props.get("x-declare", {}) -- GitLab From 1893cabadbca8ed6f5370c292548bee477c81b58 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 20:11:13 +0000 Subject: [PATCH 383/933] Task #8993: C++11 fix: Avoid use of real() and imag() as lvalues. --- CEP/DP3/AOFlagger/src/aoquality.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CEP/DP3/AOFlagger/src/aoquality.cpp b/CEP/DP3/AOFlagger/src/aoquality.cpp index 2e851b4b6e5..3fa1ca758de 100644 --- a/CEP/DP3/AOFlagger/src/aoquality.cpp +++ b/CEP/DP3/AOFlagger/src/aoquality.cpp @@ -202,9 +202,9 @@ void actionCollect(const std::string &filename, enum CollectingMode mode, Statis { case CollectDefault: if(antennaIsFlagged || timestepIndex < flaggedTimesteps) - statisticsCollection.Add(antenna1Index, antenna2Index, time, bandIndex, p, &samples[p]->real(), &samples[p]->imag(), isRFI[p], correlatorFlagsForBadAntenna, band.channels.size() - startChannel, 2, 1, 1); + statisticsCollection.Add(antenna1Index, antenna2Index, time, bandIndex, p, &reinterpret_cast<float*>(samples[p])[0], &reinterpret_cast<float*>(samples[p])[1], isRFI[p], correlatorFlagsForBadAntenna, band.channels.size() - startChannel, 2, 1, 1); else - statisticsCollection.Add(antenna1Index, antenna2Index, time, bandIndex, p, &samples[p]->real(), &samples[p]->imag(), isRFI[p], correlatorFlags, band.channels.size() - startChannel, 2, 1, 1); + statisticsCollection.Add(antenna1Index, antenna2Index, time, bandIndex, p, &reinterpret_cast<float*>(samples[p])[0], &reinterpret_cast<float*>(samples[p])[1], isRFI[p], correlatorFlags, band.channels.size() - startChannel, 2, 1, 1); break; case CollectHistograms: histogramCollection.Add(antenna1Index, antenna2Index, p, samples[p], isRFI[p], band.channels.size() - startChannel); -- GitLab From fec4a13e976ae4f9719ab62f0d3b74ed8cc5628f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 20:36:28 +0000 Subject: [PATCH 384/933] Task #8993: C++11 fix: real() and imag() cannot be used as lvalues. --- RTCP/Cobalt/CoInterface/src/cmpfloat.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/RTCP/Cobalt/CoInterface/src/cmpfloat.cc b/RTCP/Cobalt/CoInterface/src/cmpfloat.cc index e505a17de81..df0c43d01f5 100644 --- a/RTCP/Cobalt/CoInterface/src/cmpfloat.cc +++ b/RTCP/Cobalt/CoInterface/src/cmpfloat.cc @@ -188,8 +188,10 @@ bool compareValues(complex<T> v1, complex<T> v2, double epsilon, size_t pos, T imagFactor = v2.imag() / v1.imag(); // idem if (maxFactors == T(1.0)) { // first unequal val, so 1.0 must be as initialized (not a factor) - maxFactors.real() = minFactors.real() = realFactor; - maxFactors.imag() = minFactors.imag() = imagFactor; + maxFactors.real(realFactor); + minFactors.real(realFactor); + maxFactors.imag(imagFactor); + minFactors.imag(imagFactor); } else { if (realFactor > maxFactors.real()) { maxFactors.real(realFactor); -- GitLab From 752197d69d8ad4fba3961c573cb03735c6bcc2e4 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 20:51:18 +0000 Subject: [PATCH 385/933] Task #8887 #8993: Build lofar Docker images with C++11 and optimisations, use OpenBLAS instead of BLAS+LAPACK, do not build tests, pull casacore from master and casarest from jjdmol/casarest repo until C++11 fixes have been merged to main repo, drop superfluous -DLOG4CPLUS_ROOT --- Docker/lofar-base/Dockerfile.tmpl | 20 ++++++++++---------- Docker/lofar-outputproc/Dockerfile.tmpl | 8 ++++---- Docker/lofar-pipeline/Dockerfile.tmpl | 14 +++++++------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index d117010a845..e467570b156 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -18,8 +18,8 @@ ENV DEBIAN_FRONTEND=noninteractive \ # # versions # -ENV CASACORE_VERSION=2.1.0 \ - CASAREST_VERSION=1.4.1 \ +ENV CASACORE_VERSION=latest \ + CASAREST_VERSION=latest \ PYTHON_CASACORE_VERSION=2.1.2 \ BOOST_VERSION=1.58 @@ -39,7 +39,7 @@ ENV J=6 #RUN sed -i 's/archive.ubuntu.com/osmirror.rug.nl/' /etc/apt/sources.list RUN apt-get update && \ apt-get install -y python2.7 libpython2.7 && \ - apt-get install -y libblas3 liblapacke libcfitsio-bin libwcs5 libfftw3-bin libhdf5-10 libboost-python${BOOST_VERSION}.0 && \ + apt-get install -y libopenblas-base libcfitsio-bin libwcs5 libfftw3-bin libhdf5-10 libboost-python${BOOST_VERSION}.0 && \ apt-get install -y python-pip && \ pip install numpy && \ apt-get purge -y python-pip && \ @@ -63,19 +63,19 @@ RUN mkdir -p ${INSTALLDIR} # Casacore # ******************* # -RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison libopenblas-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/casacore/build && \ mkdir -p ${INSTALLDIR}/casacore/data && \ cd ${INSTALLDIR}/casacore && git clone https://github.com/casacore/casacore.git src && \ if [ "${CASACORE_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casacore/src && git checkout tags/v${CASACORE_VERSION}; fi && \ cd ${INSTALLDIR}/casacore/data && wget --retry-connrefused ftp://ftp.astron.nl/outgoing/Measures/WSRT_Measures.ztar && \ cd ${INSTALLDIR}/casacore/data && tar xf WSRT_Measures.ztar && rm -f WSRT_Measures.ztar && \ - cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DENABLE_TABLELOCKING=OFF -DUSE_OPENMP=ON -DUSE_FFTW3=TRUE -DUSE_HDF5=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-fsigned-char -O2 -DNDEBUG -march=native" ../src/ && \ + cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DENABLE_TABLELOCKING=OFF -DUSE_OPENMP=ON -DUSE_FFTW3=TRUE -DUSE_HDF5=ON -DCXX11=YES -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-fsigned-char -O2 -DNDEBUG -march=native" ../src/ && \ cd ${INSTALLDIR}/casacore/build && make -j ${J} && \ cd ${INSTALLDIR}/casacore/build && make install && \ bash -c "strip ${INSTALLDIR}/casacore/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casacore/{build,src}" && \ - apt-get purge -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ + apt-get purge -y wget git cmake g++ gfortran flex bison libopenblas-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ apt-get autoremove -y # @@ -83,16 +83,16 @@ RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison # Casarest # ******************* # -RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libblas-dev liblapacke-dev && \ +RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ mkdir -p ${INSTALLDIR}/casarest/build && \ - cd ${INSTALLDIR}/casarest && git clone https://github.com/casacore/casarest.git src && \ + cd ${INSTALLDIR}/casarest && git clone https://github.com/jjdmol/casarest.git src && \ if [ "${CASAREST_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casarest/src && git checkout tags/v${CASAREST_VERSION}; fi && \ - cd ${INSTALLDIR}/casarest/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casarest -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore -DCMAKE_BUILD_TYPE=Release ../src/ && \ + cd ${INSTALLDIR}/casarest/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casarest -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-std=c++11 -O2 -march=native -DNDEBUG" ../src/ && \ cd ${INSTALLDIR}/casarest/build && make -j ${J} && \ cd ${INSTALLDIR}/casarest/build && make install && \ bash -c "strip ${INSTALLDIR}/casarest/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casarest/{build,src}" && \ - apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libblas-dev liblapacke-dev && \ + apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ apt-get autoremove -y # diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index 7f05fa2a5b0..81f7e0f3d57 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -15,15 +15,15 @@ RUN apt-get update && apt-get install -y liblog4cplus-1.1-9 libxml2 libboost-thr # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ LOFAR_REVISION=${LOFAR_REVISION} \ - LOFAR_BUILDVARIANT=gnu_optarch + LOFAR_BUILDVARIANT=gnucxx11_optarch # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libopenblas-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} libcfitsio3-dev wcslib-dev && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Online_OutputProc -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DDAL_ROOT_DIR=${INSTALLDIR}/DAL -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Online_OutputProc -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DDAL_ROOT_DIR=${INSTALLDIR}/DAL -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ @@ -32,6 +32,6 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ setcap cap_sys_nice,cap_ipc_lock=ep ${INSTALLDIR}/lofar/bin/outputProc && \ - apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev && \ + apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ apt-get autoremove -y diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index b2f85e69964..59b5f3ecaf2 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -18,17 +18,17 @@ RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cp # ******************* # -RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libblas-dev liblapacke-dev && \ +RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libopenblas-dev && \ mkdir -p ${INSTALLDIR}/aoflagger/build && \ bash -c "cd ${INSTALLDIR}/aoflagger && wget --retry-connrefused http://downloads.sourceforge.net/project/aoflagger/aoflagger-${AOFLAGGER_VERSION%%.?}.0/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ cd ${INSTALLDIR}/aoflagger && tar xf aoflagger-${AOFLAGGER_VERSION}.tar.bz2 && \ - cd ${INSTALLDIR}/aoflagger/build && cmake -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/aoflagger ../aoflagger-${AOFLAGGER_VERSION} && \ + cd ${INSTALLDIR}/aoflagger/build && cmake -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_FLAGS="--std=c++11 -O2 -march=native -DNDEBUG" -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/aoflagger ../aoflagger-${AOFLAGGER_VERSION} && \ cd ${INSTALLDIR}/aoflagger/build && make -j ${J} && \ cd ${INSTALLDIR}/aoflagger/build && make install && \ bash -c "strip ${INSTALLDIR}/aoflagger/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/{build,aoflagger-${AOFLAGGER_VERSION}}" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ - apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libblas-dev liblapacke-dev && \ + apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libopenblas-dev && \ apt-get -y autoremove # @@ -40,15 +40,15 @@ RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12 # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ LOFAR_REVISION=${LOFAR_REVISION} \ - LOFAR_BUILDVARIANT=gnu_optarch + LOFAR_BUILDVARIANT=gnucxx11_optarch # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev libblas-dev liblapacke-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Offline -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASAREST_ROOT_DIR=${INSTALLDIR}/casarest/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DAOFLAGGER_ROOT_DIR=${INSTALLDIR}/aoflagger/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Offline -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASAREST_ROOT_DIR=${INSTALLDIR}/casarest/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DAOFLAGGER_ROOT_DIR=${INSTALLDIR}/aoflagger/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ @@ -56,6 +56,6 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "chmod a+rwx ${INSTALLDIR}/lofar/var/{log,run}" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ - apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libblas-dev liblapacke-dev && \ + apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ apt-get autoremove -y -- GitLab From 76fd5b9f66ae38ceca4e5bce867f11dc3e2cf6d6 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 18 Jun 2016 20:57:43 +0000 Subject: [PATCH 386/933] Task #8887: Pull casarest from native repo again --- Docker/lofar-base/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index e467570b156..f8bf3056d96 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -85,7 +85,7 @@ RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison # RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ mkdir -p ${INSTALLDIR}/casarest/build && \ - cd ${INSTALLDIR}/casarest && git clone https://github.com/jjdmol/casarest.git src && \ + cd ${INSTALLDIR}/casarest && git clone https://github.com/casacore/casarest.git src && \ if [ "${CASAREST_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casarest/src && git checkout tags/v${CASAREST_VERSION}; fi && \ cd ${INSTALLDIR}/casarest/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casarest -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-std=c++11 -O2 -march=native -DNDEBUG" ../src/ && \ cd ${INSTALLDIR}/casarest/build && make -j ${J} && \ -- GitLab From 1651097e535007afc05a11ce54f3f5327443d071 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sun, 19 Jun 2016 11:19:42 +0000 Subject: [PATCH 387/933] Task #8887: Fixed typo in method name --- MAC/Services/src/PipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index e250756b94e..d4074793bb7 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -442,7 +442,7 @@ class PipelineControl(OTDBBusListener): return logger.info("***** STOP Otdb ID %s *****", otdbId) - self._stop(otdbId) + self._stopPipeline(otdbId) """ More statusses we want to abort on. -- GitLab From 468683c816ec049918f9c65d4aeef1417664b7bc Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sun, 19 Jun 2016 15:05:43 +0000 Subject: [PATCH 388/933] Task #8887: Put NDPPP parset in scratch dir instead of /tmp --- CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 37 ++------------------- CEP/Pipeline/recipes/sip/master/dppp.py | 1 + CEP/Pipeline/recipes/sip/nodes/dppp.py | 7 ++-- 3 files changed, 7 insertions(+), 38 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh index adb53b90b43..89d5e265711 100755 --- a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -25,9 +25,6 @@ PARSET= # Location of pipeline-framework configuration file PIPELINE_CONFIG=$LOFARROOT/share/pipeline/pipeline.cfg -# Queue on which to post status changes -SETSTATUS_BUS=lofar.otdb.command - # ======= Parse command-line parameters function usage() { @@ -36,11 +33,10 @@ function usage() { echo " -o OBSID Task identifier" echo " -c pipeline.cfg Override pipeline configuration file (default: $PIPELINE_CONFIG)" echo " -p pipeline.parset Provide parset (default: request through QPID)" - echo " -B busname Bus name to post status changes on (default: $SETSTATUS_BUS)" exit 1 } -while getopts "ho:c:p:B:" opt; do +while getopts "ho:c:p:" opt; do case $opt in h) usage ;; @@ -50,8 +46,6 @@ while getopts "ho:c:p:B:" opt; do ;; p) PARSET="$OPTARG" ;; - B) SETSTATUS_BUS="$OPTARG" - ;; \?) error "Invalid option: -$OPTARG" ;; :) error "Option requires an argument: -$OPTARG" @@ -62,9 +56,6 @@ done # ======= Init -# Mark as started -setOTDBTreeStatus -o $OBSID -s active -B $SETSTATUS_BUS || true - if [ -z "$PARSET" ]; then # Fetch parset PARSET=${LOFARROOT}/var/run/Observation${OBSID}.parset @@ -87,28 +78,4 @@ echo "**** $(date) ****" echo "Executing: ${PROGRAM_NAME} ${OPTIONS} ${PARSET}" # Run and propagate SIGTERM, SIGINT -trap 'kill -TERM $PID' TERM INT -${PROGRAM_NAME} ${OPTIONS} ${PARSET} & -PID=$! -wait $PID # This will return >128 if this shell is killed -trap - TERM INT -wait $PID # This will return the actual exit status of our program - -RESULT=$? - -# ======= Fini - -# Process the result -setOTDBTreeStatus -o $OBSID -s completing -B $SETSTATUS_BUS || true - -if [ $RESULT -eq 0 ]; then - # Wait for feedback to propagate - sleep 60 - - # Mark as succesful - setOTDBTreeStatus -o $OBSID -s finished -B $SETSTATUS_BUS || true -fi - -# Propagate result to caller -exit $RESULT - +exec ${PROGRAM_NAME} ${OPTIONS} ${PARSET} diff --git a/CEP/Pipeline/recipes/sip/master/dppp.py b/CEP/Pipeline/recipes/sip/master/dppp.py index 5fa53690af9..8cd5cc2d369 100644 --- a/CEP/Pipeline/recipes/sip/master/dppp.py +++ b/CEP/Pipeline/recipes/sip/master/dppp.py @@ -229,6 +229,7 @@ class dppp(BaseRecipe, RemoteCommandRecipeMixIn): sdb.file, self.inputs['parset'], self.inputs['executable'], + os.path.join(self.inputs['working_directory'], self.inputs['job_name']), self.environment, self.inputs['demix_always'], self.inputs['demix_if_needed'], diff --git a/CEP/Pipeline/recipes/sip/nodes/dppp.py b/CEP/Pipeline/recipes/sip/nodes/dppp.py index f77761729c5..f0a6d28cae4 100644 --- a/CEP/Pipeline/recipes/sip/nodes/dppp.py +++ b/CEP/Pipeline/recipes/sip/nodes/dppp.py @@ -35,7 +35,7 @@ class dppp(LOFARnodeTCP): """ def run(self, infile, outfile, parmdb, sourcedb, - parsetfile, executable, environment, demix_always, demix_if_needed, + parsetfile, executable, working_directory, environment, demix_always, demix_if_needed, start_time, end_time, nthreads, clobber): """ This function contains all the needed functionality @@ -47,6 +47,7 @@ class dppp(LOFARnodeTCP): self.logger.debug("sourcedb = %s" % sourcedb) self.logger.debug("parsetfile = %s" % parsetfile) self.logger.debug("executable = %s" % executable) + self.logger.debug("working_dir = %s" % working_directory) self.logger.debug("environment = %s" % environment) self.logger.debug("demix_always = %s" % demix_always) self.logger.debug("demix_if_needed = %s" % demix_if_needed) @@ -135,14 +136,14 @@ class dppp(LOFARnodeTCP): # ***************************************************************** # 4. Add ms names to the parset, start/end times if availabe, etc. # 5. Add demixing parameters to the parset - parsetfile, self._prepare_steps(**kwargs) #, unlink=False + parsetfile, self._prepare_steps(**kwargs), output_dir=working_directory #, unlink=False ) as temp_parset_filename: self.logger.debug("Created temporary parset file: %s" % temp_parset_filename ) try: - working_dir = tempfile.mkdtemp(suffix=".%s" % (os.path.basename(__file__),)) + working_dir = tempfile.mkdtemp(dir=working_directory,suffix=".%s" % (os.path.basename(__file__),)) # **************************************************************** # 6. Run ndppp cmd = [executable, temp_parset_filename, '1'] -- GitLab From 10fdef32a1b26944a50cd38552a4e605850614b4 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sun, 19 Jun 2016 15:12:13 +0000 Subject: [PATCH 389/933] Task #8887: Set status in PipelineControl to avoid buggy pipelines from not setting it --- MAC/Services/src/PipelineControl.py | 42 +++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index d4074793bb7..9cbfbdae205 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -323,9 +323,25 @@ class PipelineControl(OTDBBusListener): os.path.expandvars("--output=/data/log/runPipeline-%s.log" % (otdbId,)), ] + def setStatus_cmdline(status): + return ( + "ssh {myhostname} '" + "source {lofarroot}/lofarinit.sh && " + "setOTDBTreeStatus -o {obsid} -s {status} -B {status_bus} -f" + "'" + .format( + myhostname = getfqdn(), + lofarroot = os.environ.get("LOFARROOT", ""), + obsid = otdbId, + status = status, + status_bus = self.otdb_service_busname, + )) + # Schedule runPipeline.sh logger.info("Scheduling SLURM job for runPipeline.sh") slurm_job_id = self.slurm.submit(self._jobName(otdbId), + # notify that we're running + "{setStatus_active}\n" # pull docker image from repository on all nodes "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull" " --no-kill" @@ -345,14 +361,24 @@ class PipelineControl(OTDBBusListener): " -e SLURM_JOB_ID=$SLURM_JOB_ID" " -v /data:/data" " {image}" - " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -B {status_bus}" + " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster}\n" + + # notify that we're tearing down + "{setStatus_completing}\n" + # wait for MoM to pick up feedback before we set finished status + "sleep 60\n" + # if we reached this point, the pipeline ran succesfully + "{setStatus_finished}\n" .format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, repository = parset.dockerRepository(), image = parset.dockerImage(), cluster = parset.processingCluster(), - status_bus = self.otdb_service_busname, + + setStatus_active = setStatus_cmdline("active"), + setStatus_completing = setStatus_cmdline("completing"), + setStatus_finished = setStatus_cmdline("finished"), ), sbatch_params=sbatch_params @@ -362,16 +388,10 @@ class PipelineControl(OTDBBusListener): # Schedule pipelineAborted.sh logger.info("Scheduling SLURM job for pipelineAborted.sh") slurm_cancel_job_id = self.slurm.submit("%s-abort-trigger" % self._jobName(otdbId), - "ssh {myhostname} '" - "source {lofarroot}/lofarinit.sh && " - "setOTDBTreeStatus -o {obsid} -s aborted -B {status_bus}" - "'" + "{setStatus_aborted}\n" .format( - myhostname = getfqdn(), - lofarroot = os.environ.get("LOFARROOT", ""), - obsid = otdbId, - status_bus = self.otdb_service_busname, - ), + setStatus_aborted = setStatus_cmdline("aborted"), + }, sbatch_params=[ "--partition=%s" % parset.processingPartition(), -- GitLab From 94f9ee6e8af0056d9b67d862ae8e3dd3432fa881 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sun, 19 Jun 2016 15:37:52 +0000 Subject: [PATCH 390/933] Task #8887: Fixed typo --- MAC/Services/src/PipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 9cbfbdae205..469fa665ea4 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -391,7 +391,7 @@ class PipelineControl(OTDBBusListener): "{setStatus_aborted}\n" .format( setStatus_aborted = setStatus_cmdline("aborted"), - }, + ), sbatch_params=[ "--partition=%s" % parset.processingPartition(), -- GitLab From 7b43f26619ca32a1e2e2b4c182201ef52be9d51a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sun, 19 Jun 2016 15:53:25 +0000 Subject: [PATCH 391/933] Task #8887: Do not delete NDPPP node parset for easier debugging. --- CEP/Pipeline/recipes/sip/nodes/dppp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/nodes/dppp.py b/CEP/Pipeline/recipes/sip/nodes/dppp.py index f0a6d28cae4..6ef03658284 100644 --- a/CEP/Pipeline/recipes/sip/nodes/dppp.py +++ b/CEP/Pipeline/recipes/sip/nodes/dppp.py @@ -136,7 +136,7 @@ class dppp(LOFARnodeTCP): # ***************************************************************** # 4. Add ms names to the parset, start/end times if availabe, etc. # 5. Add demixing parameters to the parset - parsetfile, self._prepare_steps(**kwargs), output_dir=working_directory #, unlink=False + parsetfile, self._prepare_steps(**kwargs), output_dir=working_directory, unlink=False ) as temp_parset_filename: self.logger.debug("Created temporary parset file: %s" % -- GitLab From bb281e5f2e6850b12af6d3135df20fec21a88247 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sun, 19 Jun 2016 20:34:48 +0000 Subject: [PATCH 392/933] Task #8887: Reverting back to Ubuntu 14.04, C++98, and fewer optimisations, to avoid AOFlagger crash in NDPPP --- Docker/lofar-base/Dockerfile.tmpl | 28 +++++++++++-------------- Docker/lofar-outputproc/Dockerfile.tmpl | 10 ++++----- Docker/lofar-pipeline/Dockerfile.tmpl | 16 +++++++------- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index f8bf3056d96..aef0133c364 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -1,7 +1,7 @@ # # base # -FROM ubuntu:16.04 +FROM ubuntu:14.04 # # common-environment @@ -18,10 +18,10 @@ ENV DEBIAN_FRONTEND=noninteractive \ # # versions # -ENV CASACORE_VERSION=latest \ - CASAREST_VERSION=latest \ - PYTHON_CASACORE_VERSION=2.1.2 \ - BOOST_VERSION=1.58 +ENV CASACORE_VERSION=2.1.0 \ + CASAREST_VERSION=1.4.1 \ + PYTHON_CASACORE_VERSION=2.0.1 \ + BOOST_VERSION=1.54 # # set-uid @@ -39,11 +39,7 @@ ENV J=6 #RUN sed -i 's/archive.ubuntu.com/osmirror.rug.nl/' /etc/apt/sources.list RUN apt-get update && \ apt-get install -y python2.7 libpython2.7 && \ - apt-get install -y libopenblas-base libcfitsio-bin libwcs5 libfftw3-bin libhdf5-10 libboost-python${BOOST_VERSION}.0 && \ - apt-get install -y python-pip && \ - pip install numpy && \ - apt-get purge -y python-pip && \ - apt-get autoremove -y && \ + apt-get install -y libblas3 liblapacke python-numpy libcfitsio3 libwcs4 libfftw3-bin libhdf5-7 libboost-python${BOOST_VERSION}.0 && \ apt-get install -y nano # @@ -63,19 +59,19 @@ RUN mkdir -p ${INSTALLDIR} # Casacore # ******************* # -RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison libopenblas-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/casacore/build && \ mkdir -p ${INSTALLDIR}/casacore/data && \ cd ${INSTALLDIR}/casacore && git clone https://github.com/casacore/casacore.git src && \ if [ "${CASACORE_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casacore/src && git checkout tags/v${CASACORE_VERSION}; fi && \ cd ${INSTALLDIR}/casacore/data && wget --retry-connrefused ftp://ftp.astron.nl/outgoing/Measures/WSRT_Measures.ztar && \ cd ${INSTALLDIR}/casacore/data && tar xf WSRT_Measures.ztar && rm -f WSRT_Measures.ztar && \ - cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DENABLE_TABLELOCKING=OFF -DUSE_OPENMP=ON -DUSE_FFTW3=TRUE -DUSE_HDF5=ON -DCXX11=YES -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-fsigned-char -O2 -DNDEBUG -march=native" ../src/ && \ + cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DUSE_OPENMP=True -DUSE_FFTW3=TRUE -DUSE_HDF5=ON ../src/ && \ cd ${INSTALLDIR}/casacore/build && make -j ${J} && \ cd ${INSTALLDIR}/casacore/build && make install && \ bash -c "strip ${INSTALLDIR}/casacore/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casacore/{build,src}" && \ - apt-get purge -y wget git cmake g++ gfortran flex bison libopenblas-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ + apt-get purge -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ apt-get autoremove -y # @@ -83,16 +79,16 @@ RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison # Casarest # ******************* # -RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ +RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/casarest/build && \ cd ${INSTALLDIR}/casarest && git clone https://github.com/casacore/casarest.git src && \ if [ "${CASAREST_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casarest/src && git checkout tags/v${CASAREST_VERSION}; fi && \ - cd ${INSTALLDIR}/casarest/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casarest -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-std=c++11 -O2 -march=native -DNDEBUG" ../src/ && \ + cd ${INSTALLDIR}/casarest/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casarest -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore ../src/ && \ cd ${INSTALLDIR}/casarest/build && make -j ${J} && \ cd ${INSTALLDIR}/casarest/build && make install && \ bash -c "strip ${INSTALLDIR}/casarest/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casarest/{build,src}" && \ - apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ + apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ apt-get autoremove -y # diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index 81f7e0f3d57..eb5ef0c866b 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -10,20 +10,20 @@ FROM lofar-base:${LOFAR_TAG} # # Run-time dependencies -RUN apt-get update && apt-get install -y liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libboost-regex${BOOST_VERSION}.0 +RUN apt-get update && apt-get install -y liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libboost-regex${BOOST_VERSION}.0 # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ LOFAR_REVISION=${LOFAR_REVISION} \ - LOFAR_BUILDVARIANT=gnucxx11_optarch + LOFAR_BUILDVARIANT=gnu_optarch # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libopenblas-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} libcfitsio3-dev wcslib-dev && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Online_OutputProc -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DDAL_ROOT_DIR=${INSTALLDIR}/DAL -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Online_OutputProc -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DDAL_ROOT_DIR=${INSTALLDIR}/DAL -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ @@ -32,6 +32,6 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ setcap cap_sys_nice,cap_ipc_lock=ep ${INSTALLDIR}/lofar/bin/outputProc && \ - apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ + apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev && \ apt-get autoremove -y diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 59b5f3ecaf2..eae03529529 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -6,7 +6,7 @@ FROM lofar-base:${LOFAR_TAG} ENV AOFLAGGER_VERSION=2.7.1 # Run-time dependencies -RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ +RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ apt-get -y install python-pip python-dev && \ pip install pyfits pywcs python-monetdb && \ apt-get -y purge python-pip python-dev && \ @@ -18,17 +18,17 @@ RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cp # ******************* # -RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libopenblas-dev && \ +RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ mkdir -p ${INSTALLDIR}/aoflagger/build && \ bash -c "cd ${INSTALLDIR}/aoflagger && wget --retry-connrefused http://downloads.sourceforge.net/project/aoflagger/aoflagger-${AOFLAGGER_VERSION%%.?}.0/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ cd ${INSTALLDIR}/aoflagger && tar xf aoflagger-${AOFLAGGER_VERSION}.tar.bz2 && \ - cd ${INSTALLDIR}/aoflagger/build && cmake -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_FLAGS="--std=c++11 -O2 -march=native -DNDEBUG" -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/aoflagger ../aoflagger-${AOFLAGGER_VERSION} && \ + cd ${INSTALLDIR}/aoflagger/build && cmake -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/aoflagger ../aoflagger-${AOFLAGGER_VERSION} && \ cd ${INSTALLDIR}/aoflagger/build && make -j ${J} && \ cd ${INSTALLDIR}/aoflagger/build && make install && \ bash -c "strip ${INSTALLDIR}/aoflagger/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/{build,aoflagger-${AOFLAGGER_VERSION}}" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ - apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libopenblas-dev && \ + apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ apt-get -y autoremove # @@ -40,15 +40,15 @@ RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12 # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ LOFAR_REVISION=${LOFAR_REVISION} \ - LOFAR_BUILDVARIANT=gnucxx11_optarch + LOFAR_BUILDVARIANT=gnu_optarch # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Offline -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASAREST_ROOT_DIR=${INSTALLDIR}/casarest/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DAOFLAGGER_ROOT_DIR=${INSTALLDIR}/aoflagger/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Offline -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASAREST_ROOT_DIR=${INSTALLDIR}/casarest/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DAOFLAGGER_ROOT_DIR=${INSTALLDIR}/aoflagger/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ @@ -56,6 +56,6 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "chmod a+rwx ${INSTALLDIR}/lofar/var/{log,run}" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ - apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ + apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ apt-get autoremove -y -- GitLab From 35b26f51c482250afa5e468799f8dd81cbf12e02 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sun, 19 Jun 2016 21:07:20 +0000 Subject: [PATCH 393/933] Task #8887: Removed unsupported -f for setOTDBTreeStatus --- MAC/Services/src/PipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 469fa665ea4..40ab25b3442 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -327,7 +327,7 @@ class PipelineControl(OTDBBusListener): return ( "ssh {myhostname} '" "source {lofarroot}/lofarinit.sh && " - "setOTDBTreeStatus -o {obsid} -s {status} -B {status_bus} -f" + "setOTDBTreeStatus -o {obsid} -s {status} -B {status_bus}" "'" .format( myhostname = getfqdn(), -- GitLab From 8b7e081c40e256cd8ef9fb36d5e8fdff5c0ee243 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 20 Jun 2016 08:27:07 +0000 Subject: [PATCH 394/933] Task #8887: also send task inserted/updated/deleted notifications on task_predecessor changes --- .../radbpglistener.py | 95 +++++++++++++++---- .../sql/add_notifications.sql | 60 ++++++++++++ .../sql/create_add_notifications.sql.py | 3 + 3 files changed, 139 insertions(+), 19 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py index 9ab46514d73..03065ae1c72 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py @@ -35,23 +35,35 @@ from lofar.messaging import EventMessage, ToBus from lofar.sas.resourceassignment.database.config import DEFAULT_NOTIFICATION_BUSNAME, DEFAULT_NOTIFICATION_PREFIX from lofar.common import dbcredentials +from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as DEFAULT_RADB_BUSNAME +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as DEFAULT_RADB_SERVICENAME + logger = logging.getLogger(__name__) class RADBPGListener(PostgresListener): def __init__(self, - busname=DEFAULT_NOTIFICATION_BUSNAME, + notification_busname=DEFAULT_NOTIFICATION_BUSNAME, notification_prefix=DEFAULT_NOTIFICATION_PREFIX, + radb_busname=DEFAULT_RADB_BUSNAME, + radb_servicename=DEFAULT_RADB_SERVICENAME, dbcreds=None, broker=None): super(RADBPGListener, self).__init__(dbcreds.host, dbcreds.database, dbcreds.user, dbcreds.password) self.notification_prefix = notification_prefix - self.event_bus = ToBus(busname, broker=broker) + self.event_bus = ToBus(notification_busname, broker=broker) + + self.rarpc = RARPC(busname=radb_busname, servicename=radb_servicename, broker=broker) self.subscribe('task_update_with_task_view', self.onTaskUpdated) self.subscribe('task_insert_with_task_view', self.onTaskInserted) self.subscribe('task_delete', self.onTaskDeleted) + self.subscribe('task_predecessor_insert', self.onTaskPredecessorInserted) + self.subscribe('task_predecessor_update', self.onTaskPredecessorUpdated) + self.subscribe('task_predecessor_delete', self.onTaskPredecessorDeleted) + # when the specification starttime and endtime are updated, then that effects the task as well # so subscribe to specification_update, and use task_view as view_for_row self.subscribe('specification_update_with_task_view', self.onSpecificationUpdated) @@ -64,42 +76,76 @@ class RADBPGListener(PostgresListener): self.subscribe('resource_capacity_update', self.onResourceCapacityUpdated) def onTaskUpdated(self, payload = None): - self._sendNotification('TaskUpdated', payload, ['starttime', 'endtime']) + self._convertPayloadAndSendNotification('TaskUpdated', payload, ['starttime', 'endtime']) def onTaskInserted(self, payload = None): - self._sendNotification('TaskInserted', payload, ['starttime', 'endtime']) + self._convertPayloadAndSendNotification('TaskInserted', payload, ['starttime', 'endtime']) def onTaskDeleted(self, payload = None): - self._sendNotification('TaskDeleted', payload) + self._convertPayloadAndSendNotification('TaskDeleted', payload) + + def _onTaskPredecessorChanged(self, change_type, payload = None): + logger.info(payload) + try: + content = json.loads(payload) + except Exception as e: + logger.error('Could not parse payload: %s\n%s' % (payload, e)) + return + + try: + task_content = {} + + if change_type == 'Inserted' or change_type == 'Updated': + task_id = content['new']['task_id'] + task = self.rarpc.getTask(task_id) + task_content = {'new': task } + elif change_type == 'Deleted': + task_id = content['old']['task_id'] + task_content = {'old': {'id':task_id} } + + self._sendNotification('Task%s' % change_type, task_content) + except Exception as e: + logger.error('Could not parse payload: %s\n%s' % (payload, e)) + + def onTaskPredecessorUpdated(self, payload = None): + self._onTaskPredecessorChanged('Updated', payload) + + def onTaskPredecessorInserted(self, payload = None): + self._onTaskPredecessorChanged('Inserted', payload) + + def onTaskPredecessorDeleted(self, payload = None): + self._onTaskPredecessorChanged('Deleted', payload) def onSpecificationUpdated(self, payload = None): # when the specification starttime and endtime are updated, then that effects the task as well # so send a TaskUpdated notification - self._sendNotification('TaskUpdated', payload, ['starttime', 'endtime']) + self._convertPayloadAndSendNotification('TaskUpdated', payload, ['starttime', 'endtime']) def onResourceClaimUpdated(self, payload = None): - self._sendNotification('ResourceClaimUpdated', payload, ['starttime', 'endtime']) + self._convertPayloadAndSendNotification('ResourceClaimUpdated', payload, ['starttime', 'endtime']) def onResourceClaimInserted(self, payload = None): - self._sendNotification('ResourceClaimInserted', payload, ['starttime', 'endtime']) + self._convertPayloadAndSendNotification('ResourceClaimInserted', payload, ['starttime', 'endtime']) def onResourceClaimDeleted(self, payload = None): - self._sendNotification('ResourceClaimDeleted', payload) + self._convertPayloadAndSendNotification('ResourceClaimDeleted', payload) def onResourceAvailabilityUpdated(self, payload = None): - self._sendNotification('ResourceAvailabilityUpdated', payload) + self._convertPayloadAndSendNotification('ResourceAvailabilityUpdated', payload) def onResourceCapacityUpdated(self, payload = None): - self._sendNotification('ResourceCapacityUpdated', payload) + self._convertPayloadAndSendNotification('ResourceCapacityUpdated', payload) def __enter__(self): super(RADBPGListener, self).__enter__() self.event_bus.open() + self.rarpc.open() return self def __exit__(self, exc_type, exc_val, exc_tb): super(RADBPGListener, self).__exit__(exc_type, exc_val, exc_tb) self.event_bus.close() + self.rarpc.close() def _formatTimestampsAsIso(self, fields, contentDict): '''convert all requested fields in the contentDict to proper isoformat datetime strings. @@ -127,13 +173,20 @@ class RADBPGListener(PostgresListener): return contentDict except Exception as e: - logger.error('Could not parse payload: %s\n%s' % (contentDict, e)) + logger.error('Error while convering timestamp fields \'%s\'in %s\n%s' % (fields, contentDict, e)) - def _sendNotification(self, subject, payload, timestampFields = None): + def _convertPayloadAndSendNotification(self, subject, payload, timestampFields = None): try: content = json.loads(payload) + except Exception as e: + logger.error('Could not parse payload: %s\n%s' % (payload, e)) + content=None + + return self._sendNotification(subject, content, timestampFields) + def _sendNotification(self, subject, content, timestampFields = None): + try: if 'new' in content and content['new'] and 'old' in content and content['old']: # check if new and old are equal. # however, new and old can be based on different views, @@ -149,8 +202,7 @@ class RADBPGListener(PostgresListener): if timestampFields: content = self._formatTimestampsAsIso(timestampFields, content) except Exception as e: - logger.error('Could not parse payload: %s\n%s' % (payload, e)) - content=None + logger.error('Exception while anayzing content: %s\n%s' % (content, e)) try: msg = EventMessage(context=self.notification_prefix + subject, content=content) @@ -164,8 +216,11 @@ def main(): parser = OptionParser("%prog [options]", description='runs the radb postgres listener which listens to changes on some tables in the radb and publishes the changes as notifications on the bus.') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') - parser.add_option("-b", "--busname", dest="busname", type="string", default=DEFAULT_NOTIFICATION_BUSNAME, help="Name of the publication bus on the qpid broker, [default: %default]") - parser.add_option("-n", "--notification_prefix", dest="notification_prefix", type="string", default=DEFAULT_NOTIFICATION_PREFIX, help="The prefix for all notifications of this publisher, [default: %default]") + parser.add_option('--radb_busname', dest='radb_busname', type='string', default=DEFAULT_RADB_BUSNAME, help='Name of the bus exchange on the qpid broker on which the radbservice listens, default: %default') + parser.add_option('--radb_servicename', dest='radb_servicename', type='string', default=DEFAULT_RADB_SERVICENAME, help='Name of the radbservice, default: %default') + parser.add_option('--radb_notification_busname', dest='radb_notification_busname', type='string', default=DEFAULT_NOTIFICATION_BUSNAME, help='Name of the notification bus exchange on the qpid broker on which the radb notifications are published, default: %default') + parser.add_option("--radb_notification_prefix", dest="radb_notification_prefix", type="string", default=DEFAULT_NOTIFICATION_PREFIX, help="The prefix for all notifications of this publisher, [default: %default]") + parser.add_option_group(dbcredentials.options_group(parser)) parser.set_defaults(dbcredentials="RADB") (options, args) = parser.parse_args() @@ -177,8 +232,10 @@ def main(): logger.info("Using dbcreds: %s" % dbcreds.stringWithHiddenPassword()) - with RADBPGListener(busname=options.busname, - notification_prefix=options.notification_prefix, + with RADBPGListener(notification_busname=options.radb_notification_busname, + notification_prefix=options.radb_notification_prefix, + radb_busname=options.radb_busname, + radb_servicename=options.radb_servicename, dbcreds=dbcreds, broker=options.broker) as listener: listener.waitWhileListening() diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_notifications.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_notifications.sql index afc072544f1..05fec7a48a4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_notifications.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_notifications.sql @@ -72,6 +72,66 @@ FOR EACH ROW EXECUTE PROCEDURE resource_allocation.NOTIFY_task_DELETE(); +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_INSERT ON resource_allocation.task_predecessor CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_INSERT(); + + +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_INSERT() +RETURNS TRIGGER AS $$ +BEGIN +PERFORM pg_notify(CAST('task_predecessor_insert' AS text), +'{"old":' || 'null' || ',"new":' || row_to_json(NEW)::text || '}'); +RETURN NEW; +END; +$$ LANGUAGE plpgsql; + + +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_INSERT +AFTER INSERT ON resource_allocation.task_predecessor +FOR EACH ROW +EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_INSERT(); + + +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_UPDATE ON resource_allocation.task_predecessor CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_UPDATE(); + + +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_UPDATE() +RETURNS TRIGGER AS $$ +BEGIN +PERFORM pg_notify(CAST('task_predecessor_update' AS text), +'{"old":' || row_to_json(OLD)::text || ',"new":' || row_to_json(NEW)::text || '}'); +RETURN NEW; +END; +$$ LANGUAGE plpgsql; + + +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_UPDATE +AFTER UPDATE ON resource_allocation.task_predecessor +FOR EACH ROW +EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_UPDATE(); + + +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_DELETE ON resource_allocation.task_predecessor CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_DELETE(); + + +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_DELETE() +RETURNS TRIGGER AS $$ +BEGIN +PERFORM pg_notify(CAST('task_predecessor_delete' AS text), +'{"old":' || row_to_json(OLD)::text || ',"new":' || 'null' || '}'); +RETURN OLD; +END; +$$ LANGUAGE plpgsql; + + +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_DELETE +AFTER DELETE ON resource_allocation.task_predecessor +FOR EACH ROW +EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_DELETE(); + + DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_specification_UPDATE_with_task_view ON resource_allocation.specification CASCADE; DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_specification_UPDATE_with_task_view(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_add_notifications.sql.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_add_notifications.sql.py index 3c5234398c9..750b26c4871 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_add_notifications.sql.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_add_notifications.sql.py @@ -43,6 +43,9 @@ if __name__ == '__main__': f.writelines(makePostgresNotificationQueries('resource_allocation', 'task', 'INSERT', view_for_row='task_view')) f.writelines(makePostgresNotificationQueries('resource_allocation', 'task', 'UPDATE', view_for_row='task_view')) f.writelines(makePostgresNotificationQueries('resource_allocation', 'task', 'DELETE')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'INSERT')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'UPDATE')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'DELETE')) f.writelines(makePostgresNotificationQueries('resource_allocation', 'specification', 'UPDATE', view_for_row='task_view', view_selection_id='specification_id')) f.writelines(makePostgresNotificationQueries('resource_allocation', 'resource_claim', 'INSERT', view_for_row='resource_claim_view')) f.writelines(makePostgresNotificationQueries('resource_allocation', 'resource_claim', 'UPDATE', view_for_row='resource_claim_view')) -- GitLab From 4e5d69b10446738e39a05d94f8b520b86693ee34 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 20 Jun 2016 10:02:41 +0000 Subject: [PATCH 395/933] Task #8887: getTask by task_status and/or task_type --- .../ResourceAssignmentDatabase/radb.py | 12 +++++++++++- .../ResourceAssignmentService/rpc.py | 4 ++-- .../ResourceAssignmentService/service.py | 4 +++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 8c55a6cb86e..7711d288bde 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -155,7 +155,7 @@ class RADatabase: raise KeyError('No such status: %s. Valid values are: %s' % (status_name, ', '.join(self.getResourceClaimStatusNames()))) - def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None): + def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None): query = '''SELECT * from resource_allocation.task_view''' conditions = [] @@ -177,6 +177,16 @@ class RADatabase: conditions.append('id in %s') qargs.append(tuple(task_ids)) + task_status, task_type = self._convertTaskTypeAndStatusToIds(task_status, task_type) + + if task_status is not None: + conditions.append('status_id = %s') + qargs.append(task_status) + + if task_type is not None: + conditions.append('type_id = %s') + qargs.append(task_type) + if conditions: query += ' WHERE ' + ' AND '.join(conditions) diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py index 0a3edb4b68f..0952869f376 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py @@ -178,8 +178,8 @@ class RARPC(RPCWrapper): otdb_id=otdb_id, status=status) - def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None): - tasks = self.rpc('GetTasks', lower_bound=lower_bound, upper_bound=upper_bound, task_ids=task_ids) + def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None): + tasks = self.rpc('GetTasks', lower_bound=lower_bound, upper_bound=upper_bound, task_ids=task_ids, task_status=task_status, task_type=task_type) for task in tasks: task['starttime'] = task['starttime'].datetime() task['endtime'] = task['endtime'].datetime() diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/service.py b/SAS/ResourceAssignment/ResourceAssignmentService/service.py index 4b4128f5b7b..7409475f2d4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/service.py @@ -221,7 +221,9 @@ class RADBHandler(MessageHandlerInterface): logger.info('GetTasks: %s' % dict({k:v for k,v in kwargs.items() if v != None})) return self.radb.getTasks(lower_bound=kwargs.get('lower_bound').datetime() if kwargs.get('lower_bound') else None, upper_bound=kwargs.get('upper_bound').datetime() if kwargs.get('upper_bound') else None, - task_ids=kwargs.get('task_ids')) + task_ids=kwargs.get('task_ids'), + task_status=kwargs.get('task_status'), + task_type=kwargs.get('task_type')) def _getTask(self, **kwargs): logger.info('GetTask: %s' % dict({k:v for k,v in kwargs.items() if v != None})) -- GitLab From bf860f66880e21a52ed6cf253b95d45afc0bf38a Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 20 Jun 2016 10:03:47 +0000 Subject: [PATCH 396/933] Task #8887: added ScheduleChecker, which automatically moved scheduled pipelines just ahead of now, and which extends the endtime of running pipelines to now --- .gitattributes | 1 + .../ResourceAssigner/lib/CMakeLists.txt | 1 + .../ResourceAssigner/lib/raservice.py | 6 +- .../ResourceAssigner/lib/schedulechecker.py | 102 ++++++++++++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py diff --git a/.gitattributes b/.gitattributes index 29c565268dd..1ae8efbc819 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5040,6 +5040,7 @@ SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py -text SAS/ResourceAssignment/ResourceAssigner/lib/config.py -text SAS/ResourceAssignment/ResourceAssigner/lib/rabuslistener.py -text SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py -text +SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py -text SAS/ResourceAssignment/ResourceAssigner/test/CMakeLists.txt -text SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py -text SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.run -text diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssigner/lib/CMakeLists.txt index 9311ccd8977..c8aac82b1cc 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/CMakeLists.txt @@ -5,6 +5,7 @@ python_install( raservice.py assignment.py rabuslistener.py + schedulechecker.py config.py DESTINATION lofar/sas/resourceassignment/resourceassigner) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py index 1ad6bd5994c..a8cae557c28 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py @@ -39,6 +39,7 @@ import lofar.sas.resourceassignment.resourceassignmentservice.rpc as rarpc from lofar.sas.resourceassignment.rataskspecified.config import DEFAULT_RA_TASK_SPECIFIED_NOTIFICATION_BUSNAME from lofar.sas.resourceassignment.rataskspecified.config import DEFAULT_RA_TASK_SPECIFIED_NOTIFICATION_SUBJECT from lofar.sas.resourceassignment.resourceassigner.assignment import ResourceAssigner +from lofar.sas.resourceassignment.resourceassigner.schedulechecker import ScheduleChecker logger = logging.getLogger(__name__) @@ -147,7 +148,10 @@ def main(): subject=options.notification_subject, broker=options.broker, assigner=assigner) as listener: - waitForInterrupt() + with ScheduleChecker(radb_busname=options.radb_busname, + radb_servicename=options.radb_servicename, + broker=options.broker) as schedulechecker: + waitForInterrupt() if __name__ == '__main__': main() diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py new file mode 100644 index 00000000000..c8e193d4fc1 --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# + +import qpid.messaging +import logging +from datetime import datetime, timedelta +from time import sleep +from threading import Thread + +from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as DEFAULT_RADB_BUSNAME +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as DEFAULT_RADB_SERVICENAME + +logger = logging.getLogger(__name__) + +class ScheduleChecker(): + def __init__(self, + radb_busname=DEFAULT_RADB_BUSNAME, + radb_servicename=DEFAULT_RADB_SERVICENAME, + broker=None): + """ + """ + self._thread = None + self._running = False + self._radbrpc = RARPC(servicename=radb_servicename, busname=radb_busname, broker=broker) + + def __enter__(self): + """Internal use only. (handles scope 'with')""" + self.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Internal use only. (handles scope 'with')""" + self.stop() + + def start(self): + """Open rpc connections to radb service and resource estimator service""" + self._radbrpc.open() + self._running = True + self._thread = Thread(target=self._check_loop) + self._thread.daemon = True + self._thread.start() + + def stop(self): + """Close rpc connections to radb service and resource estimator service""" + self._radbrpc.close() + self._running = False + self._thread.join(60) + + def _check_loop(self): + while self._running: + try: + now = datetime.utcnow() + + active_pipelines = self._radbrpc.getTasks(task_status='active', task_type='pipeline') + + for task in active_pipelines: + if task['endtime'] <= now: + new_endtime=now+timedelta(minutes=1) + logger.info("Extending endtime to %s for pipeline radb_id=%s otdb_id=%s", new_endtime, task['id'], task['otdb_id']) + self._radbrpc.updateTaskAndResourceClaims(task['id'], endtime=new_endtime) + except Exception as e: + logger.error("Error while checking running pipelines: %s", e) + + try: + scheduled_pipelines = self._radbrpc.getTasks(task_status='scheduled', task_type='pipeline') + + for task in scheduled_pipelines: + if task['starttime'] <= now: + logger.info("Moving ahead scheduled pipeline radb_id=%s otdb_id=%s to 1 minute from now", task['id'], task['otdb_id']) + self._radbrpc.updateTaskAndResourceClaims(task['id'], starttime=now+timedelta(seconds=60), endtime=now+timedelta(seconds=60+task['duration'])) + updated_task = self._radbrpc.getTask(task['id']) + if updated_task['status'] != u'scheduled': + logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change to %s", updated_task['id'], updated_task['otdb_id'], updated_task['status']) + #TODO: automatically resolve conflict status by moved pipeline in first free time slot. + except Exception as e: + logger.error("Error while checking scheduled pipelines: %s", e) + + for i in range(60): + sleep(1) + + if not self._running: + break -- GitLab From 6ef353d8b775f2fe38e4f011d01c6722a0f0427e Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 20 Jun 2016 12:37:28 +0000 Subject: [PATCH 397/933] Task #9192: Created working branch -- GitLab From ea224ae9b947b0a0bdd2ffdf959b3db675c001a3 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 20 Jun 2016 13:03:02 +0000 Subject: [PATCH 398/933] Task #9192: Run pipelines with lower priority (than inspection plots etc), prevent cancellation of docker pull/tag jobs, propagate runPipeline exit code --- MAC/Services/src/PipelineControl.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 40ab25b3442..1a751d8fc99 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -314,6 +314,9 @@ class PipelineControl(OTDBBusListener): # Maximum run time for job (31 days) "--time=31-0", + + # Lower priority to drop below inspection plots + "--nice=1000", "--partition=%s" % parset.processingPartition(), "--nodes=%s" % parset.processingNumberOfTasks(), @@ -344,11 +347,11 @@ class PipelineControl(OTDBBusListener): "{setStatus_active}\n" # pull docker image from repository on all nodes "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull" - " --no-kill" + " --kill-on-bad-exit=0 --wait=0" " docker pull {repository}/{image}\n" # put a local tag on the pulled image "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-tag" - " --no-kill" + " --kill-on-bad-exit=0 --wait=0" " docker tag -f {repository}/{image} {image}\n" # call runPipeline.sh in the image on this node "docker run --rm" @@ -361,7 +364,7 @@ class PipelineControl(OTDBBusListener): " -e SLURM_JOB_ID=$SLURM_JOB_ID" " -v /data:/data" " {image}" - " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster}\n" + " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} || exit $?\n" # notify that we're tearing down "{setStatus_completing}\n" -- GitLab From c2a954665cd8542b90e9525c4cfb378bca40741f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 20 Jun 2016 13:52:19 +0000 Subject: [PATCH 399/933] Task #8392: fix in redirecting jobs to lexar3/4 --- LTA/LTAIngest/ingest_config.py.in | 2 +- LTA/LTAIngest/master.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/LTA/LTAIngest/ingest_config.py.in b/LTA/LTAIngest/ingest_config.py.in index 87d9043bb41..3ae0d647c5a 100644 --- a/LTA/LTAIngest/ingest_config.py.in +++ b/LTA/LTAIngest/ingest_config.py.in @@ -21,7 +21,7 @@ ltaClient = None host = socket.gethostname() ## Using netifaces module or something similar would be nicer, but it doesn't want to install in a custom dir ## So we use this hack -if 'lexar001' in host or 'lexar002' in host: +if 'lexar00' in host: host = host + '.offline.lofar' if 'gridftp01.target.rug.nl' in host: host = 'lotar2.staging.lofar' diff --git a/LTA/LTAIngest/master.py b/LTA/LTAIngest/master.py index ed52c927b3a..55e2b5c1eed 100755 --- a/LTA/LTAIngest/master.py +++ b/LTA/LTAIngest/master.py @@ -205,7 +205,9 @@ class jobHandler(Process): self.update_job_msg.put((job, job['Status'], JobProducing, None)) job['Status'] = JobProducing self.active[job['ExportID']] = job - self.manager.get(job.get('slave_hosts')).put(job) ## sends it to the slave with the shortest queue of the possible slave_hosts + job_slave_hosts = job.get('slave_hosts') + slave_for_job = self.manager.get(job_slave_hosts) + slave_for_job.put(job) ## sends it to the slave with the shortest queue of the possible slave_hosts self.logger.debug("Job's started: %s (%i)" % (job['ExportID'], len(self.active))) first = True except Empty: pass @@ -263,8 +265,9 @@ class queueHandler(Process): job['destination'] = self.job_groups[job['job_group']].get_destination() sourcehost = job['Location'].split(':')[0] + self.logger.info("job: %s sourcehost=%s" % (fileName, sourcehost)) if 'cep4' in sourcehost.lower(): - job['slave_hosts'] = ['lexar003.lexar.control.lofar', 'lexar004.lexar.control.lofar'] + job['slave_hosts'] = ['lexar003.offline.lofar', 'lexar004.offline.lofar'] self.scheduled.put(job) # self.talker.put(job) ## Tell MoM we've done something @@ -376,7 +379,7 @@ class ltaMaster(): result = None length = sys.maxint - for k in self.slaves.keys(): + for k in slave_hosts: size = self.slaves[k].qsize() if length > size: result = self.slaves[k] -- GitLab From 42adf6991c37a848ef25219351ace21725c69e76 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 20 Jun 2016 13:53:08 +0000 Subject: [PATCH 400/933] Task #9373: Use 256ch/sb in blocksize computation as well --- SAS/Scheduler/src/blocksize.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SAS/Scheduler/src/blocksize.h b/SAS/Scheduler/src/blocksize.h index 359dcc8c972..31d60f5be84 100644 --- a/SAS/Scheduler/src/blocksize.h +++ b/SAS/Scheduler/src/blocksize.h @@ -111,8 +111,8 @@ public: if (coherentStokesEnabled) { // Add required multiples for the CS Beamformer - // 16 * 64 (DelayAndBandPass.cu) - factor = lcm(factor, 16 * 64); + // 16 * 256 (DelayAndBandPass.cu) + factor = lcm(factor, 16 * 256); // 1024 is the maxNrThreadsPerBlock (CoherentStokesKernel.cc) // Note that coherenthannelsPerSubband is always a power of 2 < 1024 @@ -124,8 +124,8 @@ public: if (incoherentStokesEnabled) { // Add required multiples for the IS Beamformer - // 16 * 64 (DelayAndBandPass.cu) - factor = lcm(factor, 16 * 64); + // 16 * 256 (DelayAndBandPass.cu) + factor = lcm(factor, 16 * 256); // integration should fit (IncoherentStokes.cu) factor = lcm(factor, incoherentTimeIntegrationFactor * incoherentChannelsPerSubband); -- GitLab From 8cdc6f16307042552d0e1c9adc42d3f56b80a3ea Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Mon, 20 Jun 2016 14:22:52 +0000 Subject: [PATCH 401/933] Task #9192: Fix for incorrect otdb_id in file names --- .../lib/translator.py | 12 ++++++------ .../sql/add_resource_allocation_statics.sql | 2 +- .../ResourceAssignmentEstimator/service.py | 19 ++++++++++++++----- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index be12371ee9b..e1d1e2cd4e5 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -184,17 +184,17 @@ class RAtoOTDBTranslator(): def CreateStorageKeys(self, otdb_id, storage_properties, project_name, io_type): result = {} if 'nr_of_uv_files' in storage_properties: - result.update(self.CreateCorrelated(otdb_id, storage_properties, project_name, io_type)) + result.update(self.CreateCorrelated(storage_properties['uv_otdb_id'], storage_properties, project_name, io_type)) if 'nr_of_cs_files' in storage_properties: - result.update(self.CreateCoherentStokes(otdb_id, storage_properties, project_name, io_type)) + result.update(self.CreateCoherentStokes(storage_properties['cs_otdb_id'], storage_properties, project_name, io_type)) if 'nr_of_is_files' in storage_properties: - result.update(self.CreateIncoherentStokes(otdb_id, storage_properties, project_name, io_type)) + result.update(self.CreateIncoherentStokes(storage_properties['is_otdb_id'], storage_properties, project_name, io_type)) if 'nr_of_im_files' in storage_properties: - result.update(self.CreateInstrumentModel(otdb_id, storage_properties, project_name, io_type)) + result.update(self.CreateInstrumentModel(storage_properties['im_otdb_id'], storage_properties, project_name, io_type)) if 'nr_of_img_files' in storage_properties: - result.update(self.CreateSkyImage(otdb_id, storage_properties, project_name, io_type)) + result.update(self.CreateSkyImage(storage_properties['img_otdb_id'], storage_properties, project_name, io_type)) if 'nr_of_pulp_files' in storage_properties: - result.update(self.CreatePulsarPipeline(otdb_id, storage_properties, project_name, io_type)) + result.update(self.CreatePulsarPipeline(storage_properties['pulp_otdb_id'], storage_properties, project_name, io_type)) return result def ProcessStorageInfo(self, otdb_id, storage_info, project_name): diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql index 5aee075a79a..512e5634beb 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql @@ -7,7 +7,7 @@ INSERT INTO resource_allocation.task_status VALUES (200, 'prepared'), (300, 'app (1150, 'error'), (1200, 'obsolete'); -- This is the list from OTDB, we'll need to merge it with the list from MoM in the future, might use different indexes? INSERT INTO resource_allocation.task_type VALUES (0, 'observation'),(1, 'pipeline'); -- We'll need more types INSERT INTO resource_allocation.resource_claim_status VALUES (0, 'claimed'), (1, 'allocated'), (2, 'conflict'); -INSERT INTO resource_allocation.resource_claim_property_type VALUES (0, 'nr_of_is_files'),(1, 'nr_of_cs_files'),(2, 'nr_of_uv_files'),(3, 'nr_of_im_files'),(4, 'nr_of_img_files'),(5, 'nr_of_pulp_files'),(6, 'nr_of_cs_stokes'),(7, 'nr_of_is_stokes'),(8, 'is_file_size'),(9, 'cs_file_size'),(10, 'uv_file_size'),(11, 'im_file_size'),(12, 'img_file_size'),(13, 'nr_of_pulp_files'),(14, 'nr_of_tabs'),(15, 'start_sb_nr'); +INSERT INTO resource_allocation.resource_claim_property_type VALUES (0, 'nr_of_is_files'),(1, 'nr_of_cs_files'),(2, 'nr_of_uv_files'),(3, 'nr_of_im_files'),(4, 'nr_of_img_files'),(5, 'nr_of_pulp_files'),(6, 'nr_of_cs_stokes'),(7, 'nr_of_is_stokes'),(8, 'is_file_size'),(9, 'cs_file_size'),(10, 'uv_file_size'),(11, 'im_file_size'),(12, 'img_file_size'),(13, 'nr_of_pulp_files'),(14, 'nr_of_tabs'),(15, 'start_sb_nr'),(16,'uv_otdb_id'),(17,'cs_otdb_id'),(18,'is_otdb_id'),(19,'im_otdb_id'),(20,'img_otdb_id'),(21,'pulp_otdb_id'); INSERT INTO resource_allocation.resource_claim_property_io_type VALUES (0, 'output'),(1, 'input'); INSERT INTO resource_allocation.config VALUES (0, 'max_fill_percentage_cep4', '85.00'), (1, 'claim_timeout', '172800'), (2, 'min_inter_task_delay', '60'); -- Just some values 172800 is two days in seconds INSERT INTO resource_allocation.conflict_reason diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index 78c707874ca..15ab7e79021 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -27,12 +27,21 @@ class ResourceEstimatorHandler(MessageHandlerInterface): specification_tree = content["specification_tree"] return self._get_estimated_resources(specification_tree) ##TODO also handle MoM tasks in RA 1.2 + ##FIXME dirty hack + def add_id(estimate, otdb_id): + if 'storage' in estimate: + if 'output_files' in estimate['storage']: #We only need to do output files, it will be someone else's input + for data_type in estimate['storage']['output_files'].keys(): + if data_type != 'saps': + estimate['storage']['output_files'][data_type][data_type + '_otdb_id'] = otdb_id + return estimate + #TODO use something else than .values()[0]['storage'] ?? def get_subtree_estimate(self, specification_tree): otdb_id = specification_tree['otdb_id'] parset = specification_tree['specification'] if specification_tree['task_type'] == 'observation': - return {str(otdb_id): self.observation.verify_and_estimate(parset)} + return {str(otdb_id): self.add_id(self.observation.verify_and_estimate(parset), otdb_id)} elif specification_tree['task_type'] == 'pipeline': branch_estimates = {} for branch in specification_tree['predecessors']: @@ -43,25 +52,25 @@ class ResourceEstimatorHandler(MessageHandlerInterface): if not 'im' in estimate.values()[0]['storage']['output_files'] and 'uv' in estimate.values()[0]['storage']['output_files']: # Not a calibrator pipeline logger.info('found %s as the target of pipeline %s' % (id, otdb_id)) input_files = estimate.values()[0]['storage']['output_files'] # Need sap here as well - return {str(otdb_id): self.calibration_pipeline.verify_and_estimate(parset, input_files)} + return {str(otdb_id): self.add_id(self.calibration_pipeline.verify_and_estimate(parset, input_files), otdb_id)} if specification_tree['task_subtype'] in ['imaging pipeline', 'imaging pipeline msss']: if len(branch_estimates) > 1: logger.error('Imaging pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] - return {str(otdb_id): self.imaging_pipeline.verify_and_estimate(parset, input_files)} + return {str(otdb_id): self.add_id(self.imaging_pipeline.verify_and_estimate(parset, input_files), otdb_id)} if specification_tree['task_subtype'] in ['long baseline pipeline']: if len(branch_estimates) > 1: logger.error('Long baseline pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] - return {str(otdb_id): self.longbaseline_pipeline.verify_and_estimate(parset, input_files)} + return {str(otdb_id): self.add_id(self.longbaseline_pipeline.verify_and_estimate(parset, input_files), otdb_id)} if specification_tree['task_subtype'] in ['pulsar pipeline']: if len(branch_estimates) > 1: logger.error('Pulsar pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] - return {str(otdb_id): self.pulsar_pipeline.verify_and_estimate(parset, input_files)} + return {str(otdb_id): self.add_id(self.pulsar_pipeline.verify_and_estimate(parset, input_files), otdb_id)} else: # reservation, maintenance, system tasks? logger.info("It's not a pipeline or observation: %s" % otdb_id) -- GitLab From 4aba4373c9cd5ad1884b8630deb39ca50061c365 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Mon, 20 Jun 2016 14:26:30 +0000 Subject: [PATCH 402/933] Task #9192: Fix for incorrect otdb_id in file names --- .../ResourceAssignmentEstimator/service.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index 15ab7e79021..2974574ffb6 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -49,9 +49,12 @@ class ResourceEstimatorHandler(MessageHandlerInterface): logger.info(str(branch_estimates)) if specification_tree['task_subtype'] in ['averaging pipeline', 'calibration pipeline']: for id, estimate in branch_estimates.iteritems(): + input_files = {} if not 'im' in estimate.values()[0]['storage']['output_files'] and 'uv' in estimate.values()[0]['storage']['output_files']: # Not a calibrator pipeline logger.info('found %s as the target of pipeline %s' % (id, otdb_id)) - input_files = estimate.values()[0]['storage']['output_files'] # Need sap here as well + input_files['uv'] = estimate.values()[0]['storage']['output_files']['uv'] + else if 'im' in estimate.values()[0]['storage']['output_files']: + input_files['im'] = estimate.values()[0]['storage']['output_files']['im'] return {str(otdb_id): self.add_id(self.calibration_pipeline.verify_and_estimate(parset, input_files), otdb_id)} if specification_tree['task_subtype'] in ['imaging pipeline', 'imaging pipeline msss']: -- GitLab From 5abc1792e281b049df37eaa02c6ab3b6cbd8e4b0 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Mon, 20 Jun 2016 14:31:03 +0000 Subject: [PATCH 403/933] Task #9192: Fix for incorrect otdb_id in file names --- SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index 2974574ffb6..ca0abb9b3eb 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -53,7 +53,7 @@ class ResourceEstimatorHandler(MessageHandlerInterface): if not 'im' in estimate.values()[0]['storage']['output_files'] and 'uv' in estimate.values()[0]['storage']['output_files']: # Not a calibrator pipeline logger.info('found %s as the target of pipeline %s' % (id, otdb_id)) input_files['uv'] = estimate.values()[0]['storage']['output_files']['uv'] - else if 'im' in estimate.values()[0]['storage']['output_files']: + elif 'im' in estimate.values()[0]['storage']['output_files']: input_files['im'] = estimate.values()[0]['storage']['output_files']['im'] return {str(otdb_id): self.add_id(self.calibration_pipeline.verify_and_estimate(parset, input_files), otdb_id)} -- GitLab From 7956f3438c1da3c14c426b469482183a392064d6 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Mon, 20 Jun 2016 15:36:58 +0000 Subject: [PATCH 404/933] Task #9192: Fix for incorrect otdb_id in file names --- .../ResourceAssignmentEstimator/service.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index ca0abb9b3eb..0c69d9b8a2a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -28,12 +28,13 @@ class ResourceEstimatorHandler(MessageHandlerInterface): return self._get_estimated_resources(specification_tree) ##TODO also handle MoM tasks in RA 1.2 ##FIXME dirty hack - def add_id(estimate, otdb_id): - if 'storage' in estimate: - if 'output_files' in estimate['storage']: #We only need to do output files, it will be someone else's input - for data_type in estimate['storage']['output_files'].keys(): + def add_id(self, estimate, otdb_id): + if 'storage' in estimate.values()[0]: + if 'output_files' in estimate.values()[0]['storage']: #We only need to do output files, it will be someone else's input + for data_type in estimate.values()[0]['storage']['output_files'].keys(): if data_type != 'saps': - estimate['storage']['output_files'][data_type][data_type + '_otdb_id'] = otdb_id + estimate.values()[0]['storage']['output_files'][data_type][data_type + '_otdb_id'] = otdb_id + logger.info('added %s to %s' % (otdb_id, str(estimate.values()[0]['storage']['output_files'][data_type]))) return estimate #TODO use something else than .values()[0]['storage'] ?? @@ -53,6 +54,7 @@ class ResourceEstimatorHandler(MessageHandlerInterface): if not 'im' in estimate.values()[0]['storage']['output_files'] and 'uv' in estimate.values()[0]['storage']['output_files']: # Not a calibrator pipeline logger.info('found %s as the target of pipeline %s' % (id, otdb_id)) input_files['uv'] = estimate.values()[0]['storage']['output_files']['uv'] + input_files['saps'] = estimate.values()[0]['storage']['output_files']['saps'] elif 'im' in estimate.values()[0]['storage']['output_files']: input_files['im'] = estimate.values()[0]['storage']['output_files']['im'] return {str(otdb_id): self.add_id(self.calibration_pipeline.verify_and_estimate(parset, input_files), otdb_id)} -- GitLab From b8c3c34071ca6d50b404fbc79f6b308ee56703d9 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Tue, 21 Jun 2016 13:26:00 +0000 Subject: [PATCH 405/933] Task #9192: Fix for phaseshift pipelines --- SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index 0c69d9b8a2a..55cb19d86e5 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -54,7 +54,8 @@ class ResourceEstimatorHandler(MessageHandlerInterface): if not 'im' in estimate.values()[0]['storage']['output_files'] and 'uv' in estimate.values()[0]['storage']['output_files']: # Not a calibrator pipeline logger.info('found %s as the target of pipeline %s' % (id, otdb_id)) input_files['uv'] = estimate.values()[0]['storage']['output_files']['uv'] - input_files['saps'] = estimate.values()[0]['storage']['output_files']['saps'] + if 'saps' in estimate.values()[0]['storage']['output_files']: + input_files['saps'] = estimate.values()[0]['storage']['output_files']['saps'] elif 'im' in estimate.values()[0]['storage']['output_files']: input_files['im'] = estimate.values()[0]['storage']['output_files']['im'] return {str(otdb_id): self.add_id(self.calibration_pipeline.verify_and_estimate(parset, input_files), otdb_id)} -- GitLab From ca2c2b70b4a6e7e8736f472a95922d52df37b372 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 21 Jun 2016 13:45:24 +0000 Subject: [PATCH 406/933] Task #9192 and #9572: fix --- .../ResourceAssigner/lib/schedulechecker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index c8e193d4fc1..51a57584046 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -83,8 +83,10 @@ class ScheduleChecker(): try: scheduled_pipelines = self._radbrpc.getTasks(task_status='scheduled', task_type='pipeline') + queued_pipelines = self._radbrpc.getTasks(task_status='queued', task_type='pipeline') + sq_pipelines = scheduled_pipelines + queued_pipelines - for task in scheduled_pipelines: + for task in sq_pipelines: if task['starttime'] <= now: logger.info("Moving ahead scheduled pipeline radb_id=%s otdb_id=%s to 1 minute from now", task['id'], task['otdb_id']) self._radbrpc.updateTaskAndResourceClaims(task['id'], starttime=now+timedelta(seconds=60), endtime=now+timedelta(seconds=60+task['duration'])) -- GitLab From 217f808cdda756a31381473f05176fb48ab99141 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 23 Jun 2016 07:25:29 +0000 Subject: [PATCH 407/933] Task #9192: show group_id and filter exact on dropdowns --- .../static/app/controllers/gridcontroller.js | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 18f3389fdcf..5ad3016e6b5 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -55,6 +55,7 @@ $scope.columns = [ enableCellEdit: true, width: '6%', filter: { + condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, selectOptions: [] }, @@ -69,16 +70,18 @@ $scope.columns = [ enableCellEdit: false, width: '6%', filter: { + condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, selectOptions: [] } }, - { field: 'mom_object_group_name', + { field: 'mom_object_group_id', displayName: 'Group', enableCellEdit: false, - cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity[col.field]}}</a>', + cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity.mom_object_group_name}} {{row.entity.mom_object_group_id}}</a>', width: '13%', filter: { + condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, selectOptions: [] } @@ -163,7 +166,12 @@ $scope.columns = [ for(var i = 0; i < options.length; i++) { var option = options[i]; - columnSelectOptions.push({ value: option, label: option }) + if(option.hasOwnProperty('value') && option.hasOwnProperty('label')) { + columnSelectOptions.push({ value: option.value, label: option.label }) + } + else { + columnSelectOptions.push({ value: option, label: option }) + } } } @@ -257,11 +265,13 @@ $scope.columns = [ function fillGroupsColumFilterSelectOptions() { var tasks = $scope.dataService.tasks; //get unique groupNames from tasks - var groupNames = tasks.map(function(t) { return t.mom_object_group_name; }); - groupNames = groupNames.filter(function(value, index, arr) { return arr.indexOf(value) == index;}) - groupNames.sort(); + var groupNamesAndIds = tasks.map(function(t) { return { name: t.mom_object_group_name, id: t.mom_object_group_id }; }); + groupNamesAndIds = groupNamesAndIds.filter(function(value, index, arr) { return arr.indexOf(value) == index;}) + groupNamesAndIds.sort(); + + var groupSelectOptions = groupNamesAndIds.map(function(obj) { return { value: obj.id, label: obj.name + ' ' + obj.id }; }); - fillColumFilterSelectOptions(groupNames, $scope.columns[7]); + fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[7]); }; $scope.$watch('dataService.momProjectsDict', fillProjectsColumFilterSelectOptions); -- GitLab From 498b2b1aa47f93d345f7e6595786ac05bccfd07f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 23 Jun 2016 08:53:47 +0000 Subject: [PATCH 408/933] Task #9192 and #9580 and #9581: fixes for filtering of tasks and responsiveness of loading/filtering data --- .../static/app/controllers/datacontroller.js | 19 +++-- .../app/controllers/ganttprojectcontroller.js | 22 +++--- .../controllers/ganttresourcecontroller.js | 20 +++-- .../static/app/controllers/gridcontroller.js | 77 ++++++++++--------- 4 files changed, 68 insertions(+), 70 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 1142b3e47eb..3659053b914 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -39,6 +39,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.initialLoadComplete = false; self.taskChangeCntr = 0; + self.filteredTaskChangeCntr = 0; self.claimChangeCntr = 0; self.resourceUsagesChangeCntr = 0; @@ -113,9 +114,11 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.toIdBasedDict = function(list) { var dict = {} - for(var i = list.length-1; i >=0; i--) { - var item = list[i]; - dict[item.id] = item; + if(list) { + for(var i = list.length-1; i >=0; i--) { + var item = list[i]; + dict[item.id] = item; + } } return dict; }; @@ -177,13 +180,11 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } self.tasks = visibleTasks; - self.taskDict = self.toIdBasedDict(self.tasks); - self.filteredTasks = self.tasks; - self.filteredTaskDict = self.taskDict; + self.taskDict = self.toIdBasedDict(visibleTasks); + self.taskChangeCntr++; self.computeMinMaxTaskTimes(); - var numClaims = self.resourceClaims.length; var visibleClaims = []; for(var i = 0; i < numClaims; i++) { @@ -240,10 +241,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } } - self.filteredTasks = self.tasks; - self.filteredTaskDict = self.taskDict; self.taskChangeCntr++; - self.computeMinMaxTaskTimes(); if(initialTaskLoad && self.tasks.length > 0) { @@ -596,6 +594,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } self.taskChangeCntr++; + self.computeMinMaxTaskTimes(); } else if(change.objectType == 'resourceClaim') { anyResourceClaims = true; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index 5d5a353fb0a..ef5752e6ffe 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -261,19 +261,15 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS $scope.ganttData = ganntRows; }; - $scope.$watch('dataService.initialLoadComplete', updateGanttData); - $scope.$watch('dataService.selected_task_id', updateGanttData); - $scope.$watch('dataService.tasks', updateGanttData); - $scope.$watch('dataService.resources', updateGanttData); - $scope.$watch('dataService.resourceClaims', updateGanttData); - $scope.$watch('dataService.resourceGroups', updateGanttData); - $scope.$watch('dataService.resourceGroupMemberships', updateGanttData); - $scope.$watch('dataService.filteredTaskDict', updateGanttData); - $scope.$watch('dataService.momProjectsDict', updateGanttData); - $scope.$watch('dataService.viewTimeSpan', updateGanttData, true); - $scope.$watch('dataService.taskChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); + $scope.$watch('dataService.initialLoadComplete', function() { $scope.$evalAsync(updateGanttData); }); + $scope.$watch('dataService.selected_task_id', function() { $scope.$evalAsync(updateGanttData); }); + $scope.$watch('dataService.viewTimeSpan', function() { $scope.$evalAsync(updateGanttData); }, true); + $scope.$watch('dataService.filteredTaskChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); $scope.$watch('dataService.lofarTime', function() { - if($scope.dataService.lofarTime.getSeconds() % 5 == 0) { - $scope.options.currentDateValue= $scope.dataService.lofarTime;}}); + $scope.$evalAsync(function() { + if($scope.dataService.lofarTime.getSeconds() % 5 == 0) { + $scope.options.currentDateValue= $scope.dataService.lofarTime;} + }); + }); } ]); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js index 1c17751ae6c..9ec685de16e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js @@ -468,18 +468,16 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat } }; - $scope.$watch('dataService.initialLoadComplete', updateGanttData); - $scope.$watch('dataService.selected_task_id', updateGanttData); - $scope.$watch('dataService.tasks', updateGanttData); - $scope.$watch('dataService.resources', updateGanttData); - $scope.$watch('dataService.resourceClaims', updateGanttData); - $scope.$watch('dataService.resourceGroups', updateGanttData); - $scope.$watch('dataService.resourceGroupMemberships', updateGanttData); - $scope.$watch('dataService.filteredTaskDict', updateGanttData); - $scope.$watch('dataService.viewTimeSpan', updateGanttData, true); + $scope.$watch('dataService.initialLoadComplete', function() { $scope.$evalAsync(updateGanttData); }); + $scope.$watch('dataService.selected_task_id', function() { $scope.$evalAsync(updateGanttData); }); + $scope.$watch('dataService.viewTimeSpan', function() { $scope.$evalAsync(updateGanttData); }, true); $scope.$watch('dataService.claimChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); + $scope.$watch('dataService.filteredTaskChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); $scope.$watch('dataService.lofarTime', function() { - if($scope.dataService.lofarTime.getSeconds() % 5 == 0) { - $scope.options.currentDateValue= $scope.dataService.lofarTime;}}); + $scope.$evalAsync(function() { + if($scope.dataService.lofarTime.getSeconds() % 5 == 0) { + $scope.options.currentDateValue= $scope.dataService.lofarTime;} + }); + }); } ]); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 5ad3016e6b5..835b7588f34 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -123,7 +123,29 @@ $scope.columns = [ onRegisterApi: function(gridApi){ $scope.gridApi = gridApi; - $scope.gridApi.core.on.rowsRendered($scope, filterTasks); + $scope.gridApi.core.on.rowsRendered($scope, function() { + //on.rowsRendered is called whenever the data/filtering of the grid changed + //update the filteredTasks in the dataService from the resulting new grid rows + $scope.$evalAsync(function() { + var taskDict = $scope.dataService.taskDict; + $scope.dataService.filteredTasks = []; + var rows = $scope.gridApi.grid.rows; + var numRows = rows.length; + for(var i = 0; i < numRows; i++) { + var row = rows[i]; + if(row.visible) + { + var task = taskDict[row.entity.id]; + if(task) { + $scope.dataService.filteredTasks.push(task); + } + } + } + + $scope.dataService.filteredTaskDict = $scope.dataService.toIdBasedDict($scope.dataService.filteredTasks); + $scope.dataService.filteredTaskChangeCntr++; + }); + }); gridApi.edit.on.afterCellEdit($scope,function(rowEntity, colDef, newValue, oldValue){ var task = $scope.dataService.taskDict[rowEntity.id]; @@ -139,26 +161,6 @@ $scope.columns = [ } }; - function filterTasks() { - var taskDict = $scope.dataService.taskDict; - var filteredTasks = []; - var filteredTaskDict = {}; - var rows = $scope.gridApi.grid.rows; - var numRows = rows.length; - for(var i = 0; i < numRows; i++) { - var row = rows[i]; - if(row.visible) - { - var task = taskDict[row.entity.id]; - filteredTasks.push(task); - filteredTaskDict[task.id] = task; - } - } - - $scope.dataService.filteredTasks = filteredTasks; - $scope.dataService.filteredTaskDict = filteredTaskDict; - }; - function fillColumFilterSelectOptions(options, columnDef) { var columnSelectOptions = []; @@ -183,10 +185,13 @@ $scope.columns = [ var viewFrom = $scope.dataService.viewTimeSpan.from; var viewTo = $scope.dataService.viewTimeSpan.to; - var tasks = []; + $scope.dataService.filteredTasks = []; + var gridTasks = []; for(var task of $scope.dataService.tasks) { if(task.endtime >= viewFrom && task.starttime <= viewTo) { + $scope.dataService.filteredTasks.push(task); + var gridTask = { id: task.id, name: task.name, @@ -204,11 +209,11 @@ $scope.columns = [ mom_object_group_name: task.mom_object_group_name, mom_object_group_mom2object_id: task.mom_object_group_mom2object_id }; - tasks.push(gridTask); + gridTasks.push(gridTask); } } - $scope.gridOptions.data = tasks; + $scope.gridOptions.data = gridTasks; } else $scope.gridOptions.data = [] @@ -227,19 +232,21 @@ $scope.columns = [ $scope.$watch('dataService.taskChangeCntr', function() { $scope.$evalAsync(populateList); }); $scope.$watch('dataService.viewTimeSpan', function() { - populateList(); - setTimeout(jumpToSelectedTaskRow, 250); + $scope.$evalAsync(populateList); + $scope.$evalAsync(jumpToSelectedTaskRow); }, true); - $scope.$watch('dataService.taskstatustypes', function() { - taskstatustypenames = $scope.dataService.taskstatustypes.map(function(x) { return x.name; }); - fillColumFilterSelectOptions(taskstatustypenames, $scope.columns[5]); - $scope.columns[6].editDropdownOptionsArray = $scope.dataService.taskstatustypes.map(function(x) { return {id:x.name, value:x.name}; }); - }); + $scope.$watch('dataService.initialLoadComplete', function() { + $scope.$evalAsync(function() { + taskstatustypenames = $scope.dataService.taskstatustypes.map(function(x) { return x.name; }); + fillColumFilterSelectOptions(taskstatustypenames, $scope.columns[5]); + $scope.columns[6].editDropdownOptionsArray = $scope.dataService.taskstatustypes.map(function(x) { return {id:x.name, value:x.name}; }); - $scope.$watch('dataService.tasktypes', function() { - tasktypenames = $scope.dataService.tasktypes.map(function(x) { return x.name; }); - fillColumFilterSelectOptions(tasktypenames, $scope.columns[6]); + tasktypenames = $scope.dataService.tasktypes.map(function(x) { return x.name; }); + fillColumFilterSelectOptions(tasktypenames, $scope.columns[6]); + + fillProjectsColumFilterSelectOptions(); + }); }); function fillProjectsColumFilterSelectOptions() { @@ -274,7 +281,6 @@ $scope.columns = [ fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[7]); }; - $scope.$watch('dataService.momProjectsDict', fillProjectsColumFilterSelectOptions); $scope.$watch('dataService.selected_task_id', jumpToSelectedTaskRow);} ]); @@ -344,7 +350,6 @@ gridControllerMod.directive('contextMenu', ['$document', function($document) { $element.bind('contextmenu', handleContextMenuEvent); $scope.$on('$destroy', function() { - console.log('destroy'); $element.unbind('contextmenu', handleContextMenuEvent); }); } -- GitLab From 7b54b7beccc11e832b3591baa68be22cd5d8bd9c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 23 Jun 2016 09:10:27 +0000 Subject: [PATCH 409/933] Task #9192 and #9579: fixes for editing from/to times --- .../lib/static/app/controllers/datacontroller.js | 16 +++++++++------- .../lib/templates/index.html | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 3659053b914..d6a0a7cf089 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -682,7 +682,7 @@ dataControllerMod.controller('DataController', $scope.openViewFromDatePopup = function() { $scope.viewFromDatePopupOpened = true; }; $scope.openViewToDatePopup = function() { $scope.viewToDatePopupOpened = true; }; $scope.zoomTimespans = [{value:30, name:'30 Minutes'}, {value:60, name:'1 Hour'}, {value:3*60, name:'3 Hours'}, {value:6*60, name:'6 Hours'}, {value:12*60, name:'12 Hours'}, {value:24*60, name:'1 Day'}, {value:2*24*60, name:'2 Days'}, {value:3*24*60, name:'3 Days'}, {value:5*24*60, name:'5 Days'}, {value:7*24*60, name:'1 Week'}, {value:14*24*60, name:'2 Weeks'}, {value:28*24*60, name:'4 Weeks'}, {value:1, name:'Custom (1 min)'}]; - $scope.zoomTimespan = $scope.zoomTimespans[5]; + $scope.zoomTimespan = $scope.zoomTimespans[4]; $scope.jumpToNow = function() { var floorLofarTime = dataService.floorDate(dataService.lofarTime, 1, 5); dataService.viewTimeSpan = { @@ -784,17 +784,19 @@ dataControllerMod.controller('DataController', } }; - $scope.$watch('dataService.viewTimeSpan.from', function() { + $scope.onViewTimeSpanFromChanged = function() { + dataService.autoFollowNow = false; if(dataService.viewTimeSpan.from >= dataService.viewTimeSpan.to) { - dataService.viewTimeSpan.from = dataService.floorDate(new Date(dataService.viewTimeSpan.to.getTime() - 5*60*1000), 1, 5); + dataService.viewTimeSpan.to = dataService.floorDate(new Date(dataService.viewTimeSpan.from.getTime() + $scope.zoomTimespan.value*60*1000), 1, 5); } - }); + }; - $scope.$watch('dataService.viewTimeSpan.to', function() { + $scope.onViewTimeSpanToChanged = function() { + dataService.autoFollowNow = false; if(dataService.viewTimeSpan.to <= dataService.viewTimeSpan.from) { - dataService.viewTimeSpan.to = dataService.floorDate(new Date(dataService.viewTimeSpan.from.getTime() + 5*60*1000), 1, 5); + dataService.viewTimeSpan.from = dataService.floorDate(new Date(dataService.viewTimeSpan.to.getTime() - $scope.zoomTimespan.value*60*1000), 1, 5); } - }); + }; $scope.$watch('dataService.viewTimeSpan', function() { $scope.selectZoomTimespan(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index 61a534f91ac..5942abcf8fd 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -60,7 +60,7 @@ <span class="input-group-btn"> <button type="button" class="btn btn-default" ng-click="openViewFromDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> </span> - <uib-timepicker ng-model="dataService.viewTimeSpan.from" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> + <uib-timepicker ng-model="dataService.viewTimeSpan.from" ng-change="onViewTimeSpanFromChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> </p> </div> <div class="col-md-3"> @@ -70,7 +70,7 @@ <span class="input-group-btn"> <button type="button" class="btn btn-default" ng-click="openViewToDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> </span> - <uib-timepicker ng-model="dataService.viewTimeSpan.to" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> + <uib-timepicker ng-model="dataService.viewTimeSpan.to" ng-change="onViewTimeSpanToChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> </p> </div> <div class="col-md-2"> -- GitLab From 4f2f7330ba4d532e0fc276a201d66762566b85f8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 23 Jun 2016 09:41:54 +0000 Subject: [PATCH 410/933] Task #9192: fix in ingest ssh command for ltacp client on cep2 --- LTA/LTAIngest/ingestpipeline.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index f26ba5b52d5..81e2204cb5e 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -194,7 +194,7 @@ class IngestPipeline(): ## SecondaryUri handling not implemented self.logger.debug(cmd) start = time.time() - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) logs = p.communicate() elapsed = time.time() - start self.logger.debug("File transfer for %s took %d sec" % (self.JobId, elapsed)) @@ -475,8 +475,8 @@ class IngestPipeline(): self.logger.debug('Unspecified SIP created for %s: %s' % (self.JobId, self.SIP[0:400])) ###raise Exception('Got a malformed SIP from MoM: %s' % self.SIP[0:50]) if not self.CheckSIPContent(): - self.logger.error('SIP has invalid content for %s\n%s' % (self.JobId, self.SIP)) - raise PipelineError('Got a SIP with wrong contents for %s : %s' % (self.JobId, self.SIP), 'GetSIP') + self.logger.error('SIP has invalid content for %s\n%s' % (self.JobId, self.SIP[0:512])) + raise PipelineError('Got a SIP with wrong contents for %s : %s' % (self.JobId, self.SIP[0:512]), 'GetSIP') def SendSIP(self): try: -- GitLab From 47fa3b333ab8c07c742a37afb973a6bfc436ac9f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 23 Jun 2016 11:18:34 +0000 Subject: [PATCH 411/933] Task #9350: factored out getPathForOTDBId into seperate class PathResolver in DataManagementCommon, which can be used by other datamanagement service as well, and also provided cmdline client getPathForTask --- .gitattributes | 5 + CMake/LofarPackageList.cmake | 3 +- SAS/DataManagement/CMakeLists.txt | 1 + .../CleanupService/CMakeLists.txt | 2 +- SAS/DataManagement/CleanupService/config.py | 1 - SAS/DataManagement/CleanupService/service.py | 68 +++------ .../DataManagementCommon/CMakeLists.txt | 16 +++ .../DataManagementCommon/__init__.py | 1 + .../DataManagementCommon/config.py | 4 + .../DataManagementCommon/getPathForTask | 5 + .../DataManagementCommon/path.py | 131 ++++++++++++++++++ 11 files changed, 185 insertions(+), 52 deletions(-) create mode 100644 SAS/DataManagement/DataManagementCommon/CMakeLists.txt create mode 100644 SAS/DataManagement/DataManagementCommon/__init__.py create mode 100644 SAS/DataManagement/DataManagementCommon/config.py create mode 100644 SAS/DataManagement/DataManagementCommon/getPathForTask create mode 100644 SAS/DataManagement/DataManagementCommon/path.py diff --git a/.gitattributes b/.gitattributes index a42a20a7cff..56e59fc1dcc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4741,6 +4741,11 @@ SAS/DataManagement/CleanupService/test/CMakeLists.txt -text SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py -text SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.run -text SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.sh -text +SAS/DataManagement/DataManagementCommon/CMakeLists.txt -text +SAS/DataManagement/DataManagementCommon/__init__.py -text +SAS/DataManagement/DataManagementCommon/config.py -text +SAS/DataManagement/DataManagementCommon/getPathForTask -text +SAS/DataManagement/DataManagementCommon/path.py -text SAS/DataManagement/StorageQueryService/CMakeLists.txt -text SAS/DataManagement/StorageQueryService/__init__.py -text SAS/DataManagement/StorageQueryService/config.py -text diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index 61816d21df8..88247a72744 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -1,7 +1,7 @@ # - Create for each LOFAR package a variable containing the absolute path to # its source directory. # -# Generated by gen_LofarPackageList_cmake.sh at wo 15 jun 2016 15:03:55 CEST +# Generated by gen_LofarPackageList_cmake.sh at do 23 jun 2016 12:19:31 CEST # # ---- DO NOT EDIT ---- # @@ -145,6 +145,7 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(CleanupTool_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/CleanupTool) set(OTDB_Services_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/OTDB_Services) set(XML_generator_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/XML_generator) + set(DataManagementCommon_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/DataManagement/DataManagementCommon) set(CleanupService_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/DataManagement/CleanupService) set(StorageQueryService_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/DataManagement/StorageQueryService) set(MoMQueryService_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/MoM/MoMQueryService) diff --git a/SAS/DataManagement/CMakeLists.txt b/SAS/DataManagement/CMakeLists.txt index d9d1f0ff4f3..ce21ce26635 100644 --- a/SAS/DataManagement/CMakeLists.txt +++ b/SAS/DataManagement/CMakeLists.txt @@ -1,4 +1,5 @@ # $Id: CMakeLists.txt 34529 2016-05-24 17:00:41Z mol $ +lofar_add_package(DataManagementCommon) lofar_add_package(CleanupService) lofar_add_package(StorageQueryService) diff --git a/SAS/DataManagement/CleanupService/CMakeLists.txt b/SAS/DataManagement/CleanupService/CMakeLists.txt index 2c8116bef9b..9d37337b1ad 100644 --- a/SAS/DataManagement/CleanupService/CMakeLists.txt +++ b/SAS/DataManagement/CleanupService/CMakeLists.txt @@ -1,6 +1,6 @@ # $Id$ -lofar_package(CleanupService 1.0 DEPENDS PyMessaging ResourceAssignmentService MoMQueryService) +lofar_package(CleanupService 1.0 DEPENDS PyMessaging DataManagementCommon) lofar_find_package(Python 2.6 REQUIRED) include(PythonInstall) diff --git a/SAS/DataManagement/CleanupService/config.py b/SAS/DataManagement/CleanupService/config.py index c7dd0f5a3ab..0e8dafdf6f6 100644 --- a/SAS/DataManagement/CleanupService/config.py +++ b/SAS/DataManagement/CleanupService/config.py @@ -5,4 +5,3 @@ from lofar.messaging import adaptNameToEnvironment DEFAULT_BUSNAME = adaptNameToEnvironment('lofar.dm.command') DEFAULT_SERVICENAME = 'CleanupService' -CEP4_DATA_MOUNTPOINT='/data' diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index e35936d7263..e01f9285d67 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -11,14 +11,12 @@ from lofar.messaging import Service from lofar.messaging import setQpidLogLevel from lofar.messaging.Service import MessageHandlerInterface from lofar.common.util import waitForInterrupt -from lofar.common import isProductionEnvironment, isTestEnvironment -from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME, CEP4_DATA_MOUNTPOINT -from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC as RADBRPC +from lofar.sas.datamanagement.common.path import PathResolver +from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT +from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME - -from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME logger = logging.getLogger(__name__) @@ -26,54 +24,26 @@ logger = logging.getLogger(__name__) class CleanupHandler(MessageHandlerInterface): def __init__(self, **kwargs): super(CleanupHandler, self).__init__(**kwargs) - self.mountpoint = kwargs.pop("mountpoint", CEP4_DATA_MOUNTPOINT) - self.projects_path = os.path.join(self.mountpoint, 'projects' if isProductionEnvironment() else 'test-projects') - self.service2MethodMap = {'RemovePath': self._removePath, 'GetPathForOTDBId': self._getPathForOTDBId, 'RemoveTaskData': self._removeTaskData} - self.radbrpc = RADBRPC(busname=kwargs.pop("radb_busname", RADB_BUSNAME), servicename=kwargs.pop("radb_servicename", RADB_SERVICENAME), broker=kwargs.pop("broker", None)) - self.momrpc = MoMQueryRPC(busname=kwargs.pop("mom_busname", DEFAULT_MOMQUERY_BUSNAME), servicename=kwargs.pop("mom_servicename", DEFAULT_MOMQUERY_SERVICENAME), broker=kwargs.pop("broker", None)) + self.path_resolver = PathResolver(mountpoint=kwargs.pop("mountpoint", CEP4_DATA_MOUNTPOINT), + radb_busname=kwargs.pop("radb_busname", RADB_BUSNAME), + radb_servicename=kwargs.pop("radb_servicename", RADB_SERVICENAME), + mom_busname=kwargs.pop("mom_busname", DEFAULT_MOMQUERY_BUSNAME), + mom_servicename=kwargs.pop("mom_servicename", DEFAULT_MOMQUERY_SERVICENAME), + broker=kwargs.pop("broker", None)) def prepare_loop(self): - self.radbrpc.open() - self.momrpc.open() - logger.info("Cleanup service started with projects_path=%s", self.projects_path) + self.path_resolver.open() + logger.info("Cleanup service started with projects_path=%s", self.path_resolver.projects_path) def finalize_loop(self): - self.radbrpc.close() - self.momrpc.close() + self.path_resolver.close() def _getPathForOTDBId(self, otdb_id): - logger.info("Get path for otdb_id %s" % (otdb_id,)) - - if not isinstance(otdb_id, int): - raise ValueError("Provided otdb_id is not an int") - - task = self.radbrpc.getTask(otdb_id=otdb_id) - - if not task: - message = "Could not find task in RADB for otdb_id=%s" % otdb_id - logger.error(message) - return {'found': False, 'message': message, 'path': None} - - logger.info("Get path for otdb_id %s, found radb task with mom_id=%s and radb_id=%s" % (otdb_id, task['mom_id'], task['id'])) - - mom_details = self.momrpc.getProjectDetails(task['mom_id']) - - if not mom_details or str(task['mom_id']) not in mom_details: - message = "Could not find mom project details for otdb_id=%s mom_id=%s radb_id=%s" % (otdb_id, task['mom_id'], task['id']) - logger.error(message) - return {'found': False, 'message': message, 'path': None} - - project_name = mom_details[str(task['mom_id'])]['project_name'] - logger.info("Found project '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (project_name, otdb_id, task['mom_id'], task['id'])) - - project_path = os.path.join(self.projects_path, "_".join(project_name.split())) - task_data_path = os.path.join(project_path, 'L%s' % otdb_id) - - return {'found': True, 'message': '', 'path': task_data_path} + return self.path_resolver.getPathForOTDBId(otdb_id) def _removeTaskData(self, otdb_id): logger.info("Remove task data for otdb_id %s" % (otdb_id,)) @@ -112,13 +82,13 @@ class CleanupHandler(MessageHandlerInterface): if len(path) > 1: path = path.rstrip('/') - if not path.startswith(self.projects_path): - message = "Invalid path '%s': Path does not start with '%s'" % (path, self.projects_path) + if not path.startswith(self.path_resolver.projects_path): + message = "Invalid path '%s': Path does not start with '%s'" % (path, self.path_resolver.projects_path) logger.error(message) return {'deleted': False, 'message': message} - if path[len(self.projects_path)+1:].count('/') == 0: - message = "Invalid path '%s': Path should be a subdir of '%s'" % (path, self.projects_path) + if path[len(self.path_resolver.projects_path)+1:].count('/') == 0: + message = "Invalid path '%s': Path should be a subdir of '%s'" % (path, self.path_resolver.projects_path) logger.error(message) return {'deleted': False, 'message': message} @@ -134,7 +104,7 @@ class CleanupHandler(MessageHandlerInterface): logger.info(message) return {'deleted': True, 'message': message} - return {'deleted': False, 'message': 'Failed to delete one or more files.', 'failed_paths' : list(failed_paths)} + return {'deleted': False, 'message': 'Failed to delete one or more files or directories:\n - %s' % '\n - '.join(failed_paths), 'failed_paths' : list(failed_paths)} def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, @@ -162,7 +132,7 @@ def main(): parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') parser.add_option("-b", "--busname", dest="busname", type="string", default=DEFAULT_BUSNAME, help="Name of the bus exchange on the qpid broker, default: %default") parser.add_option("-s", "--servicename", dest="servicename", type="string", default=DEFAULT_SERVICENAME, help="Name for this service, default: default %default") - parser.add_option("-m", "--mountpoint", dest="mountpoint", type="string", default=CEP4_DATA_MOUNTPOINT, help="path of local cep4 mount point, default: %default") + parser.add_option("--mountpoint", dest="mountpoint", type="string", default=CEP4_DATA_MOUNTPOINT, help="path of local cep4 mount point, default: %default") parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') parser.add_option("--radb_busname", dest="radb_busname", type="string", default=RADB_BUSNAME, help="Name of the bus on which the RADB service listens, default: %default") parser.add_option("--radb_servicename", dest="radb_servicename", type="string", default=RADB_SERVICENAME, help="Name of the RADB service, default: %default") diff --git a/SAS/DataManagement/DataManagementCommon/CMakeLists.txt b/SAS/DataManagement/DataManagementCommon/CMakeLists.txt new file mode 100644 index 00000000000..7740f62346c --- /dev/null +++ b/SAS/DataManagement/DataManagementCommon/CMakeLists.txt @@ -0,0 +1,16 @@ +# $Id$ + +lofar_package(DataManagementCommon 1.0 DEPENDS PyMessaging ResourceAssignmentService MoMQueryService) + +lofar_find_package(Python 2.6 REQUIRED) +include(PythonInstall) + +set(_py_files + __init__.py + config.py + path.py +) + +lofar_add_bin_scripts(getPathForTask) + +python_install(${_py_files} DESTINATION lofar/sas/datamanagement/common) diff --git a/SAS/DataManagement/DataManagementCommon/__init__.py b/SAS/DataManagement/DataManagementCommon/__init__.py new file mode 100644 index 00000000000..fbbab2d1199 --- /dev/null +++ b/SAS/DataManagement/DataManagementCommon/__init__.py @@ -0,0 +1 @@ +# $Id$ diff --git a/SAS/DataManagement/DataManagementCommon/config.py b/SAS/DataManagement/DataManagementCommon/config.py new file mode 100644 index 00000000000..bda8673e0b3 --- /dev/null +++ b/SAS/DataManagement/DataManagementCommon/config.py @@ -0,0 +1,4 @@ +#!/usr/bin/python +# $Id$ + +CEP4_DATA_MOUNTPOINT='/data' diff --git a/SAS/DataManagement/DataManagementCommon/getPathForTask b/SAS/DataManagement/DataManagementCommon/getPathForTask new file mode 100644 index 00000000000..d0ca0d5a019 --- /dev/null +++ b/SAS/DataManagement/DataManagementCommon/getPathForTask @@ -0,0 +1,5 @@ +#!/usr/bin/python + +if __name__ == '__main__': + from lofar.sas.datamanagement.common.path import main + main() diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py new file mode 100644 index 00000000000..f5fa2f03bb2 --- /dev/null +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -0,0 +1,131 @@ +#!/usr/bin/python + +import os +import os.path +import logging + +from lofar.common import isProductionEnvironment, isTestEnvironment + +from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT + +from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC as RADBRPC +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME + +from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC +from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME + +logger = logging.getLogger(__name__) + +class PathResolver: + def __init__(self, + mountpoint=CEP4_DATA_MOUNTPOINT, + radb_busname=RADB_BUSNAME, + radb_servicename=RADB_SERVICENAME, + mom_busname=DEFAULT_MOMQUERY_BUSNAME, + mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, + broker=None): + + self.mountpoint = mountpoint + self.projects_path = os.path.join(self.mountpoint, 'projects' if isProductionEnvironment() else 'test-projects') + + self.radbrpc = RADBRPC(busname=radb_busname, servicename=radb_servicename, broker=broker) + self.momrpc = MoMQueryRPC(busname=mom_busname, servicename=mom_servicename, broker=broker) + + def open(self): + self.radbrpc.open() + self.momrpc.open() + + def close(self): + self.radbrpc.close() + self.momrpc.close() + + def __enter__(self): + self.open() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def getPathForRADBId(self, radb_id): + logger.info("Get path for radb_id %s" % (radb_id,)) + return self._getPath(radb_id=radb_id) + + def getPathForMoMId(self, mom_id): + logger.info("Get path for mom_id %s" % (mom_id,)) + return self._getPath(mom_id=mom_id) + + def getPathForOTDBId(self, otdb_id): + logger.info("Get path for otdb_id %s" % (otdb_id,)) + return self._getPath(otdb_id=otdb_id) + + def _getPath(self, radb_id=None, mom_id=None, otdb_id=None): + '''get a task for either the given radb_id, or for the given mom_id, or for the given otdb_id''' + ids = [radb_id, mom_id, otdb_id] + validIds = [x for x in ids if x != None and isinstance(x, int)] + + if len(validIds) != 1: + raise KeyError("Provide one and only one id: radb_id=%s, mom_id=%s, otdb_id=%s" % (radb_id, mom_id, otdb_id)) + + task = self.radbrpc.getTask(id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + + if not task: + message = "Could not find task in RADB for radb_id=%s, mom_id=%s, otdb_id=%s" % (radb_id, mom_id, otdb_id) + logger.error(message) + return {'found': False, 'message': message, 'path': None} + + logger.info("found radb task with radb_id=%s mom_id=%s and otdb_id=%s" % (task['id'], task['mom_id'], task['otdb_id'])) + + mom_details = self.momrpc.getProjectDetails(task['mom_id']) + + if not mom_details or str(task['mom_id']) not in mom_details: + message = "Could not find mom project details for otdb_id=%s mom_id=%s radb_id=%s" % (task['otdb_id'], task['mom_id'], task['id']) + logger.error(message) + return {'found': False, 'message': message, 'path': None} + + project_name = mom_details[str(task['mom_id'])]['project_name'] + logger.info("Found project '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (project_name, task['otdb_id'], task['mom_id'], task['id'])) + + project_path = os.path.join(self.projects_path, "_".join(project_name.split())) + task_data_path = os.path.join(project_path, 'L%s' % task['otdb_id']) + + return {'found': True, 'message': '', 'path': task_data_path} + +def main(): + import sys + from optparse import OptionParser + + # Check the invocation arguments + parser = OptionParser('%prog [options]', + description='get path for otdb_id/mom_id/radb_id') + parser.add_option('-o', '--otdb_id', dest='otdb_id', type='int', default=None, help='otdb_id of task to get the path for') + parser.add_option('-m', '--mom_id', dest='mom_id', type='int', default=None, help='mom_id of task to get the path for') + parser.add_option('-r', '--radb_id', dest='radb_id', type='int', default=None, help='radb_id of task to get the path for') + parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') + parser.add_option("--mountpoint", dest="mountpoint", type="string", default=CEP4_DATA_MOUNTPOINT, help="path of local cep4 mount point, default: %default") + parser.add_option("--radb_busname", dest="radb_busname", type="string", default=RADB_BUSNAME, help="Name of the bus on which the RADB service listens, default: %default") + parser.add_option("--radb_servicename", dest="radb_servicename", type="string", default=RADB_SERVICENAME, help="Name of the RADB service, default: %default") + parser.add_option("--mom_busname", dest="mom_busname", type="string", default=DEFAULT_MOMQUERY_BUSNAME, help="Name of the bus on which the MoM service listens, default: %default") + parser.add_option("--mom_servicename", dest="mom_servicename", type="string", default=DEFAULT_MOMQUERY_SERVICENAME, help="Name of the MoM service, default: %default") + parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') + (options, args) = parser.parse_args() + + if not (options.otdb_id or options.mom_id or options.radb_id): + parser.print_help() + exit(1) + + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.INFO if options.verbose else logging.WARN) + + with PathResolver(radb_busname=options.radb_busname, radb_servicename=options.radb_servicename, mom_busname=options.mom_busname, mom_servicename=options.mom_servicename, broker=options.broker) as path_resolver: + path_result = path_resolver._getPath(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + if path_result['found']: + path = path_result['path'] + print "Path: '%s'" % (path) + exit(0) + + print "Unable to find path" + exit(1) + +if __name__ == '__main__': + main() -- GitLab From 60efadcb170ba13e46bb3139e7026c530b45e942 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 23 Jun 2016 11:47:35 +0000 Subject: [PATCH 412/933] Task #9350: made common messagehandler with pathresolver --- .gitattributes | 1 + SAS/DataManagement/CleanupService/service.py | 49 +++++++++---------- .../DataManagementCommon/CMakeLists.txt | 1 + .../DataManagementCommon/messagehandler.py | 40 +++++++++++++++ .../DataManagementCommon/path.py | 1 + 5 files changed, 66 insertions(+), 26 deletions(-) create mode 100644 SAS/DataManagement/DataManagementCommon/messagehandler.py diff --git a/.gitattributes b/.gitattributes index 56e59fc1dcc..914ea2e774d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4745,6 +4745,7 @@ SAS/DataManagement/DataManagementCommon/CMakeLists.txt -text SAS/DataManagement/DataManagementCommon/__init__.py -text SAS/DataManagement/DataManagementCommon/config.py -text SAS/DataManagement/DataManagementCommon/getPathForTask -text +SAS/DataManagement/DataManagementCommon/messagehandler.py -text SAS/DataManagement/DataManagementCommon/path.py -text SAS/DataManagement/StorageQueryService/CMakeLists.txt -text SAS/DataManagement/StorageQueryService/__init__.py -text diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index e01f9285d67..cbd92cc36cf 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -9,10 +9,9 @@ from shutil import rmtree from optparse import OptionParser from lofar.messaging import Service from lofar.messaging import setQpidLogLevel -from lofar.messaging.Service import MessageHandlerInterface from lofar.common.util import waitForInterrupt -from lofar.sas.datamanagement.common.path import PathResolver +from lofar.sas.datamanagement.common.messagehandler import MessageHandler from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME @@ -21,29 +20,27 @@ from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_M logger = logging.getLogger(__name__) -class CleanupHandler(MessageHandlerInterface): - def __init__(self, **kwargs): - super(CleanupHandler, self).__init__(**kwargs) - self.service2MethodMap = {'RemovePath': self._removePath, - 'GetPathForOTDBId': self._getPathForOTDBId, - 'RemoveTaskData': self._removeTaskData} - - self.path_resolver = PathResolver(mountpoint=kwargs.pop("mountpoint", CEP4_DATA_MOUNTPOINT), - radb_busname=kwargs.pop("radb_busname", RADB_BUSNAME), - radb_servicename=kwargs.pop("radb_servicename", RADB_SERVICENAME), - mom_busname=kwargs.pop("mom_busname", DEFAULT_MOMQUERY_BUSNAME), - mom_servicename=kwargs.pop("mom_servicename", DEFAULT_MOMQUERY_SERVICENAME), - broker=kwargs.pop("broker", None)) - - def prepare_loop(self): - self.path_resolver.open() - logger.info("Cleanup service started with projects_path=%s", self.path_resolver.projects_path) - - def finalize_loop(self): - self.path_resolver.close() - - def _getPathForOTDBId(self, otdb_id): - return self.path_resolver.getPathForOTDBId(otdb_id) +class CleanupHandler(MessageHandler): + def __init__(self, + mountpoint=CEP4_DATA_MOUNTPOINT, + radb_busname=RADB_BUSNAME, + radb_servicename=RADB_SERVICENAME, + mom_busname=DEFAULT_MOMQUERY_BUSNAME, + mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, + broker=None, + **kwargs): + + super(CleanupHandler, self).__init__(mountpoint=mountpoint, + radb_busname=radb_busname, + radb_servicename=radb_servicename, + mom_busname=mom_busname, + mom_servicename=mom_servicename, + broker=broker, + **kwargs) + + self.service2MethodMap = { 'GetPathForOTDBId': self.path_resolver.getPathForOTDBId, + 'RemovePath': self._removePath, + 'RemoveTaskData': self._removeTaskData } def _removeTaskData(self, otdb_id): logger.info("Remove task data for otdb_id %s" % (otdb_id,)) @@ -53,7 +50,7 @@ class CleanupHandler(MessageHandlerInterface): logger.error(message) return {'deleted': False, 'message': message} - result = self._getPathForOTDBId(otdb_id) + result = self.path_resolver.getPathForOTDBId(otdb_id) if result['found']: return self._removePath(result['path']) diff --git a/SAS/DataManagement/DataManagementCommon/CMakeLists.txt b/SAS/DataManagement/DataManagementCommon/CMakeLists.txt index 7740f62346c..8e7a20d457c 100644 --- a/SAS/DataManagement/DataManagementCommon/CMakeLists.txt +++ b/SAS/DataManagement/DataManagementCommon/CMakeLists.txt @@ -9,6 +9,7 @@ set(_py_files __init__.py config.py path.py + messagehandler.py ) lofar_add_bin_scripts(getPathForTask) diff --git a/SAS/DataManagement/DataManagementCommon/messagehandler.py b/SAS/DataManagement/DataManagementCommon/messagehandler.py new file mode 100644 index 00000000000..78737e13de0 --- /dev/null +++ b/SAS/DataManagement/DataManagementCommon/messagehandler.py @@ -0,0 +1,40 @@ +#!/usr/bin/python + +import logging + +from lofar.messaging.Service import MessageHandlerInterface + +from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT +from lofar.sas.datamanagement.common.path import PathResolver +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME +from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME + +logger = logging.getLogger(__name__) + +class MessageHandler(MessageHandlerInterface): + def __init__(self, + mountpoint=CEP4_DATA_MOUNTPOINT, + radb_busname=RADB_BUSNAME, + radb_servicename=RADB_SERVICENAME, + mom_busname=DEFAULT_MOMQUERY_BUSNAME, + mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, + broker=None, + **kwargs): + super(MessageHandler, self).__init__(**kwargs) + + self.path_resolver = PathResolver(mountpoint=mountpoint, + radb_busname=radb_busname, + radb_servicename=radb_servicename, + mom_busname=mom_busname, + mom_servicename=mom_servicename, + broker=broker) + + def prepare_loop(self): + self.path_resolver.open() + logger.info("service started with projects_path=%s", self.path_resolver.projects_path) + + def finalize_loop(self): + self.path_resolver.close() + + diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index f5fa2f03bb2..165ddc41af5 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -88,6 +88,7 @@ class PathResolver: project_path = os.path.join(self.projects_path, "_".join(project_name.split())) task_data_path = os.path.join(project_path, 'L%s' % task['otdb_id']) + logger.info("Found path '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (task_data_path, task['otdb_id'], task['mom_id'], task['id'])) return {'found': True, 'message': '', 'path': task_data_path} -- GitLab From 9b4f66a19d3bb0aa2088b2c0a6271cc4801b1db2 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 23 Jun 2016 13:33:49 +0000 Subject: [PATCH 413/933] Task #9350: implemented storagequeryservice which gets the diskusage for an otdb_id --- SAS/DataManagement/CleanupService/service.py | 2 +- .../StorageQueryService/CMakeLists.txt | 2 +- SAS/DataManagement/StorageQueryService/rpc.py | 19 +++- .../StorageQueryService/service.py | 106 ++++++++++++++++-- 4 files changed, 111 insertions(+), 18 deletions(-) diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index cbd92cc36cf..5fd540e30bc 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -125,7 +125,7 @@ def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, brok def main(): # Check the invocation arguments parser = OptionParser("%prog [options]", - description='runs the resourceassignment database service') + description='runs the cleanup service') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') parser.add_option("-b", "--busname", dest="busname", type="string", default=DEFAULT_BUSNAME, help="Name of the bus exchange on the qpid broker, default: %default") parser.add_option("-s", "--servicename", dest="servicename", type="string", default=DEFAULT_SERVICENAME, help="Name for this service, default: default %default") diff --git a/SAS/DataManagement/StorageQueryService/CMakeLists.txt b/SAS/DataManagement/StorageQueryService/CMakeLists.txt index 088a8038032..1bf03b6fb94 100644 --- a/SAS/DataManagement/StorageQueryService/CMakeLists.txt +++ b/SAS/DataManagement/StorageQueryService/CMakeLists.txt @@ -1,6 +1,6 @@ # $Id$ -lofar_package(StorageQueryService 1.0 DEPENDS PyMessaging MoMQueryService) +lofar_package(StorageQueryService 1.0 DEPENDS PyMessaging MoMQueryService DataManagementCommon) lofar_find_package(Python 2.6 REQUIRED) include(PythonInstall) diff --git a/SAS/DataManagement/StorageQueryService/rpc.py b/SAS/DataManagement/StorageQueryService/rpc.py index ba2d3e7717b..4839501c7d2 100644 --- a/SAS/DataManagement/StorageQueryService/rpc.py +++ b/SAS/DataManagement/StorageQueryService/rpc.py @@ -3,6 +3,7 @@ import logging from lofar.messaging.RPC import RPC, RPCException, RPCWrapper from lofar.sas.datamanagement.storagequery.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME +from lofar.common.util import humanreadablesize logger = logging.getLogger(__name__) @@ -10,10 +11,10 @@ class StorageQueryRPC(RPCWrapper): def __init__(self, busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None): - super(StorageQueryRPC, self).__init__(busname, servicename, broker) + super(StorageQueryRPC, self).__init__(busname, servicename, broker, timeout=60) - def foo(self): - return self.rpc('foo') + def getDiskUsageForOTDBId(self, otdb_id): + return self.rpc('GetDiskUsageForOTDBId', otdb_id=otdb_id) def main(): import sys @@ -22,14 +23,24 @@ def main(): # Check the invocation arguments parser = OptionParser('%prog [options]', description='do storage queries (on cep4) from the commandline') + parser.add_option('-o', '--otdb_id', dest='otdb_id', type='int', default=None, help='otdb_id of task to get the disk usage for') + parser.add_option('-m', '--mom_id', dest='mom_id', type='int', default=None, help='mom_id of task to get the disk usage for') + parser.add_option('-r', '--radb_id', dest='radb_id', type='int', default=None, help='radb_id of task to get the disk usage for') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') parser.add_option('-b', '--busname', dest='busname', type='string', default=DEFAULT_BUSNAME, help='Name of the bus exchange on the qpid broker, default: %s' % DEFAULT_BUSNAME) parser.add_option('-s', '--servicename', dest='servicename', type='string', default=DEFAULT_SERVICENAME, help='Name for this service, default: %s' % DEFAULT_SERVICENAME) parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() + if not (options.otdb_id or options.mom_id or options.radb_id): + parser.print_help() + exit(1) + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO if options.verbose else logging.WARN) with StorageQueryRPC(busname=options.busname, servicename=options.servicename, broker=options.broker) as rpc: - print rpc.foo() + result = rpc.getDiskUsageForOTDBId(options.otdb_id) + if 'disk_usage' in result: + result['disk_usage_readable'] = humanreadablesize(result['disk_usage']) + print result diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py index 5ea343f93fe..16d6e963f4d 100644 --- a/SAS/DataManagement/StorageQueryService/service.py +++ b/SAS/DataManagement/StorageQueryService/service.py @@ -2,44 +2,122 @@ # $Id$ import logging +import subprocess +import socket from optparse import OptionParser from lofar.messaging import Service from lofar.messaging import setQpidLogLevel -from lofar.messaging.Service import MessageHandlerInterface from lofar.common.util import waitForInterrupt -from lofar.common.util import convertIntKeysToString + +from lofar.sas.datamanagement.common.messagehandler import MessageHandler +from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT from lofar.sas.datamanagement.storagequery.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME +from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME logger = logging.getLogger(__name__) -class StorageQueryHandler(MessageHandlerInterface): - def __init__(self, **kwargs): - super(StorageQueryHandler, self).__init__(**kwargs) +class StorageQueryHandler(MessageHandler): + def __init__(self, + mountpoint=CEP4_DATA_MOUNTPOINT, + radb_busname=RADB_BUSNAME, + radb_servicename=RADB_SERVICENAME, + mom_busname=DEFAULT_MOMQUERY_BUSNAME, + mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, + broker=None, + **kwargs): + + super(StorageQueryHandler, self).__init__(mountpoint=mountpoint, + radb_busname=radb_busname, + radb_servicename=radb_servicename, + mom_busname=mom_busname, + mom_servicename=mom_servicename, + broker=broker, + **kwargs) - self.service2MethodMap = {'foo': self._foo} + self.service2MethodMap = {'GetDiskUsageForOTDBId': self.getDiskUsageForOTDBId} def prepare_loop(self): pass - def _foo(self): - return 'foo' + def getDiskUsageForPath(self, path): + cmd = ['rbh-du', '-bd', path] + hostname = socket.gethostname() + if not 'mgmt0' in hostname: + cmd = ['ssh', '-AXt', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd + logger.info(' '.join(cmd)) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + + if proc.returncode != 0: + logger.error(err) + return {'found': False, 'path': path, 'message': err} + + logger.info(out) + + # example of out + # Using config file '/etc/robinhood.d/tmpfs/tmp_fs_mgr_basic.conf'. + # /data/projects/2016LOFAROBS/L522380 + # dir count:3906, size:16048128, spc_used:16052224 + # file count:17568, size:42274164368, spc_used:42327519232 + + #parse out + lines = [l.strip() for l in out.split('\n')] + file_line = next(l for l in lines if 'file count' in l) + if file_line: + parts = [p.strip() for p in file_line.split(',')] + partsDict = {p.split(':')[0].strip():p.split(':')[1].strip() for p in parts} + + results = {'found': True, 'disk_usage': None, 'path': path} + + if 'size' in partsDict: + results['disk_usage'] = int(partsDict['size']) + + if 'file count' in partsDict: + results['nr_of_files'] = int(partsDict['file count']) + + logger.info('returning: %s' % results) + return results + + results = {'found': False, 'path': path } + + def getDiskUsageForOTDBId(self, otdb_id): + result = self.path_resolver.getPathForOTDBId(otdb_id) + if result['found']: + return self.getDiskUsageForPath(result['path']) + + return {'found': False, 'path': result['path']} -def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, verbose=False): +def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, + mountpoint=CEP4_DATA_MOUNTPOINT, verbose=False, + radb_busname=RADB_BUSNAME, radb_servicename=RADB_SERVICENAME, + mom_busname=DEFAULT_MOMQUERY_BUSNAME, mom_servicename=DEFAULT_MOMQUERY_SERVICENAME): return Service(servicename, StorageQueryHandler, busname=busname, broker=broker, use_service_methods=True, numthreads=2, - verbose=verbose) + verbose=verbose, + handler_args={'mountpoint': mountpoint, + 'radb_busname':RADB_BUSNAME, + 'radb_servicename':RADB_SERVICENAME, + 'mom_busname':DEFAULT_MOMQUERY_BUSNAME, + 'mom_servicename':DEFAULT_MOMQUERY_SERVICENAME, + 'broker':broker}) def main(): # Check the invocation arguments parser = OptionParser("%prog [options]", - description='runs the resourceassignment database service') + description='runs the storagequery service') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') parser.add_option("-b", "--busname", dest="busname", type="string", default=DEFAULT_BUSNAME, help="Name of the bus exchange on the qpid broker, default: %s" % DEFAULT_BUSNAME) parser.add_option("-s", "--servicename", dest="servicename", type="string", default=DEFAULT_SERVICENAME, help="Name for this service, default: %s" % DEFAULT_SERVICENAME) + parser.add_option("--radb_busname", dest="radb_busname", type="string", default=RADB_BUSNAME, help="Name of the bus on which the RADB service listens, default: %default") + parser.add_option("--radb_servicename", dest="radb_servicename", type="string", default=RADB_SERVICENAME, help="Name of the RADB service, default: %default") + parser.add_option("--mom_busname", dest="mom_busname", type="string", default=DEFAULT_MOMQUERY_BUSNAME, help="Name of the bus on which the MoM service listens, default: %default") + parser.add_option("--mom_servicename", dest="mom_servicename", type="string", default=DEFAULT_MOMQUERY_SERVICENAME, help="Name of the MoM service, default: %default") parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() @@ -50,7 +128,11 @@ def main(): with createService(busname=options.busname, servicename=options.servicename, broker=options.broker, - verbose=options.verbose): + verbose=options.verbose, + radb_busname=options.radb_busname, + radb_servicename=options.radb_servicename, + mom_busname=options.mom_busname, + mom_servicename=options.mom_busname): waitForInterrupt() if __name__ == '__main__': -- GitLab From 0eeb6fe747994ab07699e95562228d3259229d89 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 23 Jun 2016 13:47:07 +0000 Subject: [PATCH 414/933] Task #9350: always log output --- SAS/DataManagement/StorageQueryService/service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py index 16d6e963f4d..4424d48c850 100644 --- a/SAS/DataManagement/StorageQueryService/service.py +++ b/SAS/DataManagement/StorageQueryService/service.py @@ -50,12 +50,12 @@ class StorageQueryHandler(MessageHandler): proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() + logger.info(out) + if proc.returncode != 0: logger.error(err) return {'found': False, 'path': path, 'message': err} - logger.info(out) - # example of out # Using config file '/etc/robinhood.d/tmpfs/tmp_fs_mgr_basic.conf'. # /data/projects/2016LOFAROBS/L522380 -- GitLab From fb90bdc56a29f0f1de4cddfc6a1aa2cc5535dee8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 23 Jun 2016 13:47:31 +0000 Subject: [PATCH 415/933] Task #9350: expose diskusage via rest api --- .../lib/webservice.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 40d6c0d87dc..00e14f50d82 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -55,7 +55,11 @@ from lofar.sas.resourceassignment.resourceassignmenteditor.mom import updateTask from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME as DEFAULT_CLEANUP_BUSNAME from lofar.sas.datamanagement.cleanup.config import DEFAULT_SERVICENAME as DEFAULT_CLEANUP_SERVICENAME +from lofar.sas.datamanagement.storagequery.rpc import StorageQueryRPC +from lofar.sas.datamanagement.storagequery.config import DEFAULT_BUSNAME as DEFAULT_STORAGEQUERY_BUSNAME +from lofar.sas.datamanagement.storagequery.config import DEFAULT_SERVICENAME as DEFAULT_STORAGEQUERY_SERVICENAME from lofar.common import isProductionEnvironment, isTestEnvironment +from lofar.common.util import humanreadablesize #from lofar.sas.resourceassignment.resourceassigner. import updateTaskMomDetails logger = logging.getLogger(__name__) @@ -100,6 +104,7 @@ app.json_encoder = CustomJSONEncoder rarpc = None momrpc = None curpc = None +sqrpc = None momqueryrpc = None radbchangeshandler = None @@ -287,6 +292,25 @@ def getTaskDataPath(task_id): return jsonify({'datapath': result['path']}) abort(404, result['message']) +@app.route('/rest/tasks/<int:task_id>/diskusage', methods=['GET']) +def getTaskDiskUsage(task_id): + try: + task = rarpc.getTask(task_id) + + if not task: + abort(404, 'No such task (id=%s)' % task_id) + + result = sqrpc.getDiskUsageForOTDBId(task['otdb_id']) + except Exception as e: + abort(500, str(e)) + + if result['found']: + del result['found'] + if 'disk_usage' in result: + result['disk_usage_readable'] = humanreadablesize(result['disk_usage']) + return jsonify(result) + abort(404, result['message']) + @app.route('/rest/tasks/<int:task_id>/copy', methods=['PUT']) def copyTask(task_id): if isProductionEnvironment(): @@ -400,6 +424,8 @@ def main(): parser.add_option('--mom_query_servicename', dest='mom_query_servicename', type='string', default=DEFAULT_MOMQUERY_SERVICENAME, help='Name of the momqueryservice, default: %default') parser.add_option('--cleanup_busname', dest='cleanup_busname', type='string', default=DEFAULT_CLEANUP_BUSNAME, help='Name of the bus exchange on the qpid broker on which the cleanupservice listens, default: %default') parser.add_option('--cleanup_servicename', dest='cleanup_servicename', type='string', default=DEFAULT_CLEANUP_SERVICENAME, help='Name of the cleanupservice, default: %default') + parser.add_option('--storagequery_busname', dest='storagequery_busname', type='string', default=DEFAULT_STORAGEQUERY_BUSNAME, help='Name of the bus exchange on the qpid broker on which the storagequeryservice listens, default: %default') + parser.add_option('--storagequery_servicename', dest='storagequery_servicename', type='string', default=DEFAULT_STORAGEQUERY_SERVICENAME, help='Name of the storagequeryservice, default: %default') parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() @@ -412,12 +438,14 @@ def main(): momrpc = MoMRPC(busname=options.mom_busname, servicename=options.mom_servicename, timeout=2.5, broker=options.broker) global curpc curpc = CleanupRPC(busname=options.cleanup_busname, servicename=options.cleanup_servicename, broker=options.broker) + global sqrpc + sqrpc = StorageQueryRPC(busname=options.storagequery_busname, servicename=options.storagequery_servicename, broker=options.broker) global momqueryrpc momqueryrpc = MoMQueryRPC(busname=options.mom_query_busname, servicename=options.mom_query_servicename, timeout=2.5, broker=options.broker) global radbchangeshandler radbchangeshandler = RADBChangesHandler(options.radb_notification_busname, subjects=options.radb_notification_subjects, broker=options.broker, momqueryrpc=momqueryrpc) - with radbchangeshandler, rarpc, curpc, momrpc, momqueryrpc: + with radbchangeshandler, rarpc, curpc, sqrpc, momrpc, momqueryrpc: '''Start the webserver''' app.run(debug=options.verbose, threaded=True, host='0.0.0.0', port=options.port) -- GitLab From 76115bd119fab7e7edc5bb03da998ced50e614ec Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 24 Jun 2016 08:39:05 +0000 Subject: [PATCH 416/933] Task #9350: added various methods and cmdline options to get sub directories and project path info --- .../DataManagementCommon/path.py | 120 +++++++++++++++--- 1 file changed, 104 insertions(+), 16 deletions(-) diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index 165ddc41af5..8bb91fb2a23 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -3,6 +3,8 @@ import os import os.path import logging +import socket +import subprocess from lofar.common import isProductionEnvironment, isTestEnvironment @@ -49,18 +51,31 @@ class PathResolver: def getPathForRADBId(self, radb_id): logger.info("Get path for radb_id %s" % (radb_id,)) - return self._getPath(radb_id=radb_id) + return self.getPathForTask(radb_id=radb_id) def getPathForMoMId(self, mom_id): logger.info("Get path for mom_id %s" % (mom_id,)) - return self._getPath(mom_id=mom_id) + return self.getPathForTask(mom_id=mom_id) def getPathForOTDBId(self, otdb_id): logger.info("Get path for otdb_id %s" % (otdb_id,)) - return self._getPath(otdb_id=otdb_id) + return self.getPathForTask(otdb_id=otdb_id) - def _getPath(self, radb_id=None, mom_id=None, otdb_id=None): - '''get a task for either the given radb_id, or for the given mom_id, or for the given otdb_id''' + def getPathForTask(self, radb_id=None, mom_id=None, otdb_id=None): + '''get the path for a task for either the given radb_id, or for the given mom_id, or for the given otdb_id''' + result = self._getProjectPathAndDetails(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + if result['found']: + project_path = result['path'] + task = result['task'] + task_data_path = os.path.join(project_path, 'L%s' % task['otdb_id']) + logger.info("Found path '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (task_data_path, task['otdb_id'], task['mom_id'], task['id'])) + + return {'found': True, 'message': '', 'path': task_data_path} + + return result + + def _getProjectPathAndDetails(self, radb_id=None, mom_id=None, otdb_id=None): + '''get the project path and details of a task for either the given radb_id, or for the given mom_id, or for the given otdb_id''' ids = [radb_id, mom_id, otdb_id] validIds = [x for x in ids if x != None and isinstance(x, int)] @@ -87,10 +102,62 @@ class PathResolver: logger.info("Found project '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (project_name, task['otdb_id'], task['mom_id'], task['id'])) project_path = os.path.join(self.projects_path, "_".join(project_name.split())) - task_data_path = os.path.join(project_path, 'L%s' % task['otdb_id']) - logger.info("Found path '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (task_data_path, task['otdb_id'], task['mom_id'], task['id'])) + return {'found': True, 'path': project_path, 'mom_details':mom_details, 'task':task} + + def getProjectPath(self, radb_id=None, mom_id=None, otdb_id=None): + result = self._getProjectPathAndDetails(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + + if result['found']: + del result['mom_details'] + del result['task'] + + return result + + def getProjectSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): + '''get the project subdirectories of a task's project for either the given radb_id, or for the given mom_id, or for the given otdb_id''' + result = self.getProjectPath(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + if result['found']: + return self.getSubDirectories(result['path']) + return result + + def getSubDirectoriesForOTDBId(self, otdb_id): + return self.getSubDirectoriesForTask(otdb_id=otdb_id) - return {'found': True, 'message': '', 'path': task_data_path} + def getSubDirectoriesForMoMId(self, mom_id): + return self.getSubDirectoriesForTask(mom_id=mom_id) + + def getSubDirectoriesForRADBId(self, radb_id): + return self.getSubDirectoriesForTask(radb_id=radb_id) + + def getSubDirectoriesForTask(self, radb_id=None, mom_id=None, otdb_id=None): + result = self.getPathForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + if result['found']: + return self.getSubDirectories(result['path']) + return result + + def getSubDirectories(self, path): + # get the subdirectories of the given path + cmd = ['lfs', 'ls', '-l', path] + hostname = socket.gethostname() + if not 'mgmt0' in hostname: + cmd = ['ssh', '-AXt', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd + logger.info(' '.join(cmd)) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + + if proc.returncode != 0: + # lfs puts it's error message in stdout + logger.error(out) + return {'found': False, 'path': path, 'message': out} + + logger.debug(out) + + # parse out + lines = [l.strip() for l in out.split('\n')] + dir_lines = [l for l in lines if l.startswith('drwx')] + dir_names = [l.split(' ')[-1].strip() for l in dir_lines] + + return {'found': True, 'path': path, 'sub_directories': dir_names} def main(): import sys @@ -99,6 +166,9 @@ def main(): # Check the invocation arguments parser = OptionParser('%prog [options]', description='get path for otdb_id/mom_id/radb_id') + parser.add_option('-p', '--path', dest='path', action='store_true', help='get the path for the given otdb_id/mom_id/radb_id') + parser.add_option('-P', '--project', dest='project', action='store_true', help='get the project path and all its sub directories for the given otdb_id/mom_id/radb_id') + parser.add_option('-s', '--subdirs', dest='subdirs', action='store_true', help='get the sub directories of the path for the given otdb_id/mom_id/radb_id') parser.add_option('-o', '--otdb_id', dest='otdb_id', type='int', default=None, help='otdb_id of task to get the path for') parser.add_option('-m', '--mom_id', dest='mom_id', type='int', default=None, help='mom_id of task to get the path for') parser.add_option('-r', '--radb_id', dest='radb_id', type='int', default=None, help='radb_id of task to get the path for') @@ -119,14 +189,32 @@ def main(): level=logging.INFO if options.verbose else logging.WARN) with PathResolver(radb_busname=options.radb_busname, radb_servicename=options.radb_servicename, mom_busname=options.mom_busname, mom_servicename=options.mom_servicename, broker=options.broker) as path_resolver: - path_result = path_resolver._getPath(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) - if path_result['found']: - path = path_result['path'] - print "Path: '%s'" % (path) - exit(0) - - print "Unable to find path" - exit(1) + + if options.path: + result = path_resolver.getPathForTask(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + if result['found']: + print "path: %s" % (result['path']) + else: + print result['message'] + exit(1) + + if options.project: + result = path_resolver.getProjectSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + if result['found']: + print "projectpath: %s" % (result['path']) + print "subdirectories: %s" % (' '.join(result['sub_directories'])) + else: + print result['message'] + exit(1) + + if options.subdirs: + result = path_resolver.getSubDirectoriesForTask(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + if result['found']: + print "path: %s" % (result['path']) + print "subdirectories: %s" % (' '.join(result['sub_directories'])) + else: + print result['message'] + exit(1) if __name__ == '__main__': main() -- GitLab From 3a02692eaff5e338888e3aa0368bef3ee6042b39 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 24 Jun 2016 09:59:45 +0000 Subject: [PATCH 417/933] Task #9192: added missing dependencies in cmake file --- SAS/ResourceAssignment/ResourceAssignmentEditor/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/CMakeLists.txt index 62f06532429..b02ba541d0f 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/CMakeLists.txt @@ -1,9 +1,8 @@ # $Id: CMakeLists.txt 30355 2014-11-04 13:46:05Z loose $ -lofar_package(ResourceAssignmentEditor 0.1) +lofar_package(ResourceAssignmentEditor 1.0 DEPENDS MoMQueryService ResourceAssignmentService PyMessaging) include(PythonInstall) -set(USE_PYTHON_COMPILATION Off) add_subdirectory(lib) add_subdirectory(bin) -- GitLab From db7cfe193608ba3c1e45c1d5eb47e1f90b35384e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 24 Jun 2016 10:26:44 +0000 Subject: [PATCH 418/933] Task #9350: factored out diskusage in seperate module --- .gitattributes | 1 + .../StorageQueryService/CMakeLists.txt | 1 + .../StorageQueryService/diskusage.py | 79 +++++++++++++++++++ SAS/DataManagement/StorageQueryService/rpc.py | 13 ++- .../StorageQueryService/service.py | 44 +---------- 5 files changed, 93 insertions(+), 45 deletions(-) create mode 100644 SAS/DataManagement/StorageQueryService/diskusage.py diff --git a/.gitattributes b/.gitattributes index 914ea2e774d..404af8e4163 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4750,6 +4750,7 @@ SAS/DataManagement/DataManagementCommon/path.py -text SAS/DataManagement/StorageQueryService/CMakeLists.txt -text SAS/DataManagement/StorageQueryService/__init__.py -text SAS/DataManagement/StorageQueryService/config.py -text +SAS/DataManagement/StorageQueryService/diskusage.py -text SAS/DataManagement/StorageQueryService/rpc.py -text SAS/DataManagement/StorageQueryService/service.py -text SAS/DataManagement/StorageQueryService/storagequery -text diff --git a/SAS/DataManagement/StorageQueryService/CMakeLists.txt b/SAS/DataManagement/StorageQueryService/CMakeLists.txt index 1bf03b6fb94..a7f354d838c 100644 --- a/SAS/DataManagement/StorageQueryService/CMakeLists.txt +++ b/SAS/DataManagement/StorageQueryService/CMakeLists.txt @@ -10,6 +10,7 @@ set(_py_files config.py rpc.py service.py + diskusage.py ) python_install(${_py_files} DESTINATION lofar/sas/datamanagement/storagequery) diff --git a/SAS/DataManagement/StorageQueryService/diskusage.py b/SAS/DataManagement/StorageQueryService/diskusage.py new file mode 100644 index 00000000000..5d4abb47bfc --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/diskusage.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# $Id$ + +import logging +import subprocess +import socket +from optparse import OptionParser +from lofar.common.util import humanreadablesize + +logger = logging.getLogger(__name__) + +def getDiskUsageForPath(path): + cmd = ['rbh-du', '-bd', path] + hostname = socket.gethostname() + if not 'mgmt0' in hostname: + cmd = ['ssh', '-AXt', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd + logger.info(' '.join(cmd)) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + + logger.info(out) + + if proc.returncode != 0: + logger.error(out) + return {'found': False, 'path': path, 'message': out} + + # example of out + # Using config file '/etc/robinhood.d/tmpfs/tmp_fs_mgr_basic.conf'. + # /data/projects/2016LOFAROBS/L522380 + # dir count:3906, size:16048128, spc_used:16052224 + # file count:17568, size:42274164368, spc_used:42327519232 + + #parse out + lines = [l.strip() for l in out.split('\n')] + file_line = next(l for l in lines if 'file count' in l) + if file_line: + parts = [p.strip() for p in file_line.split(',')] + partsDict = {p.split(':')[0].strip():p.split(':')[1].strip() for p in parts} + + result = {'found': True, 'disk_usage': None, 'path': path} + + if 'size' in partsDict: + result['disk_usage'] = int(partsDict['size']) + + if 'file count' in partsDict: + result['nr_of_files'] = int(partsDict['file count']) + + result['disk_usage_readable'] = humanreadablesize(result['disk_usage']) + logger.info('returning: %s' % result) + return result + + result = {'found': False, 'path': path } + +def main(): + # Check the invocation arguments + parser = OptionParser("%prog [options] <path>", + description='get disk usage for (cep4) path') + parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') + (options, args) = parser.parse_args() + + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.INFO if options.verbose else logging.WARN) + + if len(args) == 0: + parser.print_help() + exit(1) + + result = getDiskUsageForPath(args[0]) + + if result['found']: + print 'path %s' % result['path'] + print 'disk_usage %s %s' % (result['disk_usage'], result['disk_usage_readbale']) + print 'nr_of_files %s' % result['nr_of_files'] + else: + print result['message'] + exit(1) + +if __name__ == '__main__': + main() diff --git a/SAS/DataManagement/StorageQueryService/rpc.py b/SAS/DataManagement/StorageQueryService/rpc.py index 4839501c7d2..c96071c843a 100644 --- a/SAS/DataManagement/StorageQueryService/rpc.py +++ b/SAS/DataManagement/StorageQueryService/rpc.py @@ -41,6 +41,13 @@ def main(): with StorageQueryRPC(busname=options.busname, servicename=options.servicename, broker=options.broker) as rpc: result = rpc.getDiskUsageForOTDBId(options.otdb_id) - if 'disk_usage' in result: - result['disk_usage_readable'] = humanreadablesize(result['disk_usage']) - print result + if result['found']: + print 'path %s' % result['path'] + print 'disk_usage %s %s' % (result['disk_usage'], result['disk_usage_readable']) + print 'nr_of_files %s' % result['nr_of_files'] + else: + print result['message'] + exit(1) + +if __name__ == '__main__': + main() diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py index 4424d48c850..497a82cea40 100644 --- a/SAS/DataManagement/StorageQueryService/service.py +++ b/SAS/DataManagement/StorageQueryService/service.py @@ -11,6 +11,7 @@ from lofar.common.util import waitForInterrupt from lofar.sas.datamanagement.common.messagehandler import MessageHandler from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT +from lofar.sas.datamanagement.storagequery.diskusage import getDiskUsageForPath from lofar.sas.datamanagement.storagequery.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME @@ -41,51 +42,10 @@ class StorageQueryHandler(MessageHandler): def prepare_loop(self): pass - def getDiskUsageForPath(self, path): - cmd = ['rbh-du', '-bd', path] - hostname = socket.gethostname() - if not 'mgmt0' in hostname: - cmd = ['ssh', '-AXt', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd - logger.info(' '.join(cmd)) - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = proc.communicate() - - logger.info(out) - - if proc.returncode != 0: - logger.error(err) - return {'found': False, 'path': path, 'message': err} - - # example of out - # Using config file '/etc/robinhood.d/tmpfs/tmp_fs_mgr_basic.conf'. - # /data/projects/2016LOFAROBS/L522380 - # dir count:3906, size:16048128, spc_used:16052224 - # file count:17568, size:42274164368, spc_used:42327519232 - - #parse out - lines = [l.strip() for l in out.split('\n')] - file_line = next(l for l in lines if 'file count' in l) - if file_line: - parts = [p.strip() for p in file_line.split(',')] - partsDict = {p.split(':')[0].strip():p.split(':')[1].strip() for p in parts} - - results = {'found': True, 'disk_usage': None, 'path': path} - - if 'size' in partsDict: - results['disk_usage'] = int(partsDict['size']) - - if 'file count' in partsDict: - results['nr_of_files'] = int(partsDict['file count']) - - logger.info('returning: %s' % results) - return results - - results = {'found': False, 'path': path } - def getDiskUsageForOTDBId(self, otdb_id): result = self.path_resolver.getPathForOTDBId(otdb_id) if result['found']: - return self.getDiskUsageForPath(result['path']) + return getDiskUsageForPath(result['path']) return {'found': False, 'path': result['path']} -- GitLab From 7b65310a243bd383e2a9df47e28debac470365d3 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 24 Jun 2016 13:21:37 +0000 Subject: [PATCH 419/933] Task #9350: added various methods to get the disk usage of task/project/subdirs --- .../DataManagementCommon/path.py | 4 +- .../StorageQueryService/diskusage.py | 10 ++-- SAS/DataManagement/StorageQueryService/rpc.py | 54 +++++++++++++++---- .../StorageQueryService/service.py | 52 +++++++++++++++++- 4 files changed, 103 insertions(+), 17 deletions(-) diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index 8bb91fb2a23..dfb75d94cbd 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -157,7 +157,9 @@ class PathResolver: dir_lines = [l for l in lines if l.startswith('drwx')] dir_names = [l.split(' ')[-1].strip() for l in dir_lines] - return {'found': True, 'path': path, 'sub_directories': dir_names} + result = {'found': True, 'path': path, 'sub_directories': dir_names} + logger.info('result: %s' % result) + return result def main(): import sys diff --git a/SAS/DataManagement/StorageQueryService/diskusage.py b/SAS/DataManagement/StorageQueryService/diskusage.py index 5d4abb47bfc..d8be95762f6 100644 --- a/SAS/DataManagement/StorageQueryService/diskusage.py +++ b/SAS/DataManagement/StorageQueryService/diskusage.py @@ -15,10 +15,11 @@ def getDiskUsageForPath(path): if not 'mgmt0' in hostname: cmd = ['ssh', '-AXt', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd logger.info(' '.join(cmd)) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() - logger.info(out) + logger.debug(out) if proc.returncode != 0: logger.error(out) @@ -46,10 +47,11 @@ def getDiskUsageForPath(path): result['nr_of_files'] = int(partsDict['file count']) result['disk_usage_readable'] = humanreadablesize(result['disk_usage']) - logger.info('returning: %s' % result) - return result + else: + result = {'found': False, 'path': path } - result = {'found': False, 'path': path } + logger.info('returning: %s' % result) + return result def main(): # Check the invocation arguments diff --git a/SAS/DataManagement/StorageQueryService/rpc.py b/SAS/DataManagement/StorageQueryService/rpc.py index c96071c843a..2931868c86e 100644 --- a/SAS/DataManagement/StorageQueryService/rpc.py +++ b/SAS/DataManagement/StorageQueryService/rpc.py @@ -11,11 +11,26 @@ class StorageQueryRPC(RPCWrapper): def __init__(self, busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None): - super(StorageQueryRPC, self).__init__(busname, servicename, broker, timeout=60) + super(StorageQueryRPC, self).__init__(busname, servicename, broker, timeout=6000) def getDiskUsageForOTDBId(self, otdb_id): return self.rpc('GetDiskUsageForOTDBId', otdb_id=otdb_id) + def getDiskUsageForMoMId(self, mom_id): + return self.rpc('GetDiskUsageForMoMId', mom_id=mom_id) + + def getDiskUsageForRADBId(self, radb_id): + return self.rpc('GetDiskUsageForRADBId', radb_id=radb_id) + + def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None): + return self.rpc('GetDiskUsageForTask', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + + def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): + return self.rpc('GetDiskUsageForTaskAndSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + + def getDiskUsageForProjectSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): + return self.rpc('GetDiskUsageForProjectSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + def main(): import sys from optparse import OptionParser @@ -26,9 +41,11 @@ def main(): parser.add_option('-o', '--otdb_id', dest='otdb_id', type='int', default=None, help='otdb_id of task to get the disk usage for') parser.add_option('-m', '--mom_id', dest='mom_id', type='int', default=None, help='mom_id of task to get the disk usage for') parser.add_option('-r', '--radb_id', dest='radb_id', type='int', default=None, help='radb_id of task to get the disk usage for') + parser.add_option('-s', '--subdirs', dest='subdirs', action='store_true', help='get the disk usage of the task and its sub directories for the given otdb_id/mom_id/radb_id') + parser.add_option('-P', '--project', dest='project', action='store_true', help='get the disk usage of the project path and all its sub directories for the given otdb_id/mom_id/radb_id') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') - parser.add_option('-b', '--busname', dest='busname', type='string', default=DEFAULT_BUSNAME, help='Name of the bus exchange on the qpid broker, default: %s' % DEFAULT_BUSNAME) - parser.add_option('-s', '--servicename', dest='servicename', type='string', default=DEFAULT_SERVICENAME, help='Name for this service, default: %s' % DEFAULT_SERVICENAME) + parser.add_option('--busname', dest='busname', type='string', default=DEFAULT_BUSNAME, help='Name of the bus exchange on the qpid broker, default: %s' % DEFAULT_BUSNAME) + parser.add_option('--servicename', dest='servicename', type='string', default=DEFAULT_SERVICENAME, help='Name for this service, default: %s' % DEFAULT_SERVICENAME) parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() @@ -40,14 +57,31 @@ def main(): level=logging.INFO if options.verbose else logging.WARN) with StorageQueryRPC(busname=options.busname, servicename=options.servicename, broker=options.broker) as rpc: - result = rpc.getDiskUsageForOTDBId(options.otdb_id) - if result['found']: - print 'path %s' % result['path'] - print 'disk_usage %s %s' % (result['disk_usage'], result['disk_usage_readable']) - print 'nr_of_files %s' % result['nr_of_files'] + if options.project: + result = rpc.getDiskUsageForProjectSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + if result['found']: + import pprint + pprint.pprint(result) + else: + print result['message'] + exit(1) + elif options.subdirs: + result = rpc.getDiskUsageForTaskAndSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + if result['found']: + import pprint + pprint.pprint(result) + else: + print result['message'] + exit(1) else: - print result['message'] - exit(1) + result = rpc.getDiskUsageForTask(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + if result['found']: + print 'path %s' % result['path'] + print 'disk_usage %s %s' % (result['disk_usage'], result['disk_usage_readable']) + print 'nr_of_files %s' % result['nr_of_files'] + else: + print result['message'] + exit(1) if __name__ == '__main__': main() diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py index 497a82cea40..86d2ab43d9f 100644 --- a/SAS/DataManagement/StorageQueryService/service.py +++ b/SAS/DataManagement/StorageQueryService/service.py @@ -4,6 +4,7 @@ import logging import subprocess import socket +import os.path from optparse import OptionParser from lofar.messaging import Service from lofar.messaging import setQpidLogLevel @@ -37,18 +38,65 @@ class StorageQueryHandler(MessageHandler): broker=broker, **kwargs) - self.service2MethodMap = {'GetDiskUsageForOTDBId': self.getDiskUsageForOTDBId} + self.service2MethodMap = {'GetDiskUsageForOTDBId': self.getDiskUsageForOTDBId, + 'GetDiskUsageForMoMId': self.getDiskUsageForMoMId, + 'GetDiskUsageForRADBId': self.getDiskUsageForRADBId, + 'GetDiskUsageForTask': self.getDiskUsageForTask, + 'GetDiskUsageForTaskAndSubDirectories': self.getDiskUsageForTaskAndSubDirectories, + 'GetDiskUsageForProjectSubDirectories': self.getDiskUsageForProjectSubDirectories} def prepare_loop(self): pass def getDiskUsageForOTDBId(self, otdb_id): - result = self.path_resolver.getPathForOTDBId(otdb_id) + return self.getDiskUsageForTask(otdb_id=otdb_id) + + def getDiskUsageForMoMId(self, mom_id): + return self.getDiskUsageForTask(mom_id=mom_id) + + def getDiskUsageForRADBId(self, radb_id): + return self.getDiskUsageForTask(radb_id=radb_id) + + def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None): + logger.info("getDiskUsageForTask(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + result = self.path_resolver.getPathForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) if result['found']: return getDiskUsageForPath(result['path']) return {'found': False, 'path': result['path']} + def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): + logger.info("getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + task_du_result = self.getDiskUsageForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + if task_du_result['found']: + task_sd_result = self.path_resolver.getSubDirectoriesForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + + if task_sd_result['found']: + subdir_paths = [os.path.join(task_du_result['path'],sd) for sd in task_sd_result['sub_directories']] + + #TODO: potential for parallelization + subdirs_du_result = { sd: getDiskUsageForPath(sd) for sd in subdir_paths } + result = {'found':True, 'task_direcory': task_du_result, 'sub_directories': subdirs_du_result } + logger.info('result: %s' % result) + return result + + return task_du_result + + def getDiskUsageForProjectSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): + logger.info("getDiskUsageForProjectSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + path_result = self.path_resolver.getProjectSubDirectories(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + if path_result['found']: + projectdir_du_result = getDiskUsageForPath(path_result['path']) + subdir_paths = [os.path.join(path_result['path'],sd) for sd in path_result['sub_directories']] + + #TODO: potential for parallelization + subdirs_du_result = { sd: getDiskUsageForPath(sd) for sd in subdir_paths } + result = {'found':True, 'projectdir': projectdir_du_result, 'sub_directories': subdirs_du_result } + logger.info('result: %s' % result) + return result + + return path_result + def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, mountpoint=CEP4_DATA_MOUNTPOINT, verbose=False, radb_busname=RADB_BUSNAME, radb_servicename=RADB_SERVICENAME, -- GitLab From 058f967104676f5a15a29053b2b59549955773a5 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 27 Jun 2016 07:29:42 +0000 Subject: [PATCH 420/933] Task #9590: Replace cbt003 by cbt009 due to broken cbt003.eth3. --- .../StaticMetaData/RSPConnections_Cobalt.dat | 33 ++++--- .../default/StationStreams.parset | 88 +++++++++---------- 2 files changed, 66 insertions(+), 55 deletions(-) diff --git a/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat b/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat index d019ae7d8bf..cd0ef87ad6b 100644 --- a/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat +++ b/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat @@ -35,8 +35,10 @@ CS001 RSP_0 cbt007-10GB01 10.168.102.1 A0:36:9F:1F:79:04 CS001 RSP_1 cbt007-10GB01 10.168.102.1 A0:36:9F:1F:79:04 -CS002 RSP_0 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C -CS002 RSP_1 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C +#CS002 RSP_0 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C +#CS002 RSP_1 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C +CS002 RSP_0 cbt009-10GB01 10.168.104.1 A0:36:9F:1F:36:E4 +CS002 RSP_1 cbt009-10GB01 10.168.104.1 A0:36:9F:1F:36:E4 CS003 RSP_0 cbt005-10GB01 10.168.100.1 A0:36:9F:1F:7B:74 CS003 RSP_1 cbt005-10GB01 10.168.100.1 A0:36:9F:1F:7B:74 @@ -53,8 +55,10 @@ CS006 RSP_1 cbt004-10GB01 10.168.99.1 A0:36:9F:1F:79:94 CS007 RSP_0 cbt006-10GB01 10.168.101.1 A0:36:9F:1F:79:A4 CS007 RSP_1 cbt006-10GB01 10.168.101.1 A0:36:9F:1F:79:A4 -CS011 RSP_0 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E -CS011 RSP_1 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E +#CS011 RSP_0 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E +#CS011 RSP_1 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E +CS011 RSP_0 cbt009-10GB02 10.168.104.2 A0:36:9F:1F:36:E6 +CS011 RSP_1 cbt009-10GB02 10.168.104.2 A0:36:9F:1F:36:E6 CS013 RSP_0 cbt005-10GB02 10.168.100.2 A0:36:9F:1F:7B:76 CS013 RSP_1 cbt005-10GB02 10.168.100.2 A0:36:9F:1F:7B:76 @@ -71,8 +75,10 @@ CS024 RSP_1 cbt007-10GB02 10.168.102.2 A0:36:9F:1F:79:06 CS026 RSP_0 cbt004-10GB02 10.168.99.2 A0:36:9F:1F:79:96 CS026 RSP_1 cbt004-10GB02 10.168.99.2 A0:36:9F:1F:79:96 -CS028 RSP_0 cbt003-10GB03 10.168.98.3 A0:36:9F:1F:7B:40 -CS028 RSP_1 cbt003-10GB03 10.168.98.3 A0:36:9F:1F:7B:40 +#CS028 RSP_0 cbt003-10GB03 10.168.98.3 A0:36:9F:1F:7B:40 +#CS028 RSP_1 cbt003-10GB03 10.168.98.3 A0:36:9F:1F:7B:40 +CS028 RSP_0 cbt009-10GB03 10.168.104.3 A0:36:9F:1F:78:64 +CS028 RSP_1 cbt009-10GB03 10.168.104.3 A0:36:9F:1F:78:64 CS030 RSP_0 cbt006-10GB02 10.168.101.2 A0:36:9F:1F:79:A6 CS030 RSP_1 cbt006-10GB02 10.168.101.2 A0:36:9F:1F:79:A6 @@ -89,8 +95,10 @@ CS101 RSP_1 cbt001-10GB03 10.168.96.3 A0:36:9F:1F:7B:44 CS103 RSP_0 cbt008-10GB02 10.168.103.2 A0:36:9F:1F:79:02 CS103 RSP_1 cbt008-10GB02 10.168.103.2 A0:36:9F:1F:79:02 -CS201 RSP_0 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C -CS201 RSP_1 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E +#CS201 RSP_0 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C +#CS201 RSP_1 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E +CS201 RSP_0 cbt009-10GB01 10.168.104.1 A0:36:9F:1F:36:E4 +CS201 RSP_1 cbt009-10GB02 10.168.104.2 A0:36:9F:1F:36:E6 CS301 RSP_0 cbt005-10GB03 10.168.100.3 A0:36:9F:1F:79:E0 CS301 RSP_1 cbt005-10GB03 10.168.100.3 A0:36:9F:1F:79:E0 @@ -141,14 +149,16 @@ DE601 RSP_0 cbt008-10GB04 10.212.1.108 A0:36:9F:1F:7B:6A DE601 RSP_0 cbt008-10GB04 10.213.1.108 A0:36:9F:1F:7B:6A DE601 RSP_0 cbt008-10GB04 10.214.1.108 A0:36:9F:1F:7B:6A -DE602 RSP_0 cbt003-10GB04 10.200.41.103 A0:36:9F:1F:7B:42 +#DE602 RSP_0 cbt003-10GB04 10.200.41.103 A0:36:9F:1F:7B:42 +DE602 RSP_0 cbt009-10GB04 10.200.41.109 A0:36:9F:1F:78:66 DE603 RSP_0 cbt004-10GB04 10.211.1.104 A0:36:9F:1F:7A:06 DE603 RSP_0 cbt004-10GB04 10.212.1.104 A0:36:9F:1F:7A:06 DE603 RSP_0 cbt004-10GB04 10.213.1.104 A0:36:9F:1F:7A:06 DE603 RSP_0 cbt004-10GB04 10.214.1.104 A0:36:9F:1F:7A:06 -DE604 RSP_0 cbt003-10GB04 10.200.81.103 A0:36:9F:1F:7B:42 +#DE604 RSP_0 cbt003-10GB04 10.200.81.103 A0:36:9F:1F:7B:42 +DE604 RSP_0 cbt009-10GB04 10.200.81.109 A0:36:9F:1F:78:66 DE605 RSP_0 cbt008-10GB04 10.211.1.108 A0:36:9F:1F:7B:6A DE605 RSP_0 cbt008-10GB04 10.212.1.108 A0:36:9F:1F:7B:6A @@ -173,7 +183,8 @@ UK608 RSP_0 cbt005-10GB04 10.214.1.105 A0:36:9F:1F:79:E2 DE609 RSP_0 cbt008-10GB04 10.200.91.108 A0:36:9F:1F:7B:6A DE609 RSP_0 cbt008-10GB04 10.200.92.108 A0:36:9F:1F:7B:6A -PL610 RSP_0 cbt003-10GB04 10.220.11.103 A0:36:9F:1F:7B:42 +#PL610 RSP_0 cbt003-10GB04 10.220.11.103 A0:36:9F:1F:7B:42 +PL610 RSP_0 cbt009-10GB04 10.220.11.109 A0:36:9F:1F:78:66 PL611 RSP_0 cbt004-10GB04 10.220.41.104 A0:36:9F:1F:7A:06 diff --git a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset index a300bea9bc7..733aa9ca0d9 100644 --- a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset +++ b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset @@ -7,14 +7,14 @@ PIC.Core.CS001HBA1.RSP.ports = [udp:cbt007-10GB01:10016, udp:cbt007-10GB01:10 PIC.Core.CS001HBA1.RSP.receiver = cbt007_0 PIC.Core.CS001LBA.RSP.ports = [udp:cbt007-10GB01:10010, udp:cbt007-10GB01:10011, udp:cbt007-10GB01:10012, udp:cbt007-10GB01:10013] PIC.Core.CS001LBA.RSP.receiver = cbt007_0 -PIC.Core.CS002HBA.RSP.ports = [udp:cbt003-10GB01:10020, udp:cbt003-10GB01:10021, udp:cbt003-10GB01:10022, udp:cbt003-10GB01:10023] -PIC.Core.CS002HBA.RSP.receiver = cbt003_0 -PIC.Core.CS002HBA0.RSP.ports = [udp:cbt003-10GB01:10020, udp:cbt003-10GB01:10021, udp:cbt003-10GB01:10022, udp:cbt003-10GB01:10023] -PIC.Core.CS002HBA0.RSP.receiver = cbt003_0 -PIC.Core.CS002HBA1.RSP.ports = [udp:cbt003-10GB01:10026, udp:cbt003-10GB01:10027, udp:cbt003-10GB01:10028, udp:cbt003-10GB01:10029] -PIC.Core.CS002HBA1.RSP.receiver = cbt003_0 -PIC.Core.CS002LBA.RSP.ports = [udp:cbt003-10GB01:10020, udp:cbt003-10GB01:10021, udp:cbt003-10GB01:10022, udp:cbt003-10GB01:10023] -PIC.Core.CS002LBA.RSP.receiver = cbt003_0 +PIC.Core.CS002HBA.RSP.ports = [udp:cbt009-10GB01:10020, udp:cbt009-10GB01:10021, udp:cbt009-10GB01:10022, udp:cbt009-10GB01:10023] +PIC.Core.CS002HBA.RSP.receiver = cbt009_0 +PIC.Core.CS002HBA0.RSP.ports = [udp:cbt009-10GB01:10020, udp:cbt009-10GB01:10021, udp:cbt009-10GB01:10022, udp:cbt009-10GB01:10023] +PIC.Core.CS002HBA0.RSP.receiver = cbt009_0 +PIC.Core.CS002HBA1.RSP.ports = [udp:cbt009-10GB01:10026, udp:cbt009-10GB01:10027, udp:cbt009-10GB01:10028, udp:cbt009-10GB01:10029] +PIC.Core.CS002HBA1.RSP.receiver = cbt009_0 +PIC.Core.CS002LBA.RSP.ports = [udp:cbt009-10GB01:10020, udp:cbt009-10GB01:10021, udp:cbt009-10GB01:10022, udp:cbt009-10GB01:10023] +PIC.Core.CS002LBA.RSP.receiver = cbt009_0 PIC.Core.CS003HBA.RSP.ports = [udp:cbt005-10GB01:10030, udp:cbt005-10GB01:10031, udp:cbt005-10GB01:10032, udp:cbt005-10GB01:10033] PIC.Core.CS003HBA.RSP.receiver = cbt005_0 PIC.Core.CS003HBA0.RSP.ports = [udp:cbt005-10GB01:10030, udp:cbt005-10GB01:10031, udp:cbt005-10GB01:10032, udp:cbt005-10GB01:10033] @@ -55,14 +55,14 @@ PIC.Core.CS007HBA1.RSP.ports = [udp:cbt006-10GB01:10076, udp:cbt006-10GB01:10 PIC.Core.CS007HBA1.RSP.receiver = cbt006_0 PIC.Core.CS007LBA.RSP.ports = [udp:cbt006-10GB01:10070, udp:cbt006-10GB01:10071, udp:cbt006-10GB01:10072, udp:cbt006-10GB01:10073] PIC.Core.CS007LBA.RSP.receiver = cbt006_0 -PIC.Core.CS011HBA.RSP.ports = [udp:cbt003-10GB02:10110, udp:cbt003-10GB02:10111, udp:cbt003-10GB02:10112, udp:cbt003-10GB02:10113] -PIC.Core.CS011HBA.RSP.receiver = cbt003_0 -PIC.Core.CS011HBA0.RSP.ports = [udp:cbt003-10GB02:10110, udp:cbt003-10GB02:10111, udp:cbt003-10GB02:10112, udp:cbt003-10GB02:10113] -PIC.Core.CS011HBA0.RSP.receiver = cbt003_0 -PIC.Core.CS011HBA1.RSP.ports = [udp:cbt003-10GB02:10116, udp:cbt003-10GB02:10117, udp:cbt003-10GB02:10118, udp:cbt003-10GB02:10119] -PIC.Core.CS011HBA1.RSP.receiver = cbt003_0 -PIC.Core.CS011LBA.RSP.ports = [udp:cbt003-10GB02:10110, udp:cbt003-10GB02:10111, udp:cbt003-10GB02:10112, udp:cbt003-10GB02:10113] -PIC.Core.CS011LBA.RSP.receiver = cbt003_0 +PIC.Core.CS011HBA.RSP.ports = [udp:cbt009-10GB02:10110, udp:cbt009-10GB02:10111, udp:cbt009-10GB02:10112, udp:cbt009-10GB02:10113] +PIC.Core.CS011HBA.RSP.receiver = cbt009_0 +PIC.Core.CS011HBA0.RSP.ports = [udp:cbt009-10GB02:10110, udp:cbt009-10GB02:10111, udp:cbt009-10GB02:10112, udp:cbt009-10GB02:10113] +PIC.Core.CS011HBA0.RSP.receiver = cbt009_0 +PIC.Core.CS011HBA1.RSP.ports = [udp:cbt009-10GB02:10116, udp:cbt009-10GB02:10117, udp:cbt009-10GB02:10118, udp:cbt009-10GB02:10119] +PIC.Core.CS011HBA1.RSP.receiver = cbt009_0 +PIC.Core.CS011LBA.RSP.ports = [udp:cbt009-10GB02:10110, udp:cbt009-10GB02:10111, udp:cbt009-10GB02:10112, udp:cbt009-10GB02:10113] +PIC.Core.CS011LBA.RSP.receiver = cbt009_0 PIC.Core.CS013HBA.RSP.ports = [udp:cbt005-10GB02:10130, udp:cbt005-10GB02:10131, udp:cbt005-10GB02:10132, udp:cbt005-10GB02:10133] PIC.Core.CS013HBA.RSP.receiver = cbt005_0 PIC.Core.CS013HBA0.RSP.ports = [udp:cbt005-10GB02:10130, udp:cbt005-10GB02:10131, udp:cbt005-10GB02:10132, udp:cbt005-10GB02:10133] @@ -103,14 +103,14 @@ PIC.Core.CS026HBA1.RSP.ports = [udp:cbt004-10GB02:10266, udp:cbt004-10GB02:10 PIC.Core.CS026HBA1.RSP.receiver = cbt004_0 PIC.Core.CS026LBA.RSP.ports = [udp:cbt004-10GB02:10260, udp:cbt004-10GB02:10261, udp:cbt004-10GB02:10262, udp:cbt004-10GB02:10263] PIC.Core.CS026LBA.RSP.receiver = cbt004_0 -PIC.Core.CS028HBA.RSP.ports = [udp:cbt003-10GB03:10280, udp:cbt003-10GB03:10281, udp:cbt003-10GB03:10282, udp:cbt003-10GB03:10283] -PIC.Core.CS028HBA.RSP.receiver = cbt003_1 -PIC.Core.CS028HBA0.RSP.ports = [udp:cbt003-10GB03:10280, udp:cbt003-10GB03:10281, udp:cbt003-10GB03:10282, udp:cbt003-10GB03:10283] -PIC.Core.CS028HBA0.RSP.receiver = cbt003_1 -PIC.Core.CS028HBA1.RSP.ports = [udp:cbt003-10GB03:10286, udp:cbt003-10GB03:10287, udp:cbt003-10GB03:10288, udp:cbt003-10GB03:10289] -PIC.Core.CS028HBA1.RSP.receiver = cbt003_1 -PIC.Core.CS028LBA.RSP.ports = [udp:cbt003-10GB03:10280, udp:cbt003-10GB03:10281, udp:cbt003-10GB03:10282, udp:cbt003-10GB03:10283] -PIC.Core.CS028LBA.RSP.receiver = cbt003_1 +PIC.Core.CS028HBA.RSP.ports = [udp:cbt009-10GB03:10280, udp:cbt009-10GB03:10281, udp:cbt009-10GB03:10282, udp:cbt009-10GB03:10283] +PIC.Core.CS028HBA.RSP.receiver = cbt009_1 +PIC.Core.CS028HBA0.RSP.ports = [udp:cbt009-10GB03:10280, udp:cbt009-10GB03:10281, udp:cbt009-10GB03:10282, udp:cbt009-10GB03:10283] +PIC.Core.CS028HBA0.RSP.receiver = cbt009_1 +PIC.Core.CS028HBA1.RSP.ports = [udp:cbt009-10GB03:10286, udp:cbt009-10GB03:10287, udp:cbt009-10GB03:10288, udp:cbt009-10GB03:10289] +PIC.Core.CS028HBA1.RSP.receiver = cbt009_1 +PIC.Core.CS028LBA.RSP.ports = [udp:cbt009-10GB03:10280, udp:cbt009-10GB03:10281, udp:cbt009-10GB03:10282, udp:cbt009-10GB03:10283] +PIC.Core.CS028LBA.RSP.receiver = cbt009_1 PIC.Core.CS030HBA.RSP.ports = [udp:cbt006-10GB02:10300, udp:cbt006-10GB02:10301, udp:cbt006-10GB02:10302, udp:cbt006-10GB02:10303] PIC.Core.CS030HBA.RSP.receiver = cbt006_0 PIC.Core.CS030HBA0.RSP.ports = [udp:cbt006-10GB02:10300, udp:cbt006-10GB02:10301, udp:cbt006-10GB02:10302, udp:cbt006-10GB02:10303] @@ -151,14 +151,14 @@ PIC.Core.CS103HBA1.RSP.ports = [udp:cbt008-10GB02:11036, udp:cbt008-10GB02:11 PIC.Core.CS103HBA1.RSP.receiver = cbt008_0 PIC.Core.CS103LBA.RSP.ports = [udp:cbt008-10GB02:11030, udp:cbt008-10GB02:11031, udp:cbt008-10GB02:11032, udp:cbt008-10GB02:11033] PIC.Core.CS103LBA.RSP.receiver = cbt008_0 -PIC.Core.CS201HBA.RSP.ports = [udp:cbt003-10GB01:12010, udp:cbt003-10GB01:12011, udp:cbt003-10GB01:12012, udp:cbt003-10GB01:12013] -PIC.Core.CS201HBA.RSP.receiver = cbt003_0 -PIC.Core.CS201HBA0.RSP.ports = [udp:cbt003-10GB01:12010, udp:cbt003-10GB01:12011, udp:cbt003-10GB01:12012, udp:cbt003-10GB01:12013] -PIC.Core.CS201HBA0.RSP.receiver = cbt003_0 -PIC.Core.CS201HBA1.RSP.ports = [udp:cbt003-10GB02:12016, udp:cbt003-10GB02:12017, udp:cbt003-10GB02:12018, udp:cbt003-10GB02:12019] -PIC.Core.CS201HBA1.RSP.receiver = cbt003_0 -PIC.Core.CS201LBA.RSP.ports = [udp:cbt003-10GB01:12010, udp:cbt003-10GB01:12011, udp:cbt003-10GB01:12012, udp:cbt003-10GB01:12013] -PIC.Core.CS201LBA.RSP.receiver = cbt003_0 +PIC.Core.CS201HBA.RSP.ports = [udp:cbt009-10GB01:12010, udp:cbt009-10GB01:12011, udp:cbt009-10GB01:12012, udp:cbt009-10GB01:12013] +PIC.Core.CS201HBA.RSP.receiver = cbt009_0 +PIC.Core.CS201HBA0.RSP.ports = [udp:cbt009-10GB01:12010, udp:cbt009-10GB01:12011, udp:cbt009-10GB01:12012, udp:cbt009-10GB01:12013] +PIC.Core.CS201HBA0.RSP.receiver = cbt009_0 +PIC.Core.CS201HBA1.RSP.ports = [udp:cbt009-10GB02:12016, udp:cbt009-10GB02:12017, udp:cbt009-10GB02:12018, udp:cbt009-10GB02:12019] +PIC.Core.CS201HBA1.RSP.receiver = cbt009_0 +PIC.Core.CS201LBA.RSP.ports = [udp:cbt009-10GB01:12010, udp:cbt009-10GB01:12011, udp:cbt009-10GB01:12012, udp:cbt009-10GB01:12013] +PIC.Core.CS201LBA.RSP.receiver = cbt009_0 PIC.Core.CS301HBA.RSP.ports = [udp:cbt005-10GB03:13010, udp:cbt005-10GB03:13011, udp:cbt005-10GB03:13012, udp:cbt005-10GB03:13013] PIC.Core.CS301HBA.RSP.receiver = cbt005_1 PIC.Core.CS301HBA0.RSP.ports = [udp:cbt005-10GB03:13010, udp:cbt005-10GB03:13011, udp:cbt005-10GB03:13012, udp:cbt005-10GB03:13013] @@ -195,18 +195,18 @@ PIC.Core.DE601HBA.RSP.ports = [udp:10.211.1.108:16010, udp:10.212.1.108:1601 PIC.Core.DE601HBA.RSP.receiver = cbt008_1 PIC.Core.DE601LBA.RSP.ports = [udp:10.211.1.108:16010, udp:10.212.1.108:16011, udp:10.213.1.108:16012, udp:10.214.1.108:16013] PIC.Core.DE601LBA.RSP.receiver = cbt008_1 -PIC.Core.DE602HBA.RSP.ports = [udp:10.200.41.103:16020, udp:10.200.41.103:16021, udp:10.200.41.103:16022, udp:10.200.41.103:16023] -PIC.Core.DE602HBA.RSP.receiver = cbt003_1 -PIC.Core.DE602LBA.RSP.ports = [udp:10.200.41.103:16020, udp:10.200.41.103:16021, udp:10.200.41.103:16022, udp:10.200.41.103:16023] -PIC.Core.DE602LBA.RSP.receiver = cbt003_1 +PIC.Core.DE602HBA.RSP.ports = [udp:10.200.41.109:16020, udp:10.200.41.109:16021, udp:10.200.41.109:16022, udp:10.200.41.109:16023] +PIC.Core.DE602HBA.RSP.receiver = cbt009_1 +PIC.Core.DE602LBA.RSP.ports = [udp:10.200.41.109:16020, udp:10.200.41.109:16021, udp:10.200.41.109:16022, udp:10.200.41.109:16023] +PIC.Core.DE602LBA.RSP.receiver = cbt009_1 PIC.Core.DE603HBA.RSP.ports = [udp:10.211.1.104:16030, udp:10.212.1.104:16031, udp:10.213.1.104:16032, udp:10.214.1.104:16033] PIC.Core.DE603HBA.RSP.receiver = cbt004_1 PIC.Core.DE603LBA.RSP.ports = [udp:10.211.1.104:16030, udp:10.212.1.104:16031, udp:10.213.1.104:16032, udp:10.214.1.104:16033] PIC.Core.DE603LBA.RSP.receiver = cbt004_1 -PIC.Core.DE604HBA.RSP.ports = [udp:10.200.81.103:16040, udp:10.200.81.103:16041, udp:10.200.81.103:16042, udp:10.200.81.103:16043] -PIC.Core.DE604HBA.RSP.receiver = cbt003_1 -PIC.Core.DE604LBA.RSP.ports = [udp:10.200.81.103:16040, udp:10.200.81.103:16041, udp:10.200.81.103:16042, udp:10.200.81.103:16043] -PIC.Core.DE604LBA.RSP.receiver = cbt003_1 +PIC.Core.DE604HBA.RSP.ports = [udp:10.200.81.109:16040, udp:10.200.81.109:16041, udp:10.200.81.109:16042, udp:10.200.81.109:16043] +PIC.Core.DE604HBA.RSP.receiver = cbt009_1 +PIC.Core.DE604LBA.RSP.ports = [udp:10.200.81.109:16040, udp:10.200.81.109:16041, udp:10.200.81.109:16042, udp:10.200.81.109:16043] +PIC.Core.DE604LBA.RSP.receiver = cbt009_1 PIC.Core.DE605HBA.RSP.ports = [udp:10.211.1.108:16050, udp:10.212.1.108:16051, udp:10.213.1.108:16052, udp:10.214.1.108:16053] PIC.Core.DE605HBA.RSP.receiver = cbt008_1 PIC.Core.DE605LBA.RSP.ports = [udp:10.211.1.108:16050, udp:10.212.1.108:16051, udp:10.213.1.108:16052, udp:10.214.1.108:16053] @@ -219,10 +219,10 @@ PIC.Core.FR606HBA.RSP.ports = [udp:10.211.1.105:16060, udp:10.212.1.105:1606 PIC.Core.FR606HBA.RSP.receiver = cbt005_1 PIC.Core.FR606LBA.RSP.ports = [udp:10.211.1.105:16060, udp:10.212.1.105:16061, udp:10.213.1.105:16062, udp:10.214.1.105:16063] PIC.Core.FR606LBA.RSP.receiver = cbt005_1 -PIC.Core.PL610HBA.RSP.ports = [udp:10.220.11.103:16100, udp:10.220.11.103:16101, udp:10.220.11.103:16102, udp:10.220.11.103:16103] -PIC.Core.PL610HBA.RSP.receiver = cbt003_1 -PIC.Core.PL610LBA.RSP.ports = [udp:10.220.11.103:16100, udp:10.220.11.103:16101, udp:10.220.11.103:16102, udp:10.220.11.103:16103] -PIC.Core.PL610LBA.RSP.receiver = cbt003_1 +PIC.Core.PL610HBA.RSP.ports = [udp:10.220.11.109:16100, udp:10.220.11.109:16101, udp:10.220.11.109:16102, udp:10.220.11.109:16103] +PIC.Core.PL610HBA.RSP.receiver = cbt009_1 +PIC.Core.PL610LBA.RSP.ports = [udp:10.220.11.109:16100, udp:10.220.11.109:16101, udp:10.220.11.109:16102, udp:10.220.11.109:16103] +PIC.Core.PL610LBA.RSP.receiver = cbt009_1 PIC.Core.PL611HBA.RSP.ports = [udp:10.220.41.104:16110, udp:10.220.41.104:16111, udp:10.220.41.104:16112, udp:10.220.41.104:16113] PIC.Core.PL611HBA.RSP.receiver = cbt004_1 PIC.Core.PL611LBA.RSP.ports = [udp:10.220.41.104:16110, udp:10.220.41.104:16111, udp:10.220.41.104:16112, udp:10.220.41.104:16113] -- GitLab From a32385d5fee44f527b3987a8afa423d7d25419cd Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 27 Jun 2016 07:46:28 +0000 Subject: [PATCH 421/933] Task #9590: Repace cbt003 with cbt009. --- .../etc/parset-additions.d/default/HardwareUsed.parset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/HardwareUsed.parset b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/HardwareUsed.parset index ac5194cf5ea..93735b73b5a 100644 --- a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/HardwareUsed.parset +++ b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/HardwareUsed.parset @@ -9,8 +9,8 @@ Cobalt.Nodes = [ cbt001_1, cbt002_0, cbt002_1, - cbt003_0, - cbt003_1, + cbt009_0, + cbt009_1, cbt004_0, cbt004_1, cbt005_0, -- GitLab From b0f183be779754ebf7b0dfa17a0155d787d487cd Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 27 Jun 2016 08:50:11 +0000 Subject: [PATCH 422/933] Task #9590: Reverted back to cbt003 --- .../StaticMetaData/RSPConnections_Cobalt.dat | 33 +++---- .../default/HardwareUsed.parset | 4 +- .../default/StationStreams.parset | 88 +++++++++---------- 3 files changed, 57 insertions(+), 68 deletions(-) diff --git a/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat b/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat index cd0ef87ad6b..d019ae7d8bf 100644 --- a/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat +++ b/MAC/Deployment/data/StaticMetaData/RSPConnections_Cobalt.dat @@ -35,10 +35,8 @@ CS001 RSP_0 cbt007-10GB01 10.168.102.1 A0:36:9F:1F:79:04 CS001 RSP_1 cbt007-10GB01 10.168.102.1 A0:36:9F:1F:79:04 -#CS002 RSP_0 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C -#CS002 RSP_1 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C -CS002 RSP_0 cbt009-10GB01 10.168.104.1 A0:36:9F:1F:36:E4 -CS002 RSP_1 cbt009-10GB01 10.168.104.1 A0:36:9F:1F:36:E4 +CS002 RSP_0 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C +CS002 RSP_1 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C CS003 RSP_0 cbt005-10GB01 10.168.100.1 A0:36:9F:1F:7B:74 CS003 RSP_1 cbt005-10GB01 10.168.100.1 A0:36:9F:1F:7B:74 @@ -55,10 +53,8 @@ CS006 RSP_1 cbt004-10GB01 10.168.99.1 A0:36:9F:1F:79:94 CS007 RSP_0 cbt006-10GB01 10.168.101.1 A0:36:9F:1F:79:A4 CS007 RSP_1 cbt006-10GB01 10.168.101.1 A0:36:9F:1F:79:A4 -#CS011 RSP_0 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E -#CS011 RSP_1 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E -CS011 RSP_0 cbt009-10GB02 10.168.104.2 A0:36:9F:1F:36:E6 -CS011 RSP_1 cbt009-10GB02 10.168.104.2 A0:36:9F:1F:36:E6 +CS011 RSP_0 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E +CS011 RSP_1 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E CS013 RSP_0 cbt005-10GB02 10.168.100.2 A0:36:9F:1F:7B:76 CS013 RSP_1 cbt005-10GB02 10.168.100.2 A0:36:9F:1F:7B:76 @@ -75,10 +71,8 @@ CS024 RSP_1 cbt007-10GB02 10.168.102.2 A0:36:9F:1F:79:06 CS026 RSP_0 cbt004-10GB02 10.168.99.2 A0:36:9F:1F:79:96 CS026 RSP_1 cbt004-10GB02 10.168.99.2 A0:36:9F:1F:79:96 -#CS028 RSP_0 cbt003-10GB03 10.168.98.3 A0:36:9F:1F:7B:40 -#CS028 RSP_1 cbt003-10GB03 10.168.98.3 A0:36:9F:1F:7B:40 -CS028 RSP_0 cbt009-10GB03 10.168.104.3 A0:36:9F:1F:78:64 -CS028 RSP_1 cbt009-10GB03 10.168.104.3 A0:36:9F:1F:78:64 +CS028 RSP_0 cbt003-10GB03 10.168.98.3 A0:36:9F:1F:7B:40 +CS028 RSP_1 cbt003-10GB03 10.168.98.3 A0:36:9F:1F:7B:40 CS030 RSP_0 cbt006-10GB02 10.168.101.2 A0:36:9F:1F:79:A6 CS030 RSP_1 cbt006-10GB02 10.168.101.2 A0:36:9F:1F:79:A6 @@ -95,10 +89,8 @@ CS101 RSP_1 cbt001-10GB03 10.168.96.3 A0:36:9F:1F:7B:44 CS103 RSP_0 cbt008-10GB02 10.168.103.2 A0:36:9F:1F:79:02 CS103 RSP_1 cbt008-10GB02 10.168.103.2 A0:36:9F:1F:79:02 -#CS201 RSP_0 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C -#CS201 RSP_1 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E -CS201 RSP_0 cbt009-10GB01 10.168.104.1 A0:36:9F:1F:36:E4 -CS201 RSP_1 cbt009-10GB02 10.168.104.2 A0:36:9F:1F:36:E6 +CS201 RSP_0 cbt003-10GB01 10.168.98.1 A0:36:9F:1F:78:0C +CS201 RSP_1 cbt003-10GB02 10.168.98.2 A0:36:9F:1F:78:0E CS301 RSP_0 cbt005-10GB03 10.168.100.3 A0:36:9F:1F:79:E0 CS301 RSP_1 cbt005-10GB03 10.168.100.3 A0:36:9F:1F:79:E0 @@ -149,16 +141,14 @@ DE601 RSP_0 cbt008-10GB04 10.212.1.108 A0:36:9F:1F:7B:6A DE601 RSP_0 cbt008-10GB04 10.213.1.108 A0:36:9F:1F:7B:6A DE601 RSP_0 cbt008-10GB04 10.214.1.108 A0:36:9F:1F:7B:6A -#DE602 RSP_0 cbt003-10GB04 10.200.41.103 A0:36:9F:1F:7B:42 -DE602 RSP_0 cbt009-10GB04 10.200.41.109 A0:36:9F:1F:78:66 +DE602 RSP_0 cbt003-10GB04 10.200.41.103 A0:36:9F:1F:7B:42 DE603 RSP_0 cbt004-10GB04 10.211.1.104 A0:36:9F:1F:7A:06 DE603 RSP_0 cbt004-10GB04 10.212.1.104 A0:36:9F:1F:7A:06 DE603 RSP_0 cbt004-10GB04 10.213.1.104 A0:36:9F:1F:7A:06 DE603 RSP_0 cbt004-10GB04 10.214.1.104 A0:36:9F:1F:7A:06 -#DE604 RSP_0 cbt003-10GB04 10.200.81.103 A0:36:9F:1F:7B:42 -DE604 RSP_0 cbt009-10GB04 10.200.81.109 A0:36:9F:1F:78:66 +DE604 RSP_0 cbt003-10GB04 10.200.81.103 A0:36:9F:1F:7B:42 DE605 RSP_0 cbt008-10GB04 10.211.1.108 A0:36:9F:1F:7B:6A DE605 RSP_0 cbt008-10GB04 10.212.1.108 A0:36:9F:1F:7B:6A @@ -183,8 +173,7 @@ UK608 RSP_0 cbt005-10GB04 10.214.1.105 A0:36:9F:1F:79:E2 DE609 RSP_0 cbt008-10GB04 10.200.91.108 A0:36:9F:1F:7B:6A DE609 RSP_0 cbt008-10GB04 10.200.92.108 A0:36:9F:1F:7B:6A -#PL610 RSP_0 cbt003-10GB04 10.220.11.103 A0:36:9F:1F:7B:42 -PL610 RSP_0 cbt009-10GB04 10.220.11.109 A0:36:9F:1F:78:66 +PL610 RSP_0 cbt003-10GB04 10.220.11.103 A0:36:9F:1F:7B:42 PL611 RSP_0 cbt004-10GB04 10.220.41.104 A0:36:9F:1F:7A:06 diff --git a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/HardwareUsed.parset b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/HardwareUsed.parset index 93735b73b5a..ac5194cf5ea 100644 --- a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/HardwareUsed.parset +++ b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/HardwareUsed.parset @@ -9,8 +9,8 @@ Cobalt.Nodes = [ cbt001_1, cbt002_0, cbt002_1, - cbt009_0, - cbt009_1, + cbt003_0, + cbt003_1, cbt004_0, cbt004_1, cbt005_0, diff --git a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset index 733aa9ca0d9..a300bea9bc7 100644 --- a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset +++ b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationStreams.parset @@ -7,14 +7,14 @@ PIC.Core.CS001HBA1.RSP.ports = [udp:cbt007-10GB01:10016, udp:cbt007-10GB01:10 PIC.Core.CS001HBA1.RSP.receiver = cbt007_0 PIC.Core.CS001LBA.RSP.ports = [udp:cbt007-10GB01:10010, udp:cbt007-10GB01:10011, udp:cbt007-10GB01:10012, udp:cbt007-10GB01:10013] PIC.Core.CS001LBA.RSP.receiver = cbt007_0 -PIC.Core.CS002HBA.RSP.ports = [udp:cbt009-10GB01:10020, udp:cbt009-10GB01:10021, udp:cbt009-10GB01:10022, udp:cbt009-10GB01:10023] -PIC.Core.CS002HBA.RSP.receiver = cbt009_0 -PIC.Core.CS002HBA0.RSP.ports = [udp:cbt009-10GB01:10020, udp:cbt009-10GB01:10021, udp:cbt009-10GB01:10022, udp:cbt009-10GB01:10023] -PIC.Core.CS002HBA0.RSP.receiver = cbt009_0 -PIC.Core.CS002HBA1.RSP.ports = [udp:cbt009-10GB01:10026, udp:cbt009-10GB01:10027, udp:cbt009-10GB01:10028, udp:cbt009-10GB01:10029] -PIC.Core.CS002HBA1.RSP.receiver = cbt009_0 -PIC.Core.CS002LBA.RSP.ports = [udp:cbt009-10GB01:10020, udp:cbt009-10GB01:10021, udp:cbt009-10GB01:10022, udp:cbt009-10GB01:10023] -PIC.Core.CS002LBA.RSP.receiver = cbt009_0 +PIC.Core.CS002HBA.RSP.ports = [udp:cbt003-10GB01:10020, udp:cbt003-10GB01:10021, udp:cbt003-10GB01:10022, udp:cbt003-10GB01:10023] +PIC.Core.CS002HBA.RSP.receiver = cbt003_0 +PIC.Core.CS002HBA0.RSP.ports = [udp:cbt003-10GB01:10020, udp:cbt003-10GB01:10021, udp:cbt003-10GB01:10022, udp:cbt003-10GB01:10023] +PIC.Core.CS002HBA0.RSP.receiver = cbt003_0 +PIC.Core.CS002HBA1.RSP.ports = [udp:cbt003-10GB01:10026, udp:cbt003-10GB01:10027, udp:cbt003-10GB01:10028, udp:cbt003-10GB01:10029] +PIC.Core.CS002HBA1.RSP.receiver = cbt003_0 +PIC.Core.CS002LBA.RSP.ports = [udp:cbt003-10GB01:10020, udp:cbt003-10GB01:10021, udp:cbt003-10GB01:10022, udp:cbt003-10GB01:10023] +PIC.Core.CS002LBA.RSP.receiver = cbt003_0 PIC.Core.CS003HBA.RSP.ports = [udp:cbt005-10GB01:10030, udp:cbt005-10GB01:10031, udp:cbt005-10GB01:10032, udp:cbt005-10GB01:10033] PIC.Core.CS003HBA.RSP.receiver = cbt005_0 PIC.Core.CS003HBA0.RSP.ports = [udp:cbt005-10GB01:10030, udp:cbt005-10GB01:10031, udp:cbt005-10GB01:10032, udp:cbt005-10GB01:10033] @@ -55,14 +55,14 @@ PIC.Core.CS007HBA1.RSP.ports = [udp:cbt006-10GB01:10076, udp:cbt006-10GB01:10 PIC.Core.CS007HBA1.RSP.receiver = cbt006_0 PIC.Core.CS007LBA.RSP.ports = [udp:cbt006-10GB01:10070, udp:cbt006-10GB01:10071, udp:cbt006-10GB01:10072, udp:cbt006-10GB01:10073] PIC.Core.CS007LBA.RSP.receiver = cbt006_0 -PIC.Core.CS011HBA.RSP.ports = [udp:cbt009-10GB02:10110, udp:cbt009-10GB02:10111, udp:cbt009-10GB02:10112, udp:cbt009-10GB02:10113] -PIC.Core.CS011HBA.RSP.receiver = cbt009_0 -PIC.Core.CS011HBA0.RSP.ports = [udp:cbt009-10GB02:10110, udp:cbt009-10GB02:10111, udp:cbt009-10GB02:10112, udp:cbt009-10GB02:10113] -PIC.Core.CS011HBA0.RSP.receiver = cbt009_0 -PIC.Core.CS011HBA1.RSP.ports = [udp:cbt009-10GB02:10116, udp:cbt009-10GB02:10117, udp:cbt009-10GB02:10118, udp:cbt009-10GB02:10119] -PIC.Core.CS011HBA1.RSP.receiver = cbt009_0 -PIC.Core.CS011LBA.RSP.ports = [udp:cbt009-10GB02:10110, udp:cbt009-10GB02:10111, udp:cbt009-10GB02:10112, udp:cbt009-10GB02:10113] -PIC.Core.CS011LBA.RSP.receiver = cbt009_0 +PIC.Core.CS011HBA.RSP.ports = [udp:cbt003-10GB02:10110, udp:cbt003-10GB02:10111, udp:cbt003-10GB02:10112, udp:cbt003-10GB02:10113] +PIC.Core.CS011HBA.RSP.receiver = cbt003_0 +PIC.Core.CS011HBA0.RSP.ports = [udp:cbt003-10GB02:10110, udp:cbt003-10GB02:10111, udp:cbt003-10GB02:10112, udp:cbt003-10GB02:10113] +PIC.Core.CS011HBA0.RSP.receiver = cbt003_0 +PIC.Core.CS011HBA1.RSP.ports = [udp:cbt003-10GB02:10116, udp:cbt003-10GB02:10117, udp:cbt003-10GB02:10118, udp:cbt003-10GB02:10119] +PIC.Core.CS011HBA1.RSP.receiver = cbt003_0 +PIC.Core.CS011LBA.RSP.ports = [udp:cbt003-10GB02:10110, udp:cbt003-10GB02:10111, udp:cbt003-10GB02:10112, udp:cbt003-10GB02:10113] +PIC.Core.CS011LBA.RSP.receiver = cbt003_0 PIC.Core.CS013HBA.RSP.ports = [udp:cbt005-10GB02:10130, udp:cbt005-10GB02:10131, udp:cbt005-10GB02:10132, udp:cbt005-10GB02:10133] PIC.Core.CS013HBA.RSP.receiver = cbt005_0 PIC.Core.CS013HBA0.RSP.ports = [udp:cbt005-10GB02:10130, udp:cbt005-10GB02:10131, udp:cbt005-10GB02:10132, udp:cbt005-10GB02:10133] @@ -103,14 +103,14 @@ PIC.Core.CS026HBA1.RSP.ports = [udp:cbt004-10GB02:10266, udp:cbt004-10GB02:10 PIC.Core.CS026HBA1.RSP.receiver = cbt004_0 PIC.Core.CS026LBA.RSP.ports = [udp:cbt004-10GB02:10260, udp:cbt004-10GB02:10261, udp:cbt004-10GB02:10262, udp:cbt004-10GB02:10263] PIC.Core.CS026LBA.RSP.receiver = cbt004_0 -PIC.Core.CS028HBA.RSP.ports = [udp:cbt009-10GB03:10280, udp:cbt009-10GB03:10281, udp:cbt009-10GB03:10282, udp:cbt009-10GB03:10283] -PIC.Core.CS028HBA.RSP.receiver = cbt009_1 -PIC.Core.CS028HBA0.RSP.ports = [udp:cbt009-10GB03:10280, udp:cbt009-10GB03:10281, udp:cbt009-10GB03:10282, udp:cbt009-10GB03:10283] -PIC.Core.CS028HBA0.RSP.receiver = cbt009_1 -PIC.Core.CS028HBA1.RSP.ports = [udp:cbt009-10GB03:10286, udp:cbt009-10GB03:10287, udp:cbt009-10GB03:10288, udp:cbt009-10GB03:10289] -PIC.Core.CS028HBA1.RSP.receiver = cbt009_1 -PIC.Core.CS028LBA.RSP.ports = [udp:cbt009-10GB03:10280, udp:cbt009-10GB03:10281, udp:cbt009-10GB03:10282, udp:cbt009-10GB03:10283] -PIC.Core.CS028LBA.RSP.receiver = cbt009_1 +PIC.Core.CS028HBA.RSP.ports = [udp:cbt003-10GB03:10280, udp:cbt003-10GB03:10281, udp:cbt003-10GB03:10282, udp:cbt003-10GB03:10283] +PIC.Core.CS028HBA.RSP.receiver = cbt003_1 +PIC.Core.CS028HBA0.RSP.ports = [udp:cbt003-10GB03:10280, udp:cbt003-10GB03:10281, udp:cbt003-10GB03:10282, udp:cbt003-10GB03:10283] +PIC.Core.CS028HBA0.RSP.receiver = cbt003_1 +PIC.Core.CS028HBA1.RSP.ports = [udp:cbt003-10GB03:10286, udp:cbt003-10GB03:10287, udp:cbt003-10GB03:10288, udp:cbt003-10GB03:10289] +PIC.Core.CS028HBA1.RSP.receiver = cbt003_1 +PIC.Core.CS028LBA.RSP.ports = [udp:cbt003-10GB03:10280, udp:cbt003-10GB03:10281, udp:cbt003-10GB03:10282, udp:cbt003-10GB03:10283] +PIC.Core.CS028LBA.RSP.receiver = cbt003_1 PIC.Core.CS030HBA.RSP.ports = [udp:cbt006-10GB02:10300, udp:cbt006-10GB02:10301, udp:cbt006-10GB02:10302, udp:cbt006-10GB02:10303] PIC.Core.CS030HBA.RSP.receiver = cbt006_0 PIC.Core.CS030HBA0.RSP.ports = [udp:cbt006-10GB02:10300, udp:cbt006-10GB02:10301, udp:cbt006-10GB02:10302, udp:cbt006-10GB02:10303] @@ -151,14 +151,14 @@ PIC.Core.CS103HBA1.RSP.ports = [udp:cbt008-10GB02:11036, udp:cbt008-10GB02:11 PIC.Core.CS103HBA1.RSP.receiver = cbt008_0 PIC.Core.CS103LBA.RSP.ports = [udp:cbt008-10GB02:11030, udp:cbt008-10GB02:11031, udp:cbt008-10GB02:11032, udp:cbt008-10GB02:11033] PIC.Core.CS103LBA.RSP.receiver = cbt008_0 -PIC.Core.CS201HBA.RSP.ports = [udp:cbt009-10GB01:12010, udp:cbt009-10GB01:12011, udp:cbt009-10GB01:12012, udp:cbt009-10GB01:12013] -PIC.Core.CS201HBA.RSP.receiver = cbt009_0 -PIC.Core.CS201HBA0.RSP.ports = [udp:cbt009-10GB01:12010, udp:cbt009-10GB01:12011, udp:cbt009-10GB01:12012, udp:cbt009-10GB01:12013] -PIC.Core.CS201HBA0.RSP.receiver = cbt009_0 -PIC.Core.CS201HBA1.RSP.ports = [udp:cbt009-10GB02:12016, udp:cbt009-10GB02:12017, udp:cbt009-10GB02:12018, udp:cbt009-10GB02:12019] -PIC.Core.CS201HBA1.RSP.receiver = cbt009_0 -PIC.Core.CS201LBA.RSP.ports = [udp:cbt009-10GB01:12010, udp:cbt009-10GB01:12011, udp:cbt009-10GB01:12012, udp:cbt009-10GB01:12013] -PIC.Core.CS201LBA.RSP.receiver = cbt009_0 +PIC.Core.CS201HBA.RSP.ports = [udp:cbt003-10GB01:12010, udp:cbt003-10GB01:12011, udp:cbt003-10GB01:12012, udp:cbt003-10GB01:12013] +PIC.Core.CS201HBA.RSP.receiver = cbt003_0 +PIC.Core.CS201HBA0.RSP.ports = [udp:cbt003-10GB01:12010, udp:cbt003-10GB01:12011, udp:cbt003-10GB01:12012, udp:cbt003-10GB01:12013] +PIC.Core.CS201HBA0.RSP.receiver = cbt003_0 +PIC.Core.CS201HBA1.RSP.ports = [udp:cbt003-10GB02:12016, udp:cbt003-10GB02:12017, udp:cbt003-10GB02:12018, udp:cbt003-10GB02:12019] +PIC.Core.CS201HBA1.RSP.receiver = cbt003_0 +PIC.Core.CS201LBA.RSP.ports = [udp:cbt003-10GB01:12010, udp:cbt003-10GB01:12011, udp:cbt003-10GB01:12012, udp:cbt003-10GB01:12013] +PIC.Core.CS201LBA.RSP.receiver = cbt003_0 PIC.Core.CS301HBA.RSP.ports = [udp:cbt005-10GB03:13010, udp:cbt005-10GB03:13011, udp:cbt005-10GB03:13012, udp:cbt005-10GB03:13013] PIC.Core.CS301HBA.RSP.receiver = cbt005_1 PIC.Core.CS301HBA0.RSP.ports = [udp:cbt005-10GB03:13010, udp:cbt005-10GB03:13011, udp:cbt005-10GB03:13012, udp:cbt005-10GB03:13013] @@ -195,18 +195,18 @@ PIC.Core.DE601HBA.RSP.ports = [udp:10.211.1.108:16010, udp:10.212.1.108:1601 PIC.Core.DE601HBA.RSP.receiver = cbt008_1 PIC.Core.DE601LBA.RSP.ports = [udp:10.211.1.108:16010, udp:10.212.1.108:16011, udp:10.213.1.108:16012, udp:10.214.1.108:16013] PIC.Core.DE601LBA.RSP.receiver = cbt008_1 -PIC.Core.DE602HBA.RSP.ports = [udp:10.200.41.109:16020, udp:10.200.41.109:16021, udp:10.200.41.109:16022, udp:10.200.41.109:16023] -PIC.Core.DE602HBA.RSP.receiver = cbt009_1 -PIC.Core.DE602LBA.RSP.ports = [udp:10.200.41.109:16020, udp:10.200.41.109:16021, udp:10.200.41.109:16022, udp:10.200.41.109:16023] -PIC.Core.DE602LBA.RSP.receiver = cbt009_1 +PIC.Core.DE602HBA.RSP.ports = [udp:10.200.41.103:16020, udp:10.200.41.103:16021, udp:10.200.41.103:16022, udp:10.200.41.103:16023] +PIC.Core.DE602HBA.RSP.receiver = cbt003_1 +PIC.Core.DE602LBA.RSP.ports = [udp:10.200.41.103:16020, udp:10.200.41.103:16021, udp:10.200.41.103:16022, udp:10.200.41.103:16023] +PIC.Core.DE602LBA.RSP.receiver = cbt003_1 PIC.Core.DE603HBA.RSP.ports = [udp:10.211.1.104:16030, udp:10.212.1.104:16031, udp:10.213.1.104:16032, udp:10.214.1.104:16033] PIC.Core.DE603HBA.RSP.receiver = cbt004_1 PIC.Core.DE603LBA.RSP.ports = [udp:10.211.1.104:16030, udp:10.212.1.104:16031, udp:10.213.1.104:16032, udp:10.214.1.104:16033] PIC.Core.DE603LBA.RSP.receiver = cbt004_1 -PIC.Core.DE604HBA.RSP.ports = [udp:10.200.81.109:16040, udp:10.200.81.109:16041, udp:10.200.81.109:16042, udp:10.200.81.109:16043] -PIC.Core.DE604HBA.RSP.receiver = cbt009_1 -PIC.Core.DE604LBA.RSP.ports = [udp:10.200.81.109:16040, udp:10.200.81.109:16041, udp:10.200.81.109:16042, udp:10.200.81.109:16043] -PIC.Core.DE604LBA.RSP.receiver = cbt009_1 +PIC.Core.DE604HBA.RSP.ports = [udp:10.200.81.103:16040, udp:10.200.81.103:16041, udp:10.200.81.103:16042, udp:10.200.81.103:16043] +PIC.Core.DE604HBA.RSP.receiver = cbt003_1 +PIC.Core.DE604LBA.RSP.ports = [udp:10.200.81.103:16040, udp:10.200.81.103:16041, udp:10.200.81.103:16042, udp:10.200.81.103:16043] +PIC.Core.DE604LBA.RSP.receiver = cbt003_1 PIC.Core.DE605HBA.RSP.ports = [udp:10.211.1.108:16050, udp:10.212.1.108:16051, udp:10.213.1.108:16052, udp:10.214.1.108:16053] PIC.Core.DE605HBA.RSP.receiver = cbt008_1 PIC.Core.DE605LBA.RSP.ports = [udp:10.211.1.108:16050, udp:10.212.1.108:16051, udp:10.213.1.108:16052, udp:10.214.1.108:16053] @@ -219,10 +219,10 @@ PIC.Core.FR606HBA.RSP.ports = [udp:10.211.1.105:16060, udp:10.212.1.105:1606 PIC.Core.FR606HBA.RSP.receiver = cbt005_1 PIC.Core.FR606LBA.RSP.ports = [udp:10.211.1.105:16060, udp:10.212.1.105:16061, udp:10.213.1.105:16062, udp:10.214.1.105:16063] PIC.Core.FR606LBA.RSP.receiver = cbt005_1 -PIC.Core.PL610HBA.RSP.ports = [udp:10.220.11.109:16100, udp:10.220.11.109:16101, udp:10.220.11.109:16102, udp:10.220.11.109:16103] -PIC.Core.PL610HBA.RSP.receiver = cbt009_1 -PIC.Core.PL610LBA.RSP.ports = [udp:10.220.11.109:16100, udp:10.220.11.109:16101, udp:10.220.11.109:16102, udp:10.220.11.109:16103] -PIC.Core.PL610LBA.RSP.receiver = cbt009_1 +PIC.Core.PL610HBA.RSP.ports = [udp:10.220.11.103:16100, udp:10.220.11.103:16101, udp:10.220.11.103:16102, udp:10.220.11.103:16103] +PIC.Core.PL610HBA.RSP.receiver = cbt003_1 +PIC.Core.PL610LBA.RSP.ports = [udp:10.220.11.103:16100, udp:10.220.11.103:16101, udp:10.220.11.103:16102, udp:10.220.11.103:16103] +PIC.Core.PL610LBA.RSP.receiver = cbt003_1 PIC.Core.PL611HBA.RSP.ports = [udp:10.220.41.104:16110, udp:10.220.41.104:16111, udp:10.220.41.104:16112, udp:10.220.41.104:16113] PIC.Core.PL611HBA.RSP.receiver = cbt004_1 PIC.Core.PL611LBA.RSP.ports = [udp:10.220.41.104:16110, udp:10.220.41.104:16111, udp:10.220.41.104:16112, udp:10.220.41.104:16113] -- GitLab From 35f48920c8f406e5a25a35c929eabfbdd48beedc Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 27 Jun 2016 08:51:53 +0000 Subject: [PATCH 423/933] Task #9192: Use FQDN of GSM monetdb host --- MAC/Deployment/data/OTDB/GSM.comp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Deployment/data/OTDB/GSM.comp b/MAC/Deployment/data/OTDB/GSM.comp index e4909af2001..3a8ee36ece1 100644 --- a/MAC/Deployment/data/OTDB/GSM.comp +++ b/MAC/Deployment/data/OTDB/GSM.comp @@ -36,7 +36,7 @@ node GSM 4.0.0 development 'node constraint' "GSM" #-------------------------------------------------------------------------------------------------------- # name dir. type unit prun. vm value constr. descr. #-------------------------------------------------------------------------------------------------------- -par monetdb_hostname I text - 10 100 "lbd002" - 'name of the host where the database is located' +par monetdb_hostname I text - 10 100 "ldb002.offline.lofar" - 'name of the host where the database is located' par monetdb_port I int - 10 100 51000 - 'port number to use for connection to database' par monetdb_name I text - 10 100 "gsm" - 'the name of the database' par monetdb_user I text - 10 100 "gsm" - 'user name used for connecting to database' -- GitLab From f8018cd101d8fb882cf5110201ea6ed813c26c37 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 27 Jun 2016 13:40:37 +0000 Subject: [PATCH 424/933] Task #9192: Also create directory for intermediate data products --- CEP/Pipeline/recipes/sip/nodes/long_baseline.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py index 244107a677c..2fe0aa3e405 100644 --- a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py +++ b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py @@ -58,6 +58,8 @@ class long_baseline(LOFARnodeTCP): # I. Create the directories used in this recipe create_directory(processed_ms_dir) create_directory(working_dir) + create_directory(os.path.dirname(output_measurement_set)) + create_directory(os.path.dirname(final_output_path)) # time slice dir_to_remove: assure empty directory: Stale data # is problematic for dppp @@ -499,12 +501,6 @@ class long_baseline(LOFARnodeTCP): table = pt.table(output_measurement_set) - try: - os.makedirs(os.path.dirname(final_output_path)) - except: - pass # do nothing, the path already exists, we can output to this - # location - table.copy(final_output_path, deep=True) -- GitLab From 8157b0e22a41e4b183b375dc3a40bb5230fac92c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 28 Jun 2016 10:48:27 +0000 Subject: [PATCH 425/933] Task #9192: log parset which is stored in OTDB --- .../RAtoOTDBTaskSpecificationPropagator/lib/propagator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py index 8d6f8e6b6fa..081a83859ed 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py @@ -137,7 +137,7 @@ class RAtoOTDBPropagator(): project_name = 'unknown' otdb_info = self.translator.CreateParset(otdb_id, ra_info, project_name) - logger.debug("Parset info for OTDB: %s" %otdb_info) + logger.info("Parset info for OTDB: %s" %otdb_info) self.setOTDBinfo(otdb_id, otdb_info, 'scheduled') except Exception as e: logger.error(e) -- GitLab From 9ab15226477d894569252e08b083accca5cc194b Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 28 Jun 2016 14:13:22 +0000 Subject: [PATCH 426/933] Task #8993: Created task branch -- GitLab From 333ce0e6dc5b6823171fc8ba223aab4eda64d701 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 28 Jun 2016 14:13:53 +0000 Subject: [PATCH 427/933] Task #8993: Use C++11 build in Dockerfiles --- Docker/lofar-base/Dockerfile.tmpl | 28 ++++++++++++++----------- Docker/lofar-outputproc/Dockerfile.tmpl | 14 ++++++------- Docker/lofar-pipeline/Dockerfile.tmpl | 19 +++++++++-------- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index aef0133c364..77228e242bc 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -1,7 +1,7 @@ # # base # -FROM ubuntu:14.04 +FROM ubuntu:16.04 # # common-environment @@ -18,10 +18,10 @@ ENV DEBIAN_FRONTEND=noninteractive \ # # versions # -ENV CASACORE_VERSION=2.1.0 \ - CASAREST_VERSION=1.4.1 \ - PYTHON_CASACORE_VERSION=2.0.1 \ - BOOST_VERSION=1.54 +ENV CASACORE_VERSION=latest \ + CASAREST_VERSION=latest \ + PYTHON_CASACORE_VERSION=2.1.2 \ + BOOST_VERSION=1.58 # # set-uid @@ -39,7 +39,11 @@ ENV J=6 #RUN sed -i 's/archive.ubuntu.com/osmirror.rug.nl/' /etc/apt/sources.list RUN apt-get update && \ apt-get install -y python2.7 libpython2.7 && \ - apt-get install -y libblas3 liblapacke python-numpy libcfitsio3 libwcs4 libfftw3-bin libhdf5-7 libboost-python${BOOST_VERSION}.0 && \ + apt-get install -y libopenblas-base libcfitsio-bin libwcs5 libfftw3-bin libhdf5-10 libboost-python${BOOST_VERSION}.0 && \ + apt-get install -y python-pip && \ + pip install numpy && \ + apt-get purge -y python-pip && \ + apt-get autoremove -y && \ apt-get install -y nano # @@ -59,19 +63,19 @@ RUN mkdir -p ${INSTALLDIR} # Casacore # ******************* # -RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison libopenblas-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/casacore/build && \ mkdir -p ${INSTALLDIR}/casacore/data && \ cd ${INSTALLDIR}/casacore && git clone https://github.com/casacore/casacore.git src && \ if [ "${CASACORE_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casacore/src && git checkout tags/v${CASACORE_VERSION}; fi && \ cd ${INSTALLDIR}/casacore/data && wget --retry-connrefused ftp://ftp.astron.nl/outgoing/Measures/WSRT_Measures.ztar && \ cd ${INSTALLDIR}/casacore/data && tar xf WSRT_Measures.ztar && rm -f WSRT_Measures.ztar && \ - cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DUSE_OPENMP=True -DUSE_FFTW3=TRUE -DUSE_HDF5=ON ../src/ && \ + cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DENABLE_TABLELOCKING=ON -DUSE_OPENMP=ON -DUSE_FFTW3=TRUE -DUSE_HDF5=ON -DCXX11=YES -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-fsigned-char -O2 -DNDEBUG -march=native" ../src/ && \ cd ${INSTALLDIR}/casacore/build && make -j ${J} && \ cd ${INSTALLDIR}/casacore/build && make install && \ bash -c "strip ${INSTALLDIR}/casacore/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casacore/{build,src}" && \ - apt-get purge -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ + apt-get purge -y wget git cmake g++ gfortran flex bison libopenblas-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ apt-get autoremove -y # @@ -79,16 +83,16 @@ RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison # Casarest # ******************* # -RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ mkdir -p ${INSTALLDIR}/casarest/build && \ cd ${INSTALLDIR}/casarest && git clone https://github.com/casacore/casarest.git src && \ if [ "${CASAREST_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casarest/src && git checkout tags/v${CASAREST_VERSION}; fi && \ - cd ${INSTALLDIR}/casarest/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casarest -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore ../src/ && \ + cd ${INSTALLDIR}/casarest/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casarest -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-std=c++11 -O2 -march=native -DNDEBUG" ../src/ && \ cd ${INSTALLDIR}/casarest/build && make -j ${J} && \ cd ${INSTALLDIR}/casarest/build && make install && \ bash -c "strip ${INSTALLDIR}/casarest/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casarest/{build,src}" && \ - apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ + apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ apt-get autoremove -y # diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index eb5ef0c866b..c25fffe979a 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -10,20 +10,20 @@ FROM lofar-base:${LOFAR_TAG} # # Run-time dependencies -RUN apt-get update && apt-get install -y liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libboost-regex${BOOST_VERSION}.0 +RUN apt-get update && apt-get install -y liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libboost-regex${BOOST_VERSION}.0 # Tell image build information -ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ +ENV LOFAR_BRANCH=${LOFAR_BRANCH} \ LOFAR_REVISION=${LOFAR_REVISION} \ - LOFAR_BUILDVARIANT=gnu_optarch + LOFAR_BUILDVARIANT=gnucxx11_optarch # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev && \ - mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libopenblas-dev && libcfitsio3-dev wcslib-dev \ + mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Online_OutputProc -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DDAL_ROOT_DIR=${INSTALLDIR}/DAL -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Online_OutputProc -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DQPID_ROOT_DIR=/opt/qpid/ -DDAL_ROOT_DIR=${INSTALLDIR}/DAL -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ @@ -32,6 +32,6 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ setcap cap_sys_nice,cap_ipc_lock=ep ${INSTALLDIR}/lofar/bin/outputProc && \ - apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev && \ + apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && libcfitsio3-dev wcslib-dev \ apt-get autoremove -y diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index eae03529529..94c6299eeff 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -3,10 +3,10 @@ # FROM lofar-base:${LOFAR_TAG} -ENV AOFLAGGER_VERSION=2.7.1 +ENV AOFLAGGER_VERSION=2.8.0 # Run-time dependencies -RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ +RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ apt-get -y install python-pip python-dev && \ pip install pyfits pywcs python-monetdb && \ apt-get -y purge python-pip python-dev && \ @@ -18,17 +18,17 @@ RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cp # ******************* # -RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ +RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libopenblas-dev && \ mkdir -p ${INSTALLDIR}/aoflagger/build && \ bash -c "cd ${INSTALLDIR}/aoflagger && wget --retry-connrefused http://downloads.sourceforge.net/project/aoflagger/aoflagger-${AOFLAGGER_VERSION%%.?}.0/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ cd ${INSTALLDIR}/aoflagger && tar xf aoflagger-${AOFLAGGER_VERSION}.tar.bz2 && \ - cd ${INSTALLDIR}/aoflagger/build && cmake -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/aoflagger ../aoflagger-${AOFLAGGER_VERSION} && \ + cd ${INSTALLDIR}/aoflagger/build && cmake -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_FLAGS="--std=c++11 -O2 -DNDEBUG" -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/aoflagger ../aoflagger-${AOFLAGGER_VERSION} && \ cd ${INSTALLDIR}/aoflagger/build && make -j ${J} && \ cd ${INSTALLDIR}/aoflagger/build && make install && \ bash -c "strip ${INSTALLDIR}/aoflagger/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/{build,aoflagger-${AOFLAGGER_VERSION}}" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ - apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ + apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libopenblas-dev && \ apt-get -y autoremove # @@ -40,15 +40,16 @@ RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12 # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ LOFAR_REVISION=${LOFAR_REVISION} \ - LOFAR_BUILDVARIANT=gnu_optarch + LOFAR_BUILDVARIANT=gnucxx11_optarch + # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Offline -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASAREST_ROOT_DIR=${INSTALLDIR}/casarest/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DAOFLAGGER_ROOT_DIR=${INSTALLDIR}/aoflagger/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Offline -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASAREST_ROOT_DIR=${INSTALLDIR}/casarest/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DAOFLAGGER_ROOT_DIR=${INSTALLDIR}/aoflagger/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ @@ -56,6 +57,6 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "chmod a+rwx ${INSTALLDIR}/lofar/var/{log,run}" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ - apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ + apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ apt-get autoremove -y -- GitLab From b582a3935f90396a698fbfe947886f4d5577b579 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 29 Jun 2016 04:29:40 +0000 Subject: [PATCH 428/933] Task #9192: Log pipeline output to pipeline-SASID-JOBID.log instead of runPipeline-SASID.log --- MAC/Services/src/PipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 1a751d8fc99..bb4ca8a5835 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -323,7 +323,7 @@ class PipelineControl(OTDBBusListener): "--cpus-per-task=%s" % parset.processingNumberOfCoresPerTask(), # Define better places to write the output - os.path.expandvars("--output=/data/log/runPipeline-%s.log" % (otdbId,)), + os.path.expandvars("--output=/data/log/pipeline-%s-%%j.log" % (otdbId,)), ] def setStatus_cmdline(status): -- GitLab From e06ee15d39feab29696adf60720ba27822f18471 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 29 Jun 2016 04:32:41 +0000 Subject: [PATCH 429/933] Task #9192: All output goes to .log files (stderr+stdout) --- MAC/Services/src/pipelinecontrol.ini | 3 ++- SAS/ResourceAssignment/Services/src/rataskspecifiedservice.ini | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/pipelinecontrol.ini b/MAC/Services/src/pipelinecontrol.ini index c5a07a437f9..e6bbb791fcc 100644 --- a/MAC/Services/src/pipelinecontrol.ini +++ b/MAC/Services/src/pipelinecontrol.ini @@ -4,4 +4,5 @@ user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true stdout_logfile=%(program_name)s.log -stderr_logfile=%(program_name)s.stderr +redirect_stderr=true +stderr_logfile=NONE diff --git a/SAS/ResourceAssignment/Services/src/rataskspecifiedservice.ini b/SAS/ResourceAssignment/Services/src/rataskspecifiedservice.ini index 1aaf8b5a77e..23d43f56ee0 100644 --- a/SAS/ResourceAssignment/Services/src/rataskspecifiedservice.ini +++ b/SAS/ResourceAssignment/Services/src/rataskspecifiedservice.ini @@ -4,4 +4,5 @@ user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true stdout_logfile=%(program_name)s.log -stderr_logfile=%(program_name)s.stderr +redirect_stderr=true +stderr_logfile=NONE -- GitLab From 2b1b54e47f030eac4d385bb9bd65f493e560ebdc Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 29 Jun 2016 04:38:46 +0000 Subject: [PATCH 430/933] Task #9192: Add debug output for predecessor/successor ids --- MAC/Services/src/PipelineControl.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index bb4ca8a5835..8a4798f18a8 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -220,10 +220,13 @@ class PipelineDependencies(object): """ radb_task = self.rarpc.getTask(otdb_id=otdb_id) - predecessor_ids = radb_task['predecessor_ids'] - predecessor_tasks = self.rarpc.getTasks(task_ids=predecessor_ids) + predecessor_radb_ids = radb_task['predecessor_ids'] + predecessor_tasks = self.rarpc.getTasks(task_ids=predecessor_radb_ids) + predecessor_states = {t["otdb_id"]: t["status"] for t in predecessor_tasks} - return {t["otdb_id"]: t["status"] for t in predecessor_tasks} + logger.debug("getPredecessorStates(%s) = %s", otdb_id, predecessor_states) + + return predecessor_states def getSuccessorIds(self, otdb_id): """ @@ -231,19 +234,27 @@ class PipelineDependencies(object): """ radb_task = self.rarpc.getTask(otdb_id=otdb_id) - successor_ids = radb_task['successor_ids'] - successor_tasks = self.rarpc.getTasks(task_ids=successor_ids) if successor_ids else [] + successor_radb_ids = radb_task['successor_ids'] + successor_tasks = self.rarpc.getTasks(task_ids=successor_ids) if successor_radb_ids else [] + successor_otdb_ids = [t["otdb_id"] for t in successor_tasks] + + logger.debug("getSuccessorIds(%s) = %s", otdb_id, successor_otdb_ids) - return [t["otdb_id"] for t in successor_tasks] + return successor_otdb_ids def canStart(self, otdbId): """ Return whether `otdbId' can start, according to the status of the predecessors and its own status. """ + myState = self.getState(otdbId) + predecessorStates = self.getPredecessorStates(otdbId) + + logger.debug("canStart(%s)? state = %s, predecessors = %s", otdbId, myState, predecessorStates) + return ( - self.getState(otdbId) == "scheduled" and - all([x == "finished" for x in self.getPredecessorStates(otdbId).values()]) + myState == "scheduled" and + all([x == "finished" for x in predecessorStates.values()]) ) class PipelineControl(OTDBBusListener): -- GitLab From e5f369ffa77bfd92f9bbcebcde4d9eb30d022aac Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 29 Jun 2016 04:39:13 +0000 Subject: [PATCH 431/933] Task #9192: Use FQDN for cluster headnode --- MAC/Services/src/PipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 8a4798f18a8..2538c27f864 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -154,7 +154,7 @@ class Parset(dict): return int(self[PARSET_PREFIX + "Observation.otdbID"]) class Slurm(object): - def __init__(self, headnode="head01.cep4"): + def __init__(self, headnode="head01.cep4.control.lofar"): self.headnode = headnode # TODO: Derive SLURM partition name -- GitLab From d526b53349da9399dd8f9debfadc9b27abb35815 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 29 Jun 2016 04:41:46 +0000 Subject: [PATCH 432/933] Task #9192: Only log runCommand output on INFO if exit status != 0 --- MAC/Services/src/PipelineControl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 2538c27f864..b0b5996ad93 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -93,12 +93,13 @@ def runCommand(cmdline, input=None): ) # Feed input and wait for termination - logger.info("runCommand input: %s", input) + logger.debug("runCommand input: %s", input) stdout, _ = proc.communicate(input) - logger.info("runCommand output: %s", stdout) + logger.debug("runCommand output: %s", stdout) # Check exit status, bail on error if proc.returncode != 0: + logger.warn("runCommand(%s) had exit status %s with output: %s", cmdline, proc.returncode, stdout) raise subprocess.CalledProcessError(proc.returncode, cmdline) # Return output -- GitLab From 02b14cf3a29520dea4cca923ada9cbed1b937183 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 29 Jun 2016 08:42:15 +0000 Subject: [PATCH 433/933] Task #9192: Guard against tasks not being present in RADB --- MAC/Services/src/PipelineControl.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index b0b5996ad93..5a3325d3bc9 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -191,6 +191,10 @@ class Slurm(object): return stdout != "" class PipelineDependencies(object): + class TaskNotFoundException(Exception): + """ Raised when a task cannot be found in the RADB. """ + pass + def __init__(self, ra_service_busname=DEFAULT_RAS_SERVICE_BUSNAME): self.rarpc = RARPC(busname=ra_service_busname) @@ -221,6 +225,9 @@ class PipelineDependencies(object): """ radb_task = self.rarpc.getTask(otdb_id=otdb_id) + if radb_task is None: + raise TaskNotFoundException("otdb_id %s not found in RADB" % (otdb_id,)) + predecessor_radb_ids = radb_task['predecessor_ids'] predecessor_tasks = self.rarpc.getTasks(task_ids=predecessor_radb_ids) predecessor_states = {t["otdb_id"]: t["status"] for t in predecessor_tasks} @@ -235,6 +242,9 @@ class PipelineDependencies(object): """ radb_task = self.rarpc.getTask(otdb_id=otdb_id) + if radb_task is None: + raise TaskNotFoundException("otdb_id %s not found in RADB" % (otdb_id,)) + successor_radb_ids = radb_task['successor_ids'] successor_tasks = self.rarpc.getTasks(task_ids=successor_ids) if successor_radb_ids else [] successor_otdb_ids = [t["otdb_id"] for t in successor_tasks] @@ -248,8 +258,13 @@ class PipelineDependencies(object): Return whether `otdbId' can start, according to the status of the predecessors and its own status. """ - myState = self.getState(otdbId) - predecessorStates = self.getPredecessorStates(otdbId) + + try: + myState = self.getState(otdbId) + predecessorStates = self.getPredecessorStates(otdbId) + except TaskNotFoundException, e: + logger.error("canStart(%s): Error obtaining task states, not starting pipeline: %s", otdbId, e) + return False logger.debug("canStart(%s)? state = %s, predecessors = %s", otdbId, myState, predecessorStates) @@ -441,7 +456,13 @@ class PipelineControl(OTDBBusListener): cancel(jobName) def _startSuccessors(self, otdbId): - for s in self.dependencies.getSuccessorIds(otdbId): + try: + successor_ids = self.dependencies.getSuccessorIds(otdbId) + except TaskNotFoundException, e: + logger.error("_startSuccessors(%s): Error obtaining task successors, not starting them: %s", otdbId, e) + return + + for s in successor_ids: parset = self._getParset(s) if not self._shouldHandle(parset): continue -- GitLab From 2b9fcd5a3caa7a2adc08591ef62e427f63bd7e7b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 29 Jun 2016 08:52:14 +0000 Subject: [PATCH 434/933] Task #9192: bug fix: apply sane defaults for start/end time of pipelines when unspecified --- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 0516cbf8040..f0e31b9c13b 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -127,8 +127,7 @@ class ResourceAssigner(): startTime = datetime.strptime(mainParset.getString('Observation.startTime'), '%Y-%m-%d %H:%M:%S') endTime = datetime.strptime(mainParset.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') except ValueError: - logger.warning('cannot parse for start/end time from specification for otdb_id=%s. skipping specification.', (otdb_id, )) - return + logger.warning('cannot parse for start/end time from specification for otdb_id=%s. searching for sane defaults...', (otdb_id, )) maxPredecessorEndTime = self.getMaxPredecessorEndTime(specification_tree) if maxPredecessorEndTime: -- GitLab From 044918f078760bc8e35f85f705995ba6c3ae2467 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 29 Jun 2016 10:02:23 +0000 Subject: [PATCH 435/933] Task #9350: added cache to service for faster response times which is prepopulated on observation finished events, stored on disk, and refreshed every 15 min --- .gitattributes | 1 + .../StorageQueryService/CMakeLists.txt | 3 +- .../StorageQueryService/cache.py | 237 ++++++++++++++++++ .../StorageQueryService/diskusage.py | 98 +++++++- SAS/DataManagement/StorageQueryService/rpc.py | 41 ++- .../StorageQueryService/service.py | 117 +++------ 6 files changed, 403 insertions(+), 94 deletions(-) create mode 100644 SAS/DataManagement/StorageQueryService/cache.py diff --git a/.gitattributes b/.gitattributes index 404af8e4163..a3955009268 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4749,6 +4749,7 @@ SAS/DataManagement/DataManagementCommon/messagehandler.py -text SAS/DataManagement/DataManagementCommon/path.py -text SAS/DataManagement/StorageQueryService/CMakeLists.txt -text SAS/DataManagement/StorageQueryService/__init__.py -text +SAS/DataManagement/StorageQueryService/cache.py -text SAS/DataManagement/StorageQueryService/config.py -text SAS/DataManagement/StorageQueryService/diskusage.py -text SAS/DataManagement/StorageQueryService/rpc.py -text diff --git a/SAS/DataManagement/StorageQueryService/CMakeLists.txt b/SAS/DataManagement/StorageQueryService/CMakeLists.txt index a7f354d838c..2db5dcd4e79 100644 --- a/SAS/DataManagement/StorageQueryService/CMakeLists.txt +++ b/SAS/DataManagement/StorageQueryService/CMakeLists.txt @@ -1,6 +1,6 @@ # $Id$ -lofar_package(StorageQueryService 1.0 DEPENDS PyMessaging MoMQueryService DataManagementCommon) +lofar_package(StorageQueryService 1.0 DEPENDS PyMessaging MoMQueryService DataManagementCommon OTDB_Services) lofar_find_package(Python 2.6 REQUIRED) include(PythonInstall) @@ -11,6 +11,7 @@ set(_py_files rpc.py service.py diskusage.py + cache.py ) python_install(${_py_files} DESTINATION lofar/sas/datamanagement/storagequery) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py new file mode 100644 index 00000000000..405c7fa89d5 --- /dev/null +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -0,0 +1,237 @@ +#!/usr/bin/python +# $Id$ + +''' +TODO: add doc +''' +import logging +import datetime +from time import sleep +import ast +from optparse import OptionParser +from threading import Thread, RLock +import os.path + +from lofar.sas.datamanagement.storagequery.diskusage import getDiskUsageForPath as du_getDiskUsageForPath +from lofar.sas.datamanagement.storagequery.diskusage import DiskUsage +from lofar.sas.otdb.OTDBBusListener import OTDBBusListener +from lofar.common.util import waitForInterrupt +from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT +from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTDB_NOTIFICATION_SUBJECT +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME +from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME + +logger = logging.getLogger(__name__) + +class CacheManager(OTDBBusListener): + def __init__(self, + mountpoint=CEP4_DATA_MOUNTPOINT, + radb_busname=RADB_BUSNAME, + radb_servicename=RADB_SERVICENAME, + mom_busname=DEFAULT_MOMQUERY_BUSNAME, + mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, + otdb_notification_busname=DEFAULT_OTDB_NOTIFICATION_BUSNAME, + otdb_notification_subject=DEFAULT_OTDB_NOTIFICATION_SUBJECT, + broker=None): + super(CacheManager, self).__init__(busname=otdb_notification_busname, + subject=otdb_notification_subject, + broker=broker) + + self._updateCacheThread = None + self._updateCacheThreadRunning = False + self._cacheLock = RLock() + + self._cache = {'paths':{}, 'otdb_ids': {}} + self._readCacheFromDisk() + + self.disk_usage = DiskUsage(mountpoint=mountpoint, + radb_busname=radb_busname, + radb_servicename=radb_servicename, + mom_busname=mom_busname, + mom_servicename=mom_servicename, + broker=broker) + + def _readCacheFromDisk(self): + # maybe this cache on disk is slow, if so, revert to proper db solution + try: + with open('.du_cache.py', 'r') as file: + with self._cacheLock: + self._cache = eval(file.read()) + if not isinstance(self._cache, dict) or 'paths' not in self._cache or 'otdb_ids' not in self._cache: + self._cache = {'paths':{}, 'otdb_ids': {}} + except Exception as e: + logger.error("Error while reading in du cache: %s", e) + with self._cacheLock: + self._cache = {'paths':{}, 'otdb_ids': {}} + + def _writeCacheToDisk(self): + try: + with open('.du_cache.py', 'w') as file: + with self._cacheLock: + file.write(str(self._cache)) + except Exception as e: + logger.error("Error while writing du cache: %s", e) + + def _updateCache(self, du_result): + if du_result['found']: + du_result['cache_timestamp'] = datetime.datetime.utcnow() + with self._cacheLock: + self._cache['paths'][du_result['path']] = du_result + if 'otdb_id' in du_result: + self._cache['otdb_ids'][du_result['otdb_id']] = du_result + + self._writeCacheToDisk() + else: + with self._cacheLock: + if 'path' in du_result: + path = du_result['path'] + logger.info('removing path \'%s\' from cache', path) + path_cache = self._cache['paths'] + del path_cache[path] + if 'otdb_id' in du_result: + otdb_id = du_result['otdb_id'] + logger.info('removing otdb_id %s from cache', otdb_id) + otdb_cache = self._cache['otdb_ids'] + del otdb_cache[otdb_id] + + def _updateOldEntriesInCache(self): + while self._updateCacheThreadRunning: + now = datetime.datetime.utcnow() + with self._cacheLock: + old_entries = {path:du_result for path,du_result in self._cache['paths'].items() if now - du_result['cache_timestamp'] > datetime.timedelta(minutes=15)} + + for path, du_result in old_entries.items(): + logger.info('updating old entry in cache: %s', path) + result = du_getDiskUsageForPath(path) + self._updateCache(result) + + for i in range(60): + sleep(1) + if self._updateCacheThreadRunning: + return + + def open(self): + self.disk_usage.open() + + self._updateCacheThread = Thread(target=self._updateOldEntriesInCache) + self._updateCacheThread.daemon = True + self._updateCacheThreadRunning = True + self._updateCacheThread.start() + + super(CacheManager, self).start_listening() + + def close(self): + self._updateCacheThreadRunning = False + self._updateCacheThread.join() + + self.disk_usage.close() + super(CacheManager, self).stop_listening() + + def __enter__(self): + self.open() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def onObservationFinished(self, otdb_id, modificationTime): + result = self.disk_usage.getDiskUsageForOTDBId(otdb_id) + self._updateCache(result) + + def onObservationAborted(self, otdb_id, modificationTime): + result = self.disk_usage.getDiskUsageForOTDBId(otdb_id) + self._updateCache(result) + + def getDiskUsageForOTDBId(self, otdb_id): + return self.getDiskUsageForTask(otdb_id=otdb_id) + + def getDiskUsageForMoMId(self, mom_id): + return self.getDiskUsageForTask(mom_id=mom_id) + + def getDiskUsageForRADBId(self, radb_id): + return self.getDiskUsageForTask(radb_id=radb_id) + + def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None): + logger.debug("cache.getDiskUsageForTask(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + result = self.disk_usage.path_resolver.getPathForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + if result['found']: + return self.getDiskUsageForPath(result['path']) + + return {'found': False, 'path': result['path']} + + def getDiskUsageForPath(self, path): + logger.debug("cache.getDiskUsageForPath(%s)", path) + needs_cache_update = False + with self._cacheLock: + needs_cache_update = path not in self._cache['paths'] + + if needs_cache_update: + result = du_getDiskUsageForPath(path) + self._updateCache(result) + + with self._cacheLock: + if path in self._cache['paths']: + result = self._cache['paths'][path] + else: + result = { 'found': False, 'path':path } + + logger.info('cache.getDiskUsageForPath result: %s' % result) + return result + + def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): + logger.debug("cache.getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + task_du_result = self.getDiskUsageForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + if task_du_result['found']: + task_sd_result = self.disk_usage.path_resolver.getSubDirectoriesForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + + if task_sd_result['found']: + subdir_paths = [os.path.join(task_du_result['path'],sd) for sd in task_sd_result['sub_directories']] + + #TODO: potential for parallelization + subdirs_du_result = { sd: self.getDiskUsageForPath(sd) for sd in subdir_paths } + + result = {'found':True, 'task_directory': task_du_result, 'sub_directories': subdirs_du_result } + logger.info('cache.getDiskUsageForTaskAndSubDirectories result: %s' % result) + return result + + return task_du_result + + def getDiskUsageForProjectSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): + logger.debug("cache.getDiskUsageForProjectSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + path_result = self.disk_usage.path_resolver.getProjectSubDirectories(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + if path_result['found']: + projectdir_du_result = self.getDiskUsageForPath(path_result['path']) + subdir_paths = [os.path.join(path_result['path'],sd) for sd in path_result['sub_directories']] + + #TODO: potential for parallelization + subdirs_du_result = { sd: self.getDiskUsageForPath(sd) for sd in subdir_paths } + result = {'found':True, 'projectdir': projectdir_du_result, 'sub_directories': subdirs_du_result } + logger.info('cache.getDiskUsageForProjectSubDirectories result: %s' % result) + return result + + return path_result + + def getDiskUsageForProjectsDirAndSubDirectories(self): + logger.debug("cache.getDiskUsageForProjectsDirAndSubDirectories") + projects_path = self.disk_usage.path_resolver.projects_path + projectsdir_du_result = self.getDiskUsageForPath(projects_path) + + result = {'found':True, 'projectdir': projectsdir_du_result } + + project_subdirs_result = self.disk_usage.path_resolver.getSubDirectories(projects_path) + if project_subdirs_result['found']: + subdir_paths = [os.path.join(projects_path,sd) for sd in project_subdirs_result['sub_directories']] + + #TODO: potential for parallelization + subdirs_du_result = { sd: self.getDiskUsageForPath(sd) for sd in subdir_paths } + result['sub_directories'] = subdirs_du_result + + logger.info('cache.getDiskUsageForProjectsDirAndSubDirectories result: %s' % result) + return result + +if __name__ == '__main__': + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO) + + with CacheManager(broker='scu099.control.lofar') as cm: + waitForInterrupt() diff --git a/SAS/DataManagement/StorageQueryService/diskusage.py b/SAS/DataManagement/StorageQueryService/diskusage.py index d8be95762f6..61202b0a29f 100644 --- a/SAS/DataManagement/StorageQueryService/diskusage.py +++ b/SAS/DataManagement/StorageQueryService/diskusage.py @@ -4,12 +4,20 @@ import logging import subprocess import socket +import os.path from optparse import OptionParser from lofar.common.util import humanreadablesize +from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT +from lofar.sas.datamanagement.common.path import PathResolver +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME +from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME + logger = logging.getLogger(__name__) def getDiskUsageForPath(path): + logger.info('getDiskUsageForPath(%s)', path) cmd = ['rbh-du', '-bd', path] hostname = socket.gethostname() if not 'mgmt0' in hostname: @@ -33,9 +41,9 @@ def getDiskUsageForPath(path): #parse out lines = [l.strip() for l in out.split('\n')] - file_line = next(l for l in lines if 'file count' in l) - if file_line: - parts = [p.strip() for p in file_line.split(',')] + file_lines = [l for l in lines if 'file count' in l] + if file_lines: + parts = [p.strip() for p in file_lines[0].split(',')] partsDict = {p.split(':')[0].strip():p.split(':')[1].strip() for p in parts} result = {'found': True, 'disk_usage': None, 'path': path} @@ -48,11 +56,93 @@ def getDiskUsageForPath(path): result['disk_usage_readable'] = humanreadablesize(result['disk_usage']) else: - result = {'found': False, 'path': path } + dir_lines = [l for l in lines if 'dir count' in l] + if dir_lines: + result = {'found': True, 'disk_usage': 0, 'nr_of_files': 0, 'path': path} + else: + result = {'found': False, 'path': path } logger.info('returning: %s' % result) return result + +class DiskUsage: + def __init__(self, + mountpoint=CEP4_DATA_MOUNTPOINT, + radb_busname=RADB_BUSNAME, + radb_servicename=RADB_SERVICENAME, + mom_busname=DEFAULT_MOMQUERY_BUSNAME, + mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, + broker=None): + self.path_resolver = PathResolver(mountpoint=mountpoint, + radb_busname=radb_busname, + radb_servicename=radb_servicename, + mom_busname=mom_busname, + mom_servicename=mom_servicename, + broker=broker) + + def open(self): + self.path_resolver.open() + + def close(self): + self.path_resolver.close() + + def __enter__(self): + self.open() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def getDiskUsageForOTDBId(self, otdb_id): + return self.getDiskUsageForTask(otdb_id=otdb_id) + + def getDiskUsageForMoMId(self, mom_id): + return self.getDiskUsageForTask(mom_id=mom_id) + + def getDiskUsageForRADBId(self, radb_id): + return self.getDiskUsageForTask(radb_id=radb_id) + + def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None): + logger.info("getDiskUsageForTask(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + result = self.path_resolver.getPathForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + if result['found']: + return getDiskUsageForPath(result['path']) + + return {'found': False, 'path': result['path']} + + def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): + logger.info("getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + task_du_result = self.getDiskUsageForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + if task_du_result['found']: + task_sd_result = self.path_resolver.getSubDirectoriesForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + + if task_sd_result['found']: + subdir_paths = [os.path.join(task_du_result['path'],sd) for sd in task_sd_result['sub_directories']] + + #TODO: potential for parallelization + subdirs_du_result = { sd: getDiskUsageForPath(sd) for sd in subdir_paths } + result = {'found':True, 'task_direcory': task_du_result, 'sub_directories': subdirs_du_result } + logger.info('result: %s' % result) + return result + + return task_du_result + + def getDiskUsageForProjectSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): + logger.info("getDiskUsageForProjectSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + path_result = self.path_resolver.getProjectSubDirectories(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + if path_result['found']: + projectdir_du_result = getDiskUsageForPath(path_result['path']) + subdir_paths = [os.path.join(path_result['path'],sd) for sd in path_result['sub_directories']] + + #TODO: potential for parallelization + subdirs_du_result = { sd: getDiskUsageForPath(sd) for sd in subdir_paths } + result = {'found':True, 'projectdir': projectdir_du_result, 'sub_directories': subdirs_du_result } + logger.info('result: %s' % result) + return result + + return path_result + def main(): # Check the invocation arguments parser = OptionParser("%prog [options] <path>", diff --git a/SAS/DataManagement/StorageQueryService/rpc.py b/SAS/DataManagement/StorageQueryService/rpc.py index 2931868c86e..b46b90becb7 100644 --- a/SAS/DataManagement/StorageQueryService/rpc.py +++ b/SAS/DataManagement/StorageQueryService/rpc.py @@ -1,6 +1,7 @@ #!/usr/bin/python import logging +import qpid from lofar.messaging.RPC import RPC, RPCException, RPCWrapper from lofar.sas.datamanagement.storagequery.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME from lofar.common.util import humanreadablesize @@ -13,23 +14,36 @@ class StorageQueryRPC(RPCWrapper): broker=None): super(StorageQueryRPC, self).__init__(busname, servicename, broker, timeout=6000) + def _convertTimestamps(self, result): + if isinstance(result, dict): + for k, v in result.items(): + if isinstance(v, dict): + self._convertTimestamps(v) + elif isinstance(v, qpid.datatypes.timestamp): + result[k] = v.datetime() + + return result + def getDiskUsageForOTDBId(self, otdb_id): - return self.rpc('GetDiskUsageForOTDBId', otdb_id=otdb_id) + return self._convertTimestamps(self.rpc('GetDiskUsageForOTDBId', otdb_id=otdb_id)) def getDiskUsageForMoMId(self, mom_id): - return self.rpc('GetDiskUsageForMoMId', mom_id=mom_id) + return self._convertTimestamps(self.rpc('GetDiskUsageForMoMId', mom_id=mom_id)) def getDiskUsageForRADBId(self, radb_id): - return self.rpc('GetDiskUsageForRADBId', radb_id=radb_id) + return self._convertTimestamps(self.rpc('GetDiskUsageForRADBId', radb_id=radb_id)) def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None): - return self.rpc('GetDiskUsageForTask', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + return self._convertTimestamps(self.rpc('GetDiskUsageForTask', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id)) def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): - return self.rpc('GetDiskUsageForTaskAndSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + return self._convertTimestamps(self.rpc('GetDiskUsageForTaskAndSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id)) def getDiskUsageForProjectSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): - return self.rpc('GetDiskUsageForProjectSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + return self._convertTimestamps(self.rpc('GetDiskUsageForProjectSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id)) + + def getDiskUsageForProjectsDirAndSubDirectories(self): + return self._convertTimestamps(self.rpc('GetDiskUsageForProjectsDirAndSubDirectories')) def main(): import sys @@ -42,14 +56,15 @@ def main(): parser.add_option('-m', '--mom_id', dest='mom_id', type='int', default=None, help='mom_id of task to get the disk usage for') parser.add_option('-r', '--radb_id', dest='radb_id', type='int', default=None, help='radb_id of task to get the disk usage for') parser.add_option('-s', '--subdirs', dest='subdirs', action='store_true', help='get the disk usage of the task and its sub directories for the given otdb_id/mom_id/radb_id') - parser.add_option('-P', '--project', dest='project', action='store_true', help='get the disk usage of the project path and all its sub directories for the given otdb_id/mom_id/radb_id') + parser.add_option('-p', '--project', dest='project', action='store_true', help='get the disk usage of the project path and all its sub directories for the given otdb_id/mom_id/radb_id') + parser.add_option('-P', '--projects', dest='projects', action='store_true', help='get the disk usage of the projects path and all its projects sub directories') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') parser.add_option('--busname', dest='busname', type='string', default=DEFAULT_BUSNAME, help='Name of the bus exchange on the qpid broker, default: %s' % DEFAULT_BUSNAME) parser.add_option('--servicename', dest='servicename', type='string', default=DEFAULT_SERVICENAME, help='Name for this service, default: %s' % DEFAULT_SERVICENAME) parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() - if not (options.otdb_id or options.mom_id or options.radb_id): + if not (options.otdb_id or options.mom_id or options.radb_id or options.projects): parser.print_help() exit(1) @@ -57,7 +72,15 @@ def main(): level=logging.INFO if options.verbose else logging.WARN) with StorageQueryRPC(busname=options.busname, servicename=options.servicename, broker=options.broker) as rpc: - if options.project: + if options.projects: + result = rpc.getDiskUsageForProjectsDirAndSubDirectories() + if result['found']: + import pprint + pprint.pprint(result) + else: + print result['message'] + exit(1) + elif options.project: result = rpc.getDiskUsageForProjectSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) if result['found']: import pprint diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py index 86d2ab43d9f..0d55af7e84f 100644 --- a/SAS/DataManagement/StorageQueryService/service.py +++ b/SAS/DataManagement/StorageQueryService/service.py @@ -7,12 +7,14 @@ import socket import os.path from optparse import OptionParser from lofar.messaging import Service +from lofar.messaging.Service import MessageHandlerInterface from lofar.messaging import setQpidLogLevel from lofar.common.util import waitForInterrupt from lofar.sas.datamanagement.common.messagehandler import MessageHandler from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT -from lofar.sas.datamanagement.storagequery.diskusage import getDiskUsageForPath +from lofar.sas.datamanagement.storagequery.cache import CacheManager +from lofar.sas.datamanagement.storagequery.diskusage import DiskUsage from lofar.sas.datamanagement.storagequery.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME @@ -20,7 +22,7 @@ from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_M logger = logging.getLogger(__name__) -class StorageQueryHandler(MessageHandler): +class StorageQueryHandler(MessageHandlerInterface): def __init__(self, mountpoint=CEP4_DATA_MOUNTPOINT, radb_busname=RADB_BUSNAME, @@ -30,90 +32,43 @@ class StorageQueryHandler(MessageHandler): broker=None, **kwargs): - super(StorageQueryHandler, self).__init__(mountpoint=mountpoint, - radb_busname=radb_busname, - radb_servicename=radb_servicename, - mom_busname=mom_busname, - mom_servicename=mom_servicename, - broker=broker, - **kwargs) - - self.service2MethodMap = {'GetDiskUsageForOTDBId': self.getDiskUsageForOTDBId, - 'GetDiskUsageForMoMId': self.getDiskUsageForMoMId, - 'GetDiskUsageForRADBId': self.getDiskUsageForRADBId, - 'GetDiskUsageForTask': self.getDiskUsageForTask, - 'GetDiskUsageForTaskAndSubDirectories': self.getDiskUsageForTaskAndSubDirectories, - 'GetDiskUsageForProjectSubDirectories': self.getDiskUsageForProjectSubDirectories} - - def prepare_loop(self): - pass - - def getDiskUsageForOTDBId(self, otdb_id): - return self.getDiskUsageForTask(otdb_id=otdb_id) - - def getDiskUsageForMoMId(self, mom_id): - return self.getDiskUsageForTask(mom_id=mom_id) - - def getDiskUsageForRADBId(self, radb_id): - return self.getDiskUsageForTask(radb_id=radb_id) - - def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None): - logger.info("getDiskUsageForTask(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) - result = self.path_resolver.getPathForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) - if result['found']: - return getDiskUsageForPath(result['path']) - - return {'found': False, 'path': result['path']} - - def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): - logger.info("getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) - task_du_result = self.getDiskUsageForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) - if task_du_result['found']: - task_sd_result = self.path_resolver.getSubDirectoriesForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) - - if task_sd_result['found']: - subdir_paths = [os.path.join(task_du_result['path'],sd) for sd in task_sd_result['sub_directories']] - - #TODO: potential for parallelization - subdirs_du_result = { sd: getDiskUsageForPath(sd) for sd in subdir_paths } - result = {'found':True, 'task_direcory': task_du_result, 'sub_directories': subdirs_du_result } - logger.info('result: %s' % result) - return result - - return task_du_result - - def getDiskUsageForProjectSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): - logger.info("getDiskUsageForProjectSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) - path_result = self.path_resolver.getProjectSubDirectories(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) - if path_result['found']: - projectdir_du_result = getDiskUsageForPath(path_result['path']) - subdir_paths = [os.path.join(path_result['path'],sd) for sd in path_result['sub_directories']] - - #TODO: potential for parallelization - subdirs_du_result = { sd: getDiskUsageForPath(sd) for sd in subdir_paths } - result = {'found':True, 'projectdir': projectdir_du_result, 'sub_directories': subdirs_du_result } - logger.info('result: %s' % result) - return result - - return path_result + self.cache = kwargs.pop('cache_manager') + + super(StorageQueryHandler, self).__init__(**kwargs) + self.disk_usage = DiskUsage(mountpoint=mountpoint, + radb_busname=radb_busname, + radb_servicename=radb_servicename, + mom_busname=mom_busname, + mom_servicename=mom_servicename, + broker=broker) + + self.service2MethodMap = {'GetDiskUsageForOTDBId': self.cache.getDiskUsageForOTDBId, + 'GetDiskUsageForMoMId': self.cache.getDiskUsageForMoMId, + 'GetDiskUsageForRADBId': self.cache.getDiskUsageForRADBId, + 'GetDiskUsageForTask': self.cache.getDiskUsageForTask, + 'GetDiskUsageForTaskAndSubDirectories': self.cache.getDiskUsageForTaskAndSubDirectories, + 'GetDiskUsageForProjectSubDirectories': self.cache.getDiskUsageForProjectSubDirectories, + 'GetDiskUsageForProjectsDirAndSubDirectories': self.cache.getDiskUsageForProjectsDirAndSubDirectories} def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, mountpoint=CEP4_DATA_MOUNTPOINT, verbose=False, radb_busname=RADB_BUSNAME, radb_servicename=RADB_SERVICENAME, - mom_busname=DEFAULT_MOMQUERY_BUSNAME, mom_servicename=DEFAULT_MOMQUERY_SERVICENAME): + mom_busname=DEFAULT_MOMQUERY_BUSNAME, mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, + cache_manager=None): return Service(servicename, StorageQueryHandler, busname=busname, broker=broker, use_service_methods=True, - numthreads=2, + numthreads=4, verbose=verbose, handler_args={'mountpoint': mountpoint, 'radb_busname':RADB_BUSNAME, 'radb_servicename':RADB_SERVICENAME, 'mom_busname':DEFAULT_MOMQUERY_BUSNAME, 'mom_servicename':DEFAULT_MOMQUERY_SERVICENAME, - 'broker':broker}) + 'broker':broker, + 'cache_manager':cache_manager}) def main(): # Check the invocation arguments @@ -133,15 +88,17 @@ def main(): logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.DEBUG if options.verbose else logging.INFO) - with createService(busname=options.busname, - servicename=options.servicename, - broker=options.broker, - verbose=options.verbose, - radb_busname=options.radb_busname, - radb_servicename=options.radb_servicename, - mom_busname=options.mom_busname, - mom_servicename=options.mom_busname): - waitForInterrupt() + with CacheManager(broker=options.broker) as cache_manager: + with createService(busname=options.busname, + servicename=options.servicename, + broker=options.broker, + verbose=options.verbose, + radb_busname=options.radb_busname, + radb_servicename=options.radb_servicename, + mom_busname=options.mom_busname, + mom_servicename=options.mom_busname, + cache_manager=cache_manager): + waitForInterrupt() if __name__ == '__main__': main() -- GitLab From 009d3f79502583702fc5ba7d2130814b3c683980 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 29 Jun 2016 10:59:45 +0000 Subject: [PATCH 436/933] Task #8993: Backport of fix from #9192 for long-baseline pipelines --- CEP/Pipeline/recipes/sip/nodes/long_baseline.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py index 244107a677c..2fe0aa3e405 100644 --- a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py +++ b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py @@ -58,6 +58,8 @@ class long_baseline(LOFARnodeTCP): # I. Create the directories used in this recipe create_directory(processed_ms_dir) create_directory(working_dir) + create_directory(os.path.dirname(output_measurement_set)) + create_directory(os.path.dirname(final_output_path)) # time slice dir_to_remove: assure empty directory: Stale data # is problematic for dppp @@ -499,12 +501,6 @@ class long_baseline(LOFARnodeTCP): table = pt.table(output_measurement_set) - try: - os.makedirs(os.path.dirname(final_output_path)) - except: - pass # do nothing, the path already exists, we can output to this - # location - table.copy(final_output_path, deep=True) -- GitLab From 2c0f80a1de573da81cee9d1b06526b4b511b1733 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 29 Jun 2016 11:55:33 +0000 Subject: [PATCH 437/933] Task #9350: expose diskusage via rest api for projects and tasks --- .../DataManagementCommon/path.py | 10 ++-- .../StorageQueryService/cache.py | 26 +++++----- .../StorageQueryService/diskusage.py | 8 +-- SAS/DataManagement/StorageQueryService/rpc.py | 9 ++-- .../StorageQueryService/service.py | 5 +- .../lib/webservice.py | 50 +++++++++++++++++-- 6 files changed, 81 insertions(+), 27 deletions(-) diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index dfb75d94cbd..4fe151e88c4 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -113,8 +113,12 @@ class PathResolver: return result - def getProjectSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): - '''get the project subdirectories of a task's project for either the given radb_id, or for the given mom_id, or for the given otdb_id''' + def getProjectDirAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None, project_name=None): + '''get the project directory and its subdirectories of either the project_name, or the task's project for either the given radb_id, or for the given mom_id, or for the given otdb_id''' + if project_name: + project_path = os.path.join(self.projects_path, "_".join(project_name.split())) + return self.getSubDirectories(project_path) + result = self.getProjectPath(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) if result['found']: return self.getSubDirectories(result['path']) @@ -201,7 +205,7 @@ def main(): exit(1) if options.project: - result = path_resolver.getProjectSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + result = path_resolver.getProjectDirAndSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) if result['found']: print "projectpath: %s" % (result['path']) print "subdirectories: %s" % (' '.join(result['sub_directories'])) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index 405c7fa89d5..878361492c9 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -86,14 +86,16 @@ class CacheManager(OTDBBusListener): with self._cacheLock: if 'path' in du_result: path = du_result['path'] - logger.info('removing path \'%s\' from cache', path) path_cache = self._cache['paths'] - del path_cache[path] + if 'path' in path_cache: + logger.info('removing path \'%s\' from cache', path) + del path_cache[path] if 'otdb_id' in du_result: otdb_id = du_result['otdb_id'] - logger.info('removing otdb_id %s from cache', otdb_id) otdb_cache = self._cache['otdb_ids'] - del otdb_cache[otdb_id] + if 'otdb_id' in otdb_cache: + logger.info('removing otdb_id %s from cache', otdb_id) + del otdb_cache[otdb_id] def _updateOldEntriesInCache(self): while self._updateCacheThreadRunning: @@ -153,7 +155,7 @@ class CacheManager(OTDBBusListener): return self.getDiskUsageForTask(radb_id=radb_id) def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None): - logger.debug("cache.getDiskUsageForTask(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + logger.info("cache.getDiskUsageForTask(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) result = self.disk_usage.path_resolver.getPathForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) if result['found']: return self.getDiskUsageForPath(result['path']) @@ -161,7 +163,7 @@ class CacheManager(OTDBBusListener): return {'found': False, 'path': result['path']} def getDiskUsageForPath(self, path): - logger.debug("cache.getDiskUsageForPath(%s)", path) + logger.info("cache.getDiskUsageForPath(%s)", path) needs_cache_update = False with self._cacheLock: needs_cache_update = path not in self._cache['paths'] @@ -180,7 +182,7 @@ class CacheManager(OTDBBusListener): return result def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): - logger.debug("cache.getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + logger.info("cache.getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) task_du_result = self.getDiskUsageForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) if task_du_result['found']: task_sd_result = self.disk_usage.path_resolver.getSubDirectoriesForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) @@ -197,9 +199,9 @@ class CacheManager(OTDBBusListener): return task_du_result - def getDiskUsageForProjectSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): - logger.debug("cache.getDiskUsageForProjectSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) - path_result = self.disk_usage.path_resolver.getProjectSubDirectories(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + def getDiskUsageForProjectDirAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None, project_name=None): + logger.info("cache.getDiskUsageForProjectDirAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + path_result = self.disk_usage.path_resolver.getProjectDirAndSubDirectories(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, project_name=project_name) if path_result['found']: projectdir_du_result = self.getDiskUsageForPath(path_result['path']) subdir_paths = [os.path.join(path_result['path'],sd) for sd in path_result['sub_directories']] @@ -207,13 +209,13 @@ class CacheManager(OTDBBusListener): #TODO: potential for parallelization subdirs_du_result = { sd: self.getDiskUsageForPath(sd) for sd in subdir_paths } result = {'found':True, 'projectdir': projectdir_du_result, 'sub_directories': subdirs_du_result } - logger.info('cache.getDiskUsageForProjectSubDirectories result: %s' % result) + logger.info('cache.getDiskUsageForProjectDirAndSubDirectories result: %s' % result) return result return path_result def getDiskUsageForProjectsDirAndSubDirectories(self): - logger.debug("cache.getDiskUsageForProjectsDirAndSubDirectories") + logger.info("cache.getDiskUsageForProjectsDirAndSubDirectories") projects_path = self.disk_usage.path_resolver.projects_path projectsdir_du_result = self.getDiskUsageForPath(projects_path) diff --git a/SAS/DataManagement/StorageQueryService/diskusage.py b/SAS/DataManagement/StorageQueryService/diskusage.py index 61202b0a29f..6a3887f971b 100644 --- a/SAS/DataManagement/StorageQueryService/diskusage.py +++ b/SAS/DataManagement/StorageQueryService/diskusage.py @@ -30,7 +30,7 @@ def getDiskUsageForPath(path): logger.debug(out) if proc.returncode != 0: - logger.error(out) + logger.error(out + err) return {'found': False, 'path': path, 'message': out} # example of out @@ -128,9 +128,9 @@ class DiskUsage: return task_du_result - def getDiskUsageForProjectSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): - logger.info("getDiskUsageForProjectSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) - path_result = self.path_resolver.getProjectSubDirectories(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + def getDiskUsageForProjectDirAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None, project_name=None): + logger.info("getDiskUsageForProjectDirAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) + path_result = self.path_resolver.getProjectDirAndSubDirectories(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, project_name=project_name) if path_result['found']: projectdir_du_result = getDiskUsageForPath(path_result['path']) subdir_paths = [os.path.join(path_result['path'],sd) for sd in path_result['sub_directories']] diff --git a/SAS/DataManagement/StorageQueryService/rpc.py b/SAS/DataManagement/StorageQueryService/rpc.py index b46b90becb7..85560d5f6b1 100644 --- a/SAS/DataManagement/StorageQueryService/rpc.py +++ b/SAS/DataManagement/StorageQueryService/rpc.py @@ -24,6 +24,9 @@ class StorageQueryRPC(RPCWrapper): return result + def getPathForOTDBId(self, otdb_id): + return self.rpc('GetPathForOTDBId', otdb_id=otdb_id) + def getDiskUsageForOTDBId(self, otdb_id): return self._convertTimestamps(self.rpc('GetDiskUsageForOTDBId', otdb_id=otdb_id)) @@ -39,8 +42,8 @@ class StorageQueryRPC(RPCWrapper): def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): return self._convertTimestamps(self.rpc('GetDiskUsageForTaskAndSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id)) - def getDiskUsageForProjectSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): - return self._convertTimestamps(self.rpc('GetDiskUsageForProjectSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id)) + def getDiskUsageForProjectDirAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None, project_name=None): + return self._convertTimestamps(self.rpc('GetDiskUsageForProjectDirAndSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, project_name=project_name)) def getDiskUsageForProjectsDirAndSubDirectories(self): return self._convertTimestamps(self.rpc('GetDiskUsageForProjectsDirAndSubDirectories')) @@ -81,7 +84,7 @@ def main(): print result['message'] exit(1) elif options.project: - result = rpc.getDiskUsageForProjectSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + result = rpc.getDiskUsageForProjectDirAndSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) if result['found']: import pprint pprint.pprint(result) diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py index 0d55af7e84f..c7dfd74e2e1 100644 --- a/SAS/DataManagement/StorageQueryService/service.py +++ b/SAS/DataManagement/StorageQueryService/service.py @@ -42,12 +42,13 @@ class StorageQueryHandler(MessageHandlerInterface): mom_servicename=mom_servicename, broker=broker) - self.service2MethodMap = {'GetDiskUsageForOTDBId': self.cache.getDiskUsageForOTDBId, + self.service2MethodMap = {'GetPathForOTDBId': self.cache.disk_usage.path_resolver.getPathForOTDBId, + 'GetDiskUsageForOTDBId': self.cache.getDiskUsageForOTDBId, 'GetDiskUsageForMoMId': self.cache.getDiskUsageForMoMId, 'GetDiskUsageForRADBId': self.cache.getDiskUsageForRADBId, 'GetDiskUsageForTask': self.cache.getDiskUsageForTask, 'GetDiskUsageForTaskAndSubDirectories': self.cache.getDiskUsageForTaskAndSubDirectories, - 'GetDiskUsageForProjectSubDirectories': self.cache.getDiskUsageForProjectSubDirectories, + 'GetDiskUsageForProjectDirAndSubDirectories': self.cache.getDiskUsageForProjectDirAndSubDirectories, 'GetDiskUsageForProjectsDirAndSubDirectories': self.cache.getDiskUsageForProjectsDirAndSubDirectories} def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 00e14f50d82..174cbbabdef 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -284,13 +284,13 @@ def getTaskDataPath(task_id): if not task: abort(404, 'No such task (id=%s)' % task_id) - result = curpc.getPathForOTDBId(task['otdb_id']) + result = sqrpc.getPathForOTDBId(task['otdb_id']) except Exception as e: abort(500, str(e)) if result['found']: return jsonify({'datapath': result['path']}) - abort(404, result['message']) + abort(404, result['message'] if result and 'message' in result else '') @app.route('/rest/tasks/<int:task_id>/diskusage', methods=['GET']) def getTaskDiskUsage(task_id): @@ -309,7 +309,7 @@ def getTaskDiskUsage(task_id): if 'disk_usage' in result: result['disk_usage_readable'] = humanreadablesize(result['disk_usage']) return jsonify(result) - abort(404, result['message']) + abort(404, result['message'] if result and 'message' in result else '') @app.route('/rest/tasks/<int:task_id>/copy', methods=['PUT']) def copyTask(task_id): @@ -375,6 +375,50 @@ def getMoMProjects(): projects.append({'name':'OTDB Only', 'mom_id':-98, 'description': 'Container project for tasks which exists only in OTDB'}) return jsonify({'momprojects': projects}) +@app.route('/rest/momprojects/<int:project_mom2id>') +def getMoMProject(project_mom2id): + try: + projects = momqueryrpc.getProjects() + project = next(x for x in projects if x['mom2id'] == project_mom2id) + return jsonify({'momproject': project}) + except StopIteration as e: + logger.error(e) + abort(404, "No project with mom2id %s" % project_mom2id) + except Exception as e: + logger.error(e) + abort(404, str(e)) + +@app.route('/rest/momprojects/<int:project_mom2id>/diskusage') +def getMoMProjectDiskUsageById(project_mom2id): + try: + projects = momqueryrpc.getProjects() + project_name = next(x['name'] for x in projects if x['mom2id'] == project_mom2id) + return getMoMProjectDiskUsageByName(project_name) + except StopIteration as e: + logger.error(e) + abort(404, "No project with mom2id %s" % project_mom2id) + except Exception as e: + logger.error(e) + abort(404, str(e)) + +@app.route('/rest/momprojects/<string:project_name>/diskusage') +def getMoMProjectDiskUsageByName(project_name): + try: + result = sqrpc.getDiskUsageForProjectDirAndSubDirectories(project_name=project_name) + return jsonify(result) + except Exception as e: + logger.error(e) + abort(404, str(e)) + +@app.route('/rest/momprojects/diskusage') +def getMoMProjectsDiskUsage(): + try: + result = sqrpc.getDiskUsageForProjectsDirAndSubDirectories() + return jsonify(result) + except Exception as e: + logger.error(e) + abort(404, str(e)) + @app.route('/rest/momobjectdetails/<int:mom2id>') def getMoMObjectDetails(mom2id): details = momqueryrpc.getProjectDetails(mom2id) -- GitLab From c83dbc0ff95b313ded4bd38f4bcce7db12fd5bd1 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 29 Jun 2016 12:35:59 +0000 Subject: [PATCH 438/933] Task #9192: Use C++11, Ubuntu 16.04, OpenBLAS, AOFlagger 2.8.0, latest casacore/casarest/python-casacore, for increased performance --- Docker/lofar-base/Dockerfile.tmpl | 28 ++++++++++++++----------- Docker/lofar-outputproc/Dockerfile.tmpl | 14 ++++++------- Docker/lofar-pipeline/Dockerfile.tmpl | 19 +++++++++-------- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index aef0133c364..77228e242bc 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -1,7 +1,7 @@ # # base # -FROM ubuntu:14.04 +FROM ubuntu:16.04 # # common-environment @@ -18,10 +18,10 @@ ENV DEBIAN_FRONTEND=noninteractive \ # # versions # -ENV CASACORE_VERSION=2.1.0 \ - CASAREST_VERSION=1.4.1 \ - PYTHON_CASACORE_VERSION=2.0.1 \ - BOOST_VERSION=1.54 +ENV CASACORE_VERSION=latest \ + CASAREST_VERSION=latest \ + PYTHON_CASACORE_VERSION=2.1.2 \ + BOOST_VERSION=1.58 # # set-uid @@ -39,7 +39,11 @@ ENV J=6 #RUN sed -i 's/archive.ubuntu.com/osmirror.rug.nl/' /etc/apt/sources.list RUN apt-get update && \ apt-get install -y python2.7 libpython2.7 && \ - apt-get install -y libblas3 liblapacke python-numpy libcfitsio3 libwcs4 libfftw3-bin libhdf5-7 libboost-python${BOOST_VERSION}.0 && \ + apt-get install -y libopenblas-base libcfitsio-bin libwcs5 libfftw3-bin libhdf5-10 libboost-python${BOOST_VERSION}.0 && \ + apt-get install -y python-pip && \ + pip install numpy && \ + apt-get purge -y python-pip && \ + apt-get autoremove -y && \ apt-get install -y nano # @@ -59,19 +63,19 @@ RUN mkdir -p ${INSTALLDIR} # Casacore # ******************* # -RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison libopenblas-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/casacore/build && \ mkdir -p ${INSTALLDIR}/casacore/data && \ cd ${INSTALLDIR}/casacore && git clone https://github.com/casacore/casacore.git src && \ if [ "${CASACORE_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casacore/src && git checkout tags/v${CASACORE_VERSION}; fi && \ cd ${INSTALLDIR}/casacore/data && wget --retry-connrefused ftp://ftp.astron.nl/outgoing/Measures/WSRT_Measures.ztar && \ cd ${INSTALLDIR}/casacore/data && tar xf WSRT_Measures.ztar && rm -f WSRT_Measures.ztar && \ - cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DUSE_OPENMP=True -DUSE_FFTW3=TRUE -DUSE_HDF5=ON ../src/ && \ + cd ${INSTALLDIR}/casacore/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casacore/ -DDATA_DIR=${INSTALLDIR}/casacore/data -DBUILD_PYTHON=True -DENABLE_TABLELOCKING=ON -DUSE_OPENMP=ON -DUSE_FFTW3=TRUE -DUSE_HDF5=ON -DCXX11=YES -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-fsigned-char -O2 -DNDEBUG -march=native" ../src/ && \ cd ${INSTALLDIR}/casacore/build && make -j ${J} && \ cd ${INSTALLDIR}/casacore/build && make install && \ bash -c "strip ${INSTALLDIR}/casacore/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casacore/{build,src}" && \ - apt-get purge -y wget git cmake g++ gfortran flex bison libblas-dev liblapacke-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio3-dev wcslib-dev && \ + apt-get purge -y wget git cmake g++ gfortran flex bison libopenblas-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ apt-get autoremove -y # @@ -79,16 +83,16 @@ RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison # Casarest # ******************* # -RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ mkdir -p ${INSTALLDIR}/casarest/build && \ cd ${INSTALLDIR}/casarest && git clone https://github.com/casacore/casarest.git src && \ if [ "${CASAREST_VERSION}" != "latest" ]; then cd ${INSTALLDIR}/casarest/src && git checkout tags/v${CASAREST_VERSION}; fi && \ - cd ${INSTALLDIR}/casarest/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casarest -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore ../src/ && \ + cd ${INSTALLDIR}/casarest/build && cmake -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/casarest -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-std=c++11 -O2 -march=native -DNDEBUG" ../src/ && \ cd ${INSTALLDIR}/casarest/build && make -j ${J} && \ cd ${INSTALLDIR}/casarest/build && make install && \ bash -c "strip ${INSTALLDIR}/casarest/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casarest/{build,src}" && \ - apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev && \ + apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ apt-get autoremove -y # diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index eb5ef0c866b..c25fffe979a 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -10,20 +10,20 @@ FROM lofar-base:${LOFAR_TAG} # # Run-time dependencies -RUN apt-get update && apt-get install -y liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libboost-regex${BOOST_VERSION}.0 +RUN apt-get update && apt-get install -y liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libboost-regex${BOOST_VERSION}.0 # Tell image build information -ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ +ENV LOFAR_BRANCH=${LOFAR_BRANCH} \ LOFAR_REVISION=${LOFAR_REVISION} \ - LOFAR_BUILDVARIANT=gnu_optarch + LOFAR_BUILDVARIANT=gnucxx11_optarch # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev && \ - mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libopenblas-dev && libcfitsio3-dev wcslib-dev \ + mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Online_OutputProc -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DDAL_ROOT_DIR=${INSTALLDIR}/DAL -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Online_OutputProc -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DQPID_ROOT_DIR=/opt/qpid/ -DDAL_ROOT_DIR=${INSTALLDIR}/DAL -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ @@ -32,6 +32,6 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ setcap cap_sys_nice,cap_ipc_lock=ep ${INSTALLDIR}/lofar/bin/outputProc && \ - apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev && \ + apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && libcfitsio3-dev wcslib-dev \ apt-get autoremove -y diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index eae03529529..94c6299eeff 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -3,10 +3,10 @@ # FROM lofar-base:${LOFAR_TAG} -ENV AOFLAGGER_VERSION=2.7.1 +ENV AOFLAGGER_VERSION=2.8.0 # Run-time dependencies -RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.0-4 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2 libgsl0ldbl openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ +RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ apt-get -y install python-pip python-dev && \ pip install pyfits pywcs python-monetdb && \ apt-get -y purge python-pip python-dev && \ @@ -18,17 +18,17 @@ RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cp # ******************* # -RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ +RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libopenblas-dev && \ mkdir -p ${INSTALLDIR}/aoflagger/build && \ bash -c "cd ${INSTALLDIR}/aoflagger && wget --retry-connrefused http://downloads.sourceforge.net/project/aoflagger/aoflagger-${AOFLAGGER_VERSION%%.?}.0/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ cd ${INSTALLDIR}/aoflagger && tar xf aoflagger-${AOFLAGGER_VERSION}.tar.bz2 && \ - cd ${INSTALLDIR}/aoflagger/build && cmake -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/aoflagger ../aoflagger-${AOFLAGGER_VERSION} && \ + cd ${INSTALLDIR}/aoflagger/build && cmake -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_FLAGS="--std=c++11 -O2 -DNDEBUG" -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/aoflagger ../aoflagger-${AOFLAGGER_VERSION} && \ cd ${INSTALLDIR}/aoflagger/build && make -j ${J} && \ cd ${INSTALLDIR}/aoflagger/build && make install && \ bash -c "strip ${INSTALLDIR}/aoflagger/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/{build,aoflagger-${AOFLAGGER_VERSION}}" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ - apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev && \ + apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libopenblas-dev && \ apt-get -y autoremove # @@ -40,15 +40,16 @@ RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12 # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ LOFAR_REVISION=${LOFAR_REVISION} \ - LOFAR_BUILDVARIANT=gnu_optarch + LOFAR_BUILDVARIANT=gnucxx11_optarch + # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev && \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Offline -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASAREST_ROOT_DIR=${INSTALLDIR}/casarest/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DAOFLAGGER_ROOT_DIR=${INSTALLDIR}/aoflagger/ -DLOG4CPLUS_ROOT_DIR=${INSTALLDIR}/log4cplus/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Offline -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASAREST_ROOT_DIR=${INSTALLDIR}/casarest/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DAOFLAGGER_ROOT_DIR=${INSTALLDIR}/aoflagger/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && sed -i '29,31d' include/ApplCommon/PosixTime.h && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ @@ -56,6 +57,6 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "chmod a+rwx ${INSTALLDIR}/lofar/var/{log,run}" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ - apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl0-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev wcslib-dev && \ + apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ apt-get autoremove -y -- GitLab From 7c3441a52268c31bf22d0d9962da24286fcffcf4 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Wed, 29 Jun 2016 13:55:00 +0000 Subject: [PATCH 439/933] Task #9599: Changed scheduler to not set input files for CEP4 and ignore pipelines managed by SLURM with a start time in the past. --- SAS/Scheduler/src/Controller.cpp | 3 +++ SAS/Scheduler/src/SASConnection.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/SAS/Scheduler/src/Controller.cpp b/SAS/Scheduler/src/Controller.cpp index 504b8b04a40..2342513510d 100644 --- a/SAS/Scheduler/src/Controller.cpp +++ b/SAS/Scheduler/src/Controller.cpp @@ -4544,6 +4544,9 @@ bool Controller::checkEarlyTasksStatus(void) { int treeID; for (std::vector<Task *>::const_iterator it = tasks.begin(); it != tasks.end(); ++it) { if ((*it)->getScheduledStart() <= now()) { + if (((*it)->getOutputDataproductCluster() == "CEP4") && (*it)->isPipeline()) { + continue; //Pipelines on CEP4: we don't care as SLURM sorts it out. + } treeID = (*it)->getSASTreeID(); if ((itsSASConnection->connect() == 0) && (treeID != 0)) { // only do the sas check for apparently too early tasks that are already in SAS , not for new tasks Task::task_status status(itsSASConnection->getTaskStatus(treeID)); diff --git a/SAS/Scheduler/src/SASConnection.cpp b/SAS/Scheduler/src/SASConnection.cpp index c33f4dd2729..66edc07fc54 100644 --- a/SAS/Scheduler/src/SASConnection.cpp +++ b/SAS/Scheduler/src/SASConnection.cpp @@ -2576,6 +2576,9 @@ bool SASConnection::saveStationSettings(int treeID, const StationTask &task, con bool SASConnection::saveInputStorageSettings(int treeID, const Task &task) { bool bResult(true); + if (task.getOutputDataproductCluster() == "CEP4") { //For CEP4 we're skipping this. /AR + return bResult; + } const TaskStorage *task_storage(task.storage()); if (task_storage) { const std::map<dataProductTypes, TaskStorage::inputDataProduct> &inputDataProducts(task_storage->getInputDataProducts()); -- GitLab From 732784b73a6d32c37223bcfd75e7a1afb31045ad Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 07:49:58 +0000 Subject: [PATCH 440/933] Task #8598: applied some patches from production --- LTA/LTAIngest/ingestpipeline.py | 37 ++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index f26ba5b52d5..df8ea0b528e 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import logging, os, time, xmlrpclib, subprocess, random, unspecifiedSIP import socket +import re from lxml import etree from cStringIO import StringIO from job_group import corr_type, bf_type, img_type, unspec_type, pulp_type @@ -260,12 +261,13 @@ class IngestPipeline(): except Exception: pass - except ltacp.LtacpException as exp: - if '550 File not found' in exp.value: - self.logger.error('Destination directory does not exist. Creating %s in LTA for %s' % (self.PrimaryUri, self.JobId)) + except Exception as exp: + if isinstance(exp, ltacp.LtacpException): + if '550 File not found' in exp.value: + self.logger.error('Destination directory does not exist. Creating %s in LTA for %s' % (self.PrimaryUri, self.JobId)) - if ltacp.create_missing_directories(self.PrimaryUri) == 0: - self.logger.info('Created path %s in LTA for %s' % (self.PrimaryUri, self.JobId)) + if ltacp.create_missing_directories(self.PrimaryUri) == 0: + self.logger.info('Created path %s in LTA for %s' % (self.PrimaryUri, self.JobId)) raise Exception('New style file transfer failed of %s\n%s' % (self.JobId, str(exp))) @@ -455,8 +457,9 @@ class IngestPipeline(): self.SIP = sip_dom.toxml("utf-8") self.SIP = self.SIP.replace('<stationType>Europe</stationType>','<stationType>International</stationType>') - self.SIP = self.SIP.replace('<source>EOR</source>','<source>EoR</source>') #EoR sips sometime contain EOR instead of EoR as source + #make sure the source in the SIP is the same as the type of the storageticket + self.SIP = re.compile('<source>eor</source>', re.IGNORECASE).sub('<source>%s</source>' % (self.Type,), self.SIP) except: self.logger.exception('Getting SIP from EoR failed') raise @@ -533,7 +536,7 @@ class IngestPipeline(): break retry += 1 if retry < times: - wait_time = random.randint(30, 60) * retry + wait_time = random.randint(5, 30) * retry self.logger.debug('waiting %d seconds before trying %s again' % (wait_time, self.JobId)) time.sleep(wait_time) if error: @@ -631,6 +634,8 @@ if __name__ == '__main__': description='Run the ingestpipeline on a single jobfile, or a directory containing many jobfiles.') parser.add_option("-u", "--user", dest="user", type="string", default=getpass.getuser(), help="username for to login on <host>, [default: %default]") parser.add_option("-p", "--parallel", dest="maxParallelJobs", type="int", default=4, help="number of parellel pipelines to run when processing a directory of jobfiles [default: %default]") + parser.add_option("", "--odd", dest="odd", action="store_true", default=None, help="only process dataproducts with an odd subband number, [default: %default]") + parser.add_option("", "--even", dest="even", action="store_true", default=None, help="only process dataproducts with an even subband number, [default: %default]") (options, args) = parser.parse_args() if len(args) != 1: @@ -655,7 +660,7 @@ if __name__ == '__main__': parser = job_parser.parser(logger) job = parser.parse(path) job['filename'] = path - logger.info("Parsed jobfile %s: %s" % (path, str(job))) + logger.info("Parsed jobfile %s" % (path,)) jobPipeline = IngestPipeline(None, job, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit) jobPipeline.run() exit(0) @@ -680,7 +685,16 @@ if __name__ == '__main__': parser = job_parser.parser(logger) job = parser.parse(jobfilepath) job['filename'] = jobfilepath - logger.info("Parsed jobfile %s: %s" % (jobfilepath, str(job))) + logger.info("Parsed jobfile %s" % (jobfilepath,)) + + if options.odd or options.even: + # parse subband number of dataproduct + # only add this job if the subband number odd/even complies with given option + subband = next(x for x in job['DataProduct'].split('_') if x.startswith('SB')) + subband_nr = int(filter(str.isdigit,subband)) + is_even = subband_nr % 2 == 0 + if (options.odd and is_even) or (options.even and not is_even): + continue if 'host' in job: host = job['host'] @@ -696,6 +710,9 @@ if __name__ == '__main__': busyHosts = set(runningPipelinesPerHost.keys()) freeHosts = set(hostJobQueues.keys()) - busyHosts + # sort free host with longest queues first, so their jobs get started first + freeHosts = sorted(freeHosts, key=lambda h: len(hostJobQueues[h]), reverse=True) + startedNewJobs = False # start new pipeline for one job per free host @@ -746,7 +763,7 @@ if __name__ == '__main__': if jobPipeline.finishedSuccessfully: try: if os.path.isdir(path): - path, filename = os.split(jobPipeline.job['filename']) + path, filename = os.path.split(jobPipeline.job['filename']) os.rename(jobPipeline.job['filename'], os.path.join(path, 'done', filename)) except Exception as e: logger.error(str(e)) -- GitLab From 84b08459eb0164faca517fa9b58fdfe04f00bd1c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 07:52:21 +0000 Subject: [PATCH 441/933] Task #9189: merged patches from ^/branches/LOFAR-Release-2_16 -c 34833 --- LTA/LTAIngest/ingestpipeline.py | 37 ++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index f26ba5b52d5..df8ea0b528e 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import logging, os, time, xmlrpclib, subprocess, random, unspecifiedSIP import socket +import re from lxml import etree from cStringIO import StringIO from job_group import corr_type, bf_type, img_type, unspec_type, pulp_type @@ -260,12 +261,13 @@ class IngestPipeline(): except Exception: pass - except ltacp.LtacpException as exp: - if '550 File not found' in exp.value: - self.logger.error('Destination directory does not exist. Creating %s in LTA for %s' % (self.PrimaryUri, self.JobId)) + except Exception as exp: + if isinstance(exp, ltacp.LtacpException): + if '550 File not found' in exp.value: + self.logger.error('Destination directory does not exist. Creating %s in LTA for %s' % (self.PrimaryUri, self.JobId)) - if ltacp.create_missing_directories(self.PrimaryUri) == 0: - self.logger.info('Created path %s in LTA for %s' % (self.PrimaryUri, self.JobId)) + if ltacp.create_missing_directories(self.PrimaryUri) == 0: + self.logger.info('Created path %s in LTA for %s' % (self.PrimaryUri, self.JobId)) raise Exception('New style file transfer failed of %s\n%s' % (self.JobId, str(exp))) @@ -455,8 +457,9 @@ class IngestPipeline(): self.SIP = sip_dom.toxml("utf-8") self.SIP = self.SIP.replace('<stationType>Europe</stationType>','<stationType>International</stationType>') - self.SIP = self.SIP.replace('<source>EOR</source>','<source>EoR</source>') #EoR sips sometime contain EOR instead of EoR as source + #make sure the source in the SIP is the same as the type of the storageticket + self.SIP = re.compile('<source>eor</source>', re.IGNORECASE).sub('<source>%s</source>' % (self.Type,), self.SIP) except: self.logger.exception('Getting SIP from EoR failed') raise @@ -533,7 +536,7 @@ class IngestPipeline(): break retry += 1 if retry < times: - wait_time = random.randint(30, 60) * retry + wait_time = random.randint(5, 30) * retry self.logger.debug('waiting %d seconds before trying %s again' % (wait_time, self.JobId)) time.sleep(wait_time) if error: @@ -631,6 +634,8 @@ if __name__ == '__main__': description='Run the ingestpipeline on a single jobfile, or a directory containing many jobfiles.') parser.add_option("-u", "--user", dest="user", type="string", default=getpass.getuser(), help="username for to login on <host>, [default: %default]") parser.add_option("-p", "--parallel", dest="maxParallelJobs", type="int", default=4, help="number of parellel pipelines to run when processing a directory of jobfiles [default: %default]") + parser.add_option("", "--odd", dest="odd", action="store_true", default=None, help="only process dataproducts with an odd subband number, [default: %default]") + parser.add_option("", "--even", dest="even", action="store_true", default=None, help="only process dataproducts with an even subband number, [default: %default]") (options, args) = parser.parse_args() if len(args) != 1: @@ -655,7 +660,7 @@ if __name__ == '__main__': parser = job_parser.parser(logger) job = parser.parse(path) job['filename'] = path - logger.info("Parsed jobfile %s: %s" % (path, str(job))) + logger.info("Parsed jobfile %s" % (path,)) jobPipeline = IngestPipeline(None, job, config.momClient, config.ltaClient, config.ipaddress, config.ltacpport, config.mailCommand, config.momRetry, config.ltaRetry, config.srmRetry, config.srmInit) jobPipeline.run() exit(0) @@ -680,7 +685,16 @@ if __name__ == '__main__': parser = job_parser.parser(logger) job = parser.parse(jobfilepath) job['filename'] = jobfilepath - logger.info("Parsed jobfile %s: %s" % (jobfilepath, str(job))) + logger.info("Parsed jobfile %s" % (jobfilepath,)) + + if options.odd or options.even: + # parse subband number of dataproduct + # only add this job if the subband number odd/even complies with given option + subband = next(x for x in job['DataProduct'].split('_') if x.startswith('SB')) + subband_nr = int(filter(str.isdigit,subband)) + is_even = subband_nr % 2 == 0 + if (options.odd and is_even) or (options.even and not is_even): + continue if 'host' in job: host = job['host'] @@ -696,6 +710,9 @@ if __name__ == '__main__': busyHosts = set(runningPipelinesPerHost.keys()) freeHosts = set(hostJobQueues.keys()) - busyHosts + # sort free host with longest queues first, so their jobs get started first + freeHosts = sorted(freeHosts, key=lambda h: len(hostJobQueues[h]), reverse=True) + startedNewJobs = False # start new pipeline for one job per free host @@ -746,7 +763,7 @@ if __name__ == '__main__': if jobPipeline.finishedSuccessfully: try: if os.path.isdir(path): - path, filename = os.split(jobPipeline.job['filename']) + path, filename = os.path.split(jobPipeline.job['filename']) os.rename(jobPipeline.job['filename'], os.path.join(path, 'done', filename)) except Exception as e: logger.error(str(e)) -- GitLab From 7b45caa49e4e541cfbbfbd54379507cb811d8642 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 08:01:48 +0000 Subject: [PATCH 442/933] Task #8598: applied some patches from production --- LTA/LTAIngest/ingestpipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index df8ea0b528e..dc6217ef01a 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -195,7 +195,7 @@ class IngestPipeline(): ## SecondaryUri handling not implemented self.logger.debug(cmd) start = time.time() - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) logs = p.communicate() elapsed = time.time() - start self.logger.debug("File transfer for %s took %d sec" % (self.JobId, elapsed)) -- GitLab From a7b94cc0c22a10dc185545da71e02b8163efd080 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 08:02:20 +0000 Subject: [PATCH 443/933] Task #8598: applied some patches from production --- LTA/LTAIngest/ingestpipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index df8ea0b528e..dc6217ef01a 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -195,7 +195,7 @@ class IngestPipeline(): ## SecondaryUri handling not implemented self.logger.debug(cmd) start = time.time() - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) logs = p.communicate() elapsed = time.time() - start self.logger.debug("File transfer for %s took %d sec" % (self.JobId, elapsed)) -- GitLab From ad4f96f27bf0c4e995da22f16e91f8acb1ce52db Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 08:07:38 +0000 Subject: [PATCH 444/933] Task #8598: applied some patches from production --- LTA/LTAIngest/ingestpipeline.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index dc6217ef01a..7033a95cefb 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -186,6 +186,9 @@ class IngestPipeline(): hostname = 'localhost' cmd = ["cd %s && %s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.PrimaryUri, self.Source)] else: + if self.HostLocation.startswith('locus') and not self.HostLocation.endswith('offline.lofar'): + self.HostLocation += '.offline.lofar' + # copy data with ltacp from a remote host, so use ssh if self.PrimaryUri: cmd = ["ssh", "-T", "ingest@" +self.HostLocation, "cd %s;%s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.PrimaryUri, self.Source)] -- GitLab From 52c158bae2bee36375661fe8ee41bbb48b8e31e6 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 08:07:48 +0000 Subject: [PATCH 445/933] Task #8598: applied some patches from production --- LTA/LTAIngest/ingestpipeline.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index dc6217ef01a..7033a95cefb 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -186,6 +186,9 @@ class IngestPipeline(): hostname = 'localhost' cmd = ["cd %s && %s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.PrimaryUri, self.Source)] else: + if self.HostLocation.startswith('locus') and not self.HostLocation.endswith('offline.lofar'): + self.HostLocation += '.offline.lofar' + # copy data with ltacp from a remote host, so use ssh if self.PrimaryUri: cmd = ["ssh", "-T", "ingest@" +self.HostLocation, "cd %s;%s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.PrimaryUri, self.Source)] -- GitLab From bd56bfc9ff11015e9d91eb8781fc48d0987dc108 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 30 Jun 2016 09:11:13 +0000 Subject: [PATCH 446/933] Task #9192: Allow parsets (and thus feedback) to be saved in a directory outside the container --- CEP/Pipeline/recipes/sip/bin/runPipeline.sh | 10 ++++++++-- MAC/Services/src/PipelineControl.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh index 89d5e265711..8c40f4a2247 100755 --- a/CEP/Pipeline/recipes/sip/bin/runPipeline.sh +++ b/CEP/Pipeline/recipes/sip/bin/runPipeline.sh @@ -25,6 +25,9 @@ PARSET= # Location of pipeline-framework configuration file PIPELINE_CONFIG=$LOFARROOT/share/pipeline/pipeline.cfg +# Directory for parset and feedback files +PARSET_DIR=$LOFARROOT/var/run + # ======= Parse command-line parameters function usage() { @@ -33,10 +36,11 @@ function usage() { echo " -o OBSID Task identifier" echo " -c pipeline.cfg Override pipeline configuration file (default: $PIPELINE_CONFIG)" echo " -p pipeline.parset Provide parset (default: request through QPID)" + echo " -P dir Directory to which to save parset and feedback files (default: $PARSET_DIR)" exit 1 } -while getopts "ho:c:p:" opt; do +while getopts "ho:c:p:P:" opt; do case $opt in h) usage ;; @@ -46,6 +50,8 @@ while getopts "ho:c:p:" opt; do ;; p) PARSET="$OPTARG" ;; + P) PARSET_DIR="$OPTARG" + ;; \?) error "Invalid option: -$OPTARG" ;; :) error "Option requires an argument: -$OPTARG" @@ -58,7 +64,7 @@ done if [ -z "$PARSET" ]; then # Fetch parset - PARSET=${LOFARROOT}/var/run/Observation${OBSID}.parset + PARSET=${PARSET_DIR}/Observation${OBSID}.parset getOTDBParset -o $OBSID >$PARSET fi diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 5a3325d3bc9..f3c535d09b4 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -391,7 +391,7 @@ class PipelineControl(OTDBBusListener): " -e SLURM_JOB_ID=$SLURM_JOB_ID" " -v /data:/data" " {image}" - " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} || exit $?\n" + " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -P /data/parsets || exit $?\n" # notify that we're tearing down "{setStatus_completing}\n" -- GitLab From 65d3142bef8d919d4a8765851b5856d974be7bb8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 10:29:41 +0000 Subject: [PATCH 447/933] Task #8598: applied some patches from production --- LTA/LTAIngest/ingestpipeline.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index 7033a95cefb..8a073bbb539 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -19,7 +19,7 @@ def humanreadablesize(num, suffix='B'): return "%.1f%s%s" % (num, 'Y', suffix) except TypeError: return str(num) - + IngestStarted = 10 ## 20 not used IngestSIPComplete = 30 @@ -183,11 +183,17 @@ class IngestPipeline(): if self.HostLocation == 'localhost': # copy data with ltacp client from HostLocation localhost, to ltacpserver at localhost # so no need for ssh + use_shell = ("lexar003" in hostname or "lexar004" in hostname) hostname = 'localhost' cmd = ["cd %s && %s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.PrimaryUri, self.Source)] else: - if self.HostLocation.startswith('locus') and not self.HostLocation.endswith('offline.lofar'): - self.HostLocation += '.offline.lofar' + if "lexar003" in hostname or "lexar004" in hostname: + #lexar003/004 are not aware of cep2, and cep2 is not aware of lexar003/004, + #so make sure we can handle the proper hostnames and ips + use_shell=True + if self.HostLocation.startswith('locus') and not self.HostLocation.endswith('cep2.lofar'): + hostname = '10.178.1.3' if "lexar003" in hostname else '10.178.1.4' + self.HostLocation += '.cep2.lofar' # copy data with ltacp from a remote host, so use ssh if self.PrimaryUri: @@ -196,9 +202,9 @@ class IngestPipeline(): cmd = ["ssh", "-T", "ingest@" + self.HostLocation, "cd %s;%s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s/%s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.tempPrimary, self.FileName, self.Source)] ## SecondaryUri handling not implemented - self.logger.debug(cmd) + self.logger.debug(' '.join(cmd)) start = time.time() - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=use_shell) logs = p.communicate() elapsed = time.time() - start self.logger.debug("File transfer for %s took %d sec" % (self.JobId, elapsed)) @@ -381,7 +387,7 @@ class IngestPipeline(): if storageTickets[0].text != str(self.ticket): self.logger.error("CheckSIPContent for %s storageTicket %s does not match expected %s" % (self.JobId, storageTickets[0].text, self.ticket)) return False - + self.logger.debug("CheckSIPContent OK for %s" % (self.JobId,)) return True except Exception as e: @@ -536,7 +542,7 @@ class IngestPipeline(): else: self.logger.debug(errortext + ' ran without a problem on %s' % self.JobId) error = '' - break + break retry += 1 if retry < times: wait_time = random.randint(5, 30) * retry @@ -581,7 +587,7 @@ class IngestPipeline(): try: if self.ticket: self.RetryRun(self.SendStatus, self.ltaRetry, 'Setting LTA status', IngestFailed) - except Exception as e: + except Exception as e: os.system('echo "Received unknown exception in SendStatus for %s to %s while handling another error:\n%s\n\nCheck LTA catalog and SRM!\n%s"|mailx -s "Warning: LTA catalog status update failed" ' % (self.JobId, IngestFailed, self._hidePassword(str(e)), self.PrimaryUri) + self.mailCommand) self.logger.error('Sent Mail: LTA catalog status update failed to ' + self.mailCommand) self.logger.exception('SendStatus IngestFailed failed') @@ -611,7 +617,7 @@ class IngestPipeline(): if self.ticket: self.RetryRun(self.SendStatus, self.ltaRetry, 'Setting LTA status', IngestFailed) raise - + def _hidePassword(self, message): ''' helper function which hides the password in the ltaClient url in the message ''' @@ -621,7 +627,7 @@ class IngestPipeline(): return message.replace(':'+password, ':HIDDENPASSWORD') except Exception as e: return message - + #----------------------------------------------------------------- selfstarter - if __name__ == '__main__': -- GitLab From f5694d732d0805676ea7fb0d7199c82d2099424b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 10:29:59 +0000 Subject: [PATCH 448/933] Task #8598: applied some patches from production --- LTA/LTAIngest/ingestpipeline.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index 7033a95cefb..8a073bbb539 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -19,7 +19,7 @@ def humanreadablesize(num, suffix='B'): return "%.1f%s%s" % (num, 'Y', suffix) except TypeError: return str(num) - + IngestStarted = 10 ## 20 not used IngestSIPComplete = 30 @@ -183,11 +183,17 @@ class IngestPipeline(): if self.HostLocation == 'localhost': # copy data with ltacp client from HostLocation localhost, to ltacpserver at localhost # so no need for ssh + use_shell = ("lexar003" in hostname or "lexar004" in hostname) hostname = 'localhost' cmd = ["cd %s && %s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.PrimaryUri, self.Source)] else: - if self.HostLocation.startswith('locus') and not self.HostLocation.endswith('offline.lofar'): - self.HostLocation += '.offline.lofar' + if "lexar003" in hostname or "lexar004" in hostname: + #lexar003/004 are not aware of cep2, and cep2 is not aware of lexar003/004, + #so make sure we can handle the proper hostnames and ips + use_shell=True + if self.HostLocation.startswith('locus') and not self.HostLocation.endswith('cep2.lofar'): + hostname = '10.178.1.3' if "lexar003" in hostname else '10.178.1.4' + self.HostLocation += '.cep2.lofar' # copy data with ltacp from a remote host, so use ssh if self.PrimaryUri: @@ -196,9 +202,9 @@ class IngestPipeline(): cmd = ["ssh", "-T", "ingest@" + self.HostLocation, "cd %s;%s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s/%s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.tempPrimary, self.FileName, self.Source)] ## SecondaryUri handling not implemented - self.logger.debug(cmd) + self.logger.debug(' '.join(cmd)) start = time.time() - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=use_shell) logs = p.communicate() elapsed = time.time() - start self.logger.debug("File transfer for %s took %d sec" % (self.JobId, elapsed)) @@ -381,7 +387,7 @@ class IngestPipeline(): if storageTickets[0].text != str(self.ticket): self.logger.error("CheckSIPContent for %s storageTicket %s does not match expected %s" % (self.JobId, storageTickets[0].text, self.ticket)) return False - + self.logger.debug("CheckSIPContent OK for %s" % (self.JobId,)) return True except Exception as e: @@ -536,7 +542,7 @@ class IngestPipeline(): else: self.logger.debug(errortext + ' ran without a problem on %s' % self.JobId) error = '' - break + break retry += 1 if retry < times: wait_time = random.randint(5, 30) * retry @@ -581,7 +587,7 @@ class IngestPipeline(): try: if self.ticket: self.RetryRun(self.SendStatus, self.ltaRetry, 'Setting LTA status', IngestFailed) - except Exception as e: + except Exception as e: os.system('echo "Received unknown exception in SendStatus for %s to %s while handling another error:\n%s\n\nCheck LTA catalog and SRM!\n%s"|mailx -s "Warning: LTA catalog status update failed" ' % (self.JobId, IngestFailed, self._hidePassword(str(e)), self.PrimaryUri) + self.mailCommand) self.logger.error('Sent Mail: LTA catalog status update failed to ' + self.mailCommand) self.logger.exception('SendStatus IngestFailed failed') @@ -611,7 +617,7 @@ class IngestPipeline(): if self.ticket: self.RetryRun(self.SendStatus, self.ltaRetry, 'Setting LTA status', IngestFailed) raise - + def _hidePassword(self, message): ''' helper function which hides the password in the ltaClient url in the message ''' @@ -621,7 +627,7 @@ class IngestPipeline(): return message.replace(':'+password, ':HIDDENPASSWORD') except Exception as e: return message - + #----------------------------------------------------------------- selfstarter - if __name__ == '__main__': -- GitLab From d79dc8bb68425835c8546680ce3f6f04b4603a65 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 10:39:22 +0000 Subject: [PATCH 449/933] Task #8598: applied some patches from production --- LTA/LTAIngest/ingestpipeline.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index 8a073bbb539..32ae589a94f 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -410,6 +410,12 @@ class IngestPipeline(): try: sip_host = self.job['SIPLocation'].split(':')[0] sip_path = self.job['SIPLocation'].split(':')[1] + + # eor dawn nodes are not known in dns + # convert to ip address + for i in range(1, 33): + sip_host = sip_host.replace('node%d.intra.dawn.rug.nl' % (i+100,), '10.196.232.%d' % (i+10,)) + cmd = ['ssh', '-tt', '-n', '-x', '-q', '%s@%s' % (self.user, sip_host), 'cat %s' % sip_path] self.logger.debug("GetSIP for %s with mom2DPId %s - StorageTicket %s - FileName %s - Uri %s - cmd %s" % (self.JobId, self.ArchiveId, self.ticket, self.FileName, self.PrimaryUri, ' ' .join(cmd))) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) -- GitLab From 8ba739984c7296dda7860a5b8bedadb92d57cd31 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 10:39:37 +0000 Subject: [PATCH 450/933] Task #8598: applied some patches from production --- LTA/LTAIngest/ingestpipeline.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index 8a073bbb539..32ae589a94f 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -410,6 +410,12 @@ class IngestPipeline(): try: sip_host = self.job['SIPLocation'].split(':')[0] sip_path = self.job['SIPLocation'].split(':')[1] + + # eor dawn nodes are not known in dns + # convert to ip address + for i in range(1, 33): + sip_host = sip_host.replace('node%d.intra.dawn.rug.nl' % (i+100,), '10.196.232.%d' % (i+10,)) + cmd = ['ssh', '-tt', '-n', '-x', '-q', '%s@%s' % (self.user, sip_host), 'cat %s' % sip_path] self.logger.debug("GetSIP for %s with mom2DPId %s - StorageTicket %s - FileName %s - Uri %s - cmd %s" % (self.JobId, self.ArchiveId, self.ticket, self.FileName, self.PrimaryUri, ' ' .join(cmd))) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) -- GitLab From 3a58b0c6ede266d3cc1002617fd7abe6aef1aa3f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 11:01:44 +0000 Subject: [PATCH 451/933] Task #8598: applied some patches from production --- LTA/LTAIngest/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LTA/LTAIngest/CMakeLists.txt b/LTA/LTAIngest/CMakeLists.txt index 733f28a62c3..ff4ead9bc04 100644 --- a/LTA/LTAIngest/CMakeLists.txt +++ b/LTA/LTAIngest/CMakeLists.txt @@ -276,7 +276,8 @@ install(PROGRAMS ${STARTUP_SCRIPTS} DESTINATION ${PYTHON_INSTALL_DIR}/LTAIngest) #deploy CHECKSUM_PROGS in same python ingest lib dir set(CHECKSUM_PROGS md5adler/a32 - md5adler/md5a32) + md5adler/md5a32 + md5adler/md5a32bc) install(PROGRAMS ${CHECKSUM_PROGS} DESTINATION ${PYTHON_INSTALL_DIR}/LTAIngest/md5adler) #determine LTAINGEST_LOG_ROOT_DIR for various build hosts (used in ingest_config.py.in) -- GitLab From 30c6416299a0123d2fc490a14c89bd2c57baf7ed Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 30 Jun 2016 11:01:57 +0000 Subject: [PATCH 452/933] Task #8598: applied some patches from production --- LTA/LTAIngest/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LTA/LTAIngest/CMakeLists.txt b/LTA/LTAIngest/CMakeLists.txt index 733f28a62c3..ff4ead9bc04 100644 --- a/LTA/LTAIngest/CMakeLists.txt +++ b/LTA/LTAIngest/CMakeLists.txt @@ -276,7 +276,8 @@ install(PROGRAMS ${STARTUP_SCRIPTS} DESTINATION ${PYTHON_INSTALL_DIR}/LTAIngest) #deploy CHECKSUM_PROGS in same python ingest lib dir set(CHECKSUM_PROGS md5adler/a32 - md5adler/md5a32) + md5adler/md5a32 + md5adler/md5a32bc) install(PROGRAMS ${CHECKSUM_PROGS} DESTINATION ${PYTHON_INSTALL_DIR}/LTAIngest/md5adler) #determine LTAINGEST_LOG_ROOT_DIR for various build hosts (used in ingest_config.py.in) -- GitLab From dd31e97186ee08115e205a403008ed25c4b8f0e0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 1 Jul 2016 06:48:29 +0000 Subject: [PATCH 453/933] Task #8598: applied some patches from production --- LTA/LTAIngest/ltacp.py | 1 - 1 file changed, 1 deletion(-) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index 035c2c1fc87..9cb636433f0 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -430,7 +430,6 @@ class LtaCp: p_remote_rm.communicate() if p_remote_rm.returncode != 0: logger.error("Could not remove remote fifo %s@%s:%s\n%s" % (self.src_user, self.src_host, self.remote_data_fifo, p_remote_rm.stderr)) - self.remote_data_fifo = None def cleanup(self): logger.debug('ltacp %s: cleaning up' % (self.logId)) -- GitLab From 5b18da92aa798a59c7b94c4cb71108dfdcc57506 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 1 Jul 2016 06:48:44 +0000 Subject: [PATCH 454/933] Task #8598: applied some patches from production --- LTA/LTAIngest/ltacp.py | 1 - 1 file changed, 1 deletion(-) diff --git a/LTA/LTAIngest/ltacp.py b/LTA/LTAIngest/ltacp.py index 035c2c1fc87..9cb636433f0 100755 --- a/LTA/LTAIngest/ltacp.py +++ b/LTA/LTAIngest/ltacp.py @@ -430,7 +430,6 @@ class LtaCp: p_remote_rm.communicate() if p_remote_rm.returncode != 0: logger.error("Could not remove remote fifo %s@%s:%s\n%s" % (self.src_user, self.src_host, self.remote_data_fifo, p_remote_rm.stderr)) - self.remote_data_fifo = None def cleanup(self): logger.debug('ltacp %s: cleaning up' % (self.logId)) -- GitLab From 7278db8914277fea90406e6011e88c65f0798c12 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 1 Jul 2016 11:54:38 +0000 Subject: [PATCH 455/933] Task #9611: Reenabled check for LOFARENV after merge from 2.16 branch --- LCS/MessageBus/src/Util.cc | 3 +-- LCS/MessageBus/src/messagebus.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/LCS/MessageBus/src/Util.cc b/LCS/MessageBus/src/Util.cc index d0eb919700f..9c18c62c28a 100644 --- a/LCS/MessageBus/src/Util.cc +++ b/LCS/MessageBus/src/Util.cc @@ -26,8 +26,7 @@ namespace LOFAR { std::string queue_prefix() { - // disable LOFARENV for 2.16 release - string lofarenv = "PRODUCTION"; //getenv_str("LOFARENV"); + string lofarenv = getenv_str("LOFARENV"); string queueprefix = getenv_str("QUEUE_PREFIX"); if (lofarenv == "PRODUCTION") { diff --git a/LCS/MessageBus/src/messagebus.py b/LCS/MessageBus/src/messagebus.py index 99cd046e97d..f6c1de6a37d 100644 --- a/LCS/MessageBus/src/messagebus.py +++ b/LCS/MessageBus/src/messagebus.py @@ -97,8 +97,7 @@ class Session: return "%s%s; {%s}" % (self._queue_prefix(), queue, options) def _queue_prefix(self): - # disable LOFARENV for 2.16 release - lofarenv = "PRODUCTION" # os.environ.get("LOFARENV", "") + lofarenv = os.environ.get("LOFARENV", "") queueprefix = os.environ.get("QUEUE_PREFIX", "") if lofarenv == "PRODUCTION": -- GitLab From fc8079eaafcc2ed12d91f516a228423cc05dc39e Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Fri, 1 Jul 2016 12:08:43 +0000 Subject: [PATCH 456/933] Task #9192: Merged resource estimator service.py from RA1_0_Integration-Task9192 after a problem in the 34853 merge --- .../ResourceAssignmentEstimator/service.py | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index baafa769017..55cb19d86e5 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -18,10 +18,10 @@ class ResourceEstimatorHandler(MessageHandlerInterface): def __init__(self, **kwargs): super(ResourceEstimatorHandler, self).__init__(**kwargs) self.observation = ObservationResourceEstimator() - #self.longbaseline_pipeline = LongBaselinePipelineResourceEstimator() - #self.calibration_pipeline = CalibrationPipelineResourceEstimator() - #self.pulsar_pipeline = PulsarPipelineResourceEstimator() - #self.imaging_pipeline = ImagePipelineResourceEstimator() + self.longbaseline_pipeline = LongBaselinePipelineResourceEstimator() + self.calibration_pipeline = CalibrationPipelineResourceEstimator() + self.pulsar_pipeline = PulsarPipelineResourceEstimator() + self.imaging_pipeline = ImagePipelineResourceEstimator() def handle_message(self, content): specification_tree = content["specification_tree"] @@ -48,7 +48,6 @@ class ResourceEstimatorHandler(MessageHandlerInterface): for branch in specification_tree['predecessors']: branch_estimates.update(self.get_subtree_estimate(branch)) logger.info(str(branch_estimates)) - if specification_tree['task_subtype'] in ['averaging pipeline', 'calibration pipeline']: for id, estimate in branch_estimates.iteritems(): input_files = {} @@ -60,30 +59,28 @@ class ResourceEstimatorHandler(MessageHandlerInterface): elif 'im' in estimate.values()[0]['storage']['output_files']: input_files['im'] = estimate.values()[0]['storage']['output_files']['im'] return {str(otdb_id): self.add_id(self.calibration_pipeline.verify_and_estimate(parset, input_files), otdb_id)} - + if specification_tree['task_subtype'] in ['imaging pipeline', 'imaging pipeline msss']: if len(branch_estimates) > 1: logger.error('Imaging pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] return {str(otdb_id): self.add_id(self.imaging_pipeline.verify_and_estimate(parset, input_files), otdb_id)} - - + if specification_tree['task_subtype'] in ['long baseline pipeline']: if len(branch_estimates) > 1: logger.error('Long baseline pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] return {str(otdb_id): self.add_id(self.longbaseline_pipeline.verify_and_estimate(parset, input_files), otdb_id)} - if specification_tree['task_subtype'] in ['pulsar pipeline']: if len(branch_estimates) > 1: logger.error('Pulsar pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] return {str(otdb_id): self.add_id(self.pulsar_pipeline.verify_and_estimate(parset, input_files), otdb_id)} - #else: # reservation, maintenance, system tasks? - #logger.info("It's not a pipeline or observation: %s" % otdb_id) - #return {str(otdb_id): {}} + else: # reservation, maintenance, system tasks? + logger.info("It's not a pipeline or observation: %s" % otdb_id) + return {str(otdb_id): {}} def _get_estimated_resources(self, specification_tree): """ Input is like: -- GitLab From 559d0ba258f5320b562cfdf483698118df00b2bd Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 1 Jul 2016 12:43:36 +0000 Subject: [PATCH 457/933] Task #9351 #9353 #9355: integrated cleanup and pie-chart datamanagement overview in webscheduler --- .../DataManagementCommon/path.py | 4 +- .../StorageQueryService/diskusage.py | 4 +- .../StorageQueryService/service.py | 4 + .../app/controllers/cleanupcontroller.js | 196 +++++++++++++++++- .../static/app/controllers/datacontroller.js | 64 ++++-- .../static/app/controllers/gridcontroller.js | 7 + .../lib/static/css/main.css | 14 +- .../lib/webservice.py | 74 ++++++- 8 files changed, 332 insertions(+), 35 deletions(-) diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index 4fe151e88c4..86acf0e8c6d 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -151,8 +151,8 @@ class PathResolver: if proc.returncode != 0: # lfs puts it's error message in stdout - logger.error(out) - return {'found': False, 'path': path, 'message': out} + logger.error(out + err) + return {'found': False, 'path': path, 'message': out + err} logger.debug(out) diff --git a/SAS/DataManagement/StorageQueryService/diskusage.py b/SAS/DataManagement/StorageQueryService/diskusage.py index 6a3887f971b..febd4c36351 100644 --- a/SAS/DataManagement/StorageQueryService/diskusage.py +++ b/SAS/DataManagement/StorageQueryService/diskusage.py @@ -46,7 +46,7 @@ def getDiskUsageForPath(path): parts = [p.strip() for p in file_lines[0].split(',')] partsDict = {p.split(':')[0].strip():p.split(':')[1].strip() for p in parts} - result = {'found': True, 'disk_usage': None, 'path': path} + result = {'found': True, 'disk_usage': None, 'path': path, 'name': path.split('/')[-1]} if 'size' in partsDict: result['disk_usage'] = int(partsDict['size']) @@ -58,7 +58,7 @@ def getDiskUsageForPath(path): else: dir_lines = [l for l in lines if 'dir count' in l] if dir_lines: - result = {'found': True, 'disk_usage': 0, 'nr_of_files': 0, 'path': path} + result = {'found': True, 'disk_usage': 0, 'nr_of_files': 0, 'path': path, 'name': path.split('/')[-1]} else: result = {'found': False, 'path': path } diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py index c7dfd74e2e1..a40f341861e 100644 --- a/SAS/DataManagement/StorageQueryService/service.py +++ b/SAS/DataManagement/StorageQueryService/service.py @@ -72,6 +72,10 @@ def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, brok 'cache_manager':cache_manager}) def main(): + # make sure we run in UTC timezone + import os + os.environ['TZ'] = 'UTC' + # Check the invocation arguments parser = OptionParser("%prog [options]", description='runs the storagequery service') diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js index 50af9f2dac5..d0848e7ee6f 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -2,7 +2,7 @@ var cleanupControllerMod = angular.module('CleanupControllerMod', ['ui.bootstrap']); -cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$http', '$q', function($scope, $uibModal, $http, $q) { +cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$http', '$q', 'dataService', function($scope, $uibModal, $http, $q, dataService) { var self = this; self.getTaskDataPath = function(task) { @@ -19,8 +19,10 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h }; self.deleteTaskDataWithConfirmation = function(task) { - self.getTaskDataPath(task).then(function(result) { - openDeleteConfirmationDialog(task, result.datapath); + self.getTaskDataPath(task).then(function(path_result) { + dataService.getTaskDiskUsageByOTDBId(task.otdb_id).then(function(du_result) { + openDeleteConfirmationDialog(task, path_result.datapath, du_result.found ? du_result.task_directory.disk_usage_readable : "??B"); + }); }); }; @@ -34,19 +36,19 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h }); }; - function openDeleteConfirmationDialog(task, path) { + function openDeleteConfirmationDialog(task, path, amount) { var modalInstance = $uibModal.open({ animation: false, template: '<div class="modal-header">\ <h3 class="modal-title">Are you sure?</h3>\ </div>\ <div class="modal-body">\ - <p>This will delete all data in ' + path + '<br>\ + <p>This will delete ' + amount + ' of data in ' + path + '<br>\ Are you sure?</p>\ </div>\ <div class="modal-footer">\ <button class="btn btn-primary" type="button" ng-click="ok()">OK</button>\ - <button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>\ + <button class="btn btn-warning" type="button" autofocus ng-click="cancel()">Cancel</button>\ </div>', controller: function ($scope, $uibModalInstance) { $scope.ok = function () { @@ -60,5 +62,187 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h } }); }; + + + self.showTaskDiskUsage = function(task) { + var modalInstance = $uibModal.open({ + animation: false, + template: '<div class="modal-header">\ + <h3 class="modal-title">Disk usage</h3>\ + </div>\ + <div class="modal-body" style="text-align:right">\ + <highchart id="chart_disk_usage" config="diskUsageChartConfig" style="width: 960px; height: 720px;" ></highchart>\ + <p>\ + <span style="margin-right:50px">Last updated at: {{leastRecentCacheTimestamp | date }}</span>\ + <button class="btn btn-primary glyphicon glyphicon-level-up" type="button" ng-click="up()" title="Up one level"></button>\ + </p>\ + </div>\ + <div class="modal-footer">\ + <button class="btn btn-primary" type="button" autofocus ng-click="ok()">OK</button>\ + </div>', + controller: function ($scope, $uibModalInstance) { + $scope.ok = function () { + $uibModalInstance.close(); + }; + + const OBJECT_TYPE_TASK = 'task'; + const OBJECT_TYPE_PROJECT = 'project'; + const OBJECT_TYPE_PROJECTS = 'projects'; + $scope.watchedObjectType = OBJECT_TYPE_TASK; + + $scope.leastRecentCacheTimestamp = ''; + + $scope.onPieClicked = function(event) { + switch($scope.watchedObjectType) { + case OBJECT_TYPE_PROJECT: + loadTaskDiskUsage(this.otdb_id); + break; + case OBJECT_TYPE_PROJECTS: + loadProjectDiskUsage(this.project_name); + break; + } + }; + + $scope.up = function () { + switch($scope.watchedObjectType) { + case OBJECT_TYPE_TASK: + loadProjectDiskUsage($scope.diskUsageChartSeries[0].project_name); + break; + case OBJECT_TYPE_PROJECT: + loadAllProjectsDiskUsage(); + break; + } + }; + + $scope.diskUsageChartSeries = [{name:'Loading data...', data:[]}]; + + $scope.diskUsageChartConfig = { + options: { + chart: { + type: 'pie', + animation: { + duration: 200 + } + }, + plotOptions: { + pie: { + allowPointSelect: true, + cursor: 'pointer', + dataLabels: { + enabled: true + }, + showInLegend: true + }, + series: { + point: { + events: { + click: $scope.onPieClicked + } + } + } + }, + tooltip: { + headerFormat: '{series.name}<br/>', + pointFormat: '{point.name}: <b>{point.percentage:.1f}%</b>' + } + }, + series: $scope.diskUsageChartSeries, + title: { + text: 'Loading data...' + }, + credits: { + enabled: false + }, + loading: false + } + + var loadTaskDiskUsage = function(otdb_id) { + dataService.getTaskDiskUsageByOTDBId(otdb_id).then(function(result) { + if(result.found) { + $scope.watchedObjectType = OBJECT_TYPE_TASK; + $scope.diskUsageChartConfig.title.text = result.task_directory.name + ' ' + result.task_directory.disk_usage_readable; + $scope.diskUsageChartSeries[0].name = result.task_directory.name + ' ' + result.task_directory.disk_usage_readable; + var path_parts = result.task_directory.path.split('/'); + $scope.diskUsageChartSeries[0].project_name = path_parts[path_parts.length-2]; + $scope.diskUsageChartSeries[0].data.splice(0, $scope.diskUsageChartSeries[0].data.length); + $scope.leastRecentCacheTimestamp = result.task_directory.cache_timestamp; + + var sub_directory_names = Object.keys(result.sub_directories); + sub_directory_names.sort(function(a, b) { return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0)); }); + for(var sub_dir of sub_directory_names) { + var sub_dir_result = result.sub_directories[sub_dir]; + $scope.diskUsageChartSeries[0].data.push({name:sub_dir_result.name + ' ' + sub_dir_result.disk_usage_readable,y:sub_dir_result.disk_usage}); + + if(sub_dir_result.cache_timestamp < $scope.leastRecentCacheTimestamp) { + $scope.leastRecentCacheTimestamp = sub_dir_result.cache_timestamp; + } + } + $scope.leastRecentCacheTimestamp = dataService.convertDatestringToLocalUTCDate($scope.leastRecentCacheTimestamp); + }else { + $scope.ok(); + } + }); + }; + + var loadProjectDiskUsage = function(project_name) { + dataService.getProjectDiskUsage(project_name).then(function(result) { + if(result.found) { + $scope.watchedObjectType = OBJECT_TYPE_PROJECT; + $scope.diskUsageChartConfig.title.text = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable; + $scope.diskUsageChartSeries[0].name = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable; + $scope.diskUsageChartSeries[0].data.splice(0, $scope.diskUsageChartSeries[0].data.length); + $scope.leastRecentCacheTimestamp = result.projectdir.cache_timestamp; + + var sub_directory_names = Object.keys(result.sub_directories); + sub_directory_names.sort(function(a, b) { return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0)); }); + for(var sub_dir of sub_directory_names) { + var sub_dir_result = result.sub_directories[sub_dir]; + $scope.diskUsageChartSeries[0].data.push({name:sub_dir_result.name + ' ' + sub_dir_result.disk_usage_readable, + y:sub_dir_result.disk_usage, + otdb_id: parseInt(sub_dir_result.name.slice(1)) }); + + if(sub_dir_result.cache_timestamp < $scope.leastRecentCacheTimestamp) { + $scope.leastRecentCacheTimestamp = sub_dir_result.cache_timestamp; + } + } + $scope.leastRecentCacheTimestamp = dataService.convertDatestringToLocalUTCDate($scope.leastRecentCacheTimestamp); + }else { + $scope.ok(); + } + }); + }; + + var loadAllProjectsDiskUsage = function() { + dataService.getProjectsDiskUsage().then(function(result) { + if(result.found) { + $scope.watchedObjectType = OBJECT_TYPE_PROJECTS; + $scope.diskUsageChartConfig.title.text = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable; + $scope.diskUsageChartSeries[0].name = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable; + $scope.diskUsageChartSeries[0].data.splice(0, $scope.diskUsageChartSeries[0].data.length); + $scope.leastRecentCacheTimestamp = result.projectdir.cache_timestamp; + + var sub_directory_names = Object.keys(result.sub_directories); + sub_directory_names.sort(function(a, b) { return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0)); }); + for(var sub_dir of sub_directory_names) { + var sub_dir_result = result.sub_directories[sub_dir]; + $scope.diskUsageChartSeries[0].data.push({name:sub_dir_result.name + ' ' + sub_dir_result.disk_usage_readable, + y:sub_dir_result.disk_usage, + project_name: sub_dir_result.name }); + + if(sub_dir_result.cache_timestamp < $scope.leastRecentCacheTimestamp) { + $scope.leastRecentCacheTimestamp = sub_dir_result.cache_timestamp; + } + } + $scope.leastRecentCacheTimestamp = dataService.convertDatestringToLocalUTCDate($scope.leastRecentCacheTimestamp); + }else { + $scope.ok(); + } + }); + }; + + loadTaskDiskUsage(task.otdb_id); + } + }); + }; }]); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 1406d27d525..ffda8c2e32a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -91,14 +91,14 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, //It's a stupid solution, but it works. //-- IMPORTANT REMARKS ABOUT UTC/LOCAL DATE -- - convertDatestringToLocalUTCDate = function(dateString) { + self.convertDatestringToLocalUTCDate = function(dateString) { //first convert the dateString to proper Date var date = new Date(dateString) //then do our trick to offset the timestamp with the utcOffset, see explanation above. return new Date(date.getTime() - self.utcOffset) }; - convertLocalUTCDateToISOString = function(local_utc_date) { + self.convertLocalUTCDateToISOString = function(local_utc_date) { //reverse trick to offset the timestamp with the utcOffset, see explanation above. var real_utc = new Date(local_utc_date.getTime() + self.utcOffset) return real_utc.toISOString(); @@ -122,7 +122,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, changedObj.hasOwnProperty(prop) && existingObj[prop] != changedObj[prop]) { if(existingObj[prop] instanceof Date && typeof changedObj[prop] === "string") { - existingObj[prop] = convertDatestringToLocalUTCDate(changedObj[prop]); + existingObj[prop] = self.convertDatestringToLocalUTCDate(changedObj[prop]); } else { existingObj[prop] = changedObj[prop]; } @@ -207,10 +207,10 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, var defer = $q.defer(); var url = '/rest/tasks'; if(from) { - url += '/' + convertLocalUTCDateToISOString(from); + url += '/' + self.convertLocalUTCDateToISOString(from); if(until) { - url += '/' + convertLocalUTCDateToISOString(until); + url += '/' + self.convertLocalUTCDateToISOString(until); } } @@ -218,8 +218,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, //convert datetime strings to Date objects for(var i in result.tasks) { var task = result.tasks[i]; - task.starttime = convertDatestringToLocalUTCDate(task.starttime); - task.endtime = convertDatestringToLocalUTCDate(task.endtime); + task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); + task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); } @@ -277,8 +277,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }; self.putTask = function(task) { - task.starttime = convertLocalUTCDateToISOString(task.starttime); - task.endtime = convertLocalUTCDateToISOString(task.endtime); + task.starttime = self.convertLocalUTCDateToISOString(task.starttime); + task.endtime = self.convertLocalUTCDateToISOString(task.endtime); $http.put('/rest/tasks/' + task.id, task).error(function(result) { console.log("Error. Could not update task. " + result); //TODO: revert to old state @@ -292,6 +292,18 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }) }; + self.getTaskDiskUsageByOTDBId = function(otdb_id) { + var defer = $q.defer(); + $http.get('/rest/tasks/otdb/' + otdb_id + '/diskusage').success(function(result) { + console.log(result); + defer.resolve(result); + }).error(function(result) { + defer.resolve({found:false}); + }); + + return defer.promise; + }; + self.computeMinMaxTaskTimes = function() { var starttimes = self.filteredTasks.map(function(t) { return t.starttime;}); var endtimes = self.filteredTasks.map(function(t) { return t.endtime;}); @@ -336,7 +348,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, for(var status in resource_usages) { var usages = resource_usages[status]; for(var usage of usages) { - usage.timestamp = convertDatestringToLocalUTCDate(usage.timestamp); + usage.timestamp = self.convertDatestringToLocalUTCDate(usage.timestamp); } } self.resourceUsagesDict[result.resourceusages[i].resource_id] = result.resourceusages[i]; @@ -352,10 +364,10 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, var defer = $q.defer(); var url = '/rest/resourceclaims'; if(from) { - url += '/' + convertLocalUTCDateToISOString(from); + url += '/' + self.convertLocalUTCDateToISOString(from); if(until) { - url += '/' + convertLocalUTCDateToISOString(until); + url += '/' + self.convertLocalUTCDateToISOString(until); } } @@ -363,8 +375,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, //convert datetime strings to Date objects for(var i in result.resourceclaims) { var resourceclaim = result.resourceclaims[i]; - resourceclaim.starttime = convertDatestringToLocalUTCDate(resourceclaim.starttime); - resourceclaim.endtime = convertDatestringToLocalUTCDate(resourceclaim.endtime); + resourceclaim.starttime = self.convertDatestringToLocalUTCDate(resourceclaim.starttime); + resourceclaim.endtime = self.convertDatestringToLocalUTCDate(resourceclaim.endtime); } var newClaimDict = self.toIdBasedDict(result.resourceclaims); @@ -487,6 +499,28 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }); }; + self.getProjectDiskUsage = function(project_name) { + var defer = $q.defer(); + $http.get('/rest/momprojects/' + project_name + '/diskusage').success(function(result) { + defer.resolve(result); + }).error(function(result) { + defer.resolve({found:false}); + }); + + return defer.promise; + }; + + self.getProjectsDiskUsage = function() { + var defer = $q.defer(); + $http.get('/rest/momprojects/diskusage').success(function(result) { + defer.resolve(result); + }).error(function(result) { + defer.resolve({found:false}); + }); + + return defer.promise; + }; + self.getConfig = function() { var defer = $q.defer(); $http.get('/rest/config').success(function(result) { @@ -506,7 +540,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self._syncLofarTimeWithServer = function() { $http.get('/rest/lofarTime', {timeout:1000}).success(function(result) { - self.lofarTime = convertDatestringToLocalUTCDate(result.lofarTime); + self.lofarTime = self.convertDatestringToLocalUTCDate(result.lofarTime); //check if local to utc offset has changed self.utcOffset = moment().utcOffset()*60000; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index dc6a2b40a69..6c5b2f8bd3c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -286,6 +286,13 @@ gridControllerMod.directive('contextMenu', ['$document', function($document) { cleanupCtrl.deleteTaskDataWithConfirmation(task); }); + var liElement = angular.element('<li><a href="#">Show disk usage</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + cleanupCtrl.showTaskDiskUsage(task); + }); + var closeContextMenu = function(cme) { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index 2abc793dd10..d03e408e6a4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -17,7 +17,7 @@ } .dropdown-menu { - z-index: 10000; + z-index: 1010; } .ui-grid-cell { @@ -28,6 +28,10 @@ /* visibility: hidden; */ } +.ui-splitbar { + z-index: 1000; +} + .ui-layout-row > .ui-splitbar { height: 6px; background-color: #CCCCCC; @@ -60,6 +64,14 @@ table.uib-timepicker td.uib-time { top: 65px; } +.modal-content { + width: 105% +} + +.modal-dialog { + width: 960px +} + .gantt-task-content { margin-top: 2.2px; } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 174cbbabdef..0f562d0b597 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -292,22 +292,25 @@ def getTaskDataPath(task_id): return jsonify({'datapath': result['path']}) abort(404, result['message'] if result and 'message' in result else '') -@app.route('/rest/tasks/<int:task_id>/diskusage', methods=['GET']) -def getTaskDiskUsage(task_id): +@app.route('/rest/tasks/otdb/<int:otdb_id>/diskusage', methods=['GET']) +def getTaskDiskUsageByOTDBId(otdb_id): try: - task = rarpc.getTask(task_id) + result = sqrpc.getDiskUsageForTaskAndSubDirectories(otdb_id=otdb_id) + except Exception as e: + abort(500, str(e)) - if not task: - abort(404, 'No such task (id=%s)' % task_id) + if result['found']: + return jsonify(result) + abort(404, result['message'] if result and 'message' in result else '') - result = sqrpc.getDiskUsageForOTDBId(task['otdb_id']) +@app.route('/rest/tasks/<int:task_id>/diskusage', methods=['GET']) +def getTaskDiskUsage(task_id): + try: + result = sqrpc.getDiskUsageForTaskAndSubDirectories(radb_id=task_id) except Exception as e: abort(500, str(e)) if result['found']: - del result['found'] - if 'disk_usage' in result: - result['disk_usage_readable'] = humanreadablesize(result['disk_usage']) return jsonify(result) abort(404, result['message'] if result and 'message' in result else '') @@ -447,6 +450,59 @@ def getUpdateEvents(): def getLofarTime(): return jsonify({'lofarTime': asIsoFormat(datetime.utcnow())}) +@app.route('/diskusage.html') +def getDiskUsagePage(): + html = '<!DOCTYPE html><html><head><title>Disk Usage</title><style>table, th, td {border: 1px solid black; border-collapse: collapse; padding: 4px;}</style>\n' + + html += '<script src="https://code.jquery.com/jquery-1.11.3.js"></script>\n' + html += '<script src="https://code.highcharts.com/highcharts.js"></script>\n' + html += '<script src="https://code.highcharts.com/modules/exporting.js"></script>\n' + + result = sqrpc.getDiskUsageForProjectDirAndSubDirectories(project_name='CEP4_commissioning') + + html += """<script type="text/javascript"> + $(function () { + $(document).ready(function () { + + // Build the chart + $('#container').highcharts({ + chart: { + plotBackgroundColor: null, + plotBorderWidth: null, + plotShadow: true, + type: 'pie' + }, + title: { + text: 'Disk usage' + }, + tooltip: { + pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>' + }, + plotOptions: { + pie: { + allowPointSelect: true, + cursor: 'pointer', + dataLabels: { + enabled: true + }, + showInLegend: true + } + }, + series: [{ + name: 'Projects', + colorByPoint: true, + data: [""" + ',\n'.join(["{name:\'%s\',y:%s}" % (x['path'].split('/')[-1], x['disk_usage']) for x in result['sub_directories'].values()]) + """]}] + }); + }); +});</script>\n""" + + html += '</head><body>\n' + html += '<div id="container" style="min-width: 310px; height: 400px; max-width: 600px; margin: 0 auto"></div>\n' + + html += '</body></html>\n' + + return html + def main(): # make sure we run in UTC timezone import os -- GitLab From 2faa4328ec303108afee1e74bcb763f744e7e834 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 1 Jul 2016 12:58:18 +0000 Subject: [PATCH 458/933] Task #9607: Created 2.17 release branch -- GitLab From 8c740038fef17caa54cb3c9a2308b06e72ba4ede Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 1 Jul 2016 14:58:40 +0000 Subject: [PATCH 459/933] Task #9330: merge task branch, DPPP can now run on empty MSs --- CEP/DP3/DPPP/src/FlagCounter.cc | 8 ++++++++ CEP/DP3/DPPP/src/MSReader.cc | 21 +++++++++++++-------- CEP/DP3/DPPP/src/MSWriter.cc | 5 +++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/CEP/DP3/DPPP/src/FlagCounter.cc b/CEP/DP3/DPPP/src/FlagCounter.cc index edd1e366187..5b96edf8ee6 100644 --- a/CEP/DP3/DPPP/src/FlagCounter.cc +++ b/CEP/DP3/DPPP/src/FlagCounter.cc @@ -265,6 +265,10 @@ namespace LOFAR { os << endl; } int64 totalnpoints = npoints * itsChanCounts.size(); + // Prevent division by zero + if (totalnpoints == 0) { + totalnpoints = 1; + } os << "Total flagged: "; showPerc3 (os, nflagged, totalnpoints); os << " (" << nflagged << " out of " << totalnpoints @@ -287,6 +291,10 @@ namespace LOFAR { void FlagCounter::showCorrelation (ostream& os, int64 ntimes) const { int64 ntotal = ntimes * itsBLCounts.size() * itsChanCounts.size(); + // Prevent division by zero + if (ntotal == 0) { + ntotal = 1; + } os << endl << "Percentage of flagged visibilities detected per correlation:" << endl; diff --git a/CEP/DP3/DPPP/src/MSReader.cc b/CEP/DP3/DPPP/src/MSReader.cc index 35dd696864e..1b6f205db5e 100644 --- a/CEP/DP3/DPPP/src/MSReader.cc +++ b/CEP/DP3/DPPP/src/MSReader.cc @@ -131,7 +131,7 @@ namespace LOFAR { } } // Prepare the MS access and get time info. - double startTime, endTime; + double startTime=0., endTime=0.; prepare (startTime, endTime, itsTimeInterval); // Start and end time can be given in the parset in case leading // or trailing time slots are missing. @@ -258,8 +258,9 @@ namespace LOFAR { itsLastMSTime = mstime; itsIter.next(); } - // Stop if at the end. - if (itsNextTime > itsLastTime && !near(itsNextTime, itsLastTime)) { + // Stop if at the end, or if there is no data at all + if ((itsNextTime > itsLastTime && !near(itsNextTime, itsLastTime)) || + itsNextTime==0.) { return false; } // Fill the buffer. @@ -390,7 +391,7 @@ namespace LOFAR { os << " ncorrelations: " << getInfo().ncorr() << std::endl; uint nrbl = getInfo().nbaselines(); os << " nbaselines: " << nrbl << std::endl; - os << " ntimes: " << itsSelMS.nrow() / nrbl << std::endl; + os << " ntimes: " << (nrbl==0 ? 0 : itsSelMS.nrow() / nrbl) << std::endl; os << " time interval: " << getInfo().timeInterval() << std::endl; os << " DATA column: " << itsDataColName; if (itsMissingData) { @@ -421,7 +422,9 @@ namespace LOFAR { void MSReader::prepare (double& firstTime, double& lastTime, double& interval) { - ASSERT (itsSelMS.nrow() > 0); + if (itsSelMS.nrow() == 0) { + DPLOG_WARN_STR ("The selected input does not contain any data."); + } TableDesc tdesc = itsMS.tableDesc(); itsHasWeightSpectrum = false; @@ -495,9 +498,11 @@ namespace LOFAR { sortms = itsSelMS.sort(sortCols); } // Get first and last time and interval from MS. - firstTime = ROScalarColumn<double>(sortms, "TIME")(0); - lastTime = ROScalarColumn<double>(sortms, "TIME")(sortms.nrow()-1); - interval = ROScalarColumn<double>(sortms, "INTERVAL")(0); + if (itsSelMS.nrow() > 0) { + firstTime = ROScalarColumn<double>(sortms, "TIME")(0); + lastTime = ROScalarColumn<double>(sortms, "TIME")(sortms.nrow()-1); + interval = ROScalarColumn<double>(sortms, "INTERVAL")(0); + } // Create iterator over time. Do not sort again. itsIter = TableIterator (sortms, Block<String>(1, "TIME"), TableIterator::Ascending, diff --git a/CEP/DP3/DPPP/src/MSWriter.cc b/CEP/DP3/DPPP/src/MSWriter.cc index eb2884247f1..a4cf6ee2fb1 100644 --- a/CEP/DP3/DPPP/src/MSWriter.cc +++ b/CEP/DP3/DPPP/src/MSWriter.cc @@ -508,6 +508,11 @@ namespace LOFAR { ArrayColumn<Complex> dataCol(out, itsDataColName); ArrayColumn<Bool> flagCol(out, "FLAG"); ScalarColumn<Bool> flagRowCol(out, "FLAG_ROW"); + + if (buf.getData().empty()) { + return; + } + dataCol.putColumn (buf.getData()); flagCol.putColumn (buf.getFlags()); // A row is flagged if no flags in the row are False. -- GitLab From a96c313ae1964adb3d4072377a24f1e726775025 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 1 Jul 2016 15:01:29 +0000 Subject: [PATCH 460/933] Task #9351 #9353 #9355: cleanup of different data types and scratch data --- SAS/DataManagement/CleanupService/rpc.py | 4 +- SAS/DataManagement/CleanupService/service.py | 56 +++++++++++++++---- .../DataManagementCommon/path.py | 11 +++- .../app/controllers/cleanupcontroller.js | 52 ++++++++++++++--- .../lib/webservice.py | 16 +++++- 5 files changed, 114 insertions(+), 25 deletions(-) diff --git a/SAS/DataManagement/CleanupService/rpc.py b/SAS/DataManagement/CleanupService/rpc.py index 7235f194137..f4024ca671e 100644 --- a/SAS/DataManagement/CleanupService/rpc.py +++ b/SAS/DataManagement/CleanupService/rpc.py @@ -18,8 +18,8 @@ class CleanupRPC(RPCWrapper): def removePath(self, path): return self.rpc('RemovePath', path=path) - def removeTaskData(self, otdb_id): - return self.rpc('RemoveTaskData', otdb_id=otdb_id) + def removeTaskData(self, otdb_id, delete_is=True, delete_cs=True, delete_uv=True, delete_im=True, delete_img=True, delete_pulp=True, delete_scratch=True): + return self.rpc('RemoveTaskData', otdb_id=otdb_id, delete_is=delete_is, delete_cs=delete_cs, delete_uv=delete_uv, delete_im=delete_im, delete_img=delete_img, delete_pulp=delete_pulp, delete_scratch=delete_scratch) def main(): import sys diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index 5fd540e30bc..7d07644676c 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -42,7 +42,7 @@ class CleanupHandler(MessageHandler): 'RemovePath': self._removePath, 'RemoveTaskData': self._removeTaskData } - def _removeTaskData(self, otdb_id): + def _removeTaskData(self, otdb_id, delete_is=True, delete_cs=True, delete_uv=True, delete_im=True, delete_img=True, delete_pulp=True, delete_scratch=True): logger.info("Remove task data for otdb_id %s" % (otdb_id,)) if not isinstance(otdb_id, int): @@ -50,11 +50,35 @@ class CleanupHandler(MessageHandler): logger.error(message) return {'deleted': False, 'message': message} - result = self.path_resolver.getPathForOTDBId(otdb_id) - if result['found']: - return self._removePath(result['path']) - - return {'deleted': False, 'message': result['message']} + path_result = self.path_resolver.getPathForOTDBId(otdb_id) + if path_result['found']: + rm_results = [] + if delete_is and delete_cs and delete_uv and delete_im and delete_img and delete_pulp: + rm_results.append(self._removePath(path_result['path'])) + else: + if delete_is: + rm_results.append(self._removePath(os.path.join(path_result['path'], 'is'))) + if delete_cs: + rm_results.append(self._removePath(os.path.join(path_result['path'], 'cs'))) + if delete_uv: + rm_results.append(self._removePath(os.path.join(path_result['path'], 'uv'))) + if delete_im: + rm_results.append(self._removePath(os.path.join(path_result['path'], 'im'))) + if delete_img: + rm_results.append(self._removePath(os.path.join(path_result['path'], 'img'))) + if delete_pulp: + rm_results.append(self._removePath(os.path.join(path_result['path'], 'pulp'))) + + if delete_scratch and 'scratch_paths' in path_result: + for scratch_path in path_result['scratch_paths']: + rm_results.append(self._removePath(scratch_path)) + + rm_result = {'deleted': all(x['deleted'] for x in rm_results), + 'message': '\n'.join(x['message'] for x in rm_results)} + + return rm_result + + return {'deleted': False, 'message': path_result['message']} def _removePath(self, path): logger.info("Remove path: %s" % (path,)) @@ -79,16 +103,24 @@ class CleanupHandler(MessageHandler): if len(path) > 1: path = path.rstrip('/') - if not path.startswith(self.path_resolver.projects_path): - message = "Invalid path '%s': Path does not start with '%s'" % (path, self.path_resolver.projects_path) - logger.error(message) - return {'deleted': False, 'message': message} + required_base_paths = [self.path_resolver.projects_path, self.path_resolver.scratch_path, self.path_resolver.share_path] - if path[len(self.path_resolver.projects_path)+1:].count('/') == 0: - message = "Invalid path '%s': Path should be a subdir of '%s'" % (path, self.path_resolver.projects_path) + if not any(path.startswith(base_path) for base_path in required_base_paths): + message = "Invalid path '%s': Path does not start with any oth the base paths: '%s'" % (path, ' '.join(required_base_paths)) logger.error(message) return {'deleted': False, 'message': message} + for base_path in required_base_paths: + if path.startswith(base_path) and path[len(base_path):].count('/') == 0: + message = "Invalid path '%s': Path should be a subdir of '%s'" % (path, base_path) + logger.error(message) + return {'deleted': False, 'message': message} + + if not os.path.exists(path): + message = "Nothing to delete, path '%s' does not exist." % (path) + logger.warn(message) + return {'deleted': True, 'message': message} + logger.info("Attempting to delete %s", path) failed_paths = set() diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index 86acf0e8c6d..363efb70588 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -30,6 +30,8 @@ class PathResolver: self.mountpoint = mountpoint self.projects_path = os.path.join(self.mountpoint, 'projects' if isProductionEnvironment() else 'test-projects') + self.scratch_path = os.path.join(self.mountpoint, 'scratch', 'pipeline') + self.share_path = os.path.join(self.mountpoint, 'share', 'pipeline') self.radbrpc = RADBRPC(busname=radb_busname, servicename=radb_servicename, broker=broker) self.momrpc = MoMQueryRPC(busname=mom_busname, servicename=mom_servicename, broker=broker) @@ -70,7 +72,14 @@ class PathResolver: task_data_path = os.path.join(project_path, 'L%s' % task['otdb_id']) logger.info("Found path '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (task_data_path, task['otdb_id'], task['mom_id'], task['id'])) - return {'found': True, 'message': '', 'path': task_data_path} + path_result = {'found': True, 'message': '', 'path': task_data_path} + + if task['type'] == 'pipeline': + scratch_path = os.path.join(self.scratch_path, 'Observation%s' % task['otdb_id']) + share_path = os.path.join(self.share_path, 'Observation%s' % task['otdb_id']) + path_result['scratch_paths'] = [scratch_path, share_path] + + return path_result return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js index d0848e7ee6f..7ed8d63afd9 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -19,15 +19,18 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h }; self.deleteTaskDataWithConfirmation = function(task) { - self.getTaskDataPath(task).then(function(path_result) { - dataService.getTaskDiskUsageByOTDBId(task.otdb_id).then(function(du_result) { - openDeleteConfirmationDialog(task, path_result.datapath, du_result.found ? du_result.task_directory.disk_usage_readable : "??B"); - }); + dataService.getTaskDiskUsageByOTDBId(task.otdb_id).then(function(du_result) { + if(du_result.found) { + openDeleteConfirmationDialog(task, du_result); + } else { + alert(du_result.message); + } }); }; - function deleteTaskData(task) { - $http.delete('/rest/tasks/' + task.id + '/cleanup').error(function(result) { + function deleteTaskData(task, delete_is, delete_cs, delete_uv, delete_im, delete_img, delete_pulp, delete_scratch) { + var params = {delete_is:delete_is, delete_cs:delete_cs, delete_uv:delete_uv, delete_im:delete_im, delete_img:delete_img, delete_pulp:delete_pulp, delete_scratch:delete_scratch}; + $http.delete('/rest/tasks/' + task.id + '/cleanup', {data: params}).error(function(result) { console.log("Error. Could cleanup data for task " + task.id + ", " + result); }).success(function(result) { if(!result.deleted) { @@ -36,24 +39,55 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h }); }; - function openDeleteConfirmationDialog(task, path, amount) { + function openDeleteConfirmationDialog(task, du_result) { + var path = du_result.task_directory.path; + var modalInstance = $uibModal.open({ animation: false, template: '<div class="modal-header">\ <h3 class="modal-title">Are you sure?</h3>\ </div>\ <div class="modal-body">\ - <p>This will delete ' + amount + ' of data in ' + path + '<br>\ + <p>This will delete all selected data in ' + path + '<br>\ Are you sure?</p>\ + <label ng-if="has_is" style="margin-left:24px">IS: {{amount_is}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_is"></label>\ + <label ng-if="has_cs" style="margin-left:24px">CS: {{amount_cs}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_cs"></label>\ + <label ng-if="has_uv" style="margin-left:24px">UV: {{amount_uv}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_uv"></label>\ + <label ng-if="has_im" style="margin-left:24px">IM: {{amount_im}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_im"></label>\ + <label ng-if="has_img" style="margin-left:24px">Images: {{amount_img}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_img"></label>\ + <label ng-if="has_pulp" style="margin-left:24px">Pulp: {{amount_pulp}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_pulp"></label>\ + <label ng-if="has_scratch" style="margin-left:24px">scratch<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_scratch"></label>\ </div>\ <div class="modal-footer">\ <button class="btn btn-primary" type="button" ng-click="ok()">OK</button>\ <button class="btn btn-warning" type="button" autofocus ng-click="cancel()">Cancel</button>\ </div>', controller: function ($scope, $uibModalInstance) { + $scope.has_is = du_result.sub_directories.hasOwnProperty(path + '/is'); + $scope.has_cs = du_result.sub_directories.hasOwnProperty(path + '/cs'); + $scope.has_uv = du_result.sub_directories.hasOwnProperty(path + '/uv'); + $scope.has_im = du_result.sub_directories.hasOwnProperty(path + '/im'); + $scope.has_img = du_result.sub_directories.hasOwnProperty(path + '/img'); + $scope.has_pulp = du_result.sub_directories.hasOwnProperty(path + '/pulp'); + $scope.has_scratch = du_result.task_directory.hasOwnProperty('scratch_paths');; + $scope.amount_is = $scope.has_is ? du_result.sub_directories[path + '/is'].disk_usage_readable : ''; + $scope.amount_cs = $scope.has_cs ? du_result.sub_directories[path + '/cs'].disk_usage_readable : ''; + $scope.amount_uv = $scope.has_uv ? du_result.sub_directories[path + '/uv'].disk_usage_readable : ''; + $scope.amount_im = $scope.has_im ? du_result.sub_directories[path + '/im'].disk_usage_readable : ''; + $scope.amount_img = $scope.has_img ? du_result.sub_directories[path + '/img'].disk_usage_readable : ''; + $scope.amount_pulp = $scope.has_pulp ? du_result.sub_directories[path + '/pulp'].disk_usage_readable : ''; + + $scope.delete_is = true; + $scope.delete_cs = true; + $scope.delete_uv = true; + $scope.delete_im = true; + $scope.delete_img = true; + $scope.delete_pulp = true; + $scope.delete_scratch = true; + $scope.ok = function () { $uibModalInstance.close(); - deleteTaskData(task); + deleteTaskData(task, $scope.delete_is, $scope.delete_cs, $scope.delete_uv, $scope.delete_im, $scope.delete_img, $scope.delete_pulp, $scope.delete_scratch); }; $scope.cancel = function () { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 0f562d0b597..4604f453dba 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -265,12 +265,26 @@ def putTask(task_id): @app.route('/rest/tasks/<int:task_id>/cleanup', methods=['DELETE']) def cleanupTaskData(task_id): try: + delete_params = {} + + if 'Content-Type' in request.headers and (request.headers['Content-Type'].startswith('application/json') or request.headers['Content-Type'].startswith('text/plain')): + delete_params = json.loads(request.data) + + print 'delete_params:', delete_params + task = rarpc.getTask(task_id) if not task: abort(404, 'No such task (id=%s)' % task_id) - result = curpc.removeTaskData(task['otdb_id']) + result = curpc.removeTaskData(task['otdb_id'], + delete_is=delete_params.get('delete_is', True), + delete_cs=delete_params.get('delete_cs', True), + delete_uv=delete_params.get('delete_uv', True), + delete_im=delete_params.get('delete_im', True), + delete_img=delete_params.get('delete_img', True), + delete_pulp=delete_params.get('delete_pulp', True), + delete_scratch=delete_params.get('delete_scratch', True)) logger.info(result) return jsonify(result) except Exception as e: -- GitLab From 65d3cc6bcc84e23646ab95b5e509e9f7e61247f7 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 1 Jul 2016 15:08:26 +0000 Subject: [PATCH 461/933] Task #9462: revert accidental checkin --- CMake/testscripts/assay | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMake/testscripts/assay b/CMake/testscripts/assay index 09256b2d7f6..d97460d4562 100755 --- a/CMake/testscripts/assay +++ b/CMake/testscripts/assay @@ -190,7 +190,7 @@ # Define exit and interrupt handler. - trap 'rm -rf core ${PROG}_tammop*; \ + trap 'rm -rf core ${PROG}_tmp*; \ trap - 0 ; \ exit $STATUS' 0 1 2 3 15 -- GitLab From 684ae2f0e4f2262b2e4eb18c7392054681aaf0cd Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Sun, 3 Jul 2016 18:40:06 +0000 Subject: [PATCH 462/933] Task #9614: restrict end time of gaincal solutions to end of MS --- CEP/DP3/DPPP/src/GainCal.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index 20280fada09..6189bbe8f3d 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -682,7 +682,8 @@ namespace LOFAR { BBS::Axis::ShPtr tdomAxis( new BBS::RegularAxis( startTime, - ntime * info().timeInterval() * itsSolInt, + min(ntime * info().timeInterval() * itsSolInt, + info().ntime() * info().timeInterval()), 1)); BBS::Axis::ShPtr fdomAxis( new BBS::RegularAxis( -- GitLab From 2a0607c55bb224ea1c10f1b00ef889917523bfc4 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Sun, 3 Jul 2016 19:08:41 +0000 Subject: [PATCH 463/933] Task #9537: many updates to TEC solver, still some bug in there --- CEP/DP3/DPPP/include/DPPP/GainCal.h | 8 +- CEP/DP3/DPPP/include/DPPP/StefCal.h | 14 +- CEP/DP3/DPPP/src/GainCal.cc | 283 ++++++++++++++++++---------- CEP/DP3/DPPP/src/StefCal.cc | 97 +++++++--- 4 files changed, 270 insertions(+), 132 deletions(-) diff --git a/CEP/DP3/DPPP/include/DPPP/GainCal.h b/CEP/DP3/DPPP/include/DPPP/GainCal.h index 6181c6f5107..8e417f003d3 100644 --- a/CEP/DP3/DPPP/include/DPPP/GainCal.h +++ b/CEP/DP3/DPPP/include/DPPP/GainCal.h @@ -135,6 +135,9 @@ namespace LOFAR { vector<casa::Matrix<casa::DComplex> > itsPrevSol; // previous solution, for propagating solutions, for each freq vector<casa::Cube<casa::DComplex> > itsSols; // for every timeslot, nCr x nSt x nFreqCells + vector<casa::Matrix<double> > itsTECSols; // for every timeslot, 2 x nSt (alpha and beta) + + vector<casa::CountedPtr<PhaseFitter> > itsPhaseFitters; // Length nSt std::vector<StefCal> iS; @@ -143,7 +146,6 @@ namespace LOFAR { ResultStep* itsResultStep; // For catching results from Predict or Beam bool itsApplyBeamToModelColumn; - casa::Vector<casa::String> itsAntennaUsedNames; map<string,int> itsParmIdMap; //# -1 = new parm name uint itsMaxIter; @@ -157,8 +159,9 @@ namespace LOFAR { uint itsTimeSlotsPerParmUpdate; uint itsConverged; uint itsNonconverged; + uint itsFailed; uint itsStalled; - vector<uint> itsNIter; // Total iterations made (for converged, stalled and nonconverged) + vector<uint> itsNIter; // Total iterations made (for converged, stalled, nonconverged, failed) uint itsStepInParmUpdate; // Timestep within parameter update double itsChunkStartTime; // First time value of chunk to be stored uint itsStepInSolInt; // Timestep within solint @@ -168,6 +171,7 @@ namespace LOFAR { NSTimer itsTimer; NSTimer itsTimerPredict; NSTimer itsTimerSolve; + NSTimer itsTimerPhaseFit; NSTimer itsTimerWrite; NSTimer itsTimerFill; }; diff --git a/CEP/DP3/DPPP/include/DPPP/StefCal.h b/CEP/DP3/DPPP/include/DPPP/StefCal.h index b6a62182c9f..51e1c8203f1 100644 --- a/CEP/DP3/DPPP/include/DPPP/StefCal.h +++ b/CEP/DP3/DPPP/include/DPPP/StefCal.h @@ -37,7 +37,7 @@ namespace LOFAR { class StefCal { public: - enum Status {CONVERGED=1, NOTCONVERGED=2, STALLED=3}; + enum Status {CONVERGED=1, NOTCONVERGED=2, STALLED=3, FAILED=4}; // mode can be "diagonal", "fulljones", "phaseonly", "scalarphase" StefCal(uint solInt, uint nChan, const string& mode, double tolerance, @@ -61,6 +61,13 @@ namespace LOFAR { // The mapping is stored in the antenna map casa::Matrix<casa::DComplex> getSolution(bool setNaNs); + double getWeight() { + return _totalWeight; + } + + // Increments the weight (only relevant for TEC-fitting) + void incrementWeight(float weight); + // Returns a reference to the visibility matrix casa::Array<casa::DComplex>& getVis() { return _vis; @@ -71,7 +78,7 @@ namespace LOFAR { return _mvis; } - std::vector<bool>& getStationFlagged() { + casa::Vector<bool>& getStationFlagged() { return _stationFlagged; } @@ -101,7 +108,7 @@ namespace LOFAR { double getAverageUnflaggedSolution(); uint _savedNCr; - std::vector<bool> _stationFlagged ; // Contains true for totally flagged stations + casa::Vector<bool> _stationFlagged ; // Contains true for totally flagged stations casa::Array<casa::DComplex> _vis; // Visibility matrix casa::Array<casa::DComplex> _mvis; // Model visibility matrix casa::Matrix<casa::DComplex> _g; // Solution, indexed by station, correlation @@ -121,6 +128,7 @@ namespace LOFAR { uint _nChan; // number of channels string _mode; // diagonal, scalarphase, fulljones or phaseonly double _tolerance; + double _totalWeight; bool _detectStalling; uint _debugLevel; diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index 9454139959f..c980b66b4e1 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -87,6 +87,7 @@ namespace LOFAR { (parset.getInt(prefix + "timeslotsperparmupdate", 500)), itsConverged (0), itsNonconverged (0), + itsFailed (0), itsStalled (0), itsStepInParmUpdate (0), itsChunkStartTime(0), @@ -111,7 +112,7 @@ namespace LOFAR { } } - itsNIter.resize(3,0); + itsNIter.resize(4,0); if (itsApplySolution) { itsBuf.resize(itsSolInt); @@ -156,8 +157,6 @@ namespace LOFAR { itsSolInt=info().ntime(); } - itsSols.reserve(itsTimeSlotsPerParmUpdate); - if (itsNChan==0) { itsNChan = info().nchan(); } @@ -169,9 +168,33 @@ namespace LOFAR { itsNFreqCells++; } - itsAntennaUsedNames.resize(info().antennaUsed().size()); - for (int ant=0, nAnts=info().antennaUsed().size(); ant<nAnts; ++ant) { - itsAntennaUsedNames[ant]=info().antennaNames()[info().antennaUsed()[ant]]; + itsSols.reserve(itsTimeSlotsPerParmUpdate); + + // Initialize phase fitters, set their frequency data + if (itsMode=="tec") { + itsTECSols.reserve(itsTimeSlotsPerParmUpdate); + + itsPhaseFitters.reserve(itsNFreqCells); // TODO: could be numthreads instead + vector<double> freqData(itsNFreqCells); + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + double meanfreq=0; + uint chmin=itsNChan*freqCell; + uint chmax=min(info().nchan(), chmin+itsNChan); + + meanfreq = std::accumulate(info().chanFreqs().data()+chmin, + info().chanFreqs().data()+chmax, 0.0); + + freqData[freqCell] = meanfreq / (chmax-chmin); + } + + uint nSt=info().antennaNames().size(); + for (uint st=0; st<nSt; ++st) { + itsPhaseFitters.push_back(CountedPtr<PhaseFitter>(new PhaseFitter(itsNFreqCells))); + double* nu = itsPhaseFitters[st]->FrequencyData(); + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + nu[freqCell] = freqData[freqCell]; + } + } } iS.reserve(itsNFreqCells); @@ -180,8 +203,7 @@ namespace LOFAR { if ((freqCell+1)*itsNChan>info().nchan()) { // Last cell can be smaller chMax-=((freqCell+1)*itsNChan)%info().nchan(); } - iS.push_back(StefCal(itsSolInt, chMax, - (itsMode=="tec"?"scalarphase":itsMode), + iS.push_back(StefCal(itsSolInt, chMax, itsMode, itsTolerance, info().antennaNames().size(), itsDetectStalling, itsDebugLevel)); } @@ -205,7 +227,7 @@ namespace LOFAR { os << " nchan: " << itsNChan <<endl; os << " max iter: " << itsMaxIter << endl; os << " tolerance: " << itsTolerance << endl; - os << " mode: " << itsMode << endl; + os << " caltype: " << itsMode << endl; os << " apply solution: " << boolalpha << itsApplySolution << endl; os << " propagate solutions: " << boolalpha << itsPropagateSolutions << endl; os << " detect stalling: " << boolalpha << itsDetectStalling << endl; @@ -236,15 +258,22 @@ namespace LOFAR { FlagCounter::showPerc1 (os, itsTimerSolve.getElapsed(), totaltime); os << " of it spent in estimating gains and computing residuals" << endl; + if (itsMode == "tec") { + os << " "; + FlagCounter::showPerc1 (os, itsTimerPhaseFit.getElapsed(), totaltime); + os << "of it spent in fitting phases" << endl; + } + os << " "; FlagCounter::showPerc1 (os, itsTimerWrite.getElapsed(), totaltime); os << " of it spent in writing gain solutions to disk" << endl; os << " "; - os <<"Converged: "<<itsConverged<<", stalled: "<<itsStalled<<", non converged: "<<itsNonconverged<<endl; + os <<"Converged: "<<itsConverged<<", stalled: "<<itsStalled<<", non converged: "<<itsNonconverged<<", failed: "<<itsFailed<<endl; os <<"Iters converged: " << (itsConverged==0?0:itsNIter[0]/itsConverged); os << ", stalled: "<< (itsStalled ==0?0:itsNIter[1]/itsStalled); - os << ", non converged: "<<(itsNonconverged==0?0:itsNIter[2]/itsNonconverged)<<endl; + os << ", non converged: "<<(itsNonconverged==0?0:itsNIter[2]/itsNonconverged); + os << ", failed: "<<(itsFailed==0?0:itsNIter[3]/itsFailed)<<endl; } bool GainCal::process (const DPBuffer& bufin) @@ -334,6 +363,7 @@ namespace LOFAR { writeSolutions(itsChunkStartTime); itsChunkStartTime += itsSolInt * itsTimeSlotsPerParmUpdate * info().timeInterval(); itsSols.clear(); + itsTECSols.clear(); itsStepInParmUpdate = 0; } @@ -400,6 +430,7 @@ namespace LOFAR { // Fills itsVis and itsMVis as matrices with all 00 polarizations in the // top left, all 11 polarizations in the bottom right, etc. + // For TEC fitting, it also sets weights for the frequency cells void GainCal::fillMatrices (casa::Complex* model, casa::Complex* data, float* weight, const casa::Bool* flag) { const size_t nBl = info().nbaselines(); @@ -417,6 +448,10 @@ namespace LOFAR { continue; } + if (itsMode=="tec") { + iS[ch/itsNChan].incrementWeight(weight[bl*nCr*nCh+ch*nCr]); + } + for (uint cr=0;cr<nCr;++cr) { iS[ch/itsNChan].getVis() (IPosition(6,ant1,cr/2,itsStepInSolInt,ch%itsNChan,cr%2,ant2)) = DComplex(data [bl*nCr*nCh+ch*nCr+cr]) * @@ -446,32 +481,46 @@ namespace LOFAR { casa::Vector<casa::uInt> dataPerAntenna; // nAnt dataPerAntenna.resize(info().antennaNames().size()); - dataPerAntenna=0; - for (uint bl=0;bl<nBl;++bl) { - uint ant1=info().getAnt1()[bl]; - uint ant2=info().getAnt2()[bl]; - if (ant1==ant2) { - continue; - } - uint chmax=min((freqCell+1)*itsNChan, nCh); - for (uint ch=freqCell*itsNChan;ch<chmax;++ch) { - for (uint cr=0;cr<nCr;++cr) { - if (!flag[bl*nCr*nCh + ch*nCr + cr]) { - dataPerAntenna(ant1)++; - dataPerAntenna(ant2)++; + // TODO: implement smarter graph algorithm here that requires + // less than O(n_ant^3) worst case operations. + bool flaggedAntsAdded=true; + uint loopcount=0; + while (flaggedAntsAdded) { + dataPerAntenna=0; + for (uint bl=0;bl<nBl;++bl) { + uint ant1=info().getAnt1()[bl]; + uint ant2=info().getAnt2()[bl]; + if (ant1==ant2) { + continue; + } + uint chmax=min((freqCell+1)*itsNChan, nCh); + for (uint ch=freqCell*itsNChan;ch<chmax;++ch) { + for (uint cr=0;cr<nCr;++cr) { + if (!(flag[bl*nCr*nCh + ch*nCr + cr] + || iS[freqCell].getStationFlagged()[ant1] + || iS[freqCell].getStationFlagged()[ant2] + )) { + dataPerAntenna(ant1)++; + dataPerAntenna(ant2)++; + } } } } - } - for (uint ant=0; ant<info().antennaNames().size(); ++ant) { - if (dataPerAntenna(ant)>nCr*itsMinBLperAnt) { - iS[freqCell].getStationFlagged()[ant]=false; // Index in stefcal numbering - } else { - //cout<<"flagging station "<<ant<<", "<<dataPerAntenna(ant)<<endl; - iS[freqCell].getStationFlagged()[ant]=true; // Not enough data + flaggedAntsAdded=false; + for (uint ant=0; ant<info().antennaNames().size(); ++ant) { + if (dataPerAntenna(ant)>nCr*itsMinBLperAnt) { + iS[freqCell].getStationFlagged()[ant]=false; // Index in stefcal numbering + } else { // Not enough data + //cout<<"flagging station "<<ant<<", "<<dataPerAntenna(ant)<<endl; + if (!iS[freqCell].getStationFlagged()[ant]) { + iS[freqCell].getStationFlagged()[ant]=true; + flaggedAntsAdded=true; + } + } } + loopcount++; } } @@ -488,22 +537,7 @@ namespace LOFAR { uint iter=0; - PhaseFitter fitter(itsNFreqCells); - - if (itsMode=="tec") { - // Set frequency data for TEC fitter - double* nu = fitter.FrequencyData(); - for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { - double meanfreq=0; - uint chmin=itsNChan*freqCell; - uint chmax=min(info().nchan(), chmin+itsNChan); - - meanfreq = std::accumulate(info().chanFreqs().data()+chmin, - info().chanFreqs().data()+chmax, 0.0); - - nu[freqCell] = meanfreq / (chmax-chmin); - } - } + casa::Matrix<double> tecsol(2, info().antennaNames().size(), 0); std::vector<StefCal::Status> converged(itsNFreqCells,StefCal::NOTCONVERGED); for (;iter<itsMaxIter;++iter) { @@ -514,77 +548,109 @@ namespace LOFAR { continue; } converged[freqCell] = iS[freqCell].doStep(iter); - if (converged[freqCell]==StefCal::NOTCONVERGED) { + if (converged[freqCell]==StefCal::NOTCONVERGED) { // Only continue if there are steps worth continuing (so not converged, failed or stalled) allConverged = false; - } else if (converged[freqCell]==StefCal::CONVERGED) { - itsNIter[0] += iter; - } + } } - double alpha=0., beta=0.; if (itsMode=="tec") { - casa::Matrix<casa::DComplex> tecsol(itsNFreqCells, info().antennaNames().size()); + itsTimerSolve.stop(); + itsTimerPhaseFit.start(); + casa::Matrix<casa::DComplex> sols_f(itsNFreqCells, info().antennaNames().size()); + uint nSt = info().antennaNames().size(); + + // TODO: set phase reference so something smarter that station 0 for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { casa::Matrix<casa::DComplex> sol = iS[freqCell].getSolution(false); - for (uint ant=0; ant<info().antennaNames().size(); ++ant) { - tecsol(freqCell, ant) = sol(ant, 0)/sol(0, 0); + if (iS[freqCell].getStationFlagged()[0]) { + // If reference station flagged, flag whole channel + for (uint st=0; st<info().antennaNames().size(); ++st) { + iS[freqCell].getStationFlagged()[st] = true; + } + } else { + for (uint st=0; st<info().antennaNames().size(); ++st) { + sols_f(freqCell, st) = sol(st, 0)/sol(0, 0); + ASSERT(isFinite(sols_f(freqCell, st))); + } } } - uint nSt = info().antennaNames().size(); - for (uint ant=0; ant<nSt; ++ant) { +#pragma omp parallel for + for (uint st=0; st<nSt; ++st) { uint numpoints=0; double cost=0; - double* phases = fitter.PhaseData(); - double* weights = fitter.WeightData(); + double* phases = itsPhaseFitters[st]->PhaseData(); + double* weights = itsPhaseFitters[st]->WeightData(); for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { - if (iS[freqCell].getStationFlagged()[ant%nSt]) { + if (iS[freqCell].getStationFlagged()[st%nSt] || + converged[freqCell]==StefCal::FAILED) { phases[freqCell] = 0; weights[freqCell] = 0; } else { - phases[freqCell] = arg(tecsol(freqCell, ant)); - weights[freqCell] = 1; + phases[freqCell] = arg(sols_f(freqCell, st)); + if (!isFinite(phases[freqCell])) { + cout<<"Yuk, phases[freqCell]="<<phases[freqCell]<<", sols_f(freqCell, st)="<<sols_f(freqCell, st)<<endl; + ASSERT(isFinite(phases[freqCell])); + } + ASSERT(iS[freqCell].getWeight()>0); + weights[freqCell] = iS[freqCell].getWeight(); + if (itsDebugLevel > 0 && st==34) { + cout<<"w["<<freqCell<<"]="<<weights[freqCell]<<endl; + } numpoints++; } } - if (itsDebugLevel>0 && ant==1) { - //cout<<endl<<"phases["<<ant<<"]=["; - cout<<"unfitted["<<iter<<"]=["; - uint freqCell=0; - for (; freqCell<itsNFreqCells-1; ++freqCell) { - cout<<phases[freqCell]<<","; + if (itsDebugLevel>0) { + cout<<"st="<<st<<", numpoints="<<numpoints<<endl; + } + + if (itsDebugLevel>0 && st==34) { + cout<<"t="<<itsStepInParmUpdate<<", st="<<st<<", unfitted["<<iter<<"]=["; + uint freqCell2=0; + for (; freqCell2<itsNFreqCells-1; ++freqCell2) { + cout<<phases[freqCell2]<<","; } - cout<<phases[freqCell]<<"];"<<endl; + cout<<phases[freqCell2]<<"];"<<endl; + } + + for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + ASSERT(isFinite(phases[freqCell])); } - if (numpoints>1) { - cost=fitter.FitDataToTECModel(alpha, beta); + + if (numpoints>1) { // TODO: limit should be higher + //cout<<"tecsol(0,"<<st<<")="<<tecsol(0,st)<<", tecsol(1,"<<st<<")="<<tecsol(1,st)<<endl; + cost=itsPhaseFitters[st]->FitDataToTECModel(tecsol(0, st), tecsol(1,st)); // Update solution in stefcal for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { - iS[freqCell].getSolution(false)(ant, 0) = polar(1., phases[freqCell]); + ASSERT(isFinite(phases[freqCell])); + iS[freqCell].getSolution(false)(st, 0) = polar(1., phases[freqCell]); } - } else if (itsDebugLevel>5) { - cout<<"Not enough points ("<<numpoints<<") for antenna "<<ant<<endl; + } else { + tecsol(0, st) = 0; //std::numeric_limits<double>::quiet_NaN(); + tecsol(1, st) = 0; //std::numeric_limits<double>::quiet_NaN(); } - if (itsDebugLevel>1 && ant==1) { - cout<<"fitted["<<iter<<"]=["; + + if (st==34 && itsDebugLevel>0) { + cout<<"fitted["<<st<<"]=["; uint freqCell=0; for (; freqCell<itsNFreqCells-1; ++freqCell) { cout<<phases[freqCell]<<","; } cout<<phases[freqCell]<<"]"<<endl; - } - if (itsDebugLevel>0 && ant==1) { - cout << "fitdata["<<iter<<"]=[" << alpha << ", " << beta << ", " << cost << "];" << endl; + cout << "fitdata["<<st<<"]=[" << tecsol(0,1) << ", " << tecsol(1,1) << ", " << cost << "];" << endl; } } + itsTimerPhaseFit.stop(); + itsTimerSolve.start(); } if (allConverged) { break; } +#ifdef DEBUG if (itsDebugLevel>1) { // Only antenna 1 cout<<"phases["<<iter<<"]=["; uint freqCell=0; @@ -601,6 +667,7 @@ namespace LOFAR { } cout<<converged[freqCell]<<"]"<<endl; } +#endif } // End niter for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { @@ -608,6 +675,7 @@ namespace LOFAR { case StefCal::CONVERGED: {itsConverged++; break;} case StefCal::STALLED: {itsStalled++; itsNIter[1]+=iter; break;} case StefCal::NOTCONVERGED: {itsNonconverged++; itsNIter[2]+=iter; break;} + case StefCal::FAILED: {itsFailed++; itsNIter[3]+=iter; break;} default: THROW(Exception, "Unknown converged status"); } @@ -635,9 +703,13 @@ namespace LOFAR { } } itsSols.push_back(sol); + if (itsMode=="tec") { + itsTECSols.push_back(tecsol); + } itsTimerSolve.stop(); - } + } // End stefcal() + void GainCal::initParmDB() { itsParmDB = boost::shared_ptr<BBS::ParmDB> @@ -739,6 +811,14 @@ namespace LOFAR { } // End initialization of parmdb uint ntime=itsSols.size(); + uint nchan, nfreqs; + if (itsMode=="tec") { + nfreqs = 1; + nchan = info().nchan(); + } else { + nfreqs = itsNFreqCells; + nchan = itsNChan; + } // Construct solution grid for the current chunk double freqWidth = getInfo().chanWidths()[0]; @@ -748,8 +828,8 @@ namespace LOFAR { BBS::Axis::ShPtr freqAxis( new BBS::RegularAxis( getInfo().chanFreqs()[0] - freqWidth * 0.5, - freqWidth*itsNChan, - itsNFreqCells)); + freqWidth*nchan, + nfreqs)); BBS::Axis::ShPtr timeAxis( new BBS::RegularAxis( startTime, @@ -772,16 +852,13 @@ namespace LOFAR { // Write the solutions per parameter. const char* str0101[] = {"0:0:","0:1:","1:0:","1:1:"}; const char* strri[] = {"Real:","Imag:"}; - Matrix<double> values(itsNFreqCells, ntime); + Matrix<double> values(nfreqs, ntime); DComplex sol; uint nSt=info().antennaNames().size(); for (size_t st=0; st<nSt; ++st) { - uint seqnr = 0; // To take care of real and imaginary part - string suffix(info().antennaNames()[st]); - for (int pol=0; pol<4; ++pol) { // For 0101 if ((itsMode=="diagonal" || itsMode=="phaseonly" || itsMode=="amplitudeonly") && (pol==1||pol==2)) { @@ -791,10 +868,9 @@ namespace LOFAR { itsMode=="tec") && pol>0) { continue; } - int realimmax; + int realimmax; // For tec, this functions as dummy between tec and commonscalarphase if (itsMode=="phaseonly" || itsMode=="scalarphase" || - itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || - itsMode=="tec") { + itsMode=="amplitudeonly" || itsMode=="scalaramplitude") { realimmax=1; } else { realimmax=2; @@ -812,36 +888,47 @@ namespace LOFAR { name=name+strri[realim]; } } + if (itsMode=="tec") { + if (realim==0) { + name="TEC:"; + } else { + name="CommonScalarPhase:"; + } + } - name+=suffix; + name+=info().antennaNames()[st]; // Collect its solutions for all times and frequency cells in a single array. for (uint ts=0; ts<ntime; ++ts) { - for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { + for (uint freqCell=0; freqCell<nfreqs; ++freqCell) { if (itsMode=="fulljones") { - if (seqnr%2==0) { - values(freqCell, ts) = real(itsSols[ts](seqnr/2,st,freqCell)); + if (realim==0) { + values(freqCell, ts) = real(itsSols[ts](pol,st,freqCell)); } else { - values(freqCell, ts) = imag(itsSols[ts](seqnr/2,st,freqCell)); + values(freqCell, ts) = imag(itsSols[ts](pol,st,freqCell)); } } else if (itsMode=="diagonal") { - if (seqnr%2==0) { + if (realim==0) { values(freqCell, ts) = real(itsSols[ts](pol/3,st,freqCell)); } else { values(freqCell, ts) = imag(itsSols[ts](pol/3,st,freqCell)); } - } else if (itsMode=="scalarphase" || itsMode=="phaseonly" || - itsMode=="tec") { + } else if (itsMode=="scalarphase" || itsMode=="phaseonly") { values(freqCell, ts) = arg(itsSols[ts](pol/3,st,freqCell)); } else if (itsMode=="scalaramplitude" || itsMode=="amplitudeonly") { values(freqCell, ts) = abs(itsSols[ts](pol/3,st,freqCell)); + } else if (itsMode=="tec") { + if (realim==0) { + values(freqCell, ts) = itsTECSols[ts](realim,st) / 8.44797245e9; + } else { + values(freqCell, ts) = -itsTECSols[ts](realim,st); // TODO: why is there a minus here? + } } else { THROW (Exception, "Unhandled mode"); } } } - seqnr++; BBS::ParmValue::ShPtr pv(new BBS::ParmValue()); pv->setScalars (solGrid, values); diff --git a/CEP/DP3/DPPP/src/StefCal.cc b/CEP/DP3/DPPP/src/StefCal.cc index 75d205de4d3..75aa83ae695 100644 --- a/CEP/DP3/DPPP/src/StefCal.cc +++ b/CEP/DP3/DPPP/src/StefCal.cc @@ -46,6 +46,7 @@ namespace LOFAR { _nChan (nChan), _mode (mode), _tolerance (tolerance), + _totalWeight (0.), _detectStalling (detectStalling), _debugLevel (debugLevel) { @@ -56,7 +57,7 @@ namespace LOFAR { _nCr=4; _nSp=1; _savedNCr=4; - } else if (_mode=="scalarphase" || _mode=="scalaramplitude") { + } else if (_mode=="scalarphase" || _mode=="tec" || _mode=="scalaramplitude") { _nCr=1; _nSp=2; _savedNCr=1; @@ -69,7 +70,7 @@ namespace LOFAR { _vis.resize(IPosition(6,_nSt,2,_solInt,_nChan,2,_nSt)); _mvis.resize(IPosition(6,_nSt,2,_solInt,_nChan,2,_nSt)); - if (_mode=="fulljones" || _mode=="scalarphase" || _mode=="scalaramplitude") { + if (_mode=="fulljones" || _mode=="scalarphase" || _mode=="tec" || _mode=="scalaramplitude") { _nUn = _nSt; } else { // mode=="phaseonly", mode=="diagonal", mode=="amplitudeonly" _nUn = 2*_nSt; @@ -90,23 +91,24 @@ namespace LOFAR { void StefCal::resetVis() { _vis=0; _mvis=0; + _totalWeight = 0.; } void StefCal::clearStationFlagged() { - fill(_stationFlagged.begin(), _stationFlagged.end(), false); + _stationFlagged=false; } void StefCal::init(bool initSolutions) { - _dg=1.0e29; - _dgx=1.0e30; + _dg = 1.0e29; + _dgx = 1.0e30; _dgs.clear(); - _badIters=0; - _veryBadIters=0; + _badIters = 0; + _veryBadIters = 0; if (initSolutions) { double ginit=1.0; - if (_mode != "phaseonly" && _mode != "scalarphase" ) { + if (_mode != "phaseonly" && _mode != "scalarphase" && _mode != "tec") { // Initialize solution with sensible amplitudes double fronormvis=0; double fronormmod=0; @@ -140,12 +142,18 @@ namespace LOFAR { _g=ginit; } } else { // Take care of NaNs in solution + double ginit=0.; + bool ginitcomputed=false; for (uint ant=0; ant<_nUn; ++ant) { - double ginit=0; if (!isFinite(_g(ant,0).real()) ) { - if (ginit==0 && !_stationFlagged[ant%_nSt]) { + if (!ginitcomputed && !_stationFlagged[ant%_nSt]) { // Avoid calling getAverageUnflaggedSolution for stations that are always flagged ginit = getAverageUnflaggedSolution(); + ginitcomputed = true; + if (ginit==0) { + init(true); + return; + } } if (_nCr==4) { _g(ant,0)=ginit; @@ -162,6 +170,9 @@ namespace LOFAR { double StefCal::getAverageUnflaggedSolution() { // Get average solution of unflagged antennas only once + // Unflagged means unflagged in previous time slot, so + // look at NaNs, don't look at stationFlagged (that's for + // the current timeslot). double total=0.; uint unflaggedstations=0; for (uint ant2=0; ant2<_nUn; ++ant2) { @@ -174,13 +185,28 @@ namespace LOFAR { } } } - return total/unflaggedstations; + if (unflaggedstations==0) { + return 0.; + } else { + return total/unflaggedstations; + } } StefCal::Status StefCal::doStep(uint iter) { _gxx = _gx; _gx = _g; + bool allFlagged=true; + for (uint st1=0;st1<_nSt;++st1) { + if (!_stationFlagged[st1]) { + allFlagged=false; + break; + } + } + if (allFlagged) { + return CONVERGED; + } + if (_mode=="fulljones") { doStep_polarized(); doStep_polarized(); @@ -287,6 +313,7 @@ namespace LOFAR { DComplex* h_p=_h.data(); for (uint st2=0;st2<_nUn;++st2) { *z_p = h_p[st2] * *mvis_p; //itsMVis(IPosition(6,st2%nSt,st2/nSt,time,ch,st1/nSt,st1%nSt)); + ASSERT(isFinite(*z_p)); ww+=norm(*z_p); tt+=conj(*z_p) * *vis_p; //itsVis(IPosition(6,st2%nSt,st2/nSt,time,ch,st1/nSt,st1%nSt)); mvis_p++; @@ -299,42 +326,50 @@ namespace LOFAR { } //cout<<"st1="<<st1%nSt<<(st1>=nSt?"y":"x")<<", t="<<tt<<" "; //cout<<", w="<<ww<<" "; + ASSERT(ww!=0); _g(st1,0)=tt/ww; //cout<<", g="<<iS.g(st1,0)<<endl; - if (_mode=="phaseonly" || _mode=="scalarphase") { + if (_mode=="phaseonly" || _mode=="scalarphase" || _mode=="tec") { + ASSERT(abs(_g(st1,0))!=0); _g(st1,0)/=abs(_g(st1,0)); + ASSERT(isFinite(_g(st1,0))); } else if (_mode=="amplitudeonly" || _mode=="scalaramplitude") { _g(st1,0) = abs(_g(st1,0)); } if (_debugLevel>2) { cout<<endl<<"gi=["; - uint ant=0; - for (; ant<_nUn-1; ++ant) { - cout<<_g(ant,0)<<","; + ASSERT(isFinite(_g(0,0))); + for (uint ant=0; ant<_nUn; ++ant) { + if (ant>0) cout<<","; + cout<<_g(ant,0); } - cout<<_g(ant,0)<<"]"<<endl; + cout<<"]"<<endl; } } } + void StefCal::incrementWeight(float weight) { + _totalWeight += weight; + } + casa::Matrix<casa::DComplex> StefCal::getSolution(bool setNaNs) { if (setNaNs && _debugLevel>0) { cout<<endl<<"dg=["; - uint iter=0; - for (; iter<_dgs.size()-1; ++iter) { - cout<<_dgs[iter]<<","; + for (uint iter=0; iter<_dgs.size(); ++iter) { + if (iter>0) cout<<","; + cout<<_dgs[iter]; } - cout<<_dgs[iter]<<"]"<<endl; + cout<<"]"<<endl; } if (_debugLevel>2) { cout<<endl<<"g=["; - uint ant=0; - for (; ant<_nUn-1; ++ant) { - cout<<_g(ant,0)<<","; + for (uint ant=0; ant<_nUn; ++ant) { + if (ant>0) cout<<","; + cout<<_g(ant,0); } - cout<<_g(ant,0)<<"]"<<endl; + cout<<"]"<<endl; } if (setNaNs) { @@ -366,17 +401,17 @@ namespace LOFAR { double c2 = 1.2; double dgxx; bool threestep = false; - uint maxBadIters=3; + uint maxBadIters=(_mode=="tec"?2:3); int sstep=0; - if (_detectStalling && iter > 3) { + if ((_detectStalling && iter > 3) || (_mode=="tec" && iter>2)) { double improvement = _dgx-_dg; if (abs(improvement) < 5.0e-2*_dg) { // This iteration did not improve much upon the previous // Stalling detection only after 4 iterations, to account for - // ''startup problems'' + // ''startup problems'' (not for tec, where stalling happens very soon) if (_debugLevel>3) { cout<<"**"<<endl; } @@ -384,15 +419,19 @@ namespace LOFAR { } else if (improvement < 0) { _veryBadIters++; } else { - //TODO slingergedrag _badIters=0; } - if (_badIters>=maxBadIters || _veryBadIters > maxBadIters) { + if (_badIters>=maxBadIters) { if (_debugLevel>3) { cout<<"Detected stall"<<endl; } return STALLED; + } else if (_veryBadIters > maxBadIters) { + if (_debugLevel>3) { + cout<<"Detected fail"<<endl; + } + return FAILED; } } -- GitLab From 57534d4afc728d2c5402070627e760082780a508 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Sun, 3 Jul 2016 19:21:26 +0000 Subject: [PATCH 464/933] Task #9537: create baseline selection member (does not do anything yet) --- CEP/DP3/DPPP/include/DPPP/GainCal.h | 2 ++ CEP/DP3/DPPP/src/BaselineSelection.cc | 2 +- CEP/DP3/DPPP/src/GainCal.cc | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CEP/DP3/DPPP/include/DPPP/GainCal.h b/CEP/DP3/DPPP/include/DPPP/GainCal.h index 8e417f003d3..d38e339561e 100644 --- a/CEP/DP3/DPPP/include/DPPP/GainCal.h +++ b/CEP/DP3/DPPP/include/DPPP/GainCal.h @@ -146,6 +146,8 @@ namespace LOFAR { ResultStep* itsResultStep; // For catching results from Predict or Beam bool itsApplyBeamToModelColumn; + BaselineSelection itsBaselineSelection; // Filter + map<string,int> itsParmIdMap; //# -1 = new parm name uint itsMaxIter; diff --git a/CEP/DP3/DPPP/src/BaselineSelection.cc b/CEP/DP3/DPPP/src/BaselineSelection.cc index fd6fcb4d25b..f2bab476f18 100644 --- a/CEP/DP3/DPPP/src/BaselineSelection.cc +++ b/CEP/DP3/DPPP/src/BaselineSelection.cc @@ -42,7 +42,7 @@ namespace LOFAR { BaselineSelection::BaselineSelection (const ParameterSet& parset, const string& prefix, bool minmax, - const string& defaultCorrType, + const string& defaultCorrType, const string& defaultBaseline) : itsStrBL (parset.getString (prefix + "baseline", defaultBaseline)), itsCorrType (parset.getString (prefix + "corrtype", defaultCorrType)), diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index c980b66b4e1..2cf9a2a1046 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -75,6 +75,7 @@ namespace LOFAR { itsDetectStalling (parset.getBool (prefix + "detectstalling", true)), itsApplySolution (parset.getBool (prefix + "applysolution", false)), itsBaselines (), + itsBaselineSelection (parset, prefix), itsMaxIter (parset.getInt (prefix + "maxiter", 50)), itsTolerance (parset.getDouble (prefix + "tolerance", 1.e-5)), itsPropagateSolutions -- GitLab From da106019c78d5f1f475408da723677f7d2455725 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 4 Jul 2016 07:59:01 +0000 Subject: [PATCH 465/933] Task #9607: Fixed typo in lofar-outputproc Dockerfile --- Docker/lofar-outputproc/Dockerfile.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index c25fffe979a..9ab90ed15ee 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -18,7 +18,7 @@ ENV LOFAR_BRANCH=${LOFAR_BRANCH} \ LOFAR_BUILDVARIANT=gnucxx11_optarch # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libopenblas-dev && libcfitsio3-dev wcslib-dev \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libopenblas-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ @@ -32,6 +32,6 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ setcap cap_sys_nice,cap_ipc_lock=ep ${INSTALLDIR}/lofar/bin/outputProc && \ - apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && libcfitsio3-dev wcslib-dev \ + apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ apt-get autoremove -y -- GitLab From e8a4b35f4584f53ae5d1a6266172ff934087fbd7 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 4 Jul 2016 08:43:24 +0000 Subject: [PATCH 466/933] Task #9607: fixed some typo's. made sure we choose sane defaults in case incorrect processing keys are specified. Added check on startup for already scheduled pipelines if they can be handed to slurm --- MAC/Services/src/PipelineControl.py | 55 +++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index f3c535d09b4..3146d9467f8 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -130,13 +130,23 @@ class Parset(dict): return self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterName"] or "CEP2" def processingPartition(self): - return self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterPartition"] or "cpu" + result = self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.clusterPartition"] or "cpu" + if '/' in result: + logger.error('clusterPartition contains invalid value: %s. Defaulting clusterPartition to \'cpu\'', result) + return 'cpu' + return result def processingNumberOfCoresPerTask(self): - return int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfCoresPerTask"]) or "20" + result = int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfCoresPerTask"]) or "20" + if result < 1 or result > 20: + logger.warn('Invalid Observation.Cluster.ProcessingCluster.numberOfCoresPerTask: %s, defaulting to %s', result, max(1, min(20, result))) + return max(1, min(20, result)) def processingNumberOfTasks(self): - return int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfTasks"]) or "24" + result = int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfTasks"]) or "24" + if result < 1 or result > 48: + logger.warn('Invalid Observation.Cluster.ProcessingCluster.numberOfTasks: %s, defaulting to %s', result, max(1, min(48, result))) + return max(1, min(48, result)) @staticmethod def dockerRepository(): @@ -246,7 +256,7 @@ class PipelineDependencies(object): raise TaskNotFoundException("otdb_id %s not found in RADB" % (otdb_id,)) successor_radb_ids = radb_task['successor_ids'] - successor_tasks = self.rarpc.getTasks(task_ids=successor_ids) if successor_radb_ids else [] + successor_tasks = self.rarpc.getTasks(task_ids=successor_radb_ids) if successor_radb_ids else [] successor_otdb_ids = [t["otdb_id"] for t in successor_tasks] logger.debug("getSuccessorIds(%s) = %s", otdb_id, successor_otdb_ids) @@ -294,12 +304,37 @@ class PipelineControl(OTDBBusListener): super(PipelineControl, self).start_listening(**kwargs) + self._checkScheduledPipelines() + def stop_listening(self, **kwargs): super(PipelineControl, self).stop_listening(**kwargs) self.dependencies.close() self.otdbrpc.close() + def _checkScheduledPipelines(self): + try: + scheduled_pipelines = self.dependencies.rarpc.getTasks(task_status='scheduled', task_type='pipeline') + logger.info("Checking %s scheduled pipelines if they can start.", len(scheduled_pipelines)) + + for pipeline in scheduled_pipelines: + logger.info("Checking if scheduled pipeline otdbId=%s can start.", pipeline['otdb_id']) + try: + otdbId = pipeline['otdb_id'] + parset = self._getParset(otdbId) + if not self._shouldHandle(parset): + return + + # Maybe the pipeline can start already + if self.dependencies.canStart(otdbId): + self._startPipeline(otdbId, parset) + else: + logger.info("Job %s was set to scheduled, but cannot start yet.", otdbId) + except Exception as e: + logger.error(e) + except Exception as e: + logger.error(e) + @staticmethod def _shouldHandle(parset): if not parset.isPipeline(): @@ -328,6 +363,8 @@ class PipelineControl(OTDBBusListener): logger.info("Pipeline %s is already queued or running in SLURM.", otdbId) return + logger.info("***** START Otdb ID %s *****", otdbId) + # Determine SLURM parameters sbatch_params = [ # Only run job if all nodes are ready @@ -344,11 +381,11 @@ class PipelineControl(OTDBBusListener): # Lower priority to drop below inspection plots "--nice=1000", - + "--partition=%s" % parset.processingPartition(), "--nodes=%s" % parset.processingNumberOfTasks(), "--cpus-per-task=%s" % parset.processingNumberOfCoresPerTask(), - + # Define better places to write the output os.path.expandvars("--output=/data/log/pipeline-%s-%%j.log" % (otdbId,)), ] @@ -391,7 +428,7 @@ class PipelineControl(OTDBBusListener): " -e SLURM_JOB_ID=$SLURM_JOB_ID" " -v /data:/data" " {image}" - " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -P /data/parsets || exit $?\n" + " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} || exit $?\n" # notify that we're tearing down "{setStatus_completing}\n" @@ -458,7 +495,7 @@ class PipelineControl(OTDBBusListener): def _startSuccessors(self, otdbId): try: successor_ids = self.dependencies.getSuccessorIds(otdbId) - except TaskNotFoundException, e: + except PipelineDependencies.TaskNotFoundException, e: logger.error("_startSuccessors(%s): Error obtaining task successors, not starting them: %s", otdbId, e) return @@ -468,7 +505,6 @@ class PipelineControl(OTDBBusListener): continue if self.dependencies.canStart(s): - logger.info("***** START Otdb ID %s *****", otdbId) self._startPipeline(s, parset) else: logger.info("Job %s still cannot start yet.", otdbId) @@ -480,7 +516,6 @@ class PipelineControl(OTDBBusListener): # Maybe the pipeline can start already if self.dependencies.canStart(otdbId): - logger.info("***** START Otdb ID %s *****", otdbId) self._startPipeline(otdbId, parset) else: logger.info("Job %s was set to scheduled, but cannot start yet.", otdbId) -- GitLab From ecdc74038743b9d7b202c0624bfadedb93653517 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 4 Jul 2016 09:07:57 +0000 Subject: [PATCH 467/933] Task #9192: Use "lfs cp" instead of "cp" on global fs for faster I/O --- CEP/Pipeline/recipes/sip/nodes/copier.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/nodes/copier.py b/CEP/Pipeline/recipes/sip/nodes/copier.py index b972b06c004..b31d2c8167c 100644 --- a/CEP/Pipeline/recipes/sip/nodes/copier.py +++ b/CEP/Pipeline/recipes/sip/nodes/copier.py @@ -52,7 +52,9 @@ class copier(LOFARnodeTCP): # construct copy command: Copy to the dir # if process runs on local host use a simple copy command. - if self.globalfs or source_node=="localhost": + if self.globalfs: + command = ["lfs", "cp", "-r","{0}".format(source_path),"{0}".format(target_path)] + elif source_node=="localhost": command = ["cp", "-r","{0}".format(source_path),"{0}".format(target_path)] else: command = ["rsync", "-r", -- GitLab From 4cb5df6877c718aa948c295c634e772a1d44f740 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 4 Jul 2016 12:55:47 +0000 Subject: [PATCH 468/933] Task #9351 #9353 #9355: minor fixes, and send notifications upon deletion --- .gitattributes | 1 - SAS/DataManagement/CleanupService/config.py | 4 + SAS/DataManagement/CleanupService/service.py | 86 ++++++++++++++----- .../DataManagementCommon/CMakeLists.txt | 1 - .../DataManagementCommon/messagehandler.py | 40 --------- SAS/DataManagement/StorageQueryService/rpc.py | 6 +- .../StorageQueryService/service.py | 2 +- 7 files changed, 74 insertions(+), 66 deletions(-) delete mode 100644 SAS/DataManagement/DataManagementCommon/messagehandler.py diff --git a/.gitattributes b/.gitattributes index a3955009268..832f8a1867c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4745,7 +4745,6 @@ SAS/DataManagement/DataManagementCommon/CMakeLists.txt -text SAS/DataManagement/DataManagementCommon/__init__.py -text SAS/DataManagement/DataManagementCommon/config.py -text SAS/DataManagement/DataManagementCommon/getPathForTask -text -SAS/DataManagement/DataManagementCommon/messagehandler.py -text SAS/DataManagement/DataManagementCommon/path.py -text SAS/DataManagement/StorageQueryService/CMakeLists.txt -text SAS/DataManagement/StorageQueryService/__init__.py -text diff --git a/SAS/DataManagement/CleanupService/config.py b/SAS/DataManagement/CleanupService/config.py index 0e8dafdf6f6..cac900bd86e 100644 --- a/SAS/DataManagement/CleanupService/config.py +++ b/SAS/DataManagement/CleanupService/config.py @@ -5,3 +5,7 @@ from lofar.messaging import adaptNameToEnvironment DEFAULT_BUSNAME = adaptNameToEnvironment('lofar.dm.command') DEFAULT_SERVICENAME = 'CleanupService' + +DEFAULT_NOTIFICATION_BUSNAME = adaptNameToEnvironment('lofar.dm.notification') +DEFAULT_NOTIFICATION_PREFIX = 'DM.' +DEFAULT_NOTIFICATION_SUBJECTS=DEFAULT_NOTIFICATION_PREFIX+'*' diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index 7d07644676c..f65b7034c00 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -8,40 +8,78 @@ import os.path from shutil import rmtree from optparse import OptionParser from lofar.messaging import Service +from lofar.messaging import EventMessage, ToBus +from lofar.messaging.Service import MessageHandlerInterface from lofar.messaging import setQpidLogLevel from lofar.common.util import waitForInterrupt -from lofar.sas.datamanagement.common.messagehandler import MessageHandler from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT +from lofar.sas.datamanagement.common.path import PathResolver from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME +from lofar.sas.datamanagement.cleanup.config import DEFAULT_NOTIFICATION_BUSNAME, DEFAULT_NOTIFICATION_PREFIX from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME logger = logging.getLogger(__name__) -class CleanupHandler(MessageHandler): +class CleanupHandler(MessageHandlerInterface): def __init__(self, mountpoint=CEP4_DATA_MOUNTPOINT, radb_busname=RADB_BUSNAME, radb_servicename=RADB_SERVICENAME, mom_busname=DEFAULT_MOMQUERY_BUSNAME, mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, + notification_busname=DEFAULT_NOTIFICATION_BUSNAME, + notification_prefix=DEFAULT_NOTIFICATION_PREFIX, broker=None, **kwargs): - super(CleanupHandler, self).__init__(mountpoint=mountpoint, - radb_busname=radb_busname, - radb_servicename=radb_servicename, - mom_busname=mom_busname, - mom_servicename=mom_servicename, - broker=broker, - **kwargs) + super(CleanupHandler, self).__init__(**kwargs) + + self.path_resolver = PathResolver(mountpoint=mountpoint, + radb_busname=radb_busname, + radb_servicename=radb_servicename, + mom_busname=mom_busname, + mom_servicename=mom_servicename, + broker=broker) + + self.notification_prefix = notification_prefix + self.event_bus = ToBus(notification_busname, broker=broker) self.service2MethodMap = { 'GetPathForOTDBId': self.path_resolver.getPathForOTDBId, 'RemovePath': self._removePath, 'RemoveTaskData': self._removeTaskData } + def prepare_loop(self): + super(CleanupHandler, self).prepare_loop() + self.path_resolver.open() + self.event_bus.open() + logger.info("cleanup service started with projects_path=%s", self.path_resolver.projects_path) + + def finalize_loop(self): + self.path_resolver.close() + self.event_bus.close() + super(CleanupHandler, self).finalize_loop() + + def _sendPathDeletedNotification(self, path): + try: + msg = EventMessage(context=self.notification_prefix + 'PathDeleted', content={ 'path': path }) + logger.info('Sending notification: %s', msg) + self.event_bus.send(msg) + except Exception as e: + logger.error(str(e)) + + def _sendTaskDeletedNotification(self, otdb_id, paths, message): + try: + msg = EventMessage(context=self.notification_prefix + 'TaskDeleted', content={'otdb_id':otdb_id, + 'paths': paths, + 'message': message}) + logger.info('Sending notification: %s', msg) + self.event_bus.send(msg) + except Exception as e: + logger.error(str(e)) + def _removeTaskData(self, otdb_id, delete_is=True, delete_cs=True, delete_uv=True, delete_im=True, delete_img=True, delete_pulp=True, delete_scratch=True): logger.info("Remove task data for otdb_id %s" % (otdb_id,)) @@ -74,7 +112,10 @@ class CleanupHandler(MessageHandler): rm_results.append(self._removePath(scratch_path)) rm_result = {'deleted': all(x['deleted'] for x in rm_results), - 'message': '\n'.join(x['message'] for x in rm_results)} + 'paths': [x.get('path') for x in rm_results], + 'message': '\n'.join(x.get('message','') for x in rm_results)} + + self._sendTaskDeletedNotification(otdb_id, rm_result['paths'], rm_result['message']) return rm_result @@ -87,17 +128,17 @@ class CleanupHandler(MessageHandler): if not isinstance(path, basestring): message = "Provided path is not a string" logger.error(message) - return {'deleted': False, 'message': message} + return {'deleted': False, 'message': message, 'path': path} if not path: message = "Empty path provided" logger.error(message) - return {'deleted': False, 'message': message} + return {'deleted': False, 'message': message, 'path': path} if '*' in path or '?' in path: message = "Invalid path '%s': No wildcards allowed" % (path,) logger.error(message) - return {'deleted': False, 'message': message} + return {'deleted': False, 'message': message, 'path': path} # remove any trailing slashes if len(path) > 1: @@ -108,18 +149,18 @@ class CleanupHandler(MessageHandler): if not any(path.startswith(base_path) for base_path in required_base_paths): message = "Invalid path '%s': Path does not start with any oth the base paths: '%s'" % (path, ' '.join(required_base_paths)) logger.error(message) - return {'deleted': False, 'message': message} + return {'deleted': False, 'message': message, 'path': path} for base_path in required_base_paths: if path.startswith(base_path) and path[len(base_path):].count('/') == 0: message = "Invalid path '%s': Path should be a subdir of '%s'" % (path, base_path) logger.error(message) - return {'deleted': False, 'message': message} + return {'deleted': False, 'message': message, 'path': path} if not os.path.exists(path): message = "Nothing to delete, path '%s' does not exist." % (path) logger.warn(message) - return {'deleted': True, 'message': message} + return {'deleted': True, 'message': message, 'path': path} logger.info("Attempting to delete %s", path) @@ -128,13 +169,18 @@ class CleanupHandler(MessageHandler): logger.warning("Failed to delete %s", failed_path) failed_paths.add(failed_path) - if rmtree(path, onerror=onerror): + rmtree(path, onerror=onerror) + + if not failed_paths: message = "Deleted '%s'" % (path) logger.info(message) - return {'deleted': True, 'message': message} - - return {'deleted': False, 'message': 'Failed to delete one or more files or directories:\n - %s' % '\n - '.join(failed_paths), 'failed_paths' : list(failed_paths)} + self._sendPathDeletedNotification(path) + return {'deleted': True, 'message': message, 'path': path} + return {'deleted': False, + 'message': 'Failed to delete one or more files or directories:\n - %s' % '\n - '.join(failed_paths), + 'path': path, + 'failed_paths' : list(failed_paths)} def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, mountpoint=CEP4_DATA_MOUNTPOINT, verbose=False, diff --git a/SAS/DataManagement/DataManagementCommon/CMakeLists.txt b/SAS/DataManagement/DataManagementCommon/CMakeLists.txt index 8e7a20d457c..7740f62346c 100644 --- a/SAS/DataManagement/DataManagementCommon/CMakeLists.txt +++ b/SAS/DataManagement/DataManagementCommon/CMakeLists.txt @@ -9,7 +9,6 @@ set(_py_files __init__.py config.py path.py - messagehandler.py ) lofar_add_bin_scripts(getPathForTask) diff --git a/SAS/DataManagement/DataManagementCommon/messagehandler.py b/SAS/DataManagement/DataManagementCommon/messagehandler.py deleted file mode 100644 index 78737e13de0..00000000000 --- a/SAS/DataManagement/DataManagementCommon/messagehandler.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/python - -import logging - -from lofar.messaging.Service import MessageHandlerInterface - -from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT -from lofar.sas.datamanagement.common.path import PathResolver -from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME -from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME -from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME - -logger = logging.getLogger(__name__) - -class MessageHandler(MessageHandlerInterface): - def __init__(self, - mountpoint=CEP4_DATA_MOUNTPOINT, - radb_busname=RADB_BUSNAME, - radb_servicename=RADB_SERVICENAME, - mom_busname=DEFAULT_MOMQUERY_BUSNAME, - mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, - broker=None, - **kwargs): - super(MessageHandler, self).__init__(**kwargs) - - self.path_resolver = PathResolver(mountpoint=mountpoint, - radb_busname=radb_busname, - radb_servicename=radb_servicename, - mom_busname=mom_busname, - mom_servicename=mom_servicename, - broker=broker) - - def prepare_loop(self): - self.path_resolver.open() - logger.info("service started with projects_path=%s", self.path_resolver.projects_path) - - def finalize_loop(self): - self.path_resolver.close() - - diff --git a/SAS/DataManagement/StorageQueryService/rpc.py b/SAS/DataManagement/StorageQueryService/rpc.py index 85560d5f6b1..6aba330c6ba 100644 --- a/SAS/DataManagement/StorageQueryService/rpc.py +++ b/SAS/DataManagement/StorageQueryService/rpc.py @@ -59,7 +59,7 @@ def main(): parser.add_option('-m', '--mom_id', dest='mom_id', type='int', default=None, help='mom_id of task to get the disk usage for') parser.add_option('-r', '--radb_id', dest='radb_id', type='int', default=None, help='radb_id of task to get the disk usage for') parser.add_option('-s', '--subdirs', dest='subdirs', action='store_true', help='get the disk usage of the task and its sub directories for the given otdb_id/mom_id/radb_id') - parser.add_option('-p', '--project', dest='project', action='store_true', help='get the disk usage of the project path and all its sub directories for the given otdb_id/mom_id/radb_id') + parser.add_option('-p', '--project', dest='project', type='string', default=None, help='get the disk usage of the project path and all its sub directories for the given project name') parser.add_option('-P', '--projects', dest='projects', action='store_true', help='get the disk usage of the projects path and all its projects sub directories') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') parser.add_option('--busname', dest='busname', type='string', default=DEFAULT_BUSNAME, help='Name of the bus exchange on the qpid broker, default: %s' % DEFAULT_BUSNAME) @@ -67,7 +67,7 @@ def main(): parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() - if not (options.otdb_id or options.mom_id or options.radb_id or options.projects): + if not (options.otdb_id or options.mom_id or options.radb_id or options.project or options.projects): parser.print_help() exit(1) @@ -84,7 +84,7 @@ def main(): print result['message'] exit(1) elif options.project: - result = rpc.getDiskUsageForProjectDirAndSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + result = rpc.getDiskUsageForProjectDirAndSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id, project_name=options.project) if result['found']: import pprint pprint.pprint(result) diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py index a40f341861e..950a750de1d 100644 --- a/SAS/DataManagement/StorageQueryService/service.py +++ b/SAS/DataManagement/StorageQueryService/service.py @@ -11,7 +11,7 @@ from lofar.messaging.Service import MessageHandlerInterface from lofar.messaging import setQpidLogLevel from lofar.common.util import waitForInterrupt -from lofar.sas.datamanagement.common.messagehandler import MessageHandler +from lofar.messaging.Service import MessageHandlerInterface from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT from lofar.sas.datamanagement.storagequery.cache import CacheManager from lofar.sas.datamanagement.storagequery.diskusage import DiskUsage -- GitLab From 52b13e1ac07509b845133081af873f75374a7f91 Mon Sep 17 00:00:00 2001 From: Jan Rinze Peterzon <peterzon@astron.nl> Date: Mon, 4 Jul 2016 14:45:32 +0000 Subject: [PATCH 469/933] Task #9607: fix ctest on old python versions for Python Messaging --- .../python/messaging/test/t_messagebus.run | 24 ++++++++++++------- .../python/messaging/test/t_messages.run | 8 ++++++- .../test/t_service_message_handler.run | 24 ++++++++++++------- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/LCS/Messaging/python/messaging/test/t_messagebus.run b/LCS/Messaging/python/messaging/test/t_messagebus.run index a64fd56ccd2..28770b2f76b 100755 --- a/LCS/Messaging/python/messaging/test/t_messagebus.run +++ b/LCS/Messaging/python/messaging/test/t_messagebus.run @@ -1,14 +1,20 @@ #!/bin/bash -e +PYTHONVERSION=$(python -V|awk '{print $1}') +if [ $PYTHONVERSION \> "2.6.9" ] ; then -# Cleanup on normal exit and on SIGHUP, SIGINT, SIGQUIT, and SIGTERM -trap 'qpid-config del queue --force $queue' 0 1 2 3 15 + # Cleanup on normal exit and on SIGHUP, SIGINT, SIGQUIT, and SIGTERM + trap 'qpid-config del queue --force $queue' 0 1 2 3 15 -# Generate randome queue name -queue=$(< /dev/urandom tr -dc [:alnum:] | head -c16) + # Generate randome queue name + queue=$(< /dev/urandom tr -dc [:alnum:] | head -c16) -# Create the queue -qpid-config add queue $queue + # Create the queue + qpid-config add queue $queue + + # Run the unit test + source python-coverage.sh + python_coverage_test "Messaging/python" t_messagebus.py $queue +else + echo "Python version to low fo ctests" +fi -# Run the unit test -source python-coverage.sh -python_coverage_test "Messaging/python" t_messagebus.py $queue diff --git a/LCS/Messaging/python/messaging/test/t_messages.run b/LCS/Messaging/python/messaging/test/t_messages.run index 2d82773f1b7..f0c8f144c09 100755 --- a/LCS/Messaging/python/messaging/test/t_messages.run +++ b/LCS/Messaging/python/messaging/test/t_messages.run @@ -2,4 +2,10 @@ # Run the unit test source python-coverage.sh -python_coverage_test "Messaging/python" t_messages.py +PYTHONVERSION=$(python -V|awk '{print $1}') +if [ $PYTHONVERSION \> "2.6.9" ] ; then + python_coverage_test "Messaging/python" t_messages.py +else + echo "python version too low for testing" +fi + diff --git a/LCS/Messaging/python/messaging/test/t_service_message_handler.run b/LCS/Messaging/python/messaging/test/t_service_message_handler.run index da523ee4c86..9bc516a5849 100755 --- a/LCS/Messaging/python/messaging/test/t_service_message_handler.run +++ b/LCS/Messaging/python/messaging/test/t_service_message_handler.run @@ -1,14 +1,20 @@ #!/bin/bash -e +PYTHONVERSION=$(python -V|awk '{print $1}') +if [ $PYTHONVERSION \> "2.6.9" ] ; then -#cleanup on normal exit and on SIGHUP, SIGINT, SIGQUIT, and SIGTERM -trap 'qpid-config del exchange --force $queue' 0 1 2 3 15 + #cleanup on normal exit and on SIGHUP, SIGINT, SIGQUIT, and SIGTERM + trap 'qpid-config del exchange --force $queue' 0 1 2 3 15 -# Generate randome queue name -queue=$(< /dev/urandom tr -dc [:alnum:] | head -c16) + # Generate randome queue name + queue=$(< /dev/urandom tr -dc [:alnum:] | head -c16) -# Create the queue -qpid-config add exchange topic $queue + # Create the queue + qpid-config add exchange topic $queue -# Run the unit test -source python-coverage.sh -python_coverage_test "Messaging/python" t_service_message_handler.py $queue + # Run the unit test + source python-coverage.sh + python_coverage_test "Messaging/python" t_service_message_handler.py $queue + +else + echo "Python version too low" +fi -- GitLab From 04567a73e57eb3e0d5ed8c4231ec47107d8c0ea4 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 4 Jul 2016 15:00:54 +0000 Subject: [PATCH 470/933] Task #9621: Created working branch -- GitLab From 1133c6ce537e29d4f4f4d2ffcc0c0f14451c6826 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 4 Jul 2016 15:08:55 +0000 Subject: [PATCH 471/933] Task #9621: Added allow_move parameter to copier script, and let calibrator pipelines use it --- .../recipes/sip/bin/calibration_pipeline.py | 6 ++++-- .../recipes/sip/bin/msss_calibrator_pipeline.py | 3 ++- CEP/Pipeline/recipes/sip/master/copier.py | 7 ++++++- CEP/Pipeline/recipes/sip/nodes/copier.py | 17 +++++++++-------- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/calibration_pipeline.py b/CEP/Pipeline/recipes/sip/bin/calibration_pipeline.py index b4b3a869030..18edcaec53a 100755 --- a/CEP/Pipeline/recipes/sip/bin/calibration_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/calibration_pipeline.py @@ -250,7 +250,8 @@ class calibration_pipeline(control): mapfile_source=bbs_mapfile, mapfile_target=output_correlated_mapfile, mapfiles_dir=mapfile_dir, - mapfile=output_correlated_mapfile + mapfile=output_correlated_mapfile, + allow_move=True ) with duration(self, "copier"): @@ -258,7 +259,8 @@ class calibration_pipeline(control): mapfile_source=parmdb_mapfile, mapfile_target=output_instrument_mapfile, mapfiles_dir=mapfile_dir, - mapfile=output_instrument_mapfile + mapfile=output_instrument_mapfile, + allow_move=True ) # ********************************************************************* diff --git a/CEP/Pipeline/recipes/sip/bin/msss_calibrator_pipeline.py b/CEP/Pipeline/recipes/sip/bin/msss_calibrator_pipeline.py index 0c627b6cb56..eb641e574a1 100755 --- a/CEP/Pipeline/recipes/sip/bin/msss_calibrator_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/msss_calibrator_pipeline.py @@ -273,7 +273,8 @@ class msss_calibrator_pipeline(control): mapfile_source=bbs_mapfile, mapfile_target=output_correlated_mapfile, mapfiles_dir=mapfile_dir, - mapfile=output_correlated_mapfile + mapfile=output_correlated_mapfile, + allow_move=True ) # ********************************************************************* diff --git a/CEP/Pipeline/recipes/sip/master/copier.py b/CEP/Pipeline/recipes/sip/master/copier.py index c15dba135c9..c9295910b2f 100644 --- a/CEP/Pipeline/recipes/sip/master/copier.py +++ b/CEP/Pipeline/recipes/sip/master/copier.py @@ -142,6 +142,11 @@ class copier(MasterNodeInterface): default=True, help="Allow renaming of basename at target location" ), + 'allow_move': ingredient.BoolField( + '--allow-move', + default=True, + help="Allow moving files instead of copying them" + ), 'mapfiles_dir': ingredient.StringField( '--mapfiles-dir', help="Path of directory, shared by all nodes, which will be used" @@ -248,7 +253,7 @@ class copier(MasterNodeInterface): # Run the compute nodes with the node specific mapfiles for source, target in zip(self.source_map, self.target_map): - args = [source.host, source.file, target.file, globalfs] + args = [source.host, source.file, target.file, globalfs, self.inputs['allow_move']] self.append_job(target.host, args) # start the jobs, return the exit status. diff --git a/CEP/Pipeline/recipes/sip/nodes/copier.py b/CEP/Pipeline/recipes/sip/nodes/copier.py index b31d2c8167c..8a30b7a5178 100644 --- a/CEP/Pipeline/recipes/sip/nodes/copier.py +++ b/CEP/Pipeline/recipes/sip/nodes/copier.py @@ -21,15 +21,15 @@ class copier(LOFARnodeTCP): """ Node script for copying files between nodes. See master script for full public interface """ - def run(self, source_node, source_path, target_path, globalfs): + def run(self, source_node, source_path, target_path, globalfs, allow_move): self.globalfs = globalfs # Time execution of this job with log_time(self.logger): return self._copy_single_file_using_rsync( - source_node, source_path, target_path) + source_node, source_path, target_path, allow_move) def _copy_single_file_using_rsync(self, source_node, source_path, - target_path): + target_path, allow_move): # assure that target dir exists (rsync creates it but.. # an error in the python code will throw a nicer error message = "No write acces to target path: {0}".format( @@ -51,11 +51,12 @@ class copier(LOFARnodeTCP): # construct copy command: Copy to the dir - # if process runs on local host use a simple copy command. - if self.globalfs: - command = ["lfs", "cp", "-r","{0}".format(source_path),"{0}".format(target_path)] - elif source_node=="localhost": - command = ["cp", "-r","{0}".format(source_path),"{0}".format(target_path)] + # if process runs on local host use a simple copy or move command. + if self.globalfs or source_node == "localhost": + if allow_move: + command = ["mv", "{0}".format(source_path), "{0}".format(target_path)] + else: + command = ["cp", "-r", "{0}".format(source_path), "{0}".format(target_path)] else: command = ["rsync", "-r", "{0}:{1}/".format(source_node, source_path), -- GitLab From e7a11ec3a44b4d2808f180db824ca03d2cae6c3f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 5 Jul 2016 06:40:47 +0000 Subject: [PATCH 472/933] Task #9607: update observation stoptime on abort --- .../OTDBtoRATaskStatusPropagator/propagator.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index 711f4721680..f2c904549c4 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -137,9 +137,12 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): def onObservationCompleting(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'completing') - def _updateStopTime(self, treeId): + def _updateStopTime(self, treeId, only_pipelines=False): radb_task = self.radb.getTask(otdb_id=treeId) - if radb_task and radb_task['type'] == 'pipeline': + if radb_task: + if only_pipelines and radb_task['type'] != 'pipeline': + return + otdb_task = self.otdb.taskGetTreeInfo(otdb_id=treeId) if otdb_task and (otdb_task['starttime'] != radb_task['starttime'] or otdb_task['stoptime'] != radb_task['endtime']): new_endtime = otdb_task['stoptime'] @@ -152,14 +155,13 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): # otdb adjusts stoptime when finishing, # reflect that in radb for pipelines - self._updateStopTime(treeId) + self._updateStopTime(treeId, only_pipelines=True) def onObservationAborted(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'aborted') # otdb adjusts stoptime when aborted, - # reflect that in radb for pipelines - self._updateStopTime(treeId) + self._updateStopTime(treeId, only_pipelines=False) def main(): # Check the invocation arguments -- GitLab From b5c1aa79cf08c695a9fd4fee6173afdc95309f11 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 5 Jul 2016 07:21:42 +0000 Subject: [PATCH 473/933] Task #9607: minor bug fix, TaskNotFoundException unknown. logging. --- MAC/Services/src/PipelineControl.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 3146d9467f8..987f5614dce 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -236,7 +236,7 @@ class PipelineDependencies(object): radb_task = self.rarpc.getTask(otdb_id=otdb_id) if radb_task is None: - raise TaskNotFoundException("otdb_id %s not found in RADB" % (otdb_id,)) + raise PipelineDependencies.TaskNotFoundException("otdb_id %s not found in RADB" % (otdb_id,)) predecessor_radb_ids = radb_task['predecessor_ids'] predecessor_tasks = self.rarpc.getTasks(task_ids=predecessor_radb_ids) @@ -253,7 +253,7 @@ class PipelineDependencies(object): radb_task = self.rarpc.getTask(otdb_id=otdb_id) if radb_task is None: - raise TaskNotFoundException("otdb_id %s not found in RADB" % (otdb_id,)) + raise PipelineDependencies.TaskNotFoundException("otdb_id %s not found in RADB" % (otdb_id,)) successor_radb_ids = radb_task['successor_ids'] successor_tasks = self.rarpc.getTasks(task_ids=successor_radb_ids) if successor_radb_ids else [] @@ -272,11 +272,11 @@ class PipelineDependencies(object): try: myState = self.getState(otdbId) predecessorStates = self.getPredecessorStates(otdbId) - except TaskNotFoundException, e: + except PipelineDependencies.TaskNotFoundException, e: logger.error("canStart(%s): Error obtaining task states, not starting pipeline: %s", otdbId, e) return False - logger.debug("canStart(%s)? state = %s, predecessors = %s", otdbId, myState, predecessorStates) + logger.info("canStart(%s)? state = %s, predecessors = %s", otdbId, myState, predecessorStates) return ( myState == "scheduled" and @@ -481,7 +481,7 @@ class PipelineControl(OTDBBusListener): # to be cancelled as well. if not self.slurm.isQueuedOrRunning(otdbId): - logger.info("_stopPipeline: Job %s not running") + logger.info("_stopPipeline: Job %s not running", otdbId) return def cancel(jobName): -- GitLab From 19a93c21b60a2ffbda096defb40d6f637826278f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 5 Jul 2016 11:15:10 +0000 Subject: [PATCH 474/933] Task #9351 #9353 #9355: ssh args --- SAS/DataManagement/StorageQueryService/diskusage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/DataManagement/StorageQueryService/diskusage.py b/SAS/DataManagement/StorageQueryService/diskusage.py index febd4c36351..e115e96b7eb 100644 --- a/SAS/DataManagement/StorageQueryService/diskusage.py +++ b/SAS/DataManagement/StorageQueryService/diskusage.py @@ -21,7 +21,7 @@ def getDiskUsageForPath(path): cmd = ['rbh-du', '-bd', path] hostname = socket.gethostname() if not 'mgmt0' in hostname: - cmd = ['ssh', '-AXt', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd + cmd = ['ssh', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd logger.info(' '.join(cmd)) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) -- GitLab From 358a7a2b1cd1b905f4a5f05ea6330503ebce4db4 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 5 Jul 2016 11:15:45 +0000 Subject: [PATCH 475/933] Task #9351 #9353 #9355: ssh args, and added method pathExists --- SAS/DataManagement/DataManagementCommon/path.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index 363efb70588..ecace76aa33 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -153,7 +153,7 @@ class PathResolver: cmd = ['lfs', 'ls', '-l', path] hostname = socket.gethostname() if not 'mgmt0' in hostname: - cmd = ['ssh', '-AXt', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd + cmd = ['ssh', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd logger.info(' '.join(cmd)) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() @@ -174,6 +174,20 @@ class PathResolver: logger.info('result: %s' % result) return result + def pathExists(self, path): + cmd = ['lfs', 'ls', path] + hostname = socket.gethostname() + if not 'mgmt0' in hostname: + cmd = ['ssh', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd + logger.debug(' '.join(cmd)) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + + if proc.returncode != 0 and 'No such file or directory' in err: + return False + + return True + def main(): import sys from optparse import OptionParser -- GitLab From c0abfa3d00c1685752530957ee6980b983f53597 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 5 Jul 2016 11:17:20 +0000 Subject: [PATCH 476/933] Task #9351 #9353 #9355: added notifications on deletion of path/task. Handle notifications with cleanupbuslistener. Various fixes and improvements in cache --- .gitattributes | 1 + .../CleanupService/CMakeLists.txt | 1 + .../CleanupService/cleanupbuslistener.py | 67 ++++++++ SAS/DataManagement/CleanupService/config.py | 6 +- SAS/DataManagement/CleanupService/service.py | 12 +- .../StorageQueryService/cache.py | 149 +++++++++++++++--- 6 files changed, 205 insertions(+), 31 deletions(-) create mode 100644 SAS/DataManagement/CleanupService/cleanupbuslistener.py diff --git a/.gitattributes b/.gitattributes index 832f8a1867c..05318bb2c1a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4732,6 +4732,7 @@ SAS/DataManagement/CMakeLists.txt -text SAS/DataManagement/CleanupService/CMakeLists.txt -text SAS/DataManagement/CleanupService/__init__.py -text SAS/DataManagement/CleanupService/cleanup -text +SAS/DataManagement/CleanupService/cleanupbuslistener.py -text SAS/DataManagement/CleanupService/cleanupservice -text SAS/DataManagement/CleanupService/cleanupservice.ini -text SAS/DataManagement/CleanupService/config.py -text diff --git a/SAS/DataManagement/CleanupService/CMakeLists.txt b/SAS/DataManagement/CleanupService/CMakeLists.txt index 9d37337b1ad..95ba5bfab4e 100644 --- a/SAS/DataManagement/CleanupService/CMakeLists.txt +++ b/SAS/DataManagement/CleanupService/CMakeLists.txt @@ -10,6 +10,7 @@ set(_py_files config.py rpc.py service.py + cleanupbuslistener.py ) python_install(${_py_files} DESTINATION lofar/sas/datamanagement/cleanup) diff --git a/SAS/DataManagement/CleanupService/cleanupbuslistener.py b/SAS/DataManagement/CleanupService/cleanupbuslistener.py new file mode 100644 index 00000000000..bc7882e9081 --- /dev/null +++ b/SAS/DataManagement/CleanupService/cleanupbuslistener.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +# CleanupBusListener.py +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# + +from lofar.messaging.messagebus import AbstractBusListener +from lofar.sas.datamanagement.cleanup.config import DEFAULT_DM_NOTIFICATION_BUSNAME, DEFAULT_DM_NOTIFICATION_SUBJECTS +from lofar.common.util import waitForInterrupt + +import qpid.messaging +import logging + +logger = logging.getLogger(__name__) + +class CleanupBusListener(AbstractBusListener): + def __init__(self, busname=DEFAULT_DM_NOTIFICATION_BUSNAME, subjects=DEFAULT_DM_NOTIFICATION_SUBJECTS, broker=None, **kwargs): + self.subject_prefix = (subjects.split('.')[0]+'.') if '.' in subjects else '' + + address = "%s/%s" % (busname, subjects) + super(CleanupBusListener, self).__init__(address, broker, **kwargs) + + def _handleMessage(self, msg): + logger.info("on%s: %s" % (msg.subject.replace(self.subject_prefix, ''), str(msg.content).replace('\n', ' '))) + + if msg.subject == '%sTaskDeleted' % self.subject_prefix: + self.onTaskDeleted(msg.content.get('otdb_id'), msg.content.get('paths')) + elif msg.subject == '%sPathDeleted' % self.subject_prefix: + self.onPathDeleted(msg.content.get('path')) + else: + logger.error("CleanupBusListener.handleMessage: unknown subject: %s" %str(msg.subject)) + + def onTaskDeleted(self, otdb_id, paths): + '''onTaskDeleted is called upon receiving a TaskDeleted message. + :param otdb_id: otdb_id of the deleted task + :param paths: list of paths of the deleted task''' + pass + + def onPathDeleted(self, path): + '''onPathDeleted is called upon receiving a PathDeleted message. + :param path: path of the deleted task''' + pass + + + +if __name__ == '__main__': + with CleanupBusListener(broker=None) as listener: + waitForInterrupt() + +__all__ = ["CleanupBusListener"] diff --git a/SAS/DataManagement/CleanupService/config.py b/SAS/DataManagement/CleanupService/config.py index cac900bd86e..30b010909a9 100644 --- a/SAS/DataManagement/CleanupService/config.py +++ b/SAS/DataManagement/CleanupService/config.py @@ -6,6 +6,6 @@ from lofar.messaging import adaptNameToEnvironment DEFAULT_BUSNAME = adaptNameToEnvironment('lofar.dm.command') DEFAULT_SERVICENAME = 'CleanupService' -DEFAULT_NOTIFICATION_BUSNAME = adaptNameToEnvironment('lofar.dm.notification') -DEFAULT_NOTIFICATION_PREFIX = 'DM.' -DEFAULT_NOTIFICATION_SUBJECTS=DEFAULT_NOTIFICATION_PREFIX+'*' +DEFAULT_DM_NOTIFICATION_BUSNAME = adaptNameToEnvironment('lofar.dm.notification') +DEFAULT_DM_NOTIFICATION_PREFIX = 'DM.' +DEFAULT_DM_NOTIFICATION_SUBJECTS=DEFAULT_DM_NOTIFICATION_PREFIX+'*' diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index f65b7034c00..7e4bba458ae 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -16,7 +16,7 @@ from lofar.common.util import waitForInterrupt from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT from lofar.sas.datamanagement.common.path import PathResolver from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME -from lofar.sas.datamanagement.cleanup.config import DEFAULT_NOTIFICATION_BUSNAME, DEFAULT_NOTIFICATION_PREFIX +from lofar.sas.datamanagement.cleanup.config import DEFAULT_DM_NOTIFICATION_BUSNAME, DEFAULT_DM_NOTIFICATION_PREFIX from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME @@ -30,8 +30,8 @@ class CleanupHandler(MessageHandlerInterface): radb_servicename=RADB_SERVICENAME, mom_busname=DEFAULT_MOMQUERY_BUSNAME, mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, - notification_busname=DEFAULT_NOTIFICATION_BUSNAME, - notification_prefix=DEFAULT_NOTIFICATION_PREFIX, + notification_busname=DEFAULT_DM_NOTIFICATION_BUSNAME, + notification_prefix=DEFAULT_DM_NOTIFICATION_PREFIX, broker=None, **kwargs): @@ -65,7 +65,7 @@ class CleanupHandler(MessageHandlerInterface): def _sendPathDeletedNotification(self, path): try: msg = EventMessage(context=self.notification_prefix + 'PathDeleted', content={ 'path': path }) - logger.info('Sending notification: %s', msg) + logger.info('Sending notification: %s', str(msg).replace('\n', ' ')) self.event_bus.send(msg) except Exception as e: logger.error(str(e)) @@ -75,7 +75,7 @@ class CleanupHandler(MessageHandlerInterface): msg = EventMessage(context=self.notification_prefix + 'TaskDeleted', content={'otdb_id':otdb_id, 'paths': paths, 'message': message}) - logger.info('Sending notification: %s', msg) + logger.info('Sending notification: %s', str(msg).replace('\n', ' ')) self.event_bus.send(msg) except Exception as e: logger.error(str(e)) @@ -157,7 +157,7 @@ class CleanupHandler(MessageHandlerInterface): logger.error(message) return {'deleted': False, 'message': message, 'path': path} - if not os.path.exists(path): + if not self.path_resolver.pathExists(path): message = "Nothing to delete, path '%s' does not exist." % (path) logger.warn(message) return {'deleted': True, 'message': message, 'path': path} diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index 878361492c9..dcab250bd68 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -12,19 +12,24 @@ from optparse import OptionParser from threading import Thread, RLock import os.path +from lofar.common.util import humanreadablesize from lofar.sas.datamanagement.storagequery.diskusage import getDiskUsageForPath as du_getDiskUsageForPath from lofar.sas.datamanagement.storagequery.diskusage import DiskUsage +from lofar.sas.datamanagement.cleanup.cleanupbuslistener import CleanupBusListener from lofar.sas.otdb.OTDBBusListener import OTDBBusListener from lofar.common.util import waitForInterrupt from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTDB_NOTIFICATION_SUBJECT +from lofar.sas.datamanagement.cleanup.config import DEFAULT_DM_NOTIFICATION_BUSNAME, DEFAULT_DM_NOTIFICATION_SUBJECTS from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME logger = logging.getLogger(__name__) -class CacheManager(OTDBBusListener): +MAX_CACHE_ENTRY_AGE = datetime.timedelta(minutes=60) + +class CacheManager: def __init__(self, mountpoint=CEP4_DATA_MOUNTPOINT, radb_busname=RADB_BUSNAME, @@ -33,13 +38,25 @@ class CacheManager(OTDBBusListener): mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, otdb_notification_busname=DEFAULT_OTDB_NOTIFICATION_BUSNAME, otdb_notification_subject=DEFAULT_OTDB_NOTIFICATION_SUBJECT, + dm_notification_busname=DEFAULT_DM_NOTIFICATION_BUSNAME, + dm_notification_subjects=DEFAULT_DM_NOTIFICATION_SUBJECTS, broker=None): - super(CacheManager, self).__init__(busname=otdb_notification_busname, - subject=otdb_notification_subject, - broker=broker) + self.otdb_listener = OTDBBusListener(busname=otdb_notification_busname, + subject=otdb_notification_subject, + broker=broker) + + self.otdb_listener.onObservationAborted = self.onObservationAborted + self.otdb_listener.onObservationFinished = self.onObservationFinished + + self.cleanup_listener = CleanupBusListener(busname=dm_notification_busname, + subjects=dm_notification_subjects, + broker=broker) + + self.cleanup_listener.onTaskDeleted = self.onTaskDeleted self._updateCacheThread = None self._updateCacheThreadRunning = False + self._continueUpdateCacheThread = False self._cacheLock = RLock() self._cache = {'paths':{}, 'otdb_ids': {}} @@ -97,21 +114,78 @@ class CacheManager(OTDBBusListener): logger.info('removing otdb_id %s from cache', otdb_id) del otdb_cache[otdb_id] - def _updateOldEntriesInCache(self): - while self._updateCacheThreadRunning: - now = datetime.datetime.utcnow() - with self._cacheLock: - old_entries = {path:du_result for path,du_result in self._cache['paths'].items() if now - du_result['cache_timestamp'] > datetime.timedelta(minutes=15)} + if du_result.get('path') == self.disk_usage.path_resolver.projects_path: + self._updateProjectsDiskUsageInRADB() - for path, du_result in old_entries.items(): - logger.info('updating old entry in cache: %s', path) - result = du_getDiskUsageForPath(path) - self._updateCache(result) + def _invalidateCacheEntryForPath(self, path): + with self._cacheLock: + if path in self._cache['paths']: + path_cache = self._cache['paths'][path] + path_cache['cache_timestamp'] = path_cache['cache_timestamp'] - MAX_CACHE_ENTRY_AGE - for i in range(60): - sleep(1) - if self._updateCacheThreadRunning: - return + def _updateOldEntriesInCache(self): + while self._updateCacheThreadRunning: + try: + now = datetime.datetime.utcnow() + with self._cacheLock: + old_entries = {path:du_result for path,du_result in self._cache['paths'].items() if now - du_result['cache_timestamp'] > MAX_CACHE_ENTRY_AGE} + + if old_entries: + logger.info('updating %s old cache entries', len(old_entries)) + + # sort them oldest to newest + old_entries = sorted(old_entries.items(), key=lambda x: x[1]['cache_timestamp']) + + cacheUpdateStart = datetime.datetime.utcnow() + + for path, du_result in old_entries: + try: + logger.info('updating old entry in cache: %s', path) + result = du_getDiskUsageForPath(path) + self._updateCache(result) + except Exception as e: + logger.error(str(e)) + + if not self._updateCacheThreadRunning: + return + + if datetime.datetime.utcnow() - cacheUpdateStart > datetime.timedelta(minutes=5): + # break out of cache update loop if full update takes more than 5min + # next loop we'll start with the oldest cache entries again + break + + for i in range(60): + sleep(1) + if not self._updateCacheThreadRunning: + return + if self._continueUpdateCacheThread: + # break out of sleep loop and continue updating old cache entries + self._continueUpdateCacheThread = False + break + except Exception as e: + logger.error(str(e)) + + def _updateProjectsDiskUsageInRADB(self): + try: + projects_du_result = self.getDiskUsageForPath(self.disk_usage.path_resolver.projects_path) + if projects_du_result['found']: + #get the total used space, and update the resource availability in the radb + radbrpc = self.disk_usage.path_resolver.radbrpc + storage_resources = radbrpc.getResources(resource_types='storage', include_availability=True) + cep4_storage_resource = next(x for x in storage_resources if 'cep4' in x['name']) + + total_capacity = cep4_storage_resource['total_capacity'] + used_capacity = projects_du_result['disk_usage'] + available_capacity = total_capacity - used_capacity + + logger.info('updating availability capacity for %s (id=%s) to %s in the RADB', + cep4_storage_resource['name'], + cep4_storage_resource['id'], + humanreadablesize(available_capacity)) + + radbrpc.updateResourceAvailability(cep4_storage_resource['id'], available_capacity=available_capacity) + except Exception as e: + logger.error(e) def open(self): self.disk_usage.open() @@ -121,14 +195,16 @@ class CacheManager(OTDBBusListener): self._updateCacheThreadRunning = True self._updateCacheThread.start() - super(CacheManager, self).start_listening() + self.otdb_listener.start_listening() + self.cleanup_listener.start_listening() def close(self): + self.otdb_listener.stop_listening() + self.cleanup_listener.stop_listening() self._updateCacheThreadRunning = False self._updateCacheThread.join() self.disk_usage.close() - super(CacheManager, self).stop_listening() def __enter__(self): self.open() @@ -138,13 +214,39 @@ class CacheManager(OTDBBusListener): self.close() def onObservationFinished(self, otdb_id, modificationTime): - result = self.disk_usage.getDiskUsageForOTDBId(otdb_id) - self._updateCache(result) + self._onDiskActivityForOTDBId(otdb_id) def onObservationAborted(self, otdb_id, modificationTime): + self._onDiskActivityForOTDBId(otdb_id) + + def onTaskDeleted(self, otdb_id, paths): + self._onDiskActivityForOTDBId(otdb_id) + + def _onDiskActivityForOTDBId(self, otdb_id): result = self.disk_usage.getDiskUsageForOTDBId(otdb_id) self._updateCache(result) + task_path = result.get('path') + projects_path = self.disk_usage.path_resolver.projects_path + + # update all paths up the tree up to the projects_path + # update the resource availability in the radb as well + path = task_path + while path: + parent_path = '/'.join(path.split('/')[:-1]) + + if projects_path.startswith(parent_path) and len(parent_path) < len(projects_path): + break + + logger.info('invalidating cache entry for %s because disk usage for task %s in %s changed', parent_path, otdb_id, task_path) + + self._invalidateCacheEntryForPath(parent_path) + + path = parent_path + + # trigger update cache thread + self._continueUpdateCacheThread = True + def getDiskUsageForOTDBId(self, otdb_id): return self.getDiskUsageForTask(otdb_id=otdb_id) @@ -166,9 +268,12 @@ class CacheManager(OTDBBusListener): logger.info("cache.getDiskUsageForPath(%s)", path) needs_cache_update = False with self._cacheLock: - needs_cache_update = path not in self._cache['paths'] + needs_cache_update |= path not in self._cache['paths'] + if path in self._cache['paths']: + needs_cache_update |= datetime.datetime.utcnow() - self._cache['paths'][path]['cache_timestamp'] > MAX_CACHE_ENTRY_AGE if needs_cache_update: + logger.info("cache update needed for %s", path) result = du_getDiskUsageForPath(path) self._updateCache(result) -- GitLab From 982bd636622f0b407f4fd1e86a3ce1f75b53afa2 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 5 Jul 2016 12:17:26 +0000 Subject: [PATCH 477/933] Task #9607: Fixed PythonControl to forward LOFARENV to pipelines --- MAC/APL/CEPCU/src/PythonControl/PythonControl.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAC/APL/CEPCU/src/PythonControl/PythonControl.cc b/MAC/APL/CEPCU/src/PythonControl/PythonControl.cc index 0fce53909dc..99125ff8251 100644 --- a/MAC/APL/CEPCU/src/PythonControl/PythonControl.cc +++ b/MAC/APL/CEPCU/src/PythonControl/PythonControl.cc @@ -212,13 +212,13 @@ bool PythonControl::_startPython(const string& pythonProg, itsPythonName = formatString("PythonServer{%d}@%s", obsID, pythonHost.c_str()); if (onRemoteMachine) { - startCmd = formatString("ssh %s %s %s %s %s %s %s", + startCmd = formatString("ssh %s LOFARENV=$LOFARENV %s %s %s %s %s %s", pythonHost.c_str(), startScript.c_str(), executable.c_str(), parSetName.c_str(), myHostname(true).c_str(), parentService.c_str(), itsPythonName.c_str()); } else { - startCmd = formatString("%s %s %s %s %s %s", + startCmd = formatString("env LOFARENV=$LOFARENV %s %s %s %s %s %s", startScript.c_str(), executable.c_str(), parSetName.c_str(), myHostname(true).c_str(), parentService.c_str(), itsPythonName.c_str()); -- GitLab From aa81875e4865060e91c9647d879d2ada73b140c6 Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Tue, 5 Jul 2016 12:51:03 +0000 Subject: [PATCH 478/933] TAsk #6516: Adding variants file for new centos7 build host --- .gitattributes | 2 ++ CMake/variants/variants.buildhostcentos7 | 1 + CMake/variants/variants.lcs157 | 15 +++++++++++++++ 3 files changed, 18 insertions(+) create mode 120000 CMake/variants/variants.buildhostcentos7 create mode 100644 CMake/variants/variants.lcs157 diff --git a/.gitattributes b/.gitattributes index afffabe1f55..e5fe3c6e44a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2326,6 +2326,7 @@ CMake/testscripts/default.debug -text CMake/testscripts/timeout -text CMake/variants/variants.MacRenting -text CMake/variants/variants.b7015 -text +CMake/variants/variants.buildhostcentos7 -text CMake/variants/variants.cbt001 -text CMake/variants/variants.cbt002 -text CMake/variants/variants.cbt003 -text @@ -2341,6 +2342,7 @@ CMake/variants/variants.dragproc -text CMake/variants/variants.fs0 -text CMake/variants/variants.gpu01 -text CMake/variants/variants.gpu1 -text +CMake/variants/variants.lcs157 -text CMake/variants/variants.lexar -text CMake/variants/variants.lexar001 -text CMake/variants/variants.lexar002 -text diff --git a/CMake/variants/variants.buildhostcentos7 b/CMake/variants/variants.buildhostcentos7 new file mode 120000 index 00000000000..0c9019c0432 --- /dev/null +++ b/CMake/variants/variants.buildhostcentos7 @@ -0,0 +1 @@ +variants.lcs157 \ No newline at end of file diff --git a/CMake/variants/variants.lcs157 b/CMake/variants/variants.lcs157 new file mode 100644 index 00000000000..026dfdfff6f --- /dev/null +++ b/CMake/variants/variants.lcs157 @@ -0,0 +1,15 @@ +# Station software and the like apparently cannot (yet) handle shared libs. +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) + +#set(ENV{JAVA_HOME} /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0) +#set(PVSS_ROOT_DIR /opt/pvss/pvss2_v3.7) +set(PVSS_ROOT_DIR /opt/WinCC_OA/3.14) +#set(LOG4CPLUS_ROOT_DIR "/usr/local/log4cplus") +set(CASACORE_ROOT_DIR "/opt/casacore") +set(QPID_ROOT_DIR /opt/qpid) + +#set(CTEST_CUSTOM_WARNING_EXCEPTION +# "/boost/date_time/time_facet.hpp:[0-9]+: warning: unused parameter" +# "/boost/date_time/time.hpp:[0-9]+: warning: unused parameter" +# "/pvss2_v3.7/api/include/(Basics|Datapoint|Manager|Messages)/" +#) -- GitLab From cedbdf6d04c7ab4c1064ffbdb119d27ac71145f1 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Tue, 5 Jul 2016 14:57:15 +0000 Subject: [PATCH 479/933] Task #9614: fix end time in GainCal again, fix broken trunk --- CEP/DP3/DPPP/src/GainCal.cc | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index 6189bbe8f3d..38ad50438a5 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -206,6 +206,7 @@ namespace LOFAR { os << " mode: " << itsMode << endl; os << " apply solution: " << boolalpha << itsApplySolution << endl; os << " propagate solutions: " << boolalpha << itsPropagateSolutions << endl; + os << " timeslotsperparmupdate: " << itsTimeSlotsPerParmUpdate << endl; os << " detect stalling: " << boolalpha << itsDetectStalling << endl; os << " use model column: " << boolalpha << itsUseModelColumn << endl; if (!itsUseModelColumn) { @@ -666,24 +667,33 @@ namespace LOFAR { if (getInfo().chanFreqs().size()>1) { // Handle data with evenly spaced gaps between channels freqWidth = info().chanFreqs()[1]-info().chanFreqs()[0]; } + + // Get end time of the current chunk. For the last chunk, this + // is chopped off at the end of the MS (only if solint > 1) + double endTime = min(startTime + ntime * info().timeInterval() * itsSolInt, + info().startTime() + info().ntime() * info().timeInterval()); + + // Make time axis (can be non regular for last chunk if solint > 1) + vector<double> lowtimes(ntime), hightimes(ntime); + for (uint t=0; t<ntime; ++t) { + lowtimes[t] = startTime + info().timeInterval() * itsSolInt * t; + hightimes[t] = min(startTime + info().timeInterval() * itsSolInt * (t+1), + endTime); + } + BBS::Axis::ShPtr timeAxis = Axis::makeAxis(lowtimes, hightimes); + BBS::Axis::ShPtr freqAxis( new BBS::RegularAxis( getInfo().chanFreqs()[0] - freqWidth * 0.5, freqWidth*itsNChan, itsNFreqCells)); - BBS::Axis::ShPtr timeAxis( - new BBS::RegularAxis( - startTime, - info().timeInterval() * itsSolInt, - ntime)); BBS::Grid solGrid(freqAxis, timeAxis); // Construct domain grid for the current chunk BBS::Axis::ShPtr tdomAxis( new BBS::RegularAxis( startTime, - min(ntime * info().timeInterval() * itsSolInt, - info().ntime() * info().timeInterval()), + endTime - startTime, 1)); BBS::Axis::ShPtr fdomAxis( new BBS::RegularAxis( -- GitLab From ac4c18564a3f3c4a76a12fd4df35a0eb8c964c8b Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Jul 2016 10:04:44 +0000 Subject: [PATCH 480/933] Task #9192: Status PREPARED is obsolete --- MAC/Services/src/PipelineControl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 987f5614dce..147fa1c8623 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -539,7 +539,6 @@ class PipelineControl(OTDBBusListener): More statusses we want to abort on. """ onObservationDescribed = onObservationAborted - onObservationPrepared = onObservationAborted onObservationApproved = onObservationAborted onObservationPrescheduled = onObservationAborted onObservationConflict = onObservationAborted -- GitLab From 7636f3f3a68638748b9ddea1e70658766da26749 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Jul 2016 10:17:15 +0000 Subject: [PATCH 481/933] Task #9192: Saner error messages if parsets are incomplete, or got deleted from OTDB --- MAC/Services/src/PipelineControl.py | 36 +++++++++++++++++---------- MAC/Services/test/tPipelineControl.py | 4 +++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 147fa1c8623..0138bab7985 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -66,7 +66,7 @@ from lofar.sas.otdb.OTDBBusListener import OTDBBusListener from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTDB_SERVICE_BUSNAME from lofar.sas.otdb.otdbrpc import OTDBRPC from lofar.common.util import waitForInterrupt -from lofar.messaging.RPC import RPCTimeoutException +from lofar.messaging.RPC import RPCTimeoutException, RPCException from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as DEFAULT_RAS_SERVICE_BUSNAME @@ -296,7 +296,12 @@ class PipelineControl(OTDBBusListener): self.otdbrpc.taskSetStatus(otdb_id=otdb_id, new_status=status) def _getParset(self, otdbId): - return Parset(self.otdbrpc.taskGetSpecification(otdb_id=otdbId)["specification"]) + try: + return Parset(self.otdbrpc.taskGetSpecification(otdb_id=otdbId)["specification"]) + except RPCException, e: + # Parset not in OTDB, probably got deleted + logger.error("Cannot retrieve parset of task %s: %s", otdbId, e) + return None def start_listening(self, **kwargs): self.otdbrpc.open() @@ -322,8 +327,8 @@ class PipelineControl(OTDBBusListener): try: otdbId = pipeline['otdb_id'] parset = self._getParset(otdbId) - if not self._shouldHandle(parset): - return + if not parset or not self._shouldHandle(parset): + continue # Maybe the pipeline can start already if self.dependencies.canStart(otdbId): @@ -337,12 +342,17 @@ class PipelineControl(OTDBBusListener): @staticmethod def _shouldHandle(parset): - if not parset.isPipeline(): - logger.info("Not processing tree: is not a pipeline") - return False - - if parset.processingCluster() == "CEP2": - logger.info("Not processing tree: is a CEP2 pipeline") + try: + if not parset.isPipeline(): + logger.info("Not processing tree: is not a pipeline") + return False + + if parset.processingCluster() == "CEP2": + logger.info("Not processing tree: is a CEP2 pipeline") + return False + except KeyError as e: + # Parset not complete + logger.error("Parset incomplete, ignoring: %s", e) return False return True @@ -501,7 +511,7 @@ class PipelineControl(OTDBBusListener): for s in successor_ids: parset = self._getParset(s) - if not self._shouldHandle(parset): + if not parset or not self._shouldHandle(parset): continue if self.dependencies.canStart(s): @@ -511,7 +521,7 @@ class PipelineControl(OTDBBusListener): def onObservationScheduled(self, otdbId, modificationTime): parset = self._getParset(otdbId) - if not self._shouldHandle(parset): + if not parset or not self._shouldHandle(parset): return # Maybe the pipeline can start already @@ -529,7 +539,7 @@ class PipelineControl(OTDBBusListener): def onObservationAborted(self, otdbId, modificationTime): parset = self._getParset(otdbId) - if not self._shouldHandle(parset): + if parset and not self._shouldHandle(parset): # stop jobs even if there's no parset return logger.info("***** STOP Otdb ID %s *****", otdbId) diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index 07e23240de4..f4994db0295 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -108,6 +108,10 @@ class MockRAService(MessageHandlerInterface): def GetTasks(self, lower_bound, upper_bound, task_ids, task_status, task_type): print "***** GetTasks(%s) *****" % (task_ids,) + if task_ids is None: + # Used on startup to check which tasks are at scheduled + return [] + return [{ 'otdb_id': t - 1000, 'status': self.status[t - 1000], -- GitLab From c014e60b186d0a33deadbca51d69d4cf7ce48c96 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Wed, 6 Jul 2016 10:24:17 +0000 Subject: [PATCH 482/933] Task #9537: add failed status, fix missing include --- CEP/DP3/DPPP/include/DPPP/GainCal.h | 1 + CEP/DP3/DPPP/src/GainCal.cc | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CEP/DP3/DPPP/include/DPPP/GainCal.h b/CEP/DP3/DPPP/include/DPPP/GainCal.h index d38e339561e..0dd60d5c2ea 100644 --- a/CEP/DP3/DPPP/include/DPPP/GainCal.h +++ b/CEP/DP3/DPPP/include/DPPP/GainCal.h @@ -30,6 +30,7 @@ #include <DPPP/DPInput.h> #include <DPPP/DPBuffer.h> #include <DPPP/phasefitter.h> +#include <DPPP/BaselineSelection.h> #include <DPPP/StefCal.h> #include <DPPP/Patch.h> #include <DPPP/Predict.h> diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index 2cf9a2a1046..72a90b790d2 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -262,15 +262,16 @@ namespace LOFAR { if (itsMode == "tec") { os << " "; FlagCounter::showPerc1 (os, itsTimerPhaseFit.getElapsed(), totaltime); - os << "of it spent in fitting phases" << endl; + os << " of it spent in fitting phases" << endl; } os << " "; FlagCounter::showPerc1 (os, itsTimerWrite.getElapsed(), totaltime); os << " of it spent in writing gain solutions to disk" << endl; - os << " "; + os << " "; os <<"Converged: "<<itsConverged<<", stalled: "<<itsStalled<<", non converged: "<<itsNonconverged<<", failed: "<<itsFailed<<endl; + os << " "; os <<"Iters converged: " << (itsConverged==0?0:itsNIter[0]/itsConverged); os << ", stalled: "<< (itsStalled ==0?0:itsNIter[1]/itsStalled); os << ", non converged: "<<(itsNonconverged==0?0:itsNIter[2]/itsNonconverged); @@ -673,7 +674,7 @@ namespace LOFAR { for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { switch (converged[freqCell]) { - case StefCal::CONVERGED: {itsConverged++; break;} + case StefCal::CONVERGED: {itsConverged++; itsNIter[0]+=iter; break;} case StefCal::STALLED: {itsStalled++; itsNIter[1]+=iter; break;} case StefCal::NOTCONVERGED: {itsNonconverged++; itsNIter[2]+=iter; break;} case StefCal::FAILED: {itsFailed++; itsNIter[3]+=iter; break;} -- GitLab From 9a4fb7c45ada89f4054afd4de15e4d69055e599a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Offringa?= <offringa@astron.nl> Date: Wed, 6 Jul 2016 12:55:15 +0000 Subject: [PATCH 483/933] Task #9537: Added support for single parameter TEC value fits to the phase fitter --- CEP/DP3/DPPP/include/DPPP/phasefitter.h | 106 ++++++++++++++++------ CEP/DP3/DPPP/src/phasefitter.cc | 116 +++++++++++++++++++----- 2 files changed, 170 insertions(+), 52 deletions(-) diff --git a/CEP/DP3/DPPP/include/DPPP/phasefitter.h b/CEP/DP3/DPPP/include/DPPP/phasefitter.h index 0f3aec2e164..e4198461b27 100644 --- a/CEP/DP3/DPPP/include/DPPP/phasefitter.h +++ b/CEP/DP3/DPPP/include/DPPP/phasefitter.h @@ -19,11 +19,14 @@ * - Perform a calibration iteration. * - Set the phase values (@ref PhaseData()) and, if not yet done, the weights (@ref WeightData()) * to the current phase solutions / weights of a single antenna. - * - Call @ref FitDataToTECModel(). + * - Call @ref FitDataToTEC1Model() or @ref FitDataToTEC2Model(). * - Read the new phase values. * - When fitting multiple polarizations, the above steps should be done twice for each * diagonal value of the Jones matrix. Also repeat for all antennae. * - Continue the iteration with the new phase values. + * + * Methods with name containing TEC1 refer to a single-parameter TEC model (no delay), while + * methods with TEC2 refer to dual-parameter TEC model (TEC + delay). */ class PhaseFitter { @@ -46,21 +49,29 @@ class PhaseFitter PhaseFitter& operator=(const PhaseFitter&) = delete; /** - * Fits the given phase values to a TEC model. This function is + * Fits the given phase values to a TEC model and returns the parameters. This function is * robust even when phase wrapping occurs. * After this call, the @ref PhaseData() satisfy the TEC model. * The TEC model has two parameters and are fitted as described in - * @ref FitTECModelParameters(). + * @ref FitTEC2ModelParameters(). * @param alpha Found value for the alpha parameter. * @param beta Found value for the beta parameter. - * * @returns Cost of the found solution. */ - double FitDataToTECModel(double& alpha, double& beta); + double FitDataToTEC2Model(double& alpha, double& beta); - /** + /** + * Like @ref FitDataToTEC2Model(double&,double&), but without returning the parameters. + */ + void FitDataToTEC2Model() + { + double a,b; + FitDataToTEC2Model(a, b); + } + + /** * Fits the given phase values to a TEC model using prior estimates of the - * model parameters. This method is similar to @ref FitDataToTECModel(), + * model parameters. This method is similar to @ref FitDataToTEC2Model(), * except that it will use the provided initial values of alpha and * beta to speed up the solution. When the provided initial values * are not accurate, the fit might not be accurate. @@ -68,12 +79,10 @@ class PhaseFitter * @todo No fast method has been implemented -- instead it will perform a full parameter search. * @param alpha Estimate of alpha parameter on input, found value on output. * @param beta Estimate of beta parameter on input, found value on output. - * - * @returns Cost of the found solution. */ - double FitDataToTECModelWithInitialValues(double& alpha, double& beta) + double FitDataToTEC2ModelWithInitialValues(double& alpha, double& beta) { - return FitDataToTECModel(alpha, beta); + return FitDataToTEC2Model(alpha, beta); } /** @@ -90,12 +99,12 @@ class PhaseFitter * @param beta Will be set to the fitted value for the beta parameter * (value on input is not used). */ - void FitTECModelParameters(double& alpha, double& beta) const; + void FitTEC2ModelParameters(double& alpha, double& beta) const; /** * Get a pointer to the array of phase values. This array should be filled with the - * phases of solutions before calling one of the fit methods. @ref FitDataToTECModel() - * sets this array to the fitted phases. + * phases of solutions before calling one of the fit methods. @ref FitDataToTEC1Model() + * and ~TEC2~ sets this array to the fitted phases. * Normally, these values should be set to std::arg(z) or atan2(z.imag(), z.real()), where * z is a complex solution for one polarizations. All phases should correspond to the same * polarizations, i.e., different polarizations (xx/yy/ll/rr, etc.) should be independently @@ -182,40 +191,81 @@ class PhaseFitter * @returns sum of | nu_i / alpha + beta - theta_i |, and each sum term is * phase unwrapped. */ - double TECModelCost(double alpha, double beta) const; + double TEC2ModelCost(double alpha, double beta) const; /** - * Like @ref TECModelFunc(), but 2-pi wrapped. + * Like @ref TEC2ModelFunc(), but 2-pi wrapped. * @param nu Frequency in Hz * @param alpha TEC parameter (in undefined units) * @param beta Phase offset parameter (in radians) * @returns | nu_i / alpha + beta | % 2pi */ - static double TECModelFunc(double nu, double alpha, double beta) + static double TEC2ModelFunc(double nu, double alpha, double beta) { return alpha / nu + beta; } + double FitDataToTEC1Model(double& alpha); + /** - * Like @ref TECModelFunc(), but 2-pi wrapped. + * Like @ref FitDataToTEC1Model(double&,double&), but without returning the parameters. + */ + void FitDataToTEC1Model() + { + double a; + FitDataToTEC1Model(a); + } + + /** + * Like @ref TEC2ModelFunc(), but 2-pi wrapped. * @param nu Frequency in Hz * @param alpha TEC parameter (in undefined units) * @param beta Phase offset parameter (in radians) * @returns | nu_i / alpha + beta | % 2pi */ - static double TECModelFuncWrapped(double nu, double alpha, double beta) + static double TEC2ModelFuncWrapped(double nu, double alpha, double beta) { - return fmod(alpha / nu + beta, 2*M_PI); + return fmod(alpha / nu + beta, 2.0*M_PI); } - private: - std::vector<double> _phases, _frequencies, _weights; - double _fittingAccuracy; - double fitTECModelBeta(double alpha, double betaEstimate) const; - void bruteForceSearchTECModel(double& lowerAlpha, double& upperAlpha, double& beta) const; - double ternarySearchTECModelAlpha(double startAlpha, double endAlpha, double& beta) const; - void fillDataWithTECModel(double alpha, double beta); - + double FitDataToTEC1ModelWithInitialValues(double& alpha) + { + return FitDataToTEC1Model(alpha); + } + + void FitTEC1ModelParameters(double& alpha) const; + + /** + * Evaluate the cost function for given TEC model parameter. The higher the + * cost, the worser the data fit the given parameters. + * @param alpha TEC parameter + * @returns sum of | alpha / nu_i - theta_i |, and each sum term is + * phase unwrapped. + */ + double TEC1ModelCost(double alpha) const; + + /** + * Like @ref TEC1ModelFunc(), but 2-pi wrapped. + * @param nu Frequency in Hz + * @param alpha TEC parameter (in undefined units) + * @param beta Phase offset parameter (in radians) + * @returns | alpha / nu_i + beta | % 2pi + */ + static double TEC1ModelFuncWrapped(double nu, double alpha) + { + return fmod(alpha / nu, 2.0*M_PI); + } + private: + std::vector<double> _phases, _frequencies, _weights; + double _fittingAccuracy; + + double fitTEC2ModelBeta(double alpha, double betaEstimate) const; + void bruteForceSearchTEC2Model(double& lowerAlpha, double& upperAlpha, double& beta) const; + double ternarySearchTEC2ModelAlpha(double startAlpha, double endAlpha, double& beta) const; + void fillDataWithTEC2Model(double alpha, double beta); + + void bruteForceSearchTEC1Model(double& lowerAlpha, double& upperAlpha) const; + double ternarySearchTEC1ModelAlpha(double startAlpha, double endAlpha) const; }; #endif diff --git a/CEP/DP3/DPPP/src/phasefitter.cc b/CEP/DP3/DPPP/src/phasefitter.cc index ccc034f1788..6305896a472 100644 --- a/CEP/DP3/DPPP/src/phasefitter.cc +++ b/CEP/DP3/DPPP/src/phasefitter.cc @@ -3,11 +3,11 @@ #include <limits> -double PhaseFitter::TECModelCost(double alpha, double beta) const +double PhaseFitter::TEC2ModelCost(double alpha, double beta) const { double costVal = 0.0; for(int i=0; i!=Size(); ++i) { - double estphase = TECModelFuncWrapped(_frequencies[i], alpha, beta); + double estphase = TEC2ModelFuncWrapped(_frequencies[i], alpha, beta); double dCost = fmod(std::fabs(estphase - _phases[i]), 2.0*M_PI); if(dCost > M_PI) dCost = 2.0*M_PI - dCost; dCost *= _weights[i]; @@ -16,12 +16,12 @@ double PhaseFitter::TECModelCost(double alpha, double beta) const return costVal; } -double PhaseFitter::fitTECModelBeta(double alpha, double betaEstimate) const { +double PhaseFitter::fitTEC2ModelBeta(double alpha, double betaEstimate) const { double weightSum = 0.0; for(size_t iter=0; iter!=3; ++iter) { double sum = 0.0; for(size_t i=0; i!=Size(); ++i) { - double p = _phases[i], e = TECModelFunc(_frequencies[i], alpha, betaEstimate); + double p = _phases[i], e = TEC2ModelFunc(_frequencies[i], alpha, betaEstimate); double dist = fmod(p - e, 2.0*M_PI); if(dist < -M_PI) dist += 2.0*M_PI; @@ -35,20 +35,19 @@ double PhaseFitter::fitTECModelBeta(double alpha, double betaEstimate) const { return fmod(betaEstimate, 2.0*M_PI); } -void PhaseFitter::bruteForceSearchTECModel(double& lowerAlpha, double& upperAlpha, double& beta) const +void PhaseFitter::bruteForceSearchTEC2Model(double& lowerAlpha, double& upperAlpha, double& beta) const { double minCost = std::numeric_limits<double>::max(); double alphaOversampling = 256; //size_t betaOversampling = 16; double dAlpha = upperAlpha - lowerAlpha; - const double bandw = _frequencies.back() - _frequencies.front(); int alphaIndex = 0; for(int i=0; i!=alphaOversampling; ++i) { // make r between [0, 1] double r = double(i)/alphaOversampling; double alpha = lowerAlpha + r*dAlpha; - double curBeta = fitTECModelBeta(alpha, beta); - double costVal = TECModelCost(alpha, curBeta); + double curBeta = fitTEC2ModelBeta(alpha, beta); + double costVal = TEC2ModelCost(alpha, curBeta); if(costVal < minCost) { beta = curBeta; minCost = costVal; @@ -60,17 +59,17 @@ void PhaseFitter::bruteForceSearchTECModel(double& lowerAlpha, double& upperAlph lowerAlpha = newLowerAlpha; } -double PhaseFitter::ternarySearchTECModelAlpha(double startAlpha, double endAlpha, double& beta) const +double PhaseFitter::ternarySearchTEC2ModelAlpha(double startAlpha, double endAlpha, double& beta) const { size_t iter = 0; double dCost, lAlpha, rAlpha; do { lAlpha = startAlpha + (endAlpha - startAlpha) * (1.0/3.0); rAlpha = startAlpha + (endAlpha - startAlpha) * (2.0/3.0); - double lBeta = fitTECModelBeta(lAlpha, beta); - double rBeta = fitTECModelBeta(rAlpha, beta); - double lCost = TECModelCost(lAlpha, lBeta); - double rCost = TECModelCost(rAlpha, rBeta); + double lBeta = fitTEC2ModelBeta(lAlpha, beta); + double rBeta = fitTEC2ModelBeta(rAlpha, beta); + double lCost = TEC2ModelCost(lAlpha, lBeta); + double rCost = TEC2ModelCost(rAlpha, rBeta); if(lCost < rCost) { endAlpha = rAlpha; beta = lBeta; @@ -82,30 +81,99 @@ double PhaseFitter::ternarySearchTECModelAlpha(double startAlpha, double endAlph ++iter; } while(dCost > _fittingAccuracy && iter < 100); double finalAlpha = (lAlpha + rAlpha) * 0.5; - beta = fitTECModelBeta(finalAlpha, beta); + beta = fitTEC2ModelBeta(finalAlpha, beta); return finalAlpha; } -void PhaseFitter::fillDataWithTECModel(double alpha, double beta) +void PhaseFitter::fillDataWithTEC2Model(double alpha, double beta) { for(size_t ch=0; ch!=Size(); ++ch) - _phases[ch] = TECModelFunc(_frequencies[ch], alpha, beta); + _phases[ch] = TEC2ModelFunc(_frequencies[ch], alpha, beta); } -void PhaseFitter::FitTECModelParameters(double& alpha, double& beta) const +void PhaseFitter::FitTEC2ModelParameters(double& alpha, double& beta) const { double lowerAlpha = -40000.0e6, upperAlpha = 40000.0e6; - bruteForceSearchTECModel(lowerAlpha, upperAlpha, beta); + bruteForceSearchTEC2Model(lowerAlpha, upperAlpha, beta); alpha = (lowerAlpha + upperAlpha) * 0.5; //beta = fitBeta(alpha, beta); - alpha = ternarySearchTECModelAlpha(lowerAlpha, upperAlpha, beta); + alpha = ternarySearchTEC2ModelAlpha(lowerAlpha, upperAlpha, beta); } -double PhaseFitter::FitDataToTECModel(double& alpha, double& beta) +double PhaseFitter::FitDataToTEC2Model(double& alpha, double& beta) { - FitTECModelParameters(alpha, beta); - double cost = TECModelCost(alpha, beta); - fillDataWithTECModel(alpha, beta); - return cost; + FitTEC2ModelParameters(alpha, beta); + fillDataWithTEC2Model(alpha, beta); + return TEC2ModelCost(alpha, beta); +} + +void PhaseFitter::FitTEC1ModelParameters(double& alpha) const +{ + double lowerAlpha = -40000.0e6, upperAlpha = 40000.0e6; + bruteForceSearchTEC1Model(lowerAlpha, upperAlpha); + alpha = ternarySearchTEC1ModelAlpha(lowerAlpha, upperAlpha); +} + +#include <iostream> +void PhaseFitter::bruteForceSearchTEC1Model(double& lowerAlpha, double& upperAlpha) const +{ + double minCost = std::numeric_limits<double>::max(); + double alphaOversampling = 256; + double dAlpha = upperAlpha - lowerAlpha; + int alphaIndex = 0; + for(int i=0; i!=alphaOversampling; ++i) { + // make r between [0, 1] + double r = double(i)/alphaOversampling; + double alpha = lowerAlpha + r*dAlpha; + // We have to have some freedom in the fit to make sure + // we do rule out an area with an unwripping that is correct + // Hence we use the two-parameter model and allow beta to be fitted. + // The ternary search will fix alpha to accomodate a zero beta. + double curBeta = fitTEC2ModelBeta(alpha, 0.0); + double costVal = TEC2ModelCost(alpha, curBeta); + if(costVal < minCost) { + minCost = costVal; + alphaIndex = i; + } + } + double newLowerAlpha = double(alphaIndex-1)/alphaOversampling*dAlpha + lowerAlpha; + upperAlpha = double(alphaIndex+1)/alphaOversampling*dAlpha + lowerAlpha; + lowerAlpha = newLowerAlpha; + //std::cout << "alpha in " << lowerAlpha << "-" << upperAlpha << '\n'; +} + +double PhaseFitter::TEC1ModelCost(double alpha) const +{ + double costVal = 0.0; + for(int i=0; i!=Size(); ++i) { + double estphase = TEC1ModelFuncWrapped(_frequencies[i], alpha); + double dCost = fmod(std::fabs(estphase - _phases[i]), 2.0*M_PI); + if(dCost > M_PI) dCost = 2.0*M_PI - dCost; + dCost *= _weights[i]; + costVal += dCost; + } + return costVal; +} + +double PhaseFitter::ternarySearchTEC1ModelAlpha(double startAlpha, double endAlpha) const +{ + size_t iter = 0; + double dCost, lAlpha, rAlpha; + do { + lAlpha = startAlpha + (endAlpha - startAlpha) * (1.0/3.0); + rAlpha = startAlpha + (endAlpha - startAlpha) * (2.0/3.0); + double lCost = TEC1ModelCost(lAlpha); + double rCost = TEC1ModelCost(rAlpha); + if(lCost < rCost) { + endAlpha = rAlpha; + } else { + startAlpha = lAlpha; + } + dCost = std::fabs(lCost - rCost); + ++iter; + //std::cout << iter << '\t' << startAlpha << '\t' << endAlpha << '\n'; + } while(dCost > _fittingAccuracy && iter < 100); + double finalAlpha = (lAlpha + rAlpha) * 0.5; + return finalAlpha; } -- GitLab From 9e49fce628d49673b94fe7e7eb8d22aa4b1e0e8c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 6 Jul 2016 14:49:12 +0000 Subject: [PATCH 484/933] Task #9607: added logging in MACScheduler::_updatePlannedList() to identify if the missed observations are due to a potential race-condiction in itsOTDBconnection->getTreeGroup and the timeBeforeStart check --- .../MainCU/src/MACScheduler/MACScheduler.cc | 72 ++++++++++--------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc b/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc index 268a664f815..2b1be5279aa 100644 --- a/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc +++ b/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc @@ -65,7 +65,7 @@ namespace LOFAR { // static (this) pointer used for signal handling static MACScheduler* pMacScheduler = 0; - + // // MACScheduler() // @@ -108,7 +108,7 @@ MACScheduler::MACScheduler() : } } - ASSERTSTR(itsMaxPlanned + itsMaxFinished < MAX_CONCURRENT_OBSERVATIONS, + ASSERTSTR(itsMaxPlanned + itsMaxFinished < MAX_CONCURRENT_OBSERVATIONS, "maxPlannedList + maxFinishedList should be less than " << MAX_CONCURRENT_OBSERVATIONS); // Read the schedule periods for starting observations. @@ -192,12 +192,12 @@ void MACScheduler::_databaseEventHandler(GCFEvent& event) itsQueuePeriod = newVal; } #endif - } + } break; default: break; - } + } } @@ -211,7 +211,7 @@ GCFEvent::TResult MACScheduler::initial_state(GCFEvent& event, GCFPortInterface& LOG_DEBUG_STR ("initial_state:" << eventName(event)); GCFEvent::TResult status = GCFEvent::HANDLED; - + switch (event.signal) { case F_INIT: break; @@ -235,7 +235,7 @@ GCFEvent::TResult MACScheduler::initial_state(GCFEvent& event, GCFPortInterface& itsTimerPort->setTimer(0.0); } break; - + case F_TIMER: { // must be timer that PropSet is enabled. // update PVSS. LOG_TRACE_FLOW ("Updateing state to PVSS"); @@ -249,7 +249,7 @@ GCFEvent::TResult MACScheduler::initial_state(GCFEvent& event, GCFPortInterface& itsPropertySet->setValue(PN_MS_PLANNED_OBSERVATIONS, GCFPVDynArr(LPT_STRING, emptyArr)); itsPropertySet->setValue(PN_MS_FINISHED_OBSERVATIONS, GCFPVDynArr(LPT_STRING, emptyArr)); - + // Try to connect to the SAS database. ParameterSet* pParamSet = globalParameterSet(); string username = pParamSet->getString("OTDBusername"); @@ -261,7 +261,7 @@ GCFEvent::TResult MACScheduler::initial_state(GCFEvent& event, GCFPortInterface& itsOTDBconnection= new OTDBconnection(username, password, DBname, hostname); ASSERTSTR (itsOTDBconnection, "Memory allocation error (OTDB)"); ASSERTSTR (itsOTDBconnection->connect(), - "Unable to connect to database " << DBname << " on " << hostname << + "Unable to connect to database " << DBname << " on " << hostname << " using " << username << "," << password); LOG_INFO ("Connected to the OTDB"); itsPropertySet->setValue(PN_MS_OTDB_CONNECTED, GCFPVBool(true)); @@ -285,12 +285,12 @@ GCFEvent::TResult MACScheduler::initial_state(GCFEvent& event, GCFPortInterface& case F_DISCONNECTED: break; - + default: LOG_DEBUG ("MACScheduler::initial, default"); status = GCFEvent::NOT_HANDLED; break; - } + } return (status); } @@ -320,15 +320,15 @@ GCFEvent::TResult MACScheduler::recover_state(GCFEvent& event, GCFPortInterface& // TODO: do recovery TRAN(MACScheduler::active_state); - + break; } - + default: LOG_DEBUG("recover_state, default"); status = GCFEvent::NOT_HANDLED; break; - } + } return (status); } @@ -349,7 +349,7 @@ GCFEvent::TResult MACScheduler::active_state(GCFEvent& event, GCFPortInterface& break; case F_ENTRY: { - // install my own signal handler. GCFTask also installs a handler so we have + // install my own signal handler. GCFTask also installs a handler so we have // to install our handler later than the GCFTask handler. pMacScheduler = this; signal (SIGINT, MACScheduler::sigintHandler); // ctrl-c @@ -367,12 +367,12 @@ GCFEvent::TResult MACScheduler::active_state(GCFEvent& event, GCFPortInterface& case F_ACCEPT_REQ: break; - case F_CONNECTED: + case F_CONNECTED: // Should be from the (lost) connection with the SD _connectedHandler(port); break; - case F_DISCONNECTED: + case F_DISCONNECTED: // Can be from StartDaemon or ObsController. // StartDaemon: trouble! Try to reconnect asap. // ObsController: ok when obs is finished, BIG TROUBLE when not! @@ -432,7 +432,7 @@ GCFEvent::TResult MACScheduler::active_state(GCFEvent& event, GCFPortInterface& // -------------------- EVENTS FROM CHILDCONTROL -------------------- // - // That must be events from the ObservationControllers that are currently + // That must be events from the ObservationControllers that are currently // started or running. // case CONTROL_STARTED: { @@ -562,16 +562,16 @@ GCFEvent::TResult MACScheduler::finishing_state(GCFEvent& event, GCFPortInterfac itsTimerPort->setTimer(1L); break; } - + case F_TIMER: GCFScheduler::instance()->stop(); break; - + default: LOG_DEBUG("finishing_state, default"); status = GCFEvent::NOT_HANDLED; break; - } + } return (status); } @@ -624,25 +624,27 @@ void MACScheduler::_doOTDBcheck() // void MACScheduler::_updatePlannedList() { - LOG_DEBUG("_updatePlannedList()"); + LOG_INFO("_updatePlannedList()"); // get time info time_t now = time(0); ptime currentTime = from_time_t(now); ASSERTSTR (currentTime != not_a_date_time, "Can't determine systemtime, bailing out"); + LOG_INFO_STR("calling getTreeGroup"); // get new list (list is ordered on starttime) of planned observations - vector<OTDBtree> plannedDBlist = itsOTDBconnection->getTreeGroup(1, itsPlannedPeriod, itsExclPLcluster); + vector<OTDBtree> plannedDBlist = itsOTDBconnection->getTreeGroup(1, itsPlannedPeriod, itsExclPLcluster); + LOG_INFO_STR("called getTreeGroup, nr of items in plannedDBlist: " << plannedDBlist.size()); if (!plannedDBlist.empty()) { - LOG_DEBUG(formatString("OTDBCheck:First planned observation (%d) is at %s (active over %d seconds)", - plannedDBlist[0].treeID(), to_simple_string(plannedDBlist[0].starttime).c_str(), + LOG_DEBUG(formatString("OTDBCheck:First planned observation (%d) is at %s (active over %d seconds)", + plannedDBlist[0].treeID(), to_simple_string(plannedDBlist[0].starttime).c_str(), time_duration(plannedDBlist[0].starttime - currentTime).total_seconds())); } // NOTE: do not exit routine on emptylist: we need to write an empty list to clear the DB // make a copy of the current prepared observations (= observations shown in the navigator in the 'future' - // list). By eliminating the observations that are in the current SAS list we end up (at the end of this function) + // list). By eliminating the observations that are in the current SAS list we end up (at the end of this function) // with a list of observations that were in the SASlist the last time but now not anymore. Normally those observations // will appear in the active-list and will be removed there from the prepared list but WHEN AN OPERATOR CHANGES // THE STATUS MANUALLY into something different (e.g. ON-HOLD) the observation stays in the preparedlist. @@ -672,14 +674,14 @@ void MACScheduler::_updatePlannedList() // must we claim this observation at the claimMgr? OLiter prepIter = itsPreparedObs.find(obsID); - if ((prepIter == itsPreparedObs.end()) || (prepIter->second.prepReady == false) || + if ((prepIter == itsPreparedObs.end()) || (prepIter->second.prepReady == false) || (prepIter->second.modTime != modTime)) { // create a ParameterFile for this Observation TreeMaintenance tm(itsOTDBconnection); OTDBnode topNode = tm.getTopNode(obsID); string filename(observationParset(obsID)); if (!tm.exportTree(obsID, topNode.nodeID(), filename)) { - LOG_ERROR_STR ("Cannot create ParameterSet '" << filename << + LOG_ERROR_STR ("Cannot create ParameterSet '" << filename << "' for new observation. Observation CANNOT BE STARTED!"); } else { @@ -694,10 +696,14 @@ void MACScheduler::_updatePlannedList() // otherwise thing will go wrong in the Navigator plannedArr.push_back(new GCFPVString(obsName)); } - + // should this observation (have) be(en) started? int timeBeforeStart = time_duration(plannedDBlist[idx].starttime - currentTime).total_seconds(); -// LOG_DEBUG_STR(obsName << " starts over " << timeBeforeStart << " seconds"); + LOG_INFO(formatString("%s starts at %s which is in %d seconds", + obsName.c_str(), + to_simple_string(plannedDBlist[idx].starttime).c_str(), + timeBeforeStart)); + if (timeBeforeStart > 0 && timeBeforeStart <= (int)itsQueuePeriod) { if (itsPreparedObs[obsID].prepReady == false) { LOG_INFO_STR("Observation " << obsID << " must be started but is not claimed yet."); @@ -710,22 +716,22 @@ void MACScheduler::_updatePlannedList() string cntlrName(controllerName(CNTLRTYPE_OBSERVATIONCTRL, 0, obsID)); if (itsControllerMap.find(cntlrName) == itsControllerMap.end()) { LOG_INFO_STR("Requesting start of " << cntlrName); - itsChildControl->startChild(CNTLRTYPE_OBSERVATIONCTRL, - obsID, + itsChildControl->startChild(CNTLRTYPE_OBSERVATIONCTRL, + obsID, 0, // instanceNr myHostname(true)); // Note: controller is now in state NO_STATE/CONNECTED (C/R) // add controller to our 'monitor' administration itsControllerMap[cntlrName] = obsID; - LOG_DEBUG_STR("itsControllerMap[" << cntlrName << "]=" << obsID); + LOG_INFO_STR("itsControllerMap[" << cntlrName << "]=" << obsID); if (!itsPreparedObs[obsID].parsetDistributed) { _setParsetOnMsgBus(observationParset(obsID)); itsPreparedObs[obsID].parsetDistributed = true; } } else { - LOG_DEBUG_STR("Observation " << obsID << " is already (being) started"); + LOG_INFO_STR("Observation " << obsID << " is already (being) started"); } } } -- GitLab From 5df0ed4800105506522e27dd99353740b67a4749 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Jul 2016 14:56:05 +0000 Subject: [PATCH 485/933] Task #9623: Created task branch -- GitLab From 270be85252e1325c0f2371eb0c0437f0b50152df Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Jul 2016 14:59:15 +0000 Subject: [PATCH 486/933] Task #9623: try nthreads=4 for NDPPP --- CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in index 76693d17ad7..61c51bf715b 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in @@ -1,6 +1,6 @@ [ndppp] nproc = 0 -nthreads = 10 +nthreads = 4 [setupparmdb] nproc = 0 @@ -24,7 +24,7 @@ nthreads = 10 [dppp] max_per_node = 0 -nthreads = 10 +nthreads = 4 [awimager] max_per_node = 0 -- GitLab From 5443fc37d39fd4e89e48598a142e9002aea4b6db Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Jul 2016 16:30:46 +0000 Subject: [PATCH 487/933] Task #9623: Tile size of 4MByte is optimal for Lustre --- MAC/Deployment/data/OTDB/DPPP.comp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Deployment/data/OTDB/DPPP.comp b/MAC/Deployment/data/OTDB/DPPP.comp index 482cb96050b..e9cb7f00021 100644 --- a/MAC/Deployment/data/OTDB/DPPP.comp +++ b/MAC/Deployment/data/OTDB/DPPP.comp @@ -37,7 +37,7 @@ node msout 4.0.0 development 'node constraint' "Output MeasurementSe #par name I text - 10 0 "-" - "Name of the MeasurementSet" par overwrite I bool - 10 0 F - "When creating a new MS, overwrite if already existing?" par tilenchan I int - 10 0 0 - "For expert user: maximum number of channels per tile in output MS (0 is all channels)" -par tilesize I int - 10 0 1024 - "For expert user: tile size (in Kbytes) for the data columns in the output MS" +par tilesize I int - 10 0 4096 - "For expert user: tile size (in Kbytes) for the data columns in the output MS" par vdsdir I text - 10 0 "A" - "Directory where to put the VDS file; if empty, the MS directory is used." par writefullresflag I bool - 10 0 T - "Write the full resolution flags" -- GitLab From 3764b4d790cd041b08d745706edb77439cf74039 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 6 Jul 2016 18:48:32 +0000 Subject: [PATCH 488/933] Task #9623: Try 2 threads per NDPPP process --- CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in index 61c51bf715b..bc8e4f1ef5e 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in @@ -1,6 +1,6 @@ [ndppp] nproc = 0 -nthreads = 4 +nthreads = 2 [setupparmdb] nproc = 0 @@ -24,7 +24,7 @@ nthreads = 10 [dppp] max_per_node = 0 -nthreads = 4 +nthreads = 2 [awimager] max_per_node = 0 -- GitLab From cad7ac874c3ded18b374fa3218cbc07f3e8ca69d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 7 Jul 2016 08:31:49 +0000 Subject: [PATCH 489/933] Task #9623: Support "min,max" nr of nodes pair in the parset --- MAC/Services/src/PipelineControl.py | 37 +++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 3146d9467f8..0f9e7682179 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -143,10 +143,39 @@ class Parset(dict): return max(1, min(20, result)) def processingNumberOfTasks(self): - result = int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfTasks"]) or "24" - if result < 1 or result > 48: - logger.warn('Invalid Observation.Cluster.ProcessingCluster.numberOfTasks: %s, defaulting to %s', result, max(1, min(48, result))) - return max(1, min(48, result)) + """ Parse the number of nodes to allocate from "Observation.Cluster.ProcessingCluster.numberOfTasks", + which can have either the format "{number}" or "{min},{max}". """ + + parsetValue = self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfTasks"].strip() + + # force a number of nodes between bounds applicable for this cluster + def bound(n): + return max(1, min(48, n)) + + if "," in parsetValue: + # min,max + _min, _max = parsetValue.split(",") + + # apply bound + _min = bound(_min) + _max = bound(_max) + + # collapse if not min <= max + if _min > _max: + result = _min + else: + result = "%s,%s" % (_min, _max) + else: + # plain number + result = int(parsetValue) or 24 + + # apply bound + result = bound(result) + + if result != parsetValue: + logger.error('Invalid Observation.Cluster.ProcessingCluster.numberOfTasks: %s, defaulting to %s', parsetValue, result) + + return result @staticmethod def dockerRepository(): -- GitLab From d9b940b5a5b5d770ddd579ef36ccbc45386fd640 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Thu, 7 Jul 2016 08:32:03 +0000 Subject: [PATCH 490/933] Task #9537: add GPL headers to phase fitter --- CEP/DP3/DPPP/include/DPPP/phasefitter.h | 23 +++++++++++++++++++++++ CEP/DP3/DPPP/src/phasefitter.cc | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/CEP/DP3/DPPP/include/DPPP/phasefitter.h b/CEP/DP3/DPPP/include/DPPP/phasefitter.h index e4198461b27..55d3f9ce19e 100644 --- a/CEP/DP3/DPPP/include/DPPP/phasefitter.h +++ b/CEP/DP3/DPPP/include/DPPP/phasefitter.h @@ -1,3 +1,26 @@ +//# phasefitter.h: Class to perform phase fitting (TEC), allowing phase wraps +//# Copyright (C) 2016 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: phasefitter.cc 21598 2012-07-16 08:07:34Z offringa $ +//# +//# @author André Offringa + /** * @file phasefitter.h Implements TEC model phase filter @ref PhaseFitter. * @author André Offringa diff --git a/CEP/DP3/DPPP/src/phasefitter.cc b/CEP/DP3/DPPP/src/phasefitter.cc index 6305896a472..07c171dd743 100644 --- a/CEP/DP3/DPPP/src/phasefitter.cc +++ b/CEP/DP3/DPPP/src/phasefitter.cc @@ -1,3 +1,26 @@ +//# phasefitter.cc: Class to perform phase fitting (TEC), allowing phase wraps +//# Copyright (C) 2016 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: phasefitter.cc 21598 2012-07-16 08:07:34Z offringa $ +//# +//# @author Andre Offringa + #include <lofar_config.h> #include "DPPP/phasefitter.h" -- GitLab From 8520cb8f78040d0a2f8f8aefef1f32b26627b53f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 7 Jul 2016 08:46:26 +0000 Subject: [PATCH 491/933] Task #9623: Added variants file for JDM laptop --- .gitattributes | 1 + CMake/variants/variants.dop320 | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 CMake/variants/variants.dop320 diff --git a/.gitattributes b/.gitattributes index afffabe1f55..3b8efb9171e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2337,6 +2337,7 @@ CMake/variants/variants.cbt008 -text CMake/variants/variants.cbt009 -text CMake/variants/variants.cbt010 -text CMake/variants/variants.dop282 -text +CMake/variants/variants.dop320 -text CMake/variants/variants.dragproc -text CMake/variants/variants.fs0 -text CMake/variants/variants.gpu01 -text diff --git a/CMake/variants/variants.dop320 b/CMake/variants/variants.dop320 new file mode 100644 index 00000000000..e11707efd90 --- /dev/null +++ b/CMake/variants/variants.dop320 @@ -0,0 +1,2 @@ +# Fix for Debian (FindPython tends to settle on /usr/bin/python2.6, which is a "minimal" python install) +set(PYTHON_EXECUTABLE "/usr/bin/python2.7") -- GitLab From 54f74e4c0223ca50f06521630f026943f5d3c3d4 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 7 Jul 2016 12:00:51 +0000 Subject: [PATCH 492/933] Task #9634: fixed issue. The otdb function getTreeGroup used to return its results ordered by starttime, but it did not anymore after the excludePLCluster change. The fix is simple: order the result set by starttime again --- SAS/OTDB/sql/getTreeGroup_func.sql | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SAS/OTDB/sql/getTreeGroup_func.sql b/SAS/OTDB/sql/getTreeGroup_func.sql index b1b1dc95e23..053363ad9ae 100644 --- a/SAS/OTDB/sql/getTreeGroup_func.sql +++ b/SAS/OTDB/sql/getTreeGroup_func.sql @@ -61,6 +61,7 @@ CREATE OR REPLACE FUNCTION getTreeGroup(INT, INT, VARCHAR(20)) vWhere TEXT; vQuery TEXT; vSortOrder TEXT; + vSortOrderExcept TEXT; vExcept TEXT; vCluster TEXT; @@ -113,6 +114,8 @@ CREATE OR REPLACE FUNCTION getTreeGroup(INT, INT, VARCHAR(20)) END IF; END IF; + vSortOrderExcept := replace(vSortOrder, 't.', ''); + -- do selection FOR vRecord IN EXECUTE ' WITH VICtrees AS ( @@ -140,7 +143,7 @@ CREATE OR REPLACE FUNCTION getTreeGroup(INT, INT, VARCHAR(20)) AND t.classif = 3 ' || vWhere || ' ORDER BY ' || vSortOrder || ') - ' || vQuery || vExcept + ' || vQuery || vExcept || ' ORDER BY ' || vSortOrderExcept LOOP RETURN NEXT vRecord; END LOOP; -- GitLab From c5132ba7537ba2442ddabf8c6f4cd1c440338487 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 7 Jul 2016 13:37:09 +0000 Subject: [PATCH 493/933] Task #9634: removed some of the needed logging to solve the issue. I left the logging of starttime per observation on, which helps a lot to see what macscheduler is doing --- MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc b/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc index 2b1be5279aa..ee6cd42d954 100644 --- a/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc +++ b/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc @@ -624,17 +624,15 @@ void MACScheduler::_doOTDBcheck() // void MACScheduler::_updatePlannedList() { - LOG_INFO("_updatePlannedList()"); + LOG_DEBUG("_updatePlannedList()"); // get time info time_t now = time(0); ptime currentTime = from_time_t(now); ASSERTSTR (currentTime != not_a_date_time, "Can't determine systemtime, bailing out"); - LOG_INFO_STR("calling getTreeGroup"); // get new list (list is ordered on starttime) of planned observations vector<OTDBtree> plannedDBlist = itsOTDBconnection->getTreeGroup(1, itsPlannedPeriod, itsExclPLcluster); - LOG_INFO_STR("called getTreeGroup, nr of items in plannedDBlist: " << plannedDBlist.size()); if (!plannedDBlist.empty()) { LOG_DEBUG(formatString("OTDBCheck:First planned observation (%d) is at %s (active over %d seconds)", @@ -724,14 +722,14 @@ void MACScheduler::_updatePlannedList() // add controller to our 'monitor' administration itsControllerMap[cntlrName] = obsID; - LOG_INFO_STR("itsControllerMap[" << cntlrName << "]=" << obsID); + LOG_DEBUG_STR("itsControllerMap[" << cntlrName << "]=" << obsID); if (!itsPreparedObs[obsID].parsetDistributed) { _setParsetOnMsgBus(observationParset(obsID)); itsPreparedObs[obsID].parsetDistributed = true; } } else { - LOG_INFO_STR("Observation " << obsID << " is already (being) started"); + LOG_DEBUG_STR("Observation " << obsID << " is already (being) started"); } } } -- GitLab From 68f5f2ab7a987e864b39b314841dd98f308d3de4 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 7 Jul 2016 13:57:28 +0000 Subject: [PATCH 494/933] Task #9351 #9353 #9355: added notifications on updates of diskusage for path. Handle notifications with datamanagementbuslistener (which was the cleanupbuslistener before). --- .gitattributes | 2 +- .../CleanupService/CMakeLists.txt | 1 - SAS/DataManagement/CleanupService/config.py | 4 - SAS/DataManagement/CleanupService/service.py | 2 +- .../DataManagementCommon/CMakeLists.txt | 1 + .../DataManagementCommon/config.py | 6 ++ .../datamanagementbuslistener.py} | 22 +++-- .../StorageQueryService/cache.py | 80 +++++++++++++------ 8 files changed, 78 insertions(+), 40 deletions(-) rename SAS/DataManagement/{CleanupService/cleanupbuslistener.py => DataManagementCommon/datamanagementbuslistener.py} (70%) diff --git a/.gitattributes b/.gitattributes index 05318bb2c1a..ddeaf82279f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4732,7 +4732,6 @@ SAS/DataManagement/CMakeLists.txt -text SAS/DataManagement/CleanupService/CMakeLists.txt -text SAS/DataManagement/CleanupService/__init__.py -text SAS/DataManagement/CleanupService/cleanup -text -SAS/DataManagement/CleanupService/cleanupbuslistener.py -text SAS/DataManagement/CleanupService/cleanupservice -text SAS/DataManagement/CleanupService/cleanupservice.ini -text SAS/DataManagement/CleanupService/config.py -text @@ -4745,6 +4744,7 @@ SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.sh -text SAS/DataManagement/DataManagementCommon/CMakeLists.txt -text SAS/DataManagement/DataManagementCommon/__init__.py -text SAS/DataManagement/DataManagementCommon/config.py -text +SAS/DataManagement/DataManagementCommon/datamanagementbuslistener.py -text SAS/DataManagement/DataManagementCommon/getPathForTask -text SAS/DataManagement/DataManagementCommon/path.py -text SAS/DataManagement/StorageQueryService/CMakeLists.txt -text diff --git a/SAS/DataManagement/CleanupService/CMakeLists.txt b/SAS/DataManagement/CleanupService/CMakeLists.txt index 95ba5bfab4e..9d37337b1ad 100644 --- a/SAS/DataManagement/CleanupService/CMakeLists.txt +++ b/SAS/DataManagement/CleanupService/CMakeLists.txt @@ -10,7 +10,6 @@ set(_py_files config.py rpc.py service.py - cleanupbuslistener.py ) python_install(${_py_files} DESTINATION lofar/sas/datamanagement/cleanup) diff --git a/SAS/DataManagement/CleanupService/config.py b/SAS/DataManagement/CleanupService/config.py index 30b010909a9..0e8dafdf6f6 100644 --- a/SAS/DataManagement/CleanupService/config.py +++ b/SAS/DataManagement/CleanupService/config.py @@ -5,7 +5,3 @@ from lofar.messaging import adaptNameToEnvironment DEFAULT_BUSNAME = adaptNameToEnvironment('lofar.dm.command') DEFAULT_SERVICENAME = 'CleanupService' - -DEFAULT_DM_NOTIFICATION_BUSNAME = adaptNameToEnvironment('lofar.dm.notification') -DEFAULT_DM_NOTIFICATION_PREFIX = 'DM.' -DEFAULT_DM_NOTIFICATION_SUBJECTS=DEFAULT_DM_NOTIFICATION_PREFIX+'*' diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index 7e4bba458ae..b59fc9f0537 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -16,7 +16,7 @@ from lofar.common.util import waitForInterrupt from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT from lofar.sas.datamanagement.common.path import PathResolver from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME -from lofar.sas.datamanagement.cleanup.config import DEFAULT_DM_NOTIFICATION_BUSNAME, DEFAULT_DM_NOTIFICATION_PREFIX +from lofar.sas.datamanagement.common.config import DEFAULT_DM_NOTIFICATION_BUSNAME, DEFAULT_DM_NOTIFICATION_PREFIX from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME diff --git a/SAS/DataManagement/DataManagementCommon/CMakeLists.txt b/SAS/DataManagement/DataManagementCommon/CMakeLists.txt index 7740f62346c..fbe4a2ccc0c 100644 --- a/SAS/DataManagement/DataManagementCommon/CMakeLists.txt +++ b/SAS/DataManagement/DataManagementCommon/CMakeLists.txt @@ -9,6 +9,7 @@ set(_py_files __init__.py config.py path.py + datamanagementbuslistener.py ) lofar_add_bin_scripts(getPathForTask) diff --git a/SAS/DataManagement/DataManagementCommon/config.py b/SAS/DataManagement/DataManagementCommon/config.py index bda8673e0b3..f008c9f12ff 100644 --- a/SAS/DataManagement/DataManagementCommon/config.py +++ b/SAS/DataManagement/DataManagementCommon/config.py @@ -1,4 +1,10 @@ #!/usr/bin/python # $Id$ +from lofar.messaging import adaptNameToEnvironment + CEP4_DATA_MOUNTPOINT='/data' + +DEFAULT_DM_NOTIFICATION_BUSNAME = adaptNameToEnvironment('lofar.dm.notification') +DEFAULT_DM_NOTIFICATION_PREFIX = 'DM.' +DEFAULT_DM_NOTIFICATION_SUBJECTS=DEFAULT_DM_NOTIFICATION_PREFIX+'*' diff --git a/SAS/DataManagement/CleanupService/cleanupbuslistener.py b/SAS/DataManagement/DataManagementCommon/datamanagementbuslistener.py similarity index 70% rename from SAS/DataManagement/CleanupService/cleanupbuslistener.py rename to SAS/DataManagement/DataManagementCommon/datamanagementbuslistener.py index bc7882e9081..02304b53924 100644 --- a/SAS/DataManagement/CleanupService/cleanupbuslistener.py +++ b/SAS/DataManagement/DataManagementCommon/datamanagementbuslistener.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# CleanupBusListener.py +# DataManagementBusListener.py # # Copyright (C) 2015 # ASTRON (Netherlands Institute for Radio Astronomy) @@ -22,7 +22,7 @@ # from lofar.messaging.messagebus import AbstractBusListener -from lofar.sas.datamanagement.cleanup.config import DEFAULT_DM_NOTIFICATION_BUSNAME, DEFAULT_DM_NOTIFICATION_SUBJECTS +from lofar.sas.datamanagement.common.config import DEFAULT_DM_NOTIFICATION_BUSNAME, DEFAULT_DM_NOTIFICATION_SUBJECTS from lofar.common.util import waitForInterrupt import qpid.messaging @@ -30,12 +30,12 @@ import logging logger = logging.getLogger(__name__) -class CleanupBusListener(AbstractBusListener): +class DataManagementBusListener(AbstractBusListener): def __init__(self, busname=DEFAULT_DM_NOTIFICATION_BUSNAME, subjects=DEFAULT_DM_NOTIFICATION_SUBJECTS, broker=None, **kwargs): self.subject_prefix = (subjects.split('.')[0]+'.') if '.' in subjects else '' address = "%s/%s" % (busname, subjects) - super(CleanupBusListener, self).__init__(address, broker, **kwargs) + super(DataManagementBusListener, self).__init__(address, broker, **kwargs) def _handleMessage(self, msg): logger.info("on%s: %s" % (msg.subject.replace(self.subject_prefix, ''), str(msg.content).replace('\n', ' '))) @@ -44,8 +44,10 @@ class CleanupBusListener(AbstractBusListener): self.onTaskDeleted(msg.content.get('otdb_id'), msg.content.get('paths')) elif msg.subject == '%sPathDeleted' % self.subject_prefix: self.onPathDeleted(msg.content.get('path')) + elif msg.subject == '%sDiskUsageChanged' % self.subject_prefix: + self.onDiskUsageChanged(msg.content.get('path'), msg.content.get('disk_usage')) else: - logger.error("CleanupBusListener.handleMessage: unknown subject: %s" %str(msg.subject)) + logger.error("DataManagementBusListener.handleMessage: unknown subject: %s" %str(msg.subject)) def onTaskDeleted(self, otdb_id, paths): '''onTaskDeleted is called upon receiving a TaskDeleted message. @@ -58,10 +60,16 @@ class CleanupBusListener(AbstractBusListener): :param path: path of the deleted task''' pass + def onDiskUsageChanged(self, path, disk_usage): + '''onDiskUsageChanged is called upon receiving a DiskUsageChanged message. + :param path: path for which the disk_usage changed + :param path: the disk_usage of the path''' + pass + if __name__ == '__main__': - with CleanupBusListener(broker=None) as listener: + with DataManagementBusListener(broker=None) as listener: waitForInterrupt() -__all__ = ["CleanupBusListener"] +__all__ = ["DataManagementBusListener"] diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index dcab250bd68..a2d30f85a3c 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -12,15 +12,16 @@ from optparse import OptionParser from threading import Thread, RLock import os.path +from lofar.messaging import EventMessage, ToBus from lofar.common.util import humanreadablesize from lofar.sas.datamanagement.storagequery.diskusage import getDiskUsageForPath as du_getDiskUsageForPath from lofar.sas.datamanagement.storagequery.diskusage import DiskUsage -from lofar.sas.datamanagement.cleanup.cleanupbuslistener import CleanupBusListener +from lofar.sas.datamanagement.common.datamanagementbuslistener import DataManagementBusListener from lofar.sas.otdb.OTDBBusListener import OTDBBusListener from lofar.common.util import waitForInterrupt from lofar.sas.datamanagement.common.config import CEP4_DATA_MOUNTPOINT from lofar.sas.otdb.config import DEFAULT_OTDB_NOTIFICATION_BUSNAME, DEFAULT_OTDB_NOTIFICATION_SUBJECT -from lofar.sas.datamanagement.cleanup.config import DEFAULT_DM_NOTIFICATION_BUSNAME, DEFAULT_DM_NOTIFICATION_SUBJECTS +from lofar.sas.datamanagement.common.config import DEFAULT_DM_NOTIFICATION_BUSNAME, DEFAULT_DM_NOTIFICATION_PREFIX from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME @@ -39,20 +40,25 @@ class CacheManager: otdb_notification_busname=DEFAULT_OTDB_NOTIFICATION_BUSNAME, otdb_notification_subject=DEFAULT_OTDB_NOTIFICATION_SUBJECT, dm_notification_busname=DEFAULT_DM_NOTIFICATION_BUSNAME, - dm_notification_subjects=DEFAULT_DM_NOTIFICATION_SUBJECTS, + dm_notification_prefix=DEFAULT_DM_NOTIFICATION_PREFIX, broker=None): self.otdb_listener = OTDBBusListener(busname=otdb_notification_busname, subject=otdb_notification_subject, - broker=broker) + broker=broker, + numthreads=2) self.otdb_listener.onObservationAborted = self.onObservationAborted self.otdb_listener.onObservationFinished = self.onObservationFinished - self.cleanup_listener = CleanupBusListener(busname=dm_notification_busname, - subjects=dm_notification_subjects, - broker=broker) + self.dm_listener = DataManagementBusListener(busname=dm_notification_busname, + subjects=dm_notification_prefix + '*', + broker=broker, + numthreads=2) - self.cleanup_listener.onTaskDeleted = self.onTaskDeleted + self.dm_listener.onTaskDeleted = self.onTaskDeleted + + self.notification_prefix = dm_notification_prefix + self.event_bus = ToBus(dm_notification_busname, broker=broker) self._updateCacheThread = None self._updateCacheThreadRunning = False @@ -69,6 +75,17 @@ class CacheManager: mom_servicename=mom_servicename, broker=broker) + def _sendDiskUsageChangedNotification(self, path, disk_usage): + try: + msg = EventMessage(context=self.notification_prefix + 'DiskUsageChanged', + content={ 'path': path, + 'disk_usage': disk_usage, + 'disk_usage_readable': humanreadablesize(disk_usage) }) + logger.info('Sending notification: %s', str(msg).replace('\n', ' ')) + self.event_bus.send(msg) + except Exception as e: + logger.error(str(e)) + def _readCacheFromDisk(self): # maybe this cache on disk is slow, if so, revert to proper db solution try: @@ -91,29 +108,37 @@ class CacheManager: logger.error("Error while writing du cache: %s", e) def _updateCache(self, du_result): - if du_result['found']: - du_result['cache_timestamp'] = datetime.datetime.utcnow() - with self._cacheLock: - self._cache['paths'][du_result['path']] = du_result + if not 'path' in du_result: + return + + with self._cacheLock: + path = du_result['path'] + paths_cache = self._cache['paths'] + + if du_result['found']: + if not path in paths_cache or paths_cache[path]['disk_usage'] != du_result['disk_usage']: + paths_cache[path] = du_result + self._sendDiskUsageChangedNotification(path, du_result['disk_usage']) + + paths_cache[path]['cache_timestamp'] = datetime.datetime.utcnow() + if 'otdb_id' in du_result: self._cache['otdb_ids'][du_result['otdb_id']] = du_result + else: + if path in paths_cache: + logger.info('removing path \'%s\' from cache', path) + del paths_cache[path] + self._sendDiskUsageChangedNotification(path, 0) - self._writeCacheToDisk() - else: - with self._cacheLock: - if 'path' in du_result: - path = du_result['path'] - path_cache = self._cache['paths'] - if 'path' in path_cache: - logger.info('removing path \'%s\' from cache', path) - del path_cache[path] if 'otdb_id' in du_result: otdb_id = du_result['otdb_id'] otdb_cache = self._cache['otdb_ids'] - if 'otdb_id' in otdb_cache: + if otdb_id in otdb_cache: logger.info('removing otdb_id %s from cache', otdb_id) del otdb_cache[otdb_id] + self._writeCacheToDisk() + if du_result.get('path') == self.disk_usage.path_resolver.projects_path: self._updateProjectsDiskUsageInRADB() @@ -131,7 +156,7 @@ class CacheManager: old_entries = {path:du_result for path,du_result in self._cache['paths'].items() if now - du_result['cache_timestamp'] > MAX_CACHE_ENTRY_AGE} if old_entries: - logger.info('updating %s old cache entries', len(old_entries)) + logger.info('%s old cache entries need to be updated', len(old_entries)) # sort them oldest to newest old_entries = sorted(old_entries.items(), key=lambda x: x[1]['cache_timestamp']) @@ -140,7 +165,7 @@ class CacheManager: for path, du_result in old_entries: try: - logger.info('updating old entry in cache: %s', path) + logger.info('examining old entry in cache: %s', path) result = du_getDiskUsageForPath(path) self._updateCache(result) except Exception as e: @@ -152,6 +177,7 @@ class CacheManager: if datetime.datetime.utcnow() - cacheUpdateStart > datetime.timedelta(minutes=5): # break out of cache update loop if full update takes more than 5min # next loop we'll start with the oldest cache entries again + logger.info('skipping remaining old cache entries updates, they will be updated next time') break for i in range(60): @@ -189,6 +215,7 @@ class CacheManager: def open(self): self.disk_usage.open() + self.event_bus.open() self._updateCacheThread = Thread(target=self._updateOldEntriesInCache) self._updateCacheThread.daemon = True @@ -196,14 +223,15 @@ class CacheManager: self._updateCacheThread.start() self.otdb_listener.start_listening() - self.cleanup_listener.start_listening() + self.dm_listener.start_listening() def close(self): self.otdb_listener.stop_listening() - self.cleanup_listener.stop_listening() + self.dm_listener.stop_listening() self._updateCacheThreadRunning = False self._updateCacheThread.join() + self.event_bus.close() self.disk_usage.close() def __enter__(self): -- GitLab From 1a15b11dd4c98ee48bdd87601c653cf5b6b43c32 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 7 Jul 2016 14:12:24 +0000 Subject: [PATCH 495/933] Task #9351 #9353 #9355: let the cache be updated in the background only, yield cache results immediately in getDiskUsageForPath, even if they are old --- SAS/DataManagement/StorageQueryService/cache.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index a2d30f85a3c..6b3bd6148e1 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -297,8 +297,6 @@ class CacheManager: needs_cache_update = False with self._cacheLock: needs_cache_update |= path not in self._cache['paths'] - if path in self._cache['paths']: - needs_cache_update |= datetime.datetime.utcnow() - self._cache['paths'][path]['cache_timestamp'] > MAX_CACHE_ENTRY_AGE if needs_cache_update: logger.info("cache update needed for %s", path) -- GitLab From 8a9cd166d9ad5f237a331b43a6769eff29822081 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 7 Jul 2016 14:39:10 +0000 Subject: [PATCH 496/933] Task #9351 #9353 #9355: default timeout of 3min --- SAS/DataManagement/CleanupService/rpc.py | 2 +- SAS/DataManagement/StorageQueryService/rpc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/DataManagement/CleanupService/rpc.py b/SAS/DataManagement/CleanupService/rpc.py index f4024ca671e..590c1e62ae0 100644 --- a/SAS/DataManagement/CleanupService/rpc.py +++ b/SAS/DataManagement/CleanupService/rpc.py @@ -10,7 +10,7 @@ class CleanupRPC(RPCWrapper): def __init__(self, busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None): - super(CleanupRPC, self).__init__(busname, servicename, broker) + super(CleanupRPC, self).__init__(busname, servicename, broker, timeout=18000) def getPathForOTDBId(self, otdb_id): return self.rpc('GetPathForOTDBId', otdb_id=otdb_id) diff --git a/SAS/DataManagement/StorageQueryService/rpc.py b/SAS/DataManagement/StorageQueryService/rpc.py index 6aba330c6ba..bf8bbd2036a 100644 --- a/SAS/DataManagement/StorageQueryService/rpc.py +++ b/SAS/DataManagement/StorageQueryService/rpc.py @@ -12,7 +12,7 @@ class StorageQueryRPC(RPCWrapper): def __init__(self, busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None): - super(StorageQueryRPC, self).__init__(busname, servicename, broker, timeout=6000) + super(StorageQueryRPC, self).__init__(busname, servicename, broker, timeout=18000) def _convertTimestamps(self, result): if isinstance(result, dict): -- GitLab From d816fa494eb1205bf768cb7c0ae84a31b7817518 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 7 Jul 2016 14:39:46 +0000 Subject: [PATCH 497/933] Task #9351 #9353 #9355: only add scratch paths for pipelines if they exist on disk --- SAS/DataManagement/DataManagementCommon/path.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index ecace76aa33..98c60842722 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -75,9 +75,15 @@ class PathResolver: path_result = {'found': True, 'message': '', 'path': task_data_path} if task['type'] == 'pipeline': + path_result['scratch_paths'] = [] + scratch_path = os.path.join(self.scratch_path, 'Observation%s' % task['otdb_id']) + if self.pathExists(scratch_path): + path_result['scratch_paths'].append(scratch_path) + share_path = os.path.join(self.share_path, 'Observation%s' % task['otdb_id']) - path_result['scratch_paths'] = [scratch_path, share_path] + if self.pathExists(share_path): + path_result['scratch_paths'].append(share_path) return path_result -- GitLab From 3dcf9ec82f3ad47eac3e18b853b45a274abf26b0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 7 Jul 2016 14:40:13 +0000 Subject: [PATCH 498/933] Task #9351 #9353 #9355: log message --- SAS/DataManagement/StorageQueryService/cache.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index 6b3bd6148e1..e7e8cd38628 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -307,7 +307,9 @@ class CacheManager: if path in self._cache['paths']: result = self._cache['paths'][path] else: - result = { 'found': False, 'path':path } + result = { 'found': False, 'path':path, 'message': 'unknown error' } + if not self.disk_usage.path_resolver.pathExists(path): + result['message'] = 'No such path: %s' % path logger.info('cache.getDiskUsageForPath result: %s' % result) return result -- GitLab From a3ddbf976d7fda00fdc0f2d3a0cbb977665bba10 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 7 Jul 2016 14:40:40 +0000 Subject: [PATCH 499/933] Task #9351 #9353 #9355: delete via ssh and normal rm -rf --- SAS/DataManagement/CleanupService/service.py | 23 ++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index b59fc9f0537..e9f0d060f84 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -5,7 +5,8 @@ ''' import logging import os.path -from shutil import rmtree +import socket +import subprocess from optparse import OptionParser from lofar.messaging import Service from lofar.messaging import EventMessage, ToBus @@ -164,23 +165,23 @@ class CleanupHandler(MessageHandlerInterface): logger.info("Attempting to delete %s", path) - failed_paths = set() - def onerror(func, failed_path, excinfo): - logger.warning("Failed to delete %s", failed_path) - failed_paths.add(failed_path) + cmd = ['rm', '-rf', path] + hostname = socket.gethostname() + if not 'mgmt0' in hostname: + cmd = ['ssh', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd + logger.info(' '.join(cmd)) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() - rmtree(path, onerror=onerror) - - if not failed_paths: + if proc.returncode == 0: message = "Deleted '%s'" % (path) logger.info(message) self._sendPathDeletedNotification(path) return {'deleted': True, 'message': message, 'path': path} return {'deleted': False, - 'message': 'Failed to delete one or more files or directories:\n - %s' % '\n - '.join(failed_paths), - 'path': path, - 'failed_paths' : list(failed_paths)} + 'message': 'Failed to delete (part of) %s' % path, + 'path': path } def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, mountpoint=CEP4_DATA_MOUNTPOINT, verbose=False, -- GitLab From 2a31b1163baa75e18bd2c41c9d53048a23c38c6c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 7 Jul 2016 14:50:19 +0000 Subject: [PATCH 500/933] Task #9351 #9353 #9355: always show deleted message --- .../lib/static/app/controllers/cleanupcontroller.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js index 7ed8d63afd9..68d18145ecf 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -33,9 +33,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h $http.delete('/rest/tasks/' + task.id + '/cleanup', {data: params}).error(function(result) { console.log("Error. Could cleanup data for task " + task.id + ", " + result); }).success(function(result) { - if(!result.deleted) { - alert(result.message); - } + alert(result.message); }); }; -- GitLab From b43d287b88a493e904e77b69d2de1230cd02c61a Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 7 Jul 2016 14:50:38 +0000 Subject: [PATCH 501/933] Task #9351 #9353 #9355: hide copy task entry in conext menu --- .../lib/static/app/controllers/gridcontroller.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 6c5b2f8bd3c..eef2585271d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -272,12 +272,12 @@ gridControllerMod.directive('contextMenu', ['$document', function($document) { var contextmenuElement = angular.element('<div id="grid-context-menu"></div>'); var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>'); contextmenuElement.append(ulElement); - var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); - ulElement.append(liElement); - liElement.on('click', function() { - closeContextMenu(); - dataService.copyTask(task); - }); +// var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); +// ulElement.append(liElement); +// liElement.on('click', function() { +// closeContextMenu(); +// dataService.copyTask(task); +// }); var liElement = angular.element('<li><a href="#">Delete data</a></li>'); ulElement.append(liElement); -- GitLab From 175b6164fabef8795c6026e3296e2e7ef5aeedce Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 8 Jul 2016 08:45:04 +0000 Subject: [PATCH 502/933] Task #9621: Create symlinks to simulate moved input in imager_prepare and long_baseline --- CEP/Pipeline/recipes/sip/nodes/imager_prepare.py | 3 ++- CEP/Pipeline/recipes/sip/nodes/long_baseline.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py b/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py index 5abce8b6e7c..eb4dd79e1fa 100644 --- a/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py +++ b/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py @@ -181,7 +181,8 @@ class imager_prepare(LOFARnodeTCP): input_item.host, input_item.file), "{0}".format(processed_ms_dir)] if self.globalfs or input_item.host == "localhost": - command = ["cp", "-r", "{0}".format(input_item.file), + # symlinking is enough + command = ["ln", "-sfT", "{0}".format(input_item.file), "{0}".format(processed_ms_dir)] self.logger.debug("executing: " + " ".join(command)) diff --git a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py index 2fe0aa3e405..42f667a80e3 100644 --- a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py +++ b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py @@ -199,7 +199,8 @@ class long_baseline(LOFARnodeTCP): input_item.host, input_item.file), "{0}".format(processed_ms_dir)] if self.globalfs or input_item.host == "localhost": - command = ["cp", "-r", "{0}".format(input_item.file), + # symlinking is enough + command = ["ln", "-sfT", "{0}".format(input_item.file), "{0}".format(processed_ms_dir)] self.logger.debug("executing: " + " ".join(command)) -- GitLab From 379b8df537e75f7508735795cfbb3acfd22b9176 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 8 Jul 2016 08:46:47 +0000 Subject: [PATCH 503/933] Task #9621: Ported copier mv/ln semantics to release 2.17 --- .../recipes/sip/bin/calibration_pipeline.py | 6 ++++-- .../recipes/sip/bin/msss_calibrator_pipeline.py | 3 ++- CEP/Pipeline/recipes/sip/master/copier.py | 7 ++++++- CEP/Pipeline/recipes/sip/nodes/copier.py | 17 +++++++++-------- .../recipes/sip/nodes/imager_prepare.py | 3 ++- CEP/Pipeline/recipes/sip/nodes/long_baseline.py | 3 ++- 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/calibration_pipeline.py b/CEP/Pipeline/recipes/sip/bin/calibration_pipeline.py index b4b3a869030..18edcaec53a 100755 --- a/CEP/Pipeline/recipes/sip/bin/calibration_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/calibration_pipeline.py @@ -250,7 +250,8 @@ class calibration_pipeline(control): mapfile_source=bbs_mapfile, mapfile_target=output_correlated_mapfile, mapfiles_dir=mapfile_dir, - mapfile=output_correlated_mapfile + mapfile=output_correlated_mapfile, + allow_move=True ) with duration(self, "copier"): @@ -258,7 +259,8 @@ class calibration_pipeline(control): mapfile_source=parmdb_mapfile, mapfile_target=output_instrument_mapfile, mapfiles_dir=mapfile_dir, - mapfile=output_instrument_mapfile + mapfile=output_instrument_mapfile, + allow_move=True ) # ********************************************************************* diff --git a/CEP/Pipeline/recipes/sip/bin/msss_calibrator_pipeline.py b/CEP/Pipeline/recipes/sip/bin/msss_calibrator_pipeline.py index 0c627b6cb56..eb641e574a1 100755 --- a/CEP/Pipeline/recipes/sip/bin/msss_calibrator_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/msss_calibrator_pipeline.py @@ -273,7 +273,8 @@ class msss_calibrator_pipeline(control): mapfile_source=bbs_mapfile, mapfile_target=output_correlated_mapfile, mapfiles_dir=mapfile_dir, - mapfile=output_correlated_mapfile + mapfile=output_correlated_mapfile, + allow_move=True ) # ********************************************************************* diff --git a/CEP/Pipeline/recipes/sip/master/copier.py b/CEP/Pipeline/recipes/sip/master/copier.py index c15dba135c9..c9295910b2f 100644 --- a/CEP/Pipeline/recipes/sip/master/copier.py +++ b/CEP/Pipeline/recipes/sip/master/copier.py @@ -142,6 +142,11 @@ class copier(MasterNodeInterface): default=True, help="Allow renaming of basename at target location" ), + 'allow_move': ingredient.BoolField( + '--allow-move', + default=True, + help="Allow moving files instead of copying them" + ), 'mapfiles_dir': ingredient.StringField( '--mapfiles-dir', help="Path of directory, shared by all nodes, which will be used" @@ -248,7 +253,7 @@ class copier(MasterNodeInterface): # Run the compute nodes with the node specific mapfiles for source, target in zip(self.source_map, self.target_map): - args = [source.host, source.file, target.file, globalfs] + args = [source.host, source.file, target.file, globalfs, self.inputs['allow_move']] self.append_job(target.host, args) # start the jobs, return the exit status. diff --git a/CEP/Pipeline/recipes/sip/nodes/copier.py b/CEP/Pipeline/recipes/sip/nodes/copier.py index b31d2c8167c..8a30b7a5178 100644 --- a/CEP/Pipeline/recipes/sip/nodes/copier.py +++ b/CEP/Pipeline/recipes/sip/nodes/copier.py @@ -21,15 +21,15 @@ class copier(LOFARnodeTCP): """ Node script for copying files between nodes. See master script for full public interface """ - def run(self, source_node, source_path, target_path, globalfs): + def run(self, source_node, source_path, target_path, globalfs, allow_move): self.globalfs = globalfs # Time execution of this job with log_time(self.logger): return self._copy_single_file_using_rsync( - source_node, source_path, target_path) + source_node, source_path, target_path, allow_move) def _copy_single_file_using_rsync(self, source_node, source_path, - target_path): + target_path, allow_move): # assure that target dir exists (rsync creates it but.. # an error in the python code will throw a nicer error message = "No write acces to target path: {0}".format( @@ -51,11 +51,12 @@ class copier(LOFARnodeTCP): # construct copy command: Copy to the dir - # if process runs on local host use a simple copy command. - if self.globalfs: - command = ["lfs", "cp", "-r","{0}".format(source_path),"{0}".format(target_path)] - elif source_node=="localhost": - command = ["cp", "-r","{0}".format(source_path),"{0}".format(target_path)] + # if process runs on local host use a simple copy or move command. + if self.globalfs or source_node == "localhost": + if allow_move: + command = ["mv", "{0}".format(source_path), "{0}".format(target_path)] + else: + command = ["cp", "-r", "{0}".format(source_path), "{0}".format(target_path)] else: command = ["rsync", "-r", "{0}:{1}/".format(source_node, source_path), diff --git a/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py b/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py index 5abce8b6e7c..eb4dd79e1fa 100644 --- a/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py +++ b/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py @@ -181,7 +181,8 @@ class imager_prepare(LOFARnodeTCP): input_item.host, input_item.file), "{0}".format(processed_ms_dir)] if self.globalfs or input_item.host == "localhost": - command = ["cp", "-r", "{0}".format(input_item.file), + # symlinking is enough + command = ["ln", "-sfT", "{0}".format(input_item.file), "{0}".format(processed_ms_dir)] self.logger.debug("executing: " + " ".join(command)) diff --git a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py index 2fe0aa3e405..42f667a80e3 100644 --- a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py +++ b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py @@ -199,7 +199,8 @@ class long_baseline(LOFARnodeTCP): input_item.host, input_item.file), "{0}".format(processed_ms_dir)] if self.globalfs or input_item.host == "localhost": - command = ["cp", "-r", "{0}".format(input_item.file), + # symlinking is enough + command = ["ln", "-sfT", "{0}".format(input_item.file), "{0}".format(processed_ms_dir)] self.logger.debug("executing: " + " ".join(command)) -- GitLab From a839c17272dbb78246369bbada405b85da765d4b Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 8 Jul 2016 10:03:21 +0000 Subject: [PATCH 504/933] Task #9623: Fixed SLURM nodes range, and use 12-25 as a default --- MAC/Services/src/PipelineControl.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 0f9e7682179..d1ba3387293 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -144,21 +144,14 @@ class Parset(dict): def processingNumberOfTasks(self): """ Parse the number of nodes to allocate from "Observation.Cluster.ProcessingCluster.numberOfTasks", - which can have either the format "{number}" or "{min},{max}". """ + which can have either the format "{number}" or "{min}-{max}". """ + defaultValue = "12-25" parsetValue = self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfTasks"].strip() - # force a number of nodes between bounds applicable for this cluster - def bound(n): - return max(1, min(48, n)) - - if "," in parsetValue: + if "-" in parsetValue: # min,max - _min, _max = parsetValue.split(",") - - # apply bound - _min = bound(_min) - _max = bound(_max) + _min, _max = parsetValue.split("-") # collapse if not min <= max if _min > _max: @@ -167,10 +160,11 @@ class Parset(dict): result = "%s,%s" % (_min, _max) else: # plain number - result = int(parsetValue) or 24 + result = int(parsetValue) # apply bound - result = bound(result) + if result < 1 or result > 50: + result = defaultValue if result != parsetValue: logger.error('Invalid Observation.Cluster.ProcessingCluster.numberOfTasks: %s, defaulting to %s', parsetValue, result) -- GitLab From fac507670edd26e228015ca28818ec7346d69ca6 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 8 Jul 2016 10:11:04 +0000 Subject: [PATCH 505/933] Task #9623: Added omitted log parameter --- MAC/Services/src/PipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index d1ba3387293..608b519e649 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -504,7 +504,7 @@ class PipelineControl(OTDBBusListener): # to be cancelled as well. if not self.slurm.isQueuedOrRunning(otdbId): - logger.info("_stopPipeline: Job %s not running") + logger.info("_stopPipeline: Job %s not running", otdbId) return def cancel(jobName): -- GitLab From 7a84560e1b1d0f99130e3b13050835507693488b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 8 Jul 2016 10:35:56 +0000 Subject: [PATCH 506/933] Task #9351 #9353 #9355: fixes in kwargs parsing --- .../ResourceAssignmentService/service.py | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/service.py b/SAS/ResourceAssignment/ResourceAssignmentService/service.py index c48ae9867ec..c72e9e79e77 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/service.py @@ -151,12 +151,11 @@ class RADBHandler(MessageHandlerInterface): updated = self.radb.updateResourceClaim(id, resource_id=kwargs.get('resource_id'), task_id=kwargs.get('task_id'), - starttime=kwargs['starttime'].datetime() if 'starttime' in kwargs else None, - endtime=kwargs['endtime'].datetime() if 'endtime' in kwargs else None, + starttime=kwargs.get('starttime').datetime() if kwargs.get('starttime') else None, + endtime=kwargs.get('endtime').datetime() if kwargs.get('endtime') else None, status=kwargs.get('status_id', kwargs.get('status')), session_id=kwargs.get('session_id'), claim_size=kwargs.get('claim_size'), - nr_of_parts=kwargs.get('nr_of_parts'), username=kwargs.get('username'), user_id=kwargs.get('user_id')) return {'id': id, 'updated': updated} @@ -164,17 +163,10 @@ class RADBHandler(MessageHandlerInterface): def _updateTaskAndResourceClaims(self, **kwargs): logger.info('UpdateTaskAndResourceClaims: %s' % dict({k:v for k,v in kwargs.items() if v != None})) task_id = kwargs['task_id'] - starttime = kwargs.get('starttime') - if starttime: - starttime = starttime.datetime(); - - endtime = kwargs.get('endtime') - if endtime: - endtime = endtime.datetime(); updated = self.radb.updateTaskAndResourceClaims(task_id, - starttime=starttime, - endtime=endtime, + starttime=kwargs.get('starttime').datetime() if kwargs.get('starttime') else None, + endtime=kwargs.get('endtime').datetime() if kwargs.get('endtime') else None, task_status=kwargs.get('task_status_id', kwargs.get('task_status')), claim_status=kwargs.get('claim_status_id', kwargs.get('claim_status')), session_id=kwargs.get('session_id'), @@ -284,14 +276,14 @@ class RADBHandler(MessageHandlerInterface): kwargs['otdb_id'], kwargs['task_status'], kwargs['task_type'], - kwargs['starttime'].datetime(), - kwargs['endtime'].datetime(), + kwargs.get('starttime').datetime() if kwargs.get('starttime') else None, + kwargs.get('endtime').datetime() if kwargs.get('endtime') else None, kwargs['content']) def _insertSpecification(self, **kwargs): logger.info('InsertSpecification: %s' % dict({k:v for k,v in kwargs.items() if v != None})) - specification_id = self.radb.insertSpecification(kwargs['starttime'].datetime(), - kwargs['endtime'].datetime(), + specification_id = self.radb.insertSpecification(kwargs.get('starttime').datetime() if kwargs.get('starttime') else None, + kwargs.get('endtime').datetime() if kwargs.get('endtime') else None, kwargs['content']) return {'id':specification_id} -- GitLab From 02eb2a6993e189517625ac5999aef336a1dc5768 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 8 Jul 2016 10:37:50 +0000 Subject: [PATCH 507/933] Task #9351 #9353 #9355: _endStorageResourceClaim updates storage claim endtime when task data deleted --- SAS/DataManagement/CleanupService/service.py | 36 ++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index e9f0d060f84..a6e04d60be2 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -7,6 +7,7 @@ import logging import os.path import socket import subprocess +from datetime import datetime from optparse import OptionParser from lofar.messaging import Service from lofar.messaging import EventMessage, ToBus @@ -66,7 +67,7 @@ class CleanupHandler(MessageHandlerInterface): def _sendPathDeletedNotification(self, path): try: msg = EventMessage(context=self.notification_prefix + 'PathDeleted', content={ 'path': path }) - logger.info('Sending notification: %s', str(msg).replace('\n', ' ')) + logger.info('Sending notification with subject %s to %s: %s', msg.subject, self.event_bus.address, msg.content) self.event_bus.send(msg) except Exception as e: logger.error(str(e)) @@ -76,7 +77,7 @@ class CleanupHandler(MessageHandlerInterface): msg = EventMessage(context=self.notification_prefix + 'TaskDeleted', content={'otdb_id':otdb_id, 'paths': paths, 'message': message}) - logger.info('Sending notification: %s', str(msg).replace('\n', ' ')) + logger.info('Sending notification with subject %s to %s: %s', msg.subject, self.event_bus.address, msg.content) self.event_bus.send(msg) except Exception as e: logger.error(str(e)) @@ -118,10 +119,35 @@ class CleanupHandler(MessageHandlerInterface): self._sendTaskDeletedNotification(otdb_id, rm_result['paths'], rm_result['message']) + self._endStorageResourceClaim(otdb_id) + return rm_result return {'deleted': False, 'message': path_result['message']} + def _endStorageResourceClaim(self, otdb_id): + try: + #check if all data has actually been removed, + #and adjust end time of claim on storage + path_result = self.path_resolver.getPathForOTDBId(otdb_id) + if path_result['found']: + path = path_result['path'] + + if not self.path_resolver.pathExists(path): + # data was actually deleted + #update resource claim + radbrpc = self.path_resolver.radbrpc + storage_resources = radbrpc.getResources(resource_types='storage') + cep4_storage_resource = next(x for x in storage_resources if 'cep4' in x['name']) + task = radbrpc.getTask(otdb_id=otdb_id) + if task: + claims = radbrpc.getResourceClaims(task_ids=task['id'], resource_type='storage') + cep4_storage_claim_ids = [c['id'] for c in claims if c['resource_id'] == cep4_storage_resource['id']] + for claim_id in cep4_storage_claim_ids: + radbrpc.updateResourceClaim(claim_id, endtime=datetime.utcnow()) + except Exception as e: + logger.error(str(e)) + def _removePath(self, path): logger.info("Remove path: %s" % (path,)) @@ -202,6 +228,10 @@ def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, brok 'broker':broker}) def main(): + # make sure we run in UTC timezone + import os + os.environ['TZ'] = 'UTC' + # Check the invocation arguments parser = OptionParser("%prog [options]", description='runs the cleanup service') @@ -216,9 +246,9 @@ def main(): parser.add_option("--mom_servicename", dest="mom_servicename", type="string", default=DEFAULT_MOMQUERY_SERVICENAME, help="Name of the MoM service, default: %default") (options, args) = parser.parse_args() - setQpidLogLevel(logging.INFO) logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.DEBUG if options.verbose else logging.INFO) + setQpidLogLevel(logging.INFO) with createService(busname=options.busname, servicename=options.servicename, -- GitLab From 45dd78cb9fc896cf18a0c9ed70dc6301842137f4 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 8 Jul 2016 10:38:13 +0000 Subject: [PATCH 508/933] Task #9351 #9353 #9355: logging --- SAS/DataManagement/StorageQueryService/diskusage.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/SAS/DataManagement/StorageQueryService/diskusage.py b/SAS/DataManagement/StorageQueryService/diskusage.py index e115e96b7eb..96aa0906ecc 100644 --- a/SAS/DataManagement/StorageQueryService/diskusage.py +++ b/SAS/DataManagement/StorageQueryService/diskusage.py @@ -27,8 +27,6 @@ def getDiskUsageForPath(path): proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() - logger.debug(out) - if proc.returncode != 0: logger.error(out + err) return {'found': False, 'path': path, 'message': out} -- GitLab From 4c48445fe511f404e0e8736fff9f7acd4f848e62 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 8 Jul 2016 10:38:49 +0000 Subject: [PATCH 509/933] Task #9351 #9353 #9355: logging --- .../DataManagementCommon/path.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index 98c60842722..390c0ac5d24 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -52,25 +52,26 @@ class PathResolver: self.close() def getPathForRADBId(self, radb_id): - logger.info("Get path for radb_id %s" % (radb_id,)) + logger.debug("Get path for radb_id %s" % (radb_id,)) return self.getPathForTask(radb_id=radb_id) def getPathForMoMId(self, mom_id): - logger.info("Get path for mom_id %s" % (mom_id,)) + logger.debug("Get path for mom_id %s" % (mom_id,)) return self.getPathForTask(mom_id=mom_id) def getPathForOTDBId(self, otdb_id): - logger.info("Get path for otdb_id %s" % (otdb_id,)) + logger.debug("Get path for otdb_id %s" % (otdb_id,)) return self.getPathForTask(otdb_id=otdb_id) def getPathForTask(self, radb_id=None, mom_id=None, otdb_id=None): + logger.info("getPathForTask(radb_id=%s, mom_id=%s, otdb_id=%s)", radb_id, mom_id, otdb_id) '''get the path for a task for either the given radb_id, or for the given mom_id, or for the given otdb_id''' result = self._getProjectPathAndDetails(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) if result['found']: project_path = result['path'] task = result['task'] task_data_path = os.path.join(project_path, 'L%s' % task['otdb_id']) - logger.info("Found path '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (task_data_path, task['otdb_id'], task['mom_id'], task['id'])) + logger.info("found path '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (task_data_path, task['otdb_id'], task['mom_id'], task['id'])) path_result = {'found': True, 'message': '', 'path': task_data_path} @@ -78,15 +79,19 @@ class PathResolver: path_result['scratch_paths'] = [] scratch_path = os.path.join(self.scratch_path, 'Observation%s' % task['otdb_id']) + share_path = os.path.join(self.share_path, 'Observation%s' % task['otdb_id']) + logger.debug("Checking scratch paths %s %s for otdb_id=%s mom_id=%s radb_id=%s" % (scratch_path, share_path, task['otdb_id'], task['mom_id'], task['id'])) + if self.pathExists(scratch_path): path_result['scratch_paths'].append(scratch_path) - share_path = os.path.join(self.share_path, 'Observation%s' % task['otdb_id']) if self.pathExists(share_path): path_result['scratch_paths'].append(share_path) + logger.info("result for getPathForTask(radb_id=%s, mom_id=%s, otdb_id=%s): %s", radb_id, mom_id, otdb_id, path_result) return path_result + logger.warn("result for getPathForTask(radb_id=%s, mom_id=%s, otdb_id=%s): %s", radb_id, mom_id, otdb_id, result) return result def _getProjectPathAndDetails(self, radb_id=None, mom_id=None, otdb_id=None): @@ -114,7 +119,7 @@ class PathResolver: return {'found': False, 'message': message, 'path': None} project_name = mom_details[str(task['mom_id'])]['project_name'] - logger.info("Found project '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (project_name, task['otdb_id'], task['mom_id'], task['id'])) + logger.info("found project '%s' for otdb_id=%s mom_id=%s radb_id=%s" % (project_name, task['otdb_id'], task['mom_id'], task['id'])) project_path = os.path.join(self.projects_path, "_".join(project_name.split())) return {'found': True, 'path': project_path, 'mom_details':mom_details, 'task':task} @@ -155,12 +160,13 @@ class PathResolver: return result def getSubDirectories(self, path): + logger.debug('getSubDirectories(%s)', path) # get the subdirectories of the given path cmd = ['lfs', 'ls', '-l', path] hostname = socket.gethostname() if not 'mgmt0' in hostname: cmd = ['ssh', 'lofarsys@mgmt01.cep4.control.lofar'] + cmd - logger.info(' '.join(cmd)) + logger.debug(' '.join(cmd)) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() @@ -169,15 +175,13 @@ class PathResolver: logger.error(out + err) return {'found': False, 'path': path, 'message': out + err} - logger.debug(out) - # parse out lines = [l.strip() for l in out.split('\n')] dir_lines = [l for l in lines if l.startswith('drwx')] dir_names = [l.split(' ')[-1].strip() for l in dir_lines] result = {'found': True, 'path': path, 'sub_directories': dir_names} - logger.info('result: %s' % result) + logger.info('getSubDirectories(%s) result: %s', path, result) return result def pathExists(self, path): -- GitLab From 728169a8609295363a7d3003adacc2e068708626 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 8 Jul 2016 10:39:44 +0000 Subject: [PATCH 510/933] Task #9351 #9353 #9355: logging --- SAS/DataManagement/StorageQueryService/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py index 950a750de1d..e08c18b2a70 100644 --- a/SAS/DataManagement/StorageQueryService/service.py +++ b/SAS/DataManagement/StorageQueryService/service.py @@ -89,9 +89,9 @@ def main(): parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() - setQpidLogLevel(logging.INFO) logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.DEBUG if options.verbose else logging.INFO) + setQpidLogLevel(logging.INFO) with CacheManager(broker=options.broker) as cache_manager: with createService(busname=options.busname, -- GitLab From 02156a9e266f6ad704e1a9e07525d48c796476ce Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 8 Jul 2016 10:41:35 +0000 Subject: [PATCH 511/933] Task #9351 #9353 #9355: logging. simplified cache (only for paths). get subdirs of task fsater --- .../StorageQueryService/cache.py | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index e7e8cd38628..0e79fed215b 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -65,7 +65,7 @@ class CacheManager: self._continueUpdateCacheThread = False self._cacheLock = RLock() - self._cache = {'paths':{}, 'otdb_ids': {}} + self._cache = {} self._readCacheFromDisk() self.disk_usage = DiskUsage(mountpoint=mountpoint, @@ -93,11 +93,11 @@ class CacheManager: with self._cacheLock: self._cache = eval(file.read()) if not isinstance(self._cache, dict) or 'paths' not in self._cache or 'otdb_ids' not in self._cache: - self._cache = {'paths':{}, 'otdb_ids': {}} + self._cache = {} except Exception as e: logger.error("Error while reading in du cache: %s", e) with self._cacheLock: - self._cache = {'paths':{}, 'otdb_ids': {}} + self._cache = {} def _writeCacheToDisk(self): try: @@ -113,29 +113,20 @@ class CacheManager: with self._cacheLock: path = du_result['path'] - paths_cache = self._cache['paths'] if du_result['found']: - if not path in paths_cache or paths_cache[path]['disk_usage'] != du_result['disk_usage']: - paths_cache[path] = du_result + if not path in self._cache or self._cache[path]['disk_usage'] != du_result['disk_usage']: + logger.debug('updating cache entry: %s', du_result) + self._cache[path] = du_result self._sendDiskUsageChangedNotification(path, du_result['disk_usage']) - paths_cache[path]['cache_timestamp'] = datetime.datetime.utcnow() - - if 'otdb_id' in du_result: - self._cache['otdb_ids'][du_result['otdb_id']] = du_result + self._cache[path]['cache_timestamp'] = datetime.datetime.utcnow() else: - if path in paths_cache: - logger.info('removing path \'%s\' from cache', path) - del paths_cache[path] - self._sendDiskUsageChangedNotification(path, 0) - - if 'otdb_id' in du_result: - otdb_id = du_result['otdb_id'] - otdb_cache = self._cache['otdb_ids'] - if otdb_id in otdb_cache: - logger.info('removing otdb_id %s from cache', otdb_id) - del otdb_cache[otdb_id] + if 'could not resolve hostname' not in du_result['message'].lower() : + if path in self._cache: + logger.info('removing path \'%s\' from cache', path) + del self._cache[path] + self._sendDiskUsageChangedNotification(path, 0) self._writeCacheToDisk() @@ -144,16 +135,15 @@ class CacheManager: def _invalidateCacheEntryForPath(self, path): with self._cacheLock: - if path in self._cache['paths']: - path_cache = self._cache['paths'][path] - path_cache['cache_timestamp'] = path_cache['cache_timestamp'] - MAX_CACHE_ENTRY_AGE + if path in self._cache: + self._cache['cache_timestamp'] = self._cache['cache_timestamp'] - MAX_CACHE_ENTRY_AGE def _updateOldEntriesInCache(self): while self._updateCacheThreadRunning: try: now = datetime.datetime.utcnow() with self._cacheLock: - old_entries = {path:du_result for path,du_result in self._cache['paths'].items() if now - du_result['cache_timestamp'] > MAX_CACHE_ENTRY_AGE} + old_entries = {path:du_result for path,du_result in self._cache.items() if now - du_result['cache_timestamp'] > MAX_CACHE_ENTRY_AGE} if old_entries: logger.info('%s old cache entries need to be updated', len(old_entries)) @@ -167,7 +157,9 @@ class CacheManager: try: logger.info('examining old entry in cache: %s', path) result = du_getDiskUsageForPath(path) - self._updateCache(result) + if result['found']: + logger.info('updating old entry in cache: %s', result) + self._updateCache(result) except Exception as e: logger.error(str(e)) @@ -296,7 +288,7 @@ class CacheManager: logger.info("cache.getDiskUsageForPath(%s)", path) needs_cache_update = False with self._cacheLock: - needs_cache_update |= path not in self._cache['paths'] + needs_cache_update |= path not in self._cache if needs_cache_update: logger.info("cache update needed for %s", path) @@ -304,8 +296,8 @@ class CacheManager: self._updateCache(result) with self._cacheLock: - if path in self._cache['paths']: - result = self._cache['paths'][path] + if path in self._cache: + result = self._cache[path] else: result = { 'found': False, 'path':path, 'message': 'unknown error' } if not self.disk_usage.path_resolver.pathExists(path): @@ -318,7 +310,7 @@ class CacheManager: logger.info("cache.getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) task_du_result = self.getDiskUsageForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) if task_du_result['found']: - task_sd_result = self.disk_usage.path_resolver.getSubDirectoriesForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + task_sd_result = self.disk_usage.path_resolver.getSubDirectories(task_du_result['path']) if task_sd_result['found']: subdir_paths = [os.path.join(task_du_result['path'],sd) for sd in task_sd_result['sub_directories']] @@ -327,9 +319,10 @@ class CacheManager: subdirs_du_result = { sd: self.getDiskUsageForPath(sd) for sd in subdir_paths } result = {'found':True, 'task_directory': task_du_result, 'sub_directories': subdirs_du_result } - logger.info('cache.getDiskUsageForTaskAndSubDirectories result: %s' % result) + logger.info("result for cache.getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s): %s", radb_id, mom_id, otdb_id, result) return result + logger.warn("result for cache.getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s): %s", radb_id, mom_id, otdb_id, task_du_result) return task_du_result def getDiskUsageForProjectDirAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None, project_name=None): -- GitLab From c41b022b1d3a68a1833361c3bd372d4eb3f998a2 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 8 Jul 2016 12:08:35 +0000 Subject: [PATCH 512/933] Task #9623: Improved signal propation in pipeline run script --- MAC/Services/src/PipelineControl.py | 80 ++++++++++++++++++----------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 608b519e649..ae8463a3400 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -430,36 +430,56 @@ class PipelineControl(OTDBBusListener): # Schedule runPipeline.sh logger.info("Scheduling SLURM job for runPipeline.sh") slurm_job_id = self.slurm.submit(self._jobName(otdbId), - # notify that we're running - "{setStatus_active}\n" - # pull docker image from repository on all nodes - "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull" - " --kill-on-bad-exit=0 --wait=0" - " docker pull {repository}/{image}\n" - # put a local tag on the pulled image - "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-tag" - " --kill-on-bad-exit=0 --wait=0" - " docker tag -f {repository}/{image} {image}\n" - # call runPipeline.sh in the image on this node - "docker run --rm" - " --net=host" - " -e LOFARENV={lofarenv}" - " -u $UID" - " -e USER=$USER" - " -e HOME=$HOME" - " -v $HOME/.ssh:$HOME/.ssh:ro" - " -e SLURM_JOB_ID=$SLURM_JOB_ID" - " -v /data:/data" - " {image}" - " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} || exit $?\n" - - # notify that we're tearing down - "{setStatus_completing}\n" - # wait for MoM to pick up feedback before we set finished status - "sleep 60\n" - # if we reached this point, the pipeline ran succesfully - "{setStatus_finished}\n" - .format( +""" +# Run a command, but propagate SIGINT and SIGTERM +function runcmd { + trap 'kill -s SIGTERM $PID' SIGTERM + trap 'kill -s SIGINT $PID' SIGINT + + "$@" & + PID=$! + wait $PID # returns the exit status of "wait" if interrupted + wait $PID # returns the exit status of $PID + RESULT=$? + + trap - SIGTERM SIGINT + + return $RESULT +} + +# print some info +echo Running on $SLURM_NODELIST + +# notify that we're running +runcmd {setStatus_active} + +# pull docker image from repository on all nodes +srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull \ + --kill-on-bad-exit=0 --wait=0 \ + docker pull {repository}/{image} + +# put a local tag on the pulled image +srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-tag \ + --kill-on-bad-exit=0 --wait=0 \ + docker tag -f {repository}/{image} {image} +runcmd docker run --rm --net=host \ + -e LOFARENV={lofarenv} \ + -u $UID -e USER=$USER \ + -e HOME=$HOME -v $HOME/.ssh:$HOME/.ssh:ro \ + -e SLURM_JOB_ID=$SLURM_JOB_ID \ + -v /data:/data \ + {image} \ + runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} + +# notify that we're tearing down +runcmd {setStatus_completing} + +# wait for MoM to pick up feedback before we set finished status +runcmd sleep 60 + +# if we reached this point, the pipeline ran succesfully +runcmd {setStatus_finished} +""".format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, repository = parset.dockerRepository(), -- GitLab From 67ea2c9cdebc5df4d1f16c2ef9deb0323c9df0f2 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 8 Jul 2016 12:25:52 +0000 Subject: [PATCH 513/933] Task #9623: Double literal braces in """ --- MAC/Services/src/PipelineControl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index ae8463a3400..1d037c4c7fe 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -432,7 +432,7 @@ class PipelineControl(OTDBBusListener): slurm_job_id = self.slurm.submit(self._jobName(otdbId), """ # Run a command, but propagate SIGINT and SIGTERM -function runcmd { +function runcmd {{ trap 'kill -s SIGTERM $PID' SIGTERM trap 'kill -s SIGINT $PID' SIGINT @@ -445,7 +445,7 @@ function runcmd { trap - SIGTERM SIGINT return $RESULT -} +}} # print some info echo Running on $SLURM_NODELIST @@ -462,6 +462,8 @@ srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull \ srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-tag \ --kill-on-bad-exit=0 --wait=0 \ docker tag -f {repository}/{image} {image} + +# run the pipeline runcmd docker run --rm --net=host \ -e LOFARENV={lofarenv} \ -u $UID -e USER=$USER \ -- GitLab From ce472d470c3a78b177f74b386e6aee3dc4d0fb9f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 8 Jul 2016 13:02:19 +0000 Subject: [PATCH 514/933] Task #9623: Use 4 threads/NDPPP --- CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in index bc8e4f1ef5e..61c51bf715b 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in @@ -1,6 +1,6 @@ [ndppp] nproc = 0 -nthreads = 2 +nthreads = 4 [setupparmdb] nproc = 0 @@ -24,7 +24,7 @@ nthreads = 10 [dppp] max_per_node = 0 -nthreads = 2 +nthreads = 4 [awimager] max_per_node = 0 -- GitLab From 3423b57a3962757204c8abc49355d1adb6737dee Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 8 Jul 2016 13:03:35 +0000 Subject: [PATCH 515/933] Task #9623: CEP4 tuning for pipelines --- .gitattributes | 1 + CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in | 4 +- CMake/variants/variants.dop320 | 2 + MAC/Deployment/data/OTDB/DPPP.comp | 2 +- MAC/Services/src/PipelineControl.py | 113 ++++++++++++++------- 5 files changed, 85 insertions(+), 37 deletions(-) create mode 100644 CMake/variants/variants.dop320 diff --git a/.gitattributes b/.gitattributes index 6359d5e4c00..b6617db58c3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2338,6 +2338,7 @@ CMake/variants/variants.cbt008 -text CMake/variants/variants.cbt009 -text CMake/variants/variants.cbt010 -text CMake/variants/variants.dop282 -text +CMake/variants/variants.dop320 -text CMake/variants/variants.dragproc -text CMake/variants/variants.fs0 -text CMake/variants/variants.gpu01 -text diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in index 76693d17ad7..61c51bf715b 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in @@ -1,6 +1,6 @@ [ndppp] nproc = 0 -nthreads = 10 +nthreads = 4 [setupparmdb] nproc = 0 @@ -24,7 +24,7 @@ nthreads = 10 [dppp] max_per_node = 0 -nthreads = 10 +nthreads = 4 [awimager] max_per_node = 0 diff --git a/CMake/variants/variants.dop320 b/CMake/variants/variants.dop320 new file mode 100644 index 00000000000..e11707efd90 --- /dev/null +++ b/CMake/variants/variants.dop320 @@ -0,0 +1,2 @@ +# Fix for Debian (FindPython tends to settle on /usr/bin/python2.6, which is a "minimal" python install) +set(PYTHON_EXECUTABLE "/usr/bin/python2.7") diff --git a/MAC/Deployment/data/OTDB/DPPP.comp b/MAC/Deployment/data/OTDB/DPPP.comp index 482cb96050b..e9cb7f00021 100644 --- a/MAC/Deployment/data/OTDB/DPPP.comp +++ b/MAC/Deployment/data/OTDB/DPPP.comp @@ -37,7 +37,7 @@ node msout 4.0.0 development 'node constraint' "Output MeasurementSe #par name I text - 10 0 "-" - "Name of the MeasurementSet" par overwrite I bool - 10 0 F - "When creating a new MS, overwrite if already existing?" par tilenchan I int - 10 0 0 - "For expert user: maximum number of channels per tile in output MS (0 is all channels)" -par tilesize I int - 10 0 1024 - "For expert user: tile size (in Kbytes) for the data columns in the output MS" +par tilesize I int - 10 0 4096 - "For expert user: tile size (in Kbytes) for the data columns in the output MS" par vdsdir I text - 10 0 "A" - "Directory where to put the VDS file; if empty, the MS directory is used." par writefullresflag I bool - 10 0 T - "Write the full resolution flags" diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 0138bab7985..4115f5f42ce 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -143,10 +143,33 @@ class Parset(dict): return max(1, min(20, result)) def processingNumberOfTasks(self): - result = int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfTasks"]) or "24" - if result < 1 or result > 48: - logger.warn('Invalid Observation.Cluster.ProcessingCluster.numberOfTasks: %s, defaulting to %s', result, max(1, min(48, result))) - return max(1, min(48, result)) + """ Parse the number of nodes to allocate from "Observation.Cluster.ProcessingCluster.numberOfTasks", + which can have either the format "{number}" or "{min}-{max}". """ + + defaultValue = "12-25" + parsetValue = self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfTasks"].strip() + + if "-" in parsetValue: + # min,max + _min, _max = parsetValue.split("-") + + # collapse if not min <= max + if _min > _max: + result = _min + else: + result = "%s,%s" % (_min, _max) + else: + # plain number + result = int(parsetValue) + + # apply bound + if result < 1 or result > 50: + result = defaultValue + + if result != parsetValue: + logger.error('Invalid Observation.Cluster.ProcessingCluster.numberOfTasks: %s, defaulting to %s', parsetValue, result) + + return result @staticmethod def dockerRepository(): @@ -417,36 +440,58 @@ class PipelineControl(OTDBBusListener): # Schedule runPipeline.sh logger.info("Scheduling SLURM job for runPipeline.sh") slurm_job_id = self.slurm.submit(self._jobName(otdbId), - # notify that we're running - "{setStatus_active}\n" - # pull docker image from repository on all nodes - "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull" - " --kill-on-bad-exit=0 --wait=0" - " docker pull {repository}/{image}\n" - # put a local tag on the pulled image - "srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-tag" - " --kill-on-bad-exit=0 --wait=0" - " docker tag -f {repository}/{image} {image}\n" - # call runPipeline.sh in the image on this node - "docker run --rm" - " --net=host" - " -e LOFARENV={lofarenv}" - " -u $UID" - " -e USER=$USER" - " -e HOME=$HOME" - " -v $HOME/.ssh:$HOME/.ssh:ro" - " -e SLURM_JOB_ID=$SLURM_JOB_ID" - " -v /data:/data" - " {image}" - " runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} || exit $?\n" - - # notify that we're tearing down - "{setStatus_completing}\n" - # wait for MoM to pick up feedback before we set finished status - "sleep 60\n" - # if we reached this point, the pipeline ran succesfully - "{setStatus_finished}\n" - .format( +""" +# Run a command, but propagate SIGINT and SIGTERM +function runcmd {{ + trap 'kill -s SIGTERM $PID' SIGTERM + trap 'kill -s SIGINT $PID' SIGINT + + "$@" & + PID=$! + wait $PID # returns the exit status of "wait" if interrupted + wait $PID # returns the exit status of $PID + RESULT=$? + + trap - SIGTERM SIGINT + + return $RESULT +}} + +# print some info +echo Running on $SLURM_NODELIST + +# notify that we're running +runcmd {setStatus_active} + +# pull docker image from repository on all nodes +srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull \ + --kill-on-bad-exit=0 --wait=0 \ + docker pull {repository}/{image} + +# put a local tag on the pulled image +srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-tag \ + --kill-on-bad-exit=0 --wait=0 \ + docker tag -f {repository}/{image} {image} + +# run the pipeline +runcmd docker run --rm --net=host \ + -e LOFARENV={lofarenv} \ + -u $UID -e USER=$USER \ + -e HOME=$HOME -v $HOME/.ssh:$HOME/.ssh:ro \ + -e SLURM_JOB_ID=$SLURM_JOB_ID \ + -v /data:/data \ + {image} \ + runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} + +# notify that we're tearing down +runcmd {setStatus_completing} + +# wait for MoM to pick up feedback before we set finished status +runcmd sleep 60 + +# if we reached this point, the pipeline ran succesfully +runcmd {setStatus_finished} +""".format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, repository = parset.dockerRepository(), -- GitLab From 608c8ea3e22f6c031e3f82aaeda768315ee0ed7e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 8 Jul 2016 13:30:57 +0000 Subject: [PATCH 516/933] Task #9607 #9351 #9353 #9355: fixed supervisor ini files for datamanagement services --- SAS/DataManagement/CleanupService/cleanupservice.ini | 2 +- SAS/DataManagement/StorageQueryService/storagequeryservice.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/DataManagement/CleanupService/cleanupservice.ini b/SAS/DataManagement/CleanupService/cleanupservice.ini index 0e705c1191c..b92b6b0636f 100644 --- a/SAS/DataManagement/CleanupService/cleanupservice.ini +++ b/SAS/DataManagement/CleanupService/cleanupservice.ini @@ -1,5 +1,5 @@ [program:cleanupservice] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec cleanupservice --log-queries' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec cleanupservice' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals diff --git a/SAS/DataManagement/StorageQueryService/storagequeryservice.ini b/SAS/DataManagement/StorageQueryService/storagequeryservice.ini index 45bcc52ad0c..d65bbc12413 100644 --- a/SAS/DataManagement/StorageQueryService/storagequeryservice.ini +++ b/SAS/DataManagement/StorageQueryService/storagequeryservice.ini @@ -1,5 +1,5 @@ [program:storagequeryservice] -command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec storagequeryservice --log-queries' +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec storagequeryservice' user=lofarsys stopsignal=INT ; KeyboardInterrupt stopasgroup=true ; bash does not propagate signals -- GitLab From 0b2266b14be4ca0a2b4b2d38c10835e94faec055 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 8 Jul 2016 13:34:57 +0000 Subject: [PATCH 517/933] Task #9607 #9351 #9353 #9355: fixed cmake file for webscheduler, cleanupcontroller.js was missing --- .../ResourceAssignmentEditor/lib/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt index ffbddce6f5c..1bfa8364628 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt @@ -31,6 +31,7 @@ set(app_files static/favicon.ico static/app/app.js static/app/controllers/datacontroller.js + static/app/controllers/cleanupcontroller.js static/app/controllers/gridcontroller.js static/app/controllers/ganttresourcecontroller.js static/app/controllers/chartresourceusagecontroller.js -- GitLab From a53ef5bdb812e651b0f5aff99ec9deb0f66d58a3 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 8 Jul 2016 13:59:30 +0000 Subject: [PATCH 518/933] Task #9607: claim conflict computions below are incorrect. They cause observations/pipelines to go to conflict, although there is ample space --- .../ResourceAssignmentDatabase/radb.py | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 123e4c1c3db..d2df8fc9a63 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -1188,29 +1188,31 @@ class RADatabase: if claim['id'] not in claimDict: resource2otherClaims[claim['resource_id']].append(claim) - for claim_id, claim in claimDict.items(): - task_id = claim['task_id'] - task_status = taskDict[task_id]['status'] - if task_status in ['prepared', 'approved', 'on_hold', 'conflict', 'prescheduled']: - claimSize = claim['claim_size'] - resource_id = claim['resource_id'] - resource = resources[resource_id] - resourceOtherClaims = resource2otherClaims[resource_id] - totalOtherClaimSize = sum(c['claim_size'] for c in resourceOtherClaims) - - logger.info('resource_id=%s claimSize=%s totalOtherClaimSize=%s total=%s available_capacity=%s' % - (resource_id, - claimSize, - totalOtherClaimSize, - totalOtherClaimSize + claimSize, - resource['available_capacity'])) - - if totalOtherClaimSize + claimSize >= resource['available_capacity']: - logger.info("totalOtherClaimSize (%s) + claimSize (%s) >= resource_available_capacity %s for claim %s on resource %s %s for task %s", - totalOtherClaimSize, claimSize, resource['available_capacity'], claim_id, resource_id, resource['name'], task_id) - newClaimStatuses[conflistStatusId].append(claim_id) - elif claim['status_id'] != allocatedStatusId: - newClaimStatuses[claimedStatusId].append(claim_id) + # TODO: claim conflict computions below are incorrect + # they cause observations/pipelines to go to conflict, although there is ample space + #for claim_id, claim in claimDict.items(): + #task_id = claim['task_id'] + #task_status = taskDict[task_id]['status'] + #if task_status in ['prepared', 'approved', 'on_hold', 'conflict', 'prescheduled']: + #claimSize = claim['claim_size'] + #resource_id = claim['resource_id'] + #resource = resources[resource_id] + #resourceOtherClaims = resource2otherClaims[resource_id] + #totalOtherClaimSize = sum(c['claim_size'] for c in resourceOtherClaims) + + #logger.info('resource_id=%s claimSize=%s totalOtherClaimSize=%s total=%s available_capacity=%s' % + #(resource_id, + #claimSize, + #totalOtherClaimSize, + #totalOtherClaimSize + claimSize, + #resource['available_capacity'])) + + #if totalOtherClaimSize + claimSize >= resource['available_capacity']: + #logger.info("totalOtherClaimSize (%s) + claimSize (%s) >= resource_available_capacity %s for claim %s on resource %s %s for task %s", + #totalOtherClaimSize, claimSize, resource['available_capacity'], claim_id, resource_id, resource['name'], task_id) + #newClaimStatuses[conflistStatusId].append(claim_id) + #elif claim['status_id'] != allocatedStatusId: + #newClaimStatuses[claimedStatusId].append(claim_id) if newClaimStatuses: for status_id, claim_ids in newClaimStatuses.items(): -- GitLab From 4500476c81544f7ba68536a11a83b490e9426b3f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 11 Jul 2016 07:29:10 +0000 Subject: [PATCH 519/933] Task #9607: fixed binding between UI and dataservice --- .../lib/static/app/controllers/datacontroller.js | 2 +- .../lib/templates/index.html | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 31d14181326..a2e911f744f 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -782,7 +782,7 @@ dataControllerMod.controller('DataController', }; }; - $scope.onZoomTimespanChanged = function(span) { + $scope.onZoomTimespanChanged = function() { var viewTimeSpanInmsec = dataService.viewTimeSpan.to.getTime() - dataService.viewTimeSpan.from.getTime(); var focusTime = new Date(dataService.viewTimeSpan.from + 0.5*viewTimeSpanInmsec); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index f577115eff0..4261f285027 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -58,27 +58,27 @@ <div class="col-md-3"> <label>From:</label> <p class="input-group"> - <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="dataService.viewTimeSpan.from" is-open="viewFromDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> + <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.from" is-open="viewFromDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> <span class="input-group-btn"> <button type="button" class="btn btn-default" ng-click="openViewFromDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> </span> - <uib-timepicker ng-model="dataService.viewTimeSpan.from" ng-change="onViewTimeSpanFromChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> + <uib-timepicker ng-model="$parent.dataService.viewTimeSpan.from" ng-change="$parent.onViewTimeSpanFromChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> </p> </div> <div class="col-md-3"> <label>To:</label> <p class="input-group"> - <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="dataService.viewTimeSpan.to" is-open="viewToDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> + <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.to" is-open="viewToDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> <span class="input-group-btn"> <button type="button" class="btn btn-default" ng-click="openViewToDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> </span> - <uib-timepicker ng-model="dataService.viewTimeSpan.to" ng-change="onViewTimeSpanToChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> + <uib-timepicker ng-model="$parent.dataService.viewTimeSpan.to" ng-change="$parent.onViewTimeSpanToChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> </p> </div> <div class="col-md-2"> <label>Scroll:</label> <p class="input-group"> - <label title="Automatically scroll 'From' and 'To' to watch live events" style="padding-right: 4px; vertical-align: top;">Live <input type="checkbox" ng-model="dataService.autoFollowNow"></label> + <label title="Automatically scroll 'From' and 'To' to watch live events" style="padding-right: 4px; vertical-align: top;">Live <input type="checkbox" ng-model="$parent.dataService.autoFollowNow"></label> <button title="Scroll back in time" type="button" class="btn btn-default" ng-click="scrollBack()"><i class="glyphicon glyphicon-step-backward"></i></button> <button title="Scroll forward in time"type="button" class="btn btn-default" ng-click="scrollForward()"><i class="glyphicon glyphicon-step-forward"></i></button> </p> @@ -86,7 +86,7 @@ <div class="col-md-2"> <label>Zoom:</label> <p class="input-group"> - <select class="form-control" ng-model=zoomTimespan ng-options="option.name for option in zoomTimespans track by option.value" ng-change="onZoomTimespanChanged(span)"></select> + <select class="form-control" ng-model="$parent.zoomTimespan" ng-options="option.name for option in $parent.zoomTimespans track by option.value" ng-change="$parent.onZoomTimespanChanged()"></select> </p> </div> </div> @@ -116,7 +116,7 @@ allow-row-switching="false"> </gantt-movable> <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> - <gantt-dependencies enabled="true"></gantt-dependencies> + <gantt-dependencies enabled="options.dependencies"></gantt-dependencies> <gantt-contextmenu enabled="true"></gantt-contextmenu> </div> </div> -- GitLab From 8c63dfa6674afba5d3606331bcaf291ff10ecc86 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 11 Jul 2016 08:39:42 +0000 Subject: [PATCH 520/933] Task #9607: hide legend. only show 'up' button if not showing projects usage --- .../lib/static/app/controllers/cleanupcontroller.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js index 68d18145ecf..be1aed8cbb7 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -106,7 +106,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h <highchart id="chart_disk_usage" config="diskUsageChartConfig" style="width: 960px; height: 720px;" ></highchart>\ <p>\ <span style="margin-right:50px">Last updated at: {{leastRecentCacheTimestamp | date }}</span>\ - <button class="btn btn-primary glyphicon glyphicon-level-up" type="button" ng-click="up()" title="Up one level"></button>\ + <button class="btn btn-primary glyphicon glyphicon-level-up" type="button" ng-click="up()" title="Up one level" ng-if="watchedObjectType!=\'projects\'"></button>\ </p>\ </div>\ <div class="modal-footer">\ @@ -156,6 +156,9 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h duration: 200 } }, + legend: { + enabled: false + }, plotOptions: { pie: { allowPointSelect: true, @@ -163,7 +166,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h dataLabels: { enabled: true }, - showInLegend: true + showInLegend: false }, series: { point: { -- GitLab From 6fdde784b4fa3e7e69218f7dad50e33214848886 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 11 Jul 2016 08:40:15 +0000 Subject: [PATCH 521/933] Task #9607: only show task dependencies when zoomed <= 3hours --- .../lib/static/app/controllers/ganttprojectcontroller.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index ef5752e6ffe..87f6ced5d7b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -38,7 +38,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS sideMode: 'Tree', autoExpand: 'both', taskOutOfRange: 'truncate', - dependencies: true, + dependencies: false, api: function(api) { // API Object is used to control methods and events from angular-gantt. $scope.api = api; @@ -182,6 +182,9 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS $scope.options.viewScale = '15 minutes'; } + //only enable dependencies (arrows between tasks) in detailed view + $scope.options.dependencies = (fullTimespanInMinutes <= 3*60); + for(var i = 0; i < numTasks; i++) { var task = tasks[i]; @@ -239,7 +242,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS rowTask.classes += ' task-selected-task'; } - if(task.predecessor_ids && task.predecessor_ids.length > 0) { + if($scope.options.dependencies && task.predecessor_ids && task.predecessor_ids.length > 0) { rowTask['dependencies'] = []; for(var predId of task.predecessor_ids) { rowTask['dependencies'].push({'from': predId}); -- GitLab From 45f51ce15581758031c5012e999a6d9af1240987 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 11 Jul 2016 08:40:45 +0000 Subject: [PATCH 522/933] Task #9607: context menu in gannt --- .../angular-gantt-contextmenu-plugin.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index ed879020df5..e7394102af7 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -51,12 +51,19 @@ var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>'); contextmenuElement.append(ulElement); - var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); +// var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); +// ulElement.append(liElement); +// liElement.on('click', function() { +// closeContextMenu(); +// //TODO: remove link to dataService in this generic plugin +// dataService.copyTask(dScope.task.model.raTask); +// }); + + var liElement = angular.element('<li><a href="#">Show disk usage</a></li>'); ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - //TODO: remove link to dataService in this generic plugin - dataService.copyTask(dScope.task.model.raTask); + cleanupCtrl.showTaskDiskUsage(dScope.task.model.raTask); }); var liElement = angular.element('<li><a href="#">Delete data</a></li>'); -- GitLab From 6a39a13a183d4269591f9bb01030be5e1eef37a0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 11 Jul 2016 11:22:50 +0000 Subject: [PATCH 523/933] Task #9607: bug fix: in case of selection by id(s), do not skip selection when number of id(s) is 0 --- .../ResourceAssignmentDatabase/radb.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index d2df8fc9a63..83feb71e677 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -173,7 +173,7 @@ class RADatabase: if isinstance(task_ids, int): # just a single id conditions.append('id = %s') qargs.append(task_ids) - elif len(task_ids) > 0: # list of id's + else: #assume a list/enumerable of id's conditions.append('id in %s') qargs.append(tuple(task_ids)) @@ -507,7 +507,7 @@ class RADatabase: if isinstance(resource_ids, int): # just a single id conditions.append('id = %s') qargs.append(resource_ids) - elif len(resource_ids) > 0: # a list of id's + else: #assume a list/enumerable of id's conditions.append('id in %s') qargs.append(tuple(resource_ids)) @@ -690,7 +690,7 @@ class RADatabase: if isinstance(claim_ids, int): # just a single id conditions.append('rcpv.resource_claim_id = %s') qargs.append(claim_ids) - elif len(claim_ids) > 0: # list of id's + else: #assume a list/enumerable of id's conditions.append('rcpv.resource_claim_id in %s') qargs.append(tuple(claim_ids)) @@ -825,7 +825,7 @@ class RADatabase: if isinstance(claim_ids, int): # just a single id conditions.append('id = %s') qargs.append(claim_ids) - elif len(claim_ids) > 0: # list of id's + else: #assume a list/enumerable of id's conditions.append('id in %s') qargs.append(tuple(claim_ids)) @@ -841,7 +841,7 @@ class RADatabase: if isinstance(resource_ids, int): # just a single id conditions.append('resource_id = %s') qargs.append(resource_ids) - elif len(resource_ids) > 0: # list of id's + else: #assume a list/enumerable of id's conditions.append('resource_id in %s') qargs.append(tuple(resource_ids)) @@ -1277,7 +1277,7 @@ class RADatabase: if isinstance(task_ids, int): # just a single id conditions.append('task_id = %s') qargs.append(task_ids) - elif len(task_ids) > 0: # list of id's + else: #assume a list/enumerable of id's conditions.append('task_id in %s') qargs.append(tuple(task_ids)) @@ -1320,7 +1320,7 @@ class RADatabase: if isinstance(claim_ids, int): # just a single id conditions.append('id = %s') qargs.append(claim_ids) - elif len(claim_ids) > 0: # list of id's + else: #assume a list/enumerable of id's conditions.append('id in %s') qargs.append(tuple(claim_ids)) @@ -1328,7 +1328,7 @@ class RADatabase: if isinstance(resource_ids, int): # just a single id conditions.append('resource_id = %s') qargs.append(resource_ids) - elif len(resource_ids) > 0: # list of id's + else: #assume a list/enumerable of id's conditions.append('resource_id in %s') qargs.append(tuple(resource_ids)) @@ -1336,7 +1336,7 @@ class RADatabase: if isinstance(task_ids, int): # just a single id conditions.append('task_id = %s') qargs.append(task_ids) - elif len(task_ids) > 0: # list of id's + else: #assume a list/enumerable of id's conditions.append('task_id in %s') qargs.append(tuple(task_ids)) -- GitLab From 769d0261d4120efa06415f39105443e918ca7407 Mon Sep 17 00:00:00 2001 From: Ruud Overeem <overeem@astron.nl> Date: Mon, 11 Jul 2016 12:14:25 +0000 Subject: [PATCH 524/933] Task #9652: copy of trunk -- GitLab From f0694c00ecdcb44d2c178cf20f050471b4c8ba2c Mon Sep 17 00:00:00 2001 From: Ruud Overeem <overeem@astron.nl> Date: Mon, 11 Jul 2016 12:59:21 +0000 Subject: [PATCH 525/933] Task #9652: each test uses its own database. --- SAS/OTDB_Services/test/t_TreeService.run | 13 +++++++------ SAS/OTDB_Services/test/t_TreeStatusEvents.run | 13 +++++++------ SAS/OTDB_Services/test/unittest_db.dump.gz | Bin 459840 -> 459852 bytes 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/SAS/OTDB_Services/test/t_TreeService.run b/SAS/OTDB_Services/test/t_TreeService.run index 6a7e1fb6904..5d1c193ccb2 100755 --- a/SAS/OTDB_Services/test/t_TreeService.run +++ b/SAS/OTDB_Services/test/t_TreeService.run @@ -3,19 +3,20 @@ DBHOST=10.149.32.21 # sas099.control.lofar #cleanup on normal exit and on SIGHUP, SIGINT, SIGQUIT, and SIGTERM -trap 'qpid-config del exchange --force $queue ; kill ${SERVER_PID}' 0 1 2 3 15 +trap 'qpid-config del exchange --force $queue ; kill ${SERVICE_PID} ; dropdb -U postgres -h ${DBHOST} ${DBNAME}' 0 1 2 3 15 # Generate randome queue name -queue=$(< /dev/urandom tr -dc [:alnum:] | head -c16) +queue=$(< /dev/urandom tr -dc [:alnum:] | head -c10) +DBNAME=unittest_$queue # Create the queue qpid-config add exchange topic $queue # Setup a clean database with predefined content -dropdb -U postgres -h ${DBHOST} unittest_db -gzip -dc $srcdir/unittest_db.dump.gz | psql -U postgres -h ${DBHOST} -f - -TreeService.py -B $queue -D unittest_db -H ${DBHOST} -U postgres & -SERVER_PID=$! +createdb -U postgres -h ${DBHOST} ${DBNAME} +gzip -dc $srcdir/unittest_db.dump.gz | psql -U postgres -h ${DBHOST} ${DBNAME} -f - +TreeService.py -B $queue -D ${DBNAME} -H ${DBHOST} -U postgres & +SERVICE_PID=$! # Starting up takes a while sleep 3 diff --git a/SAS/OTDB_Services/test/t_TreeStatusEvents.run b/SAS/OTDB_Services/test/t_TreeStatusEvents.run index 4a2d5718f82..a1f81976a97 100755 --- a/SAS/OTDB_Services/test/t_TreeStatusEvents.run +++ b/SAS/OTDB_Services/test/t_TreeStatusEvents.run @@ -3,22 +3,23 @@ DBHOST=sas099.control.lofar #cleanup on normal exit and on SIGHUP, SIGINT, SIGQUIT, and SIGTERM -trap 'qpid-config del exchange --force $queue ; kill ${SERVICE_PID}' 0 1 2 3 15 +trap 'qpid-config del exchange --force $queue ; kill ${SERVICE_PID} ; dropdb -U postgres -h ${DBHOST} ${DBNAME}' 0 1 2 3 15 # Generate randome queue name -queue=$(< /dev/urandom tr -dc [:alnum:] | head -c16) +queue=$(< /dev/urandom tr -dc [:alnum:] | head -c10) +DBNAME=unittest_${queue} # Create the queue qpid-config add exchange topic $queue # Setup a clean database with predefined content -dropdb -U postgres -h ${DBHOST} unittest_db -gzip -dc $srcdir/unittest_db.dump.gz | psql -U postgres -h ${DBHOST} -f - -TreeStatusEvents.py -B $queue -D unittest_db -H ${DBHOST} -U postgres & +createdb -U postgres -h ${DBHOST} ${DBNAME} +gzip -dc $srcdir/unittest_db.dump.gz | psql -U postgres -h ${DBHOST} ${DBNAME} -f - +TreeStatusEvents.py -B $queue -D ${DBNAME} -H ${DBHOST} -U postgres & SERVICE_PID=$! # Starting up takes a while sleep 3 # Run the unit test source python-coverage.sh -python_coverage_test "Messaging/python" t_TreeStatusEvents.py -D unittest_db -H ${DBHOST} -B $queue +python_coverage_test "Messaging/python" t_TreeStatusEvents.py -D ${DBNAME} -H ${DBHOST} -B $queue diff --git a/SAS/OTDB_Services/test/unittest_db.dump.gz b/SAS/OTDB_Services/test/unittest_db.dump.gz index 3f42fb231b6df3789e2b904cc13c484ba23a74e4..961fb48516052918b0eeb2ee562216f24662d2de 100644 GIT binary patch delta 325817 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%0DhS#4pvt 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%0A}#s5U;aLF`+K-# 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 L-K5rBX~+Noi^9Ay delta 326083 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 -- GitLab From c932a5e9245b0f69f855fabd9bd56faba927540a Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 11 Jul 2016 14:30:44 +0000 Subject: [PATCH 526/933] Task #9607: bug fix: return empty list in case a task has no predecessors or successors, not None. --- .../ResourceAssignmentDatabase/radb.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 83feb71e677..b1bf277f74c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -191,12 +191,6 @@ class RADatabase: query += ' WHERE ' + ' AND '.join(conditions) tasks = list(self._executeQuery(query, qargs, fetch=_FETCH_ALL)) - predIds = self.getTaskPredecessorIds() - succIds = self.getTaskSuccessorIds() - - for task in tasks: - task['predecessor_ids'] = predIds.get(task['id'], []) - task['successor_ids'] = succIds.get(task['id'], []) return tasks @@ -220,6 +214,13 @@ class RADatabase: task = dict(result) if result else None + if task: + if task['predecessor_ids'] is None: + task['predecessor_ids'] = [] + + if task['successor_ids'] is None: + task['successor_ids'] = [] + return task def _convertTaskTypeAndStatusToIds(self, task_status, task_type): -- GitLab From 92eef094783e4ddea72509418b93e514123aff05 Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Tue, 12 Jul 2016 11:34:09 +0000 Subject: [PATCH 527/933] Task #9655: changes related to 9656 and 9657 --- .gitattributes | 2 + MAC/Navigator2/panels/Alerts/lofar_alarms.pnl | 8 +- MAC/Navigator2/panels/Test/Event_Viewer.pnl | 335 ++------- MAC/Navigator2/panels/Test/test.pnl | 63 +- MAC/Navigator2/panels/Test/testClaim.pnl | 14 +- MAC/Navigator2/panels/Test/testclick.pnl | 29 +- .../panels/objects/Alerts/alarmsWinCCOA.pnl | 116 ++++ .../Observations/Observation_small.pnl | 5 +- .../objects/Observations/Pipeline_small.pnl | 5 +- .../Processes/SWControlerTempObs_small.pnl | 110 +-- .../objects/navigator_viewSelection.pnl | 2 +- .../panels/vision/aes/AES_properties.pnl | 648 ++++++++++++++++++ MAC/Navigator2/scripts/claim.ctl | 2 +- MAC/Navigator2/scripts/libs/claimManager.ctl | 144 ++-- MAC/Navigator2/scripts/libs/navFunct.ctl | 1 + 15 files changed, 1003 insertions(+), 481 deletions(-) create mode 100644 MAC/Navigator2/panels/objects/Alerts/alarmsWinCCOA.pnl create mode 100644 MAC/Navigator2/panels/vision/aes/AES_properties.pnl diff --git a/.gitattributes b/.gitattributes index 144580ea1ae..901aafb8a31 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3960,6 +3960,7 @@ MAC/Navigator2/panels/main.pnl -text MAC/Navigator2/panels/navigator.pnl -text MAC/Navigator2/panels/nopanel.pnl -text MAC/Navigator2/panels/objects/Alerts/alarms.pnl -text +MAC/Navigator2/panels/objects/Alerts/alarmsWinCCOA.pnl -text MAC/Navigator2/panels/objects/FRENKM_STATION.pnl -text MAC/Navigator2/panels/objects/Hardware/AARTFAAC-RSP.pnl -text MAC/Navigator2/panels/objects/Hardware/AARTFAAC-UNIBoard.pnl -text @@ -4093,6 +4094,7 @@ MAC/Navigator2/panels/objects/navigator_viewSelection.pnl -text MAC/Navigator2/panels/objects/show_legenda.pnl -text MAC/Navigator2/panels/objects/swlevel.pnl -text MAC/Navigator2/panels/objects/systemMainLine.pnl -text +MAC/Navigator2/panels/vision/aes/AES_properties.pnl -text MAC/Navigator2/pictures/16_empty.gif -text svneol=unset#image/gif MAC/Navigator2/pictures/16_hand_right.gif -text svneol=unset#image/gif MAC/Navigator2/pictures/253.bmp -text svneol=unset#image/bmp diff --git a/MAC/Navigator2/panels/Alerts/lofar_alarms.pnl b/MAC/Navigator2/panels/Alerts/lofar_alarms.pnl index d36943ad35c..20d2b5d3ecd 100644 --- a/MAC/Navigator2/panels/Alerts/lofar_alarms.pnl +++ b/MAC/Navigator2/panels/Alerts/lofar_alarms.pnl @@ -69,7 +69,7 @@ LANG:1 0 1 LANG:1 33 MS Shell Dlg,-1,11,5,50,0,0,0,0,0 0 -2 -2 1162 832 -E2 "New Alarms" 1 +E3 "New Alarms" 1 LANG:1 10 New Alarms 1 "objects/Alerts/alarms.pnl" 1 LANG:1 0 @@ -83,6 +83,12 @@ LANG:1 0 1 "$choice""Old" +"WinCCOA_Alarms" 1 +LANG:1 15 WinCC-OA Alarms +1 "objects/Alerts/alarmsWinCCOA.pnl" 1 +LANG:1 0 +0 + 0 LAYER, 1 diff --git a/MAC/Navigator2/panels/Test/Event_Viewer.pnl b/MAC/Navigator2/panels/Test/Event_Viewer.pnl index d1f0cd1235a..b278abc59f4 100644 --- a/MAC/Navigator2/panels/Test/Event_Viewer.pnl +++ b/MAC/Navigator2/panels/Test/Event_Viewer.pnl @@ -1,7 +1,7 @@ V 11 1 LANG:1 12 Event Viewer -PANEL,-1 -1 1200 843 N "_3DFace" 0 +PANEL,-1 -1 1200 813 N "_3DFace" 0 "main() { int retry=0; @@ -73,6 +73,24 @@ DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 LAYER, 0 1 LANG:1 0 +30 2 +"FRAME1" +"" +1 10 1 E E E 1 E 1 E N "_WindowText" E N {0,0,0} E E + E E +2 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 2 1 0 1 E 0.986193293885602 0 0.450511945392491 400.138067061144 249.5494880546075 0 E 10 1 518 295 +1 +LANG:1 32 Arial Black,-1,13,5,75,0,0,0,0,0 +0 1 +LANG:1 19 Navigator Framework 13 320 "PUSH_BUTTON10" "" @@ -100,28 +118,10 @@ LANG:1 14 show g_TBBList } }" 0 E E E -30 2 -"FRAME1" -"" -1 10 1 E E E 1 E 1 E N "_WindowText" E N {0,0,0} E E - E E -2 0 0 0 0 0 -E E E -0 -1 -LANG:1 0 - -1 -"dashclr"N "_Transparent" -E E 0 2 1 0 1 E 0.986193293885602 0 0.450511945392491 395.138067061144 298.549488054608 0 E 10 1 518 295 -1 -LANG:1 32 Arial Black,-1,13,5,75,0,0,0,0,0 -0 1 -LANG:1 19 Navigator Framework 14 3 "text_event" "" -1 557 348 E E E 1 E 1 E N "_WindowText" E N "_Window" E E +1 560 297.0152671755725 E E E 1 E 1 E N "_WindowText" E N "_Window" E E E E 3 0 0 0 0 0 E E E @@ -132,12 +132,12 @@ LANG:1 0 0 1 LANG:1 32 Arial Black,-1,11,5,50,0,0,0,0,0 -0 555 346 899 369 +0 558 295 902 318 3 "0s" 0 0 0 0 0 -1 E E E 2 6 "PRIMITIVE_TEXT4" "" -1 1236 409.3240071683281 E E E 1 E 1 E N "_WindowText" E N "_3DFace" E E +1 1375 364.6318240169413 E E E 1 E 1 E N "_WindowText" E N "_3DFace" E E E E 6 0 0 0 0 0 E E E @@ -147,7 +147,7 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 1 1 2 1 E 1 0 1.00763358778626 136 3.16793893129752 1 E 283 371 384 386 +E E 0 1 1 2 1 E 1 0 1.00763358778626 139 -47.81679389312999 1 E 283 371 384 386 0 2 2 "0s" 0 0 0 192 0 0 283 371 1 1 LANG:1 32 Arial Black,-1,11,5,50,0,0,0,0,0 @@ -156,7 +156,7 @@ LANG:1 18 Initiating Object: 2 7 "PRIMITIVE_TEXT5" "" -1 1241 396.4233462865291 E E E 1 E 1 E N "_WindowText" E N "_3DFace" E E +1 1381 354.8693260291744 E E E 1 E 1 E N "_WindowText" E N "_3DFace" E E E E 7 0 0 0 0 0 E E E @@ -166,7 +166,7 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 1 1 2 1 E 1 0 1.00763358778626 137 6.40458015267157 1 E 282 340 379 355 +E E 0 1 1 2 1 E 1 0 1.00763358778626 140 -44.58015267175594 1 E 282 340 379 355 0 2 2 "0s" 0 0 0 192 0 0 282 340 1 1 LANG:1 32 Arial Black,-1,11,5,50,0,0,0,0,0 @@ -175,7 +175,7 @@ LANG:1 15 Event Received: 2 8 "PRIMITIVE_TEXT6" "" -1 1241 426.3243640761156 E E E 1 E 1 E N "_WindowText" E N "_3DFace" E E +1 1381 379.5405805957806 E E E 1 E 1 E N "_WindowText" E N "_3DFace" E E E E 8 0 0 0 0 0 E E E @@ -185,7 +185,7 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 1 1 2 1 E 1 0 1.00763358778626 137 0.946564885495969 1 E 282 400 342 415 +E E 0 1 1 2 1 E 1 0 1.00763358778626 140 -50.03816793893154 1 E 282 400 342 415 0 2 2 "0s" 0 0 0 192 0 0 282 400 1 1 LANG:1 32 Arial Black,-1,11,5,50,0,0,0,0,0 @@ -194,7 +194,7 @@ LANG:1 10 Selection: 14 9 "text_initiator" "" -1 557 376 E E E 1 E 1 E N "_WindowText" E N "_Window" E E +1 560 325.0152671755725 E E E 1 E 1 E N "_WindowText" E N "_Window" E E E E 9 0 0 0 0 0 E E E @@ -205,12 +205,12 @@ LANG:1 0 0 1 LANG:1 32 Arial Black,-1,11,5,50,0,0,0,0,0 -0 555 374 899 397 +0 558 323 902 346 3 "0s" 0 0 0 0 0 -1 E E E 14 11 "text_selection" "" -1 557 403 E E E 1 E 1 E N "_WindowText" E N "_Window" E E +1 563 349.0152671755725 E E E 1 E 1 E N "_WindowText" E N "_Window" E E E E 11 0 0 0 0 0 E E E @@ -221,7 +221,7 @@ LANG:1 0 0 1 LANG:1 32 Arial Black,-1,11,5,50,0,0,0,0,0 -0 555 401 899 424 +0 561 347 905 370 3 "0s" 0 0 0 0 0 -1 E E E 4 18 "LINE4" @@ -278,7 +278,7 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 3 1 2 1 E 523 90 523 306 +E E 0 3 1 2 1 E 523 90 523 260 4 24 "LINE8" "" @@ -310,7 +310,7 @@ E E 0 3 1 2 1 E 526 91 534 102 4 28 "LINE10" "" -1 697 307 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E +1 710 280 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E E E 32 0 0 0 0 0 E E E @@ -320,7 +320,7 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 3 1 2 1 E 697 307 751 186 +E E 0 3 1 2 1 E 710 280 751 186 4 29 "LINE11" "" @@ -352,7 +352,7 @@ E E 0 3 1 2 1 E 752 188 756 199 4 33 "LINE13" "" -1 678 433 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E +1 700 380 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E E E 39 0 0 0 0 0 E E E @@ -362,11 +362,11 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 3 1 2 1 E 678 433 745 541 +E E 0 3 1 2 1 E 700 380 767 488 4 34 "LINE14" "" -1 745 539 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E +1 768 492 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E E E 40 0 0 0 0 0 E E E @@ -376,11 +376,11 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 3 1 2 1 E 745 539 749 527 +E E 0 3 1 2 1 E 768 492 772 480 4 35 "LINE15" "" -1 741 540 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E +1 764 493 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E E E 41 0 0 0 0 0 E E E @@ -390,11 +390,11 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 3 1 2 1 E 741 540 727 533 +E E 0 3 1 2 1 E 764 493 750 486 4 40 "LINE16" "" -1 530 428.9999999999999 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E +1 530 389.9999999999998 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E E E 50 0 0 0 0 0 E E E @@ -404,7 +404,7 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 3 1 2 1 E 530 429 530 740 +E E 0 3 1 2 1 E 530 390 530 740 4 41 "LINE17" "" @@ -436,7 +436,7 @@ E E 0 3 1 2 1 E 520 726 529 736 4 43 "LINE19" "" -1 473 430 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E +1 470 390 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E E E 53 0 0 0 0 0 E E E @@ -446,7 +446,7 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 3 1 2 1 E 473 430 260 648 +E E 0 3 1 2 1 E 470 390 260 648 4 44 "LINE20" "" @@ -604,7 +604,7 @@ E E 0 3 1 2 1 E 184 290 196 290 14 134 "txt_datapoint" "" -1 557 321.694656488549 E E E 1 E 1 E N "_WindowText" E N "_Window" E E +1 560 270.7099236641215 E E E 1 E 1 E N "_WindowText" E N "_Window" E E E E 123 0 0 0 0 0 E E E @@ -615,12 +615,12 @@ LANG:1 0 0 1 LANG:1 32 Arial Black,-1,11,5,50,0,0,0,0,0 -0 555 320 899 343 +0 558 269 902 292 3 "0s" 0 0 0 0 0 -1 E E E 2 135 "PRIMITIVE_TEXT11" "" -1 1466.082645446156 237.4157717744857 E E E 1 E 1 E N "_WindowText" E N "_3DFace" E E +1 1686.14777928896 166.6326860628405 E E E 1 E 1 E N "_WindowText" E N "_3DFace" E E E E 125 0 0 0 0 0 E E E @@ -630,7 +630,7 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 1 1 2 1 E 1.06930693069307 0 1.00763358778626 117.594059405941 -19.5954198473284 1 E 282 340 391 355 +E E 0 1 1 2 1 E 1.06930693069307 0 1.00763358778626 118.4554455445543 -72.59541984732846 1 E 282 340 391 355 0 2 2 "0s" 0 0 0 192 0 0 282 340 1 1 LANG:1 32 Arial Black,-1,11,5,50,0,0,0,0,0 @@ -814,7 +814,7 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 1 3 1 2 1 E 940 10 940 640 +E E 1 3 1 2 1 E 940 10 940 570 13 316 "PUSH_BUTTON6" "" @@ -1021,207 +1021,6 @@ E E 2 1 1 2 1 E U 1 E 720 680 800 704 LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 0 1 LANG:1 16 New object name: -13 496 -"PUSH_BUTTON16" -"" -1 720 710 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E - E E -170 0 0 0 0 0 -E E E -0 -1 -LANG:1 0 - -0 -1 -LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 -0 718 708 802 738 - -T -1 -LANG:1 5 claim -"main() -{ - dpSet( - \"ClaimManager.request.typeName\" , \"Observation\", - \"ClaimManager.request.newObjectName\" , NAME.text ); - - dyn_string dpNamesWait = makeDynString(\"ClaimManager.response.newObjectName:_original.._value\"); - dyn_string dpNamesReturn = makeDynString(\"ClaimManager.response.typeName:_original.._value\", - \"ClaimManager.response.newObjectName:_original.._value\", - \"ClaimManager.response.DPName:_original.._value\", - \"ClaimManager.response.claimDate:_original.._value\"); - dyn_anytype conditions=NAME.text; - dyn_anytype returnValues; - - int status = dpWaitForValue( dpNamesWait, conditions, dpNamesReturn, returnValues, 25 ); - - if ( status == -1 ) { - DebugN( \"Event_Viewer.pnl:Error in dpWaitFor Value\" ); - - DebugN( \"Event_Viewer.pnl:dpNamesWait : \" + dpNamesWait ); - - DebugN( \"Event_Viewer.pnl:conditions : \" + conditions ); - - DebugN( \"Event_Viewer.pnl:dpNamesReturn : \" + dpNamesReturn ); - - DebugN( \"returnValues : \" + returnValues ); - } else if ( dynlen(getLastError()) != 0 ) { - - DebugN( \"Event_Viewer.pnl:Error returned in message dpWaitForValue\" ); - - // Reaction: e.g. output - - DebugN( getLastError() ); - - DebugN( \"Event_Viewer.pnl:dpNamesWait : \" + dpNamesWait ); - - DebugN( \"Event_Viewer.pnl:conditions : \" + conditions ); - - DebugN( \"dpNamesReturn : \" + dpNamesReturn ); - - DebugN( \"Event_Viewer.pnl:returnValues : \" + returnValues ); - } else { - DebugN( \"Event_Viewer.pnl:dpWaitForValue : everything ok\" ); - - DebugN( \"Event_Viewer.pnl:dpNamesWait : \" + dpNamesWait ); - - DebugN( \"Event_Viewer.pnl:conditions : \" + conditions ); - - DebugN( \"Event_Viewer.pnl:dpNamesReturn : \" + dpNamesReturn ); - - DebugN( \"Event_Viewer.pnl:returnValues : \" + returnValues ); - - DebugN(\"Set txt_response to: \"+ returnValues[3]); - setValue(\"txt_response\",\"text\",returnValues[3]); - - } -}" 0 - E E E -14 498 -"NAME" -"" -1 820 680 E E E 1 E 1 E N "_WindowText" E N "_Window" E E - E E -174 0 0 0 0 0 -E E E -0 -1 -LANG:1 0 - -0 -1 -LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 -0 818 678 1032 706 -3 "0s" 0 0 0 0 0 -1 E E E -30 499 -"FRAME2" -"" -1 8 6 E E E 1 E 1 E N "_WindowText" E N {0,0,0} E E - E E -176 0 0 0 0 0 -E E E -1 -1 -LANG:1 0 - -1 -"dashclr"N "_Transparent" -E E 0 0 1 0 1 E 0.825 0 0.88888888888889 703.4 654.6666666666666 0 E 8 6 409 97 -1 -LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 -0 1 -LANG:1 5 Claim -13 504 -"PUSH_BUTTON17" -"" -1 1050 670 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E - E E -186 0 0 0 0 0 -E E E -0 -1 -LANG:1 0 - -0 -1 -LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 -0 1048 668 1154 742 - -T -1 -LANG:1 29 Display -claims -( LogViewer ) -"main() -{ - // global dyn_string strClaimDPName; // datapoint that was claimed - // global dyn_string strClaimObjectName; // Actual object name - - DebugN( \"*********************************************\" ); - DebugN( \"Our global variable 'strClaimDPName' and 'strClaimObjectName' hold following records\" ); - - if( dynlen( strClaimObjectName )) - for( int t = 1; t <= dynlen( strClaimDPName ); t++) - { - DebugN( strClaimDPName[t] + \",\" + strClaimObjectName[t] ); - } - -}" 0 - E E E -30 547 -"FRAME3" -"" -1 8 6 E E E 1 E 1 E N "_WindowText" E N {0,0,0} E E - E E -188 0 0 0 0 0 -E E E -1 -1 -LANG:1 0 - -1 -"dashclr"N "_Transparent" -E E 0 0 1 0 1 E 1.1 0 0.6666666666666674 701.2000000000001 736 0 E 8 6 409 97 -1 -LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 -0 1 -LANG:1 8 Response -2 548 -"PRIMITIVE_TEXT3" -"" -1 721.9999999999993 764 E E E 1 E 1 E N "_WindowText" E N "_Transparent" E E - E E -190 0 0 0 0 0 -E E E -0 -1 -LANG:1 0 - -1 -"dashclr"N "_Transparent" -E E 2 1 1 2 1 E 0.9999999999999999 0 1 0 0 1 E 722 764 802 788 -0 2 2 "0s" 0 0 0 64 0 0 722 764 1 -1 -LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 -0 1 -LANG:1 3 DP: -14 549 -"txt_response" -"" -1 769.9999999999998 764 E E E 1 E 1 E N "_WindowText" E N "_Window" E E - E E -192 0 0 0 0 0 -E E E -0 -1 -LANG:1 0 - -0 -1 -LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 -0 768 762 1142 790 -3 "0s" 0 0 0 0 0 -1 E E E 13 592 "PUSH_BUTTON18" "" @@ -1370,36 +1169,6 @@ LANG:1 12 alarmmessage LANG:1 32 Arial Black,-1,11,5,50,0,0,0,0,0 0 18 792 304 815 3 "0s" 0 0 0 0 0 -1 E E E -13 834 -"PUSH_BUTTON21" -"" -1 830 710 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E - E E -209 0 0 0 0 0 -E E E -0 -1 -LANG:1 0 - -0 -1 -LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 -0 828 708 912 738 - -T -1 -LANG:1 4 free -"main() -{ - int ret = dpSet( - \"ClaimManager.reset.typeName\" , \"Observation\", - \"ClaimManager.reset.objectName\" , NAME.text ); - - if (ret < 0) DebugN(\"something went wrong resetting DP \"+NAME.txt+ \" \" + getLastError()); - - -}" 0 - E E E 13 883 "PUSH_BUTTON22" "" @@ -1465,11 +1234,11 @@ LANG:1 0 1 "$name""fw_topDetailSelection" 3 2 "PANEL_REF3" -1 -"objects\\Test\\Action.pnl" 668 220 T 107 1 0 1 -78 4 +"objects\\Test\\Action.pnl" 668 220 T 107 1 0 1 -78 -10 1 "$name""fw_bottomDetailSelection" 3 4 "PANEL_REF5" -1 -"objects\\Test\\Action.pnl" 649 450 T 108 1 0 1 -77 -3 +"objects\\Test\\Action.pnl" 649 450 T 108 1 0 1 -49 -40 1 "$name""fw_locator" 3 5 "PANEL_REF6" -1 @@ -1493,7 +1262,7 @@ LANG:1 0 1 "$name""fw_bottomDetailSelection" 3 14 "PANEL_REF15" -1 -"objects\\Test\\Event.pnl" 578 531 T 114 U +"objects\\Test\\Event.pnl" 578 531 T 114 1 0 1 12 -43 1 "$name""fw_locator" 3 17 "PANEL_REF18" -1 diff --git a/MAC/Navigator2/panels/Test/test.pnl b/MAC/Navigator2/panels/Test/test.pnl index c6942e2382e..709011380dc 100644 --- a/MAC/Navigator2/panels/Test/test.pnl +++ b/MAC/Navigator2/panels/Test/test.pnl @@ -5,56 +5,33 @@ LANG:6 0 PANEL,-1 -1 838 396 N "_3DFace" 0 "main() { - dyn_string statedps; - string observation = \"402147\"; - - string CEPDBNAME = \"CCU001\"; - string connectToStates = \"SELECT '_online.._value' FROM '{LOFAR**CobaltGPUProc*.status.state,LOFAR**CobaltGPUProc*.status.childState,LOFAR**CobaltStationInput.status.state,LOFAR**CobaltStationInput.status.childState}' REMOTE '\"+CEPDBNAME+\"' WHERE 'observationName:_online.._value' == '\"+observation+\"'\"; - - dyn_dyn_anytype aResult; - dpQuery(connectToStates,aResult); - -// DebugN(\"result: \", aResult); - - // Iterate through the results and collect the state and childState dps - dyn_string stateDPs; - for( int t = 2; t <= dynlen( aResult ); t++) + dpConnect(\"monitorRunStates\",\"ExampleDP_Arg1\"); + if (dpDisconnect(\"monitorRunStates\",\"ExampleDP_Arg1\") < 0) { - // skip the lines that contain the observationNames - string line = aResult[t][1]; - if (strpos(line,\"observationName\") >= 0) continue; - if (!dynContains(stateDPs, line) && dynlen(stateDPs) < 99) dynAppend(stateDPs,line); - } - - - // append the main hardware state - dynAppend(stateDPs,CEPDBName+\"LOFAR_PIC_Cobalt.status.state\"); - dynAppend(stateDPs,CEPDBName+\"LOFAR_PIC_Cobalt.status.childState\"); - - DebugN(\"connectCobaltNodesAndProcesses nr stateDPS to connect:\", dynlen(stateDPs)); - - dpConnect(\"CB\",TRUE,stateDPs); - - dyn_errClass err = getLastError(); //test whether an error occurred - if(dynlen(err) > 0) { - errorDialog(err); - // open dialog box with errors - throwError(err); // write errors to stderr - - LOG_ERROR(\"ObservationFlow_cobaltNodeProcesses.pnl:connectCobaltNodesAndProcesses| ERROR: connect fails:\"+ stateDPs); - } else { - DebugN(\" No error\"); - } + DebugN(\"Error 1\"); + } + else + { + DebugN(\"No error 1\"); + } + if (dpDisconnect(\"monitorRunStates\",\"ExampleDP_Arg1\") < 0) + { + DebugN(\"Error 2\"); + } + else + { + DebugN(\"No error 2\"); + } } -void CB(dyn_string dpList, dyn_int stateList) +void monitorRunStates(string dp, float value) { - DebugN(\"CB| has \" + dynlen( dpList) + \" results\" ); - DebugN(dpList); + DebugN(\"in runstates\"); }" 0 E E E E 1 -1 -1 0 50 30 ""0 1 -E E 2 +E "#uses \"navPanel.ctl\"" 0 + 2 "CBRef" "1" "EClose" E "" diff --git a/MAC/Navigator2/panels/Test/testClaim.pnl b/MAC/Navigator2/panels/Test/testClaim.pnl index 87db647f2dc..4d9b61acdf8 100644 --- a/MAC/Navigator2/panels/Test/testClaim.pnl +++ b/MAC/Navigator2/panels/Test/testClaim.pnl @@ -1,7 +1,7 @@ V 11 1 LANG:1 0 -PANEL,-1 -1 855 180 N "_3DFace" 0 +PANEL,-1 -1 1205 180 N "_3DFace" 0 "main() { dyn_string dps = makeDynString(); @@ -62,7 +62,7 @@ LANG:1 0 25 0 "claimtable" "" -1 9.999999999999911 19.99999999999998 E E E 1 E 1 E N "_WindowText" E N "_Window" E E +1 9.999999999999874 19.99999999999998 E E E 1 E 1 E N "_WindowText" E N "_Window" E E E E 0 0 0 0 0 0 E E E @@ -73,32 +73,32 @@ LANG:1 0 0 1 LANG:1 30 Sans Serif,9,-1,5,50,0,0,0,0,0 -0 8 18 832 162 +0 8 18 1182 162 EE 1 0 1 4 0 "temp" 21 1 0 "s" 1 LANG:1 4 temp E 1 LANG:1 0 -200 "name" 21 1 0 "s" 1 +400 "name" 21 1 0 "s" 1 LANG:1 4 name E 1 LANG:1 0 -200 "claim" 21 1 0 "s" 1 +400 "claim" 21 1 0 "s" 1 LANG:1 5 claim E 1 LANG:1 0 -200 "free" 21 1 0 "s" 1 +150 "free" 21 1 0 "s" 1 LANG:1 4 free E 1 LANG:1 0 -200 +150 14 14 10 10 1 LANG:1 30 Sans Serif,9,-1,5,50,0,0,0,0,0 diff --git a/MAC/Navigator2/panels/Test/testclick.pnl b/MAC/Navigator2/panels/Test/testclick.pnl index 8e9cc218043..0c0d9d013ae 100644 --- a/MAC/Navigator2/panels/Test/testclick.pnl +++ b/MAC/Navigator2/panels/Test/testclick.pnl @@ -7,30 +7,11 @@ PANEL,-1 -1 500 408 N "_3DFace" 0 { - dyn_dyn_string dd_string; - - - - dyn_string d_string1=makeDynString(\"c\",\"a\",\"b\"); - - dyn_string d_string2=makeDynString(\"1\",\"3\",\"2\"); - - dyn_string d_string3=makeDynString(\"wel\",\"dit\",\"klopt\"); - - - - dd_string[1]=d_string1; - - dd_string[2]=d_string2; - - dd_string[3]=d_string3; - - dynSort(d_string1); - DebugN(d_string1); - - dynDynSort(dd_string, 1); // asc - - DebugN(dd_string); +// string sys = (strsplit(getSystemName(),\":\"))[1]+\"c.control.lofar\" + string sys = (strsplit(getSystemName(),\":\"))[1]+\".control.lofar\"; + DebugN(sys); + + DebugN(getHostByName(sys)); }" 0 E E E E 1 -1 -1 0 50 40 diff --git a/MAC/Navigator2/panels/objects/Alerts/alarmsWinCCOA.pnl b/MAC/Navigator2/panels/objects/Alerts/alarmsWinCCOA.pnl new file mode 100644 index 00000000000..ad816b45328 --- /dev/null +++ b/MAC/Navigator2/panels/objects/Alerts/alarmsWinCCOA.pnl @@ -0,0 +1,116 @@ +V 11 +1 +LANG:1 0 +PANEL,-1 -1 1242 823 N "_3DFace" 0 +E E E E E 1 -1 -1 0 10 10 +""0 1 +E E 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 0 +1 1 0 "" 26 +0 +1 2 0 "" 27 +0 +1 3 0 "" 0 +0 +1 4 0 "" 1 +0 +1 5 0 "" 17 +0 +1 6 0 "" 19 +0 +1 7 0 "" 20 +0 +1 8 0 "" 21 +0 +1 9 0 "" 25 +0 +1 10 0 "0" 0 +0 +1 11 0 "0" 1 +0 +1 12 0 "" 11 +0 +1 13 0 "" 12 +0 +1 14 0 "" 15 +0 +1 15 0 "" 14 +0 +1 16 0 "" 16 +0 +29 17 +"BackgroundCover_ewo1" +"" +1 710.0000000000003 753 E E E 1 E 1 E N "_3DText" E N {170,170,170} E E + E E +5 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 710 753 1078 793 +19 BackgroundCover.ewo +0 +E29 18 +"BackgroundCover_ewo2" +"" +1 400 680 E E E 1 E 1 E N "_3DText" E N {240,240,240} E E + E E +6 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 400 680 768 720 +19 BackgroundCover.ewo +0 +E0 +LAYER, 1 +1 +LANG:1 0 +0 +LAYER, 2 +1 +LANG:1 0 +0 +LAYER, 3 +1 +LANG:1 0 +0 +LAYER, 4 +1 +LANG:1 0 +0 +LAYER, 5 +1 +LANG:1 0 +0 +LAYER, 6 +1 +LANG:1 0 +0 +LAYER, 7 +1 +LANG:1 0 +0 +3 0 "PANEL_REF0" -1 +"vision\\aes\\AEScreen.pnl" 118 1270 T 4 1 0 1 -20 -40 +3 +"$ACTION""1" +"$FILENAME""" +"$SCREENTYPE""aes_alerts_LOFAR" +0 diff --git a/MAC/Navigator2/panels/objects/Observations/Observation_small.pnl b/MAC/Navigator2/panels/objects/Observations/Observation_small.pnl index bcdaf972b36..b1a8fac91c3 100644 --- a/MAC/Navigator2/panels/objects/Observations/Observation_small.pnl +++ b/MAC/Navigator2/panels/objects/Observations/Observation_small.pnl @@ -110,10 +110,7 @@ updateObservations(string dp1, dyn_string obs, if (!navFunct_dpReachable(obsDP) ){ LOG_ERROR(\"Observation_small.pnl:updateObservations|DP unreachable\"); updateObservationsTableValues(\"\",0,\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",true); - } - - } else if (!dpExists(obsDP)){ - LOG_ERROR(\"Observation_small.pnl:updateObservations|ERROR: Dp for LOFAR_ObsSW_\"+obs[i]+\" doesn't exist.\"); + } } } } diff --git a/MAC/Navigator2/panels/objects/Observations/Pipeline_small.pnl b/MAC/Navigator2/panels/objects/Observations/Pipeline_small.pnl index 5da3b1cd5f3..588dd15d8e0 100644 --- a/MAC/Navigator2/panels/objects/Observations/Pipeline_small.pnl +++ b/MAC/Navigator2/panels/objects/Observations/Pipeline_small.pnl @@ -107,10 +107,7 @@ updatePipelines(string dp1, dyn_string obs, if (!navFunct_dpReachable(obsDP) ){ LOG_ERROR(\"Pipeline_small.pnl:updatePipelines|DP unreachable\"); updatePipelinesTableValues(\"\",0,\"\",\"\",\"\",\"\",\"\",\"\",\"\",true); - } - - } else if( !dpExists(obsDP) ) { - LOG_ERROR(\"Pipeline_small.pnl:updatePipelines|ERROR: Dp for LOFAR_ObsSW_\"+obs[i]+\" doesn't exist.\"); + } } } } diff --git a/MAC/Navigator2/panels/objects/Processes/SWControlerTempObs_small.pnl b/MAC/Navigator2/panels/objects/Processes/SWControlerTempObs_small.pnl index 6f39ca7a1d2..a6c5a9acccf 100644 --- a/MAC/Navigator2/panels/objects/Processes/SWControlerTempObs_small.pnl +++ b/MAC/Navigator2/panels/objects/Processes/SWControlerTempObs_small.pnl @@ -11,28 +11,36 @@ PANEL,-1 -1 399 95 N "_3DFace" 2 baseDP = station+\"LOFAR_\"+$name; setValue(\"process\", \"toolTipText\", baseDP); - string lvlDP = station+\"LOFAR_PermSW_Daemons_SoftwareMonitor\"; // Connect to SWLevel - if (navFunct_dpReachable(lvlDP)) { - if(dpExists(lvlDP+\".SWLevel\")) { + if (navFunct_dpReachable(lvlDP)) + { + if(dpExists(lvlDP+\".SWLevel\")) + { if (dpConnect(\"callbackLevelChanged\", lvlDP+\".SWLevel:_online.._value\", - lvlDP+\".SWLevel:_online.._invalid\") == -1) { + lvlDP+\".SWLevel:_online.._invalid\") == -1) + { LOG_ERROR(\"SWControlerTempObs_small.pnl:main|Couldn't connect to: \"+lvlDP+\".SWLevel\"); } - } else { + } + else + { LOG_ERROR(\"SWControlerTempObs_small.pnl:main|\"+lvlDP+\".SWLevel not found\"); } } // Connect to active observations - if (navFunct_dpReachable(MainDBName+\"LOFAR_PermSW_MACScheduler.activeObservations\")) { - if (dpConnect(\"callbackObservationChanged\", MainDBName+\"LOFAR_PermSW_MACScheduler.activeObservations:_online.._value\") == -1) { + if (navFunct_dpReachable(MainDBName+\"LOFAR_PermSW_MACScheduler.activeObservations\")) + { + if (dpConnect(\"callbackObservationChanged\", MainDBName+\"LOFAR_PermSW_MACScheduler.activeObservations:_online.._value\") == -1) + { LOG_ERROR(\"SWControlerTempObs_small.pnl:main|Couldn't connect to: \"+MainDBName+\":LOFAR_PermSW_MACScheduler.activeObservations: \"+getLastError()); } - } else { + } + else + { if (!isStandalone()) LOG_ERROR(\"SWControlerTempObs_small.pnl:main|\"+MainDBName+\" seems offline\"); } @@ -43,28 +51,26 @@ void callbackLevelChanged(string dp1, int swlvlCB, string dp2, bool swinvalidCB) { // Store in scope var + if (swlvl == swlvlCB) { + return; + } + swlvl = swlvlCB; swinvalid = swinvalidCB; + checkStateConnection(); } - - - - - private void callbackObservationChanged(string dp1, dyn_string observationsCB) { // Store in scope var observations = observationsCB; + checkStateConnection(); } - - - private void checkStateConnection() { @@ -72,22 +78,28 @@ private void checkStateConnection() string firstPipeline = \"\"; // for now the object will only show the controller for the first observation // We also have to check if the swleve;l is higher then 6 for the obs dependant ctrl'ers - if (dynlen(observations) >= 1) { - for (int i = 1; i <= dynlen(observations); i++) { - if ($name != \"PythonControl\" && navFunct_isObservation(observations[i]) && firstObservation == \"\") { + if (dynlen(observations) >= 1) + { + for (int i = 1; i <= dynlen(observations); i++) + { + if ($name != \"PythonControl\" && navFunct_isObservation(observations[i]) && firstObservation == \"\") + { firstObservation = observations[i]; - } else if ($name == \"PythonControl\" && !navFunct_isObservation(observations[i]) && firstPipeline == \"\") { + } + else if ($name == \"PythonControl\" && !navFunct_isObservation(observations[i]) && firstPipeline == \"\") + { firstPipeline = observations[i]; } } - - if ($name != \"PythonControl\" && firstObservation == \"\") { + if ($name != \"PythonControl\" && firstObservation == \"\") + { setValue(\"process\", \"backCol\", \"Lofar_off\"); return; } - if ($name == \"PythonControl\" && firstPipeline == \"\") { + if ($name == \"PythonControl\" && firstPipeline == \"\") + { setValue(\"process\", \"backCol\", \"Lofar_off\"); return; } @@ -114,10 +126,12 @@ private void checkStateConnection() // Disconnect if( (baseDPconnected != \"\") && (baseDP != baseDPconnected) ) { + if (dpDisconnect(\"updateSWController\", baseDPconnected +\".status.state:_online.._value\", - baseDPconnected +\".status.state:_online.._invalid\") == -1) { - baseDPconnected = \"\"; - } + baseDPconnected +\".status.state:_online.._invalid\") != -1) + { + setValue(\"process\", \"backCol\", \"Lofar_off\"); + } } @@ -125,31 +139,43 @@ private void checkStateConnection() if( (baseDP != \"\") && (baseDP != baseDPconnected) ) { - if (navFunct_dpReachable(baseDP+\".status.state\")) { - if (dpExists(baseDP+\".status.state\")) { + if (navFunct_dpReachable(baseDP+\".status.state\")) + { + if (dpExists(baseDP+\".status.state\")) + { if (dpConnect(\"updateSWController\", baseDP +\".status.state:_online.._value\", - baseDP +\".status.state:_online.._invalid\") == -1) { + baseDP +\".status.state:_online.._invalid\") == -1) + { setValue(\"process\", \"backCol\", \"Lofar_dpdoesnotexist\"); LOG_ERROR(\"SWControlerTempObs_small.pnl:reload|Couldn't connect updateSWController: \"+getLastError()); - } else { + } + else + { baseDPconnected = baseDP; } - } else { - setValue(\"process\", \"backCol\", \"Lofar_dpdoesnotexist\"); + } + else + { + setValue(\"process\", \"backCol\", \"Lofar_dpdoesnotexist\"); } - } else { + } + else + { setValue(\"process\", \"backCol\", \"Lofar_dpOffline\"); } } - if ($name == \"PythonControl\") + if ($name == \"PythonControl\" && firstPipeline != \"\" && swlvl == 6) { - setValue(\"process\", \"backCol\", \"Lofar_off\"); + setValue(\"process\", \"backCol\", \"Lofar_operational\"); } - + else if ($name == \"PythonControl\") + { + setValue(\"process\", \"backCol\", \"Lofar_off\"); + } } @@ -159,7 +185,12 @@ private void checkStateConnection() updateSWController(string dp1, int status, string dp2, bool invalid) { - + if (!navFunct_dpReachable(station)) + { + setValue(\"process\", \"backCol\", \"Lofar_dpOffline\"); + return; + } + if (invalid) { setValue(\"process\", \"backCol\", \"Lofar_invalid\"); @@ -168,8 +199,7 @@ updateSWController(string dp1, int status, else { setValue(\"process\", \"backCol\", getStateColor(status)); - } - + } } " 0 @@ -218,7 +248,7 @@ void dblClick() { bDoubleClicked = true; if (dpExists(baseDP) ) { - LOG_DEBUG(\"SWcontroller_small.pnl:DoubleClick|Setting currentDatapoint from : \"+g_currentDatapoint+\" to \"+baseDP); + LOG_DEBUG(\"SWcontrollerTempObs_small.pnl:DoubleClick|Setting currentDatapoint from : \"+g_currentDatapoint+\" to \"+baseDP); g_currentDatapoint=baseDP; //we also have to set the tabctrl to think this was initiated from the ProcessesTab, otherwise we will get the wrong panel. navPanel_setEvent(\"Processes\",\"ChangeTab\"); diff --git a/MAC/Navigator2/panels/objects/navigator_viewSelection.pnl b/MAC/Navigator2/panels/objects/navigator_viewSelection.pnl index ecd62b860c5..c71f7e5d026 100644 --- a/MAC/Navigator2/panels/objects/navigator_viewSelection.pnl +++ b/MAC/Navigator2/panels/objects/navigator_viewSelection.pnl @@ -220,7 +220,7 @@ LANG:1 5 email if (id == \"3\") { ModuleOnWithPanel(\"LOFAR logger settings\", -1, -1, 0, 0, 1, 1,\"\", \"objects/lofar_logger.pnl\", \"\" , makeDynString()); } else if (id == \"4\") { - ModuleOnWithPanel(\"Test Panel\", -1, -1, 0, 0, 1, 1, \"\", \"Test/Event_Viewer.pnl\", \"\" , makeDynString()); + ModuleOnWithPanel(\"Test Panel\", -1, -1, 0, 0, 1, 1, \"\", \"Test/Navigator_testPanel.pnl\", \"\" , makeDynString()); } else if (id == \"5\") { ModuleOnWithPanel(\"Email settings\", -1, -1, 0, 0, 1, 1, \"\", \"Settings/mail.pnl\", \"\" , makeDynString()); } diff --git a/MAC/Navigator2/panels/vision/aes/AES_properties.pnl b/MAC/Navigator2/panels/vision/aes/AES_properties.pnl new file mode 100644 index 00000000000..6e491155d31 --- /dev/null +++ b/MAC/Navigator2/panels/vision/aes/AES_properties.pnl @@ -0,0 +1,648 @@ +V 11 +2 +LANG:1 25 Properties of alert panel +LANG:0 29 Eigenschaften von Meldeschirm +PANEL,-1 -1 480 554 N "_3DFace" 0 +"//#uses \"AES_peter.ctl\" + +main() +{ + dyn_string configNames; + + // we need this call to build new panelglobal vstn/ddares object +// aes_reload(); + + // saving all important values to panelglobal variables + g_propDpName =getDollarValue( AESREGDOLLAR_PROPDP ); + g_balPropDpName=getDollarValue( AESREGDOLLAR_BALPROPDP ); + g_configName =getDollarValue( AESREGDOLLAR_CONFIGNAME ); + g_alertRow =getDollarValue( AESREGDOLLAR_ALERTROW ); + g_configPanel =getDollarValue( AESREGDOLLAR_CONFIGPANEL ); + + if( isDollarDefined( AESREGDOLLAR_COLTITLES ) ) + { + g_colTitles=aec_s2ds( getDollarValue( AESREGDOLLAR_COLTITLES ), AEC_SEP ); + } + + if( isDollarDefined( AESREGDOLLAR_COLNAMES ) ) + { + g_colNames=aec_s2ds( getDollarValue( AESREGDOLLAR_COLNAMES ), AEC_SEP ); + } + +//DebugN(\"Properties titles(\" + dynlen(g_colTitles) + \")=\" + g_colTitles ); +//DebugN(\"Properties names(\" + dynlen(g_colNames) + \")=\" + g_colNames ); + + aes_getScreenType( g_propDpName, g_screenType ); + aes_getTabType( g_propDpName, g_tabType ); + + if( !aes_operationPermission( g_configName, AES_OPERTYPE_PROPERTIES, AES_OPER_REMOVE ) ) + { + //��� dialog + setMultiValue( \"pb_saveConfig\", \"enabled\", false, + \"pb_deleteConfig\", \"enabled\", false ); + } + else + { + int deletePermission; //contains the permission to delete a DP + dpGet(\"_System.Auth.Dp:_original.._value\", deletePermission); // Querie the userpermission which is needed to delete a DP + setMultiValue( \"pb_saveConfig\", \"enabled\", true, + \"pb_deleteConfig\", \"enabled\", getUserPermission(deletePermission)); // set enabled if user is allowed to delete a DP + } + + // if we were in config panel, we have to copy property settings to dummy runtime dp + // even screenType, tabType + if( g_configPanel ) + { + //aes_copyDp(); + } + + aes_propInit( g_screenType, g_tabType, g_configName, g_propDpName ); + + if( g_alertRow ) + { + ti_type.enabled=false; + } + + setValue( \"sl_tmpVisCol\", \"items\", g_colTitles ); + + reg_main.visible=true; +}" 0 + E E E E 1 -1 -1 0 13 8 +""0 1 +E "//////// neu ******* begin +// neu +dyn_dyn_anytype vstn; +dyn_anytype ddaRes; + +// neu - jetzt �ber dollar var +dyn_string g_colNames; +dyn_string g_colTitles; + +// the following variables will be set in initial section +string g_propDpName; +string g_balPropDpName; // balanced propDpName +string g_configName; +int g_screenType; +int g_tabType; +bool g_alertRow; +bool g_configPanel; + +const int +COLIDX=1, +PANEL=2, +COLNAME=3, +COLVIS=4, +COLSCRRENTYPE=5, +COLTABTYPE=6, +COLCONFIGNAME=7; + +int +firstAlertReg, lastAlertReg, +firstEventReg, lastEventReg; + +// const +int +//alerts +PREGA_TIMERANGE, +PREGA_FILTER, +PREGA_FILTERTYPES, +PREGA_FILTERSTATE, +PREGA_FILETERSYSTEM, +PREGA_SORT, +PREGA_VISIBLE, +PREGA_GENERAL, +// events +PREGE_TIMERANGE, +PREGE_FILTER, +PREGE_FILTERTYPES, +PREGE_FILTERSTATE, +PREGE_FILETERSYSTEM, +PREGE_SORT, +PREGE_VISIBLE, +PREGE_GENERAL; + +closePanel() +{ + string config; + dyn_string ds, configList; + dyn_float df; + int pos; + + configList=configName.items; + config=configName.text; + + if( dynlen( configList ) > 0 ) + { + ds=configList; + pos=dynContains( configList, config ); + if( pos > 0 ) + { + df[1]=pos; + } + else + { + df[1]=0; + } + } + else + { + df[1]=0; + ds=makeDynString(); + } + + df[2]=AES_CONF_CANCEL; + + // we don't write back data to runtime dp at cancel + PanelOffReturn( df, ds ); +}" 0 + 2 +"CBRef" "0" +"EClose" "main() +{ + closePanel(); +}" 0 + +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 6 Layer1 +30 18 +"Frame1" +"" +1 10 499 E E E 1 E 1 E N "_3DText" E N "_Transparent" E E + E E +18 0 0 0 0 0 +E E E +1 +2 +LANG:1 0 +LANG:0 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 0 1 E 1 0 0.9322033898305084 0 29.83050847457629 0 E 10 440 470 500 +2 +LANG:1 26 Arial,10,-1,5,75,0,0,0,0,0 +LANG:0 26 Arial,10,-1,5,75,0,0,0,0,0 +0 2 +LANG:1 10 Properties +LANG:0 13 Eigenschaften +2 20 +"Text1" +"" +1 110 510 E E E 1 E 0 E N {0,0,0} E N "_Transparent" E E + E E +19 0 0 0 0 0 +E E E +0 +2 +LANG:1 0 +LANG:0 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 0 1 E U 0 E 112 512 189 528 +0 2 0 "0s" 0 0 0 192 0 0 112 512 1 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 2 +LANG:1 14 Active config: +LANG:0 14 Aktive Konfig: +13 14 +"Button4" +"" +1 340 459 E E E 0 E 0 E N "_ButtonText" E N "_Button" E E + E E +14 0 0 0 0 0 +E E E +0 +2 +LANG:1 11 Open config +LANG:0 13 Konfig �ffnen + +0 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 338 457 366 485 + +P +10862530 +"pictures/StandardIcons/Open_20.png" +2 +LANG:1 0 +LANG:0 0 +"main() +{ + aes_loadPropertyConfig(); +}" 0 + E E E +22 5 +"configName" +"" +1 17.84375 460 E E E 1 E 1 E N {0,0,0} E N "_Window" E E + E E +2 0 0 0 0 0 +E E E +0 +2 +LANG:1 0 +LANG:0 0 + +0 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 18 458 367 484 +0 + +E +"main() +{ + aes_loadPropertyConfig(); +}" 0 + +E + 0 0 +13 1 +"ok" +"" +1 285 519 E E E 1 E 1 E N {0,0,0} E N "_Button" E E + E E +6 0 0 0 0 0 +E E E +0 +2 +LANG:1 0 +LANG:0 0 + +0 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 283 518 371 546 + +T +2 +LANG:1 2 OK +LANG:0 2 OK +"main() +{ + + int screenType, tabType; + string config; + dyn_string ds, configList; + dyn_float df; + int pos, ret; + + configList=configName.items; + config=configName.text; + + // only this set operation write datas to runtime dp + aes_setProps( g_propDpName, config, g_screenType, g_tabType, ret, false, false); + + // saving configchange to runtime dp + dpSetWait( g_propDpName + \".Settings.Config\" + AES_ORIVAL, config ); + + if( dynlen( configList ) > 0 ) + { + ds=configList; + pos=dynContains( configList, config ); + + if( pos > 0 ) + { + df[1]=pos; + } + else + { + df[1]=0; + } + } + else + { + df[1]=0; + ds=makeDynString(); + } + + df[2]=AES_CONF_OK; + + PanelOffReturn( df, ds ); +}" 0 + E E E +13 2 +"abbrechen" +"" +1 375 519 E E E 1 E 1 E N {0,0,0} E N "_Button" E E + E E +7 0 0 0 27 0 +E E E +0 +2 +LANG:1 0 +LANG:0 0 + +0 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 373 518 461 546 + +T +2 +LANG:1 6 Cancel +LANG:0 9 Abbrechen +"main() +{ + closePanel(); +}" 0 + E E E +28 11 +"reg_main" +"" +1 70 85 E E E 1 E 0 E N "_3DText" E N "_3DFace" E E + E E +12 0 0 0 0 0 +E E E +0 +2 +LANG:1 0 +LANG:0 0 + +0 +2 +LANG:1 26 Arial,-1,12,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,12,5,40,0,0,0,0,0 +0 8 8 472 432 +E8 "#0" 2 +LANG:1 2 #A +LANG:0 2 #A +0 +"#1" 2 +LANG:1 2 #B +LANG:0 2 #C +0 +"#2" 2 +LANG:1 2 #C +LANG:0 2 #D +0 +"#3" 2 +LANG:1 2 #D +LANG:0 2 #E +0 +"#4" 2 +LANG:1 2 #E +LANG:0 2 #F +0 +"#5" 2 +LANG:1 2 #F +LANG:0 2 #G +0 +"#6" 2 +LANG:1 2 #G +LANG:0 2 #H +0 +"#7" 2 +LANG:1 2 #H +LANG:0 2 #I +0 + +13 12 +"pb_saveConfig" +"" +1 405 459 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E + E E +12 0 0 0 0 0 +E E E +0 +2 +LANG:1 13 Save property +LANG:0 21 Eigenschaft speichern + +0 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 403 457 431 485 + +P +13160660 +"pictures/StandardIcons/Save_20.png" +2 +LANG:1 0 +LANG:0 0 +"main() +{ + aes_savePropertyConfig(); +}" 0 + E E E +13 13 +"pb_deleteConfig" +"" +1 434 459 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E + E E +13 0 0 0 0 0 +E E E +0 +2 +LANG:1 15 Delete property +LANG:0 19 Eigenschaft l�schen + +0 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 433 457 461 485 + +P +13160660 +"pictures/StandardIcons/delete_20.png" +2 +LANG:1 0 +LANG:0 0 +"main() +{ + aes_removePropertyConfig(); +}" 0 + E E E +13 15 +"Button1" +"" +1 20 510 E E E 1 E 0 E N "_ButtonText" E N "_Button" E E + E E +15 0 0 0 0 0 +E E E +0 +2 +LANG:1 0 +LANG:0 0 + +0 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 18 518 46 546 + +P +13434828 +"15.png" +2 +LANG:1 0 +LANG:0 0 +E E E E +13 16 +"Button2" +"" +1 80 510 E E E 1 E 0 E N "_ButtonText" E N "_Button" E E + E E +16 0 0 0 0 0 +E E E +0 +2 +LANG:1 0 +LANG:0 0 + +0 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 78 518 106 546 + +P +13434828 +"16.png" +2 +LANG:1 0 +LANG:0 0 +E E E E +13 17 +"Button3" +"" +1 50 510 E E E 1 E 0 E N "_ButtonText" E N "_Button" E E + E E +17 0 0 0 0 0 +E E E +0 +2 +LANG:1 0 +LANG:0 0 + +0 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 48 518 76 546 + +P +13434828 +"17.png" +2 +LANG:1 0 +LANG:0 0 +E E E E +13 19 +"pb_newConfig" +"" +1 374 459 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E + E E +19 0 0 0 0 0 +E E E +0 +2 +LANG:1 12 New property +LANG:0 23 Eigenschaft neu anlegen + +0 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 373 457 401 485 + +P +13160660 +"pictures/StandardIcons/new_20.png" +2 +LANG:1 0 +LANG:0 0 +"main() +{ + aes_addPropertyConfig(); +}" 0 + E E E +14 21 +"txtConfigName" +"" +1 150 520 E E E 1 E 0 E N "_WindowText" E N "_3DFace" E E + E E +20 0 0 0 0 0 +E E E +0 +2 +LANG:1 0 +LANG:0 0 + +0 +2 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +LANG:0 26 Arial,-1,13,5,40,0,0,0,0,0 +0 148 518 252 544 +3 "0s" 0 0 0 0 0 -1 E E E +1 25 0 "" 0 +0 +1 26 0 "" 1 +0 +29 24 +"BackgroundCover_ewo2" +"" +1 370 455 E E E 1 E 1 E N "_3DText" E N {240,240,240} E E + E E +21 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 370 455 465 488 +19 BackgroundCover.ewo +0 +"main() +{ + this.visible = !isModeExtended(); +}" 0 +0 +LAYER, 1 +1 +LANG:1 6 Layer2 +0 +LAYER, 2 +1 +LANG:1 6 Layer3 +0 +LAYER, 3 +1 +LANG:1 6 Layer4 +0 +LAYER, 4 +1 +LANG:1 6 Layer5 +0 +LAYER, 5 +1 +LANG:1 6 Layer6 +0 +LAYER, 6 +1 +LANG:1 6 Layer7 +0 +LAYER, 7 +1 +LANG:1 6 Layer8 +0 +3 0 "PANEL_REF0" -1 +"objects_parts\\STD_OBJECTS\\ButtonBarBackground.pnl" 15 510 T 21 1.050100200400802 0 1 -25.75150300601199 0 +0 +0 diff --git a/MAC/Navigator2/scripts/claim.ctl b/MAC/Navigator2/scripts/claim.ctl index 3f7548a3058..b25728b4251 100644 --- a/MAC/Navigator2/scripts/claim.ctl +++ b/MAC/Navigator2/scripts/claim.ctl @@ -386,7 +386,7 @@ void clientAddClaimCallback( // Description: // Here the claiming mechanism will start // Obvious it's the same for master as well as client, -// since they bove have their own internal mapping. +// since they both have their own internal mapping. // // Returns: // None diff --git a/MAC/Navigator2/scripts/libs/claimManager.ctl b/MAC/Navigator2/scripts/libs/claimManager.ctl index b8583737fef..458e8335413 100644 --- a/MAC/Navigator2/scripts/libs/claimManager.ctl +++ b/MAC/Navigator2/scripts/libs/claimManager.ctl @@ -35,8 +35,11 @@ // the name of the datapoints plus the actual name // This is used to quickly determine the name of the real datapoint // from the graphical user interface -global dyn_string strClaimDPName; // datapoint that was claimed -global dyn_string strClaimObjectName; // Actual object name +global dyn_string strClaimDPName; // datapoint that was claimed +global dyn_string strClaimObjectName; // Actual object name +global int g_freeClaims = 0; // keeps track of free claims +global int g_usedClaims = 0; // keeps track of used claims +global int g_unusedClaims = 0; // keeps track of unused claims // **************************************** @@ -117,93 +120,88 @@ string claimManager_realNameToName( string strName ) void claimManager_queryConnectClaims() { - // Local data - string DPT = "Observation"; - if (g_standAlone) { - DPT = "StnObservation"; - } - string strQuery = "SELECT '.claim.name:_original.._value, .claim.claimDate:_original.._value, .claim.freeDate:_original.._value' FROM 'LOFAR_ObsSW_*' WHERE _DPT = \""+DPT+"\""; - - LOG_DEBUG( "claimManager.ctl:claimManager_queryConnectClaims|*** Doing a query for : claimManager_QueryConnectClaims() " ); - - // Trigger a single query that gets an update when one - // claim changes - if (dpQueryConnectSingle( "claimManager_queryConnectClaim_Callback", 1, "ident_claim", strQuery ) == -1) { - LOG_ERROR( "claimManager.ctl:claimManager_queryConnectClaims|dpQueryConnectSingle failed" ); - if ( g_initializing ) { + string baseDP = MainDBName+"ClaimManager.cache"; + if (dpExists(baseDP)) { + if (dpConnect("claimManager_updateCacheClaims",true, baseDP + ".newObjectNames:_online.._value", + baseDP + ".DPNames:_online.._value", + baseDP + ".claimDates:_online.._value", + baseDP + ".freeDates:_online.._value", + baseDP + ".newObjectNames:_online.._invalid") == -1) + { + LOG_DEBUG("Claim_Viewer.pnl:main|Couldn't connect to: "+baseDP); + } + else + { + LOG_DEBUG("Claim_Viewer.pnl:main|Connected to: " + baseDP); + } + } + else + { + if (!isStandalone()) { + LOG_ERROR( "claimManager.ctl:claimManager_queryConnectClaims|Couldn't find DP to connect to: "+baseDP); + } + if ( g_initializing ) + { writeInitProcess("connectClaimsFinished"); } } } -void claimManager_queryConnectClaim_Callback(string strIdent, dyn_dyn_anytype aResult) +claimManager_updateCacheClaims(string dp1, dyn_string objectNames, + string dp2, dyn_string DPNames, + string dp3, dyn_time claimDates, + string dp4, dyn_time freeDates, + string dp5, bool invalid) { - // Local data - int iPos; - string aDP; - string strDP; - string strName; - time tClaimDate; - time tFreeDate; - bool bClaimed; + dyn_string claimedObjectNames; + dyn_string claimedDPNames; - LOG_DEBUG( "claimManager.ctl:claimManager_queryConnectClaim_Callback| has " + dynlen( aResult ) + " results" ); - LOG_DEBUG( "claimManager.ctl:claimManager_queryConnectClaim_Callback| "+aResult); - if( dynlen( aResult ) < 2 ) { - writeInitProcess("connectClaimsFinished"); - return; + if (invalid) + { + LOG_WARN("claimManager.ctl:claimManager_updateCacheClaims|ClaimManager.cache is invalid"); + if ( g_initializing ) { + writeInitProcess("connectClaimsFinished"); } + return; + } + + int unused = 0; + int claimed = 0; + int freed = 0; - // Iterate through the results - for( int t = 2; t <= dynlen( aResult ); t++) + for (int i=1; i<= dynlen(objectNames);i++) { - aDP = aResult[t][1]; - strName = aResult[t][2]; - tClaimDate = aResult[t][3]; - tFreeDate = aResult[t][4]; - - strDP=dpSubStr(aDP,DPSUB_DP); - -// LOG_DEBUG("claimManager.ctl:claimManager_queryConnectClaim_Callback| strDP : "+ strDP); -// LOG_DEBUG("claimManager.ctl:claimManager_queryConnectClaim_Callback| strName : "+strName); -// LOG_DEBUG("claimManager.ctl:claimManager_queryConnectClaim_Callback| ClaimDate : "+year(tClaimDate)); -// LOG_DEBUG("claimManager.ctl:claimManager_queryConnectClaim_Callback| FreeDate : "+year(tFreeDate)); - - - // We are unclaimed when the date == 1970 and the freedate == 1970 - if (year(tClaimDate) == 1970 && year(tFreeDate) == 1970) { - bClaimed = false; - } else { - bClaimed = true; + time claim = claimDates[i]; + time free = freeDates[i]; + dynAppend(claimedObjectNames,objectNames[i]); + dynAppend(claimedDPNames,DPNames[i]); + if (period(claim) == 0 && period(free) == 0) + { + unused += 1; } - - // Do we already have this name - iPos = dynContains( strClaimDPName, strDP ); - - LOG_TRACE("claimManager.ctl:claimManager_queryConnectClaim_Callback| found old claim at postion: "+ iPos); - - - // When we have the claim, and the datapoint is now 'not claimed' - // then - if( !bClaimed && (iPos > 0)) + else if (period(claim) == 0) { - dynRemove( strClaimDPName , iPos ); - dynRemove( strClaimObjectName , iPos ); + freed += 1; } - - // When we do not have the item and it gets 'Claimed' - // then we have to add it ! - if( bClaimed && (iPos < 1 )) + else if (period(free) == 0) { - dynAppend( strClaimDPName , strDP ); - dynAppend( strClaimObjectName , strName ); - } else if( bClaimed && (iPos > 0 )) { - // When we do have the item and it gets 'Claimed' - // then we have to alter the - strClaimObjectName[iPos] = strName; + claimed += 1; + } + else + { + full = true; } } - LOG_DEBUG("writing connectClaimsFinished"); + + + g_unusedClaims = unused; + g_usedClaims = claimed; + g_freeClaims = freed; + + strClaimDPName = claimedDPNames; + strClaimObjectName = claimedObjectNames; + + LOG_DEBUG( "claimManager.ctl:claimManager_updateCacheClaims|writing connectClaimsFinished"); if ( g_initializing ) { writeInitProcess("connectClaimsFinished"); } diff --git a/MAC/Navigator2/scripts/libs/navFunct.ctl b/MAC/Navigator2/scripts/libs/navFunct.ctl index 598aad96556..f05a3584db7 100644 --- a/MAC/Navigator2/scripts/libs/navFunct.ctl +++ b/MAC/Navigator2/scripts/libs/navFunct.ctl @@ -1476,6 +1476,7 @@ void navFunct_fillHardwareTree() { string dp=""; // add Stations + if (dynlen(g_stationList) > 1 ) { for (int i = 1; i <= dynlen(g_stationList); i++) { // for ease of selection we use stationlike objects on the main google hardware panels, such -- GitLab From 50db040903d9c276e1bed19430924b174812065d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 12 Jul 2016 11:34:36 +0000 Subject: [PATCH 528/933] Task #9614: Backported fix end time GainCal to 2.17 --- CEP/DP3/DPPP/src/GainCal.cc | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index 6189bbe8f3d..38ad50438a5 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -206,6 +206,7 @@ namespace LOFAR { os << " mode: " << itsMode << endl; os << " apply solution: " << boolalpha << itsApplySolution << endl; os << " propagate solutions: " << boolalpha << itsPropagateSolutions << endl; + os << " timeslotsperparmupdate: " << itsTimeSlotsPerParmUpdate << endl; os << " detect stalling: " << boolalpha << itsDetectStalling << endl; os << " use model column: " << boolalpha << itsUseModelColumn << endl; if (!itsUseModelColumn) { @@ -666,24 +667,33 @@ namespace LOFAR { if (getInfo().chanFreqs().size()>1) { // Handle data with evenly spaced gaps between channels freqWidth = info().chanFreqs()[1]-info().chanFreqs()[0]; } + + // Get end time of the current chunk. For the last chunk, this + // is chopped off at the end of the MS (only if solint > 1) + double endTime = min(startTime + ntime * info().timeInterval() * itsSolInt, + info().startTime() + info().ntime() * info().timeInterval()); + + // Make time axis (can be non regular for last chunk if solint > 1) + vector<double> lowtimes(ntime), hightimes(ntime); + for (uint t=0; t<ntime; ++t) { + lowtimes[t] = startTime + info().timeInterval() * itsSolInt * t; + hightimes[t] = min(startTime + info().timeInterval() * itsSolInt * (t+1), + endTime); + } + BBS::Axis::ShPtr timeAxis = Axis::makeAxis(lowtimes, hightimes); + BBS::Axis::ShPtr freqAxis( new BBS::RegularAxis( getInfo().chanFreqs()[0] - freqWidth * 0.5, freqWidth*itsNChan, itsNFreqCells)); - BBS::Axis::ShPtr timeAxis( - new BBS::RegularAxis( - startTime, - info().timeInterval() * itsSolInt, - ntime)); BBS::Grid solGrid(freqAxis, timeAxis); // Construct domain grid for the current chunk BBS::Axis::ShPtr tdomAxis( new BBS::RegularAxis( startTime, - min(ntime * info().timeInterval() * itsSolInt, - info().ntime() * info().timeInterval()), + endTime - startTime, 1)); BBS::Axis::ShPtr fdomAxis( new BBS::RegularAxis( -- GitLab From ee7f6096b90b9a5154f74cb7f5b3317c72925b5d Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Tue, 12 Jul 2016 12:25:47 +0000 Subject: [PATCH 529/933] Task #9655: changes related to sumalerts --- .gitattributes | 7 + MAC/Navigator2/panels/Test/test3.pnl | 57 ++++ .../panels/Test/testAlertRowLOFAR.pnl | 131 ++++++++ MAC/Navigator2/panels/Test/testDynDynSort.pnl | 72 +++++ MAC/Navigator2/panels/Test/testEWO.pnl | 61 ++++ .../Test/testStationSumAlertToMCUalert.pnl | 177 +++++++++++ MAC/Navigator2/panels/Test/testdptypename.pnl | 84 +++++ MAC/Navigator2/scripts/readStationConfigs.ctl | 7 + MAC/Navigator2/scripts/setSumAlerts.ctl | 291 ++++++++++++++++++ MAC/Navigator2/scripts/transferMPs.ctl | 4 + 10 files changed, 891 insertions(+) create mode 100644 MAC/Navigator2/panels/Test/test3.pnl create mode 100644 MAC/Navigator2/panels/Test/testAlertRowLOFAR.pnl create mode 100644 MAC/Navigator2/panels/Test/testDynDynSort.pnl create mode 100644 MAC/Navigator2/panels/Test/testEWO.pnl create mode 100644 MAC/Navigator2/panels/Test/testStationSumAlertToMCUalert.pnl create mode 100644 MAC/Navigator2/panels/Test/testdptypename.pnl create mode 100644 MAC/Navigator2/scripts/setSumAlerts.ctl diff --git a/.gitattributes b/.gitattributes index d2c74dd8787..2f7e99346c0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3945,15 +3945,21 @@ MAC/Navigator2/panels/Test/stringTest.pnl -text MAC/Navigator2/panels/Test/test[!!-~]colorrange.pnl -text MAC/Navigator2/panels/Test/test.pnl -text MAC/Navigator2/panels/Test/test2.pnl -text +MAC/Navigator2/panels/Test/test3.pnl -text +MAC/Navigator2/panels/Test/testAlertRowLOFAR.pnl -text MAC/Navigator2/panels/Test/testClaim.pnl -text MAC/Navigator2/panels/Test/testColorFade.pnl -text MAC/Navigator2/panels/Test/testDoubleClick.pnl -text +MAC/Navigator2/panels/Test/testDynDynSort.pnl -text MAC/Navigator2/panels/Test/testDynamicPlacement.pnl -text +MAC/Navigator2/panels/Test/testEWO.pnl -text MAC/Navigator2/panels/Test/testExist.pnl -text MAC/Navigator2/panels/Test/testHBA.pnl -text MAC/Navigator2/panels/Test/testStatesetter.pnl -text +MAC/Navigator2/panels/Test/testStationSumAlertToMCUalert.pnl -text MAC/Navigator2/panels/Test/testclick.pnl -text MAC/Navigator2/panels/Test/testdpnames.pnl -text +MAC/Navigator2/panels/Test/testdptypename.pnl -text MAC/Navigator2/panels/Test/testhtml.pnl -text MAC/Navigator2/panels/emptyPanel.pnl -text MAC/Navigator2/panels/main.pnl -text @@ -4162,6 +4168,7 @@ MAC/Navigator2/scripts/monitorStateReset.ctl -text MAC/Navigator2/scripts/monitorStationAlarms.ctl -text MAC/Navigator2/scripts/readStationConfigs.ctl -text MAC/Navigator2/scripts/readStationConnections.ctl -text +MAC/Navigator2/scripts/setSumAlerts.ctl -text MAC/Navigator2/scripts/transferMPs.ctl -text MAC/Services/src/pipelinecontrol -text MAC/Services/src/pipelinecontrol.ini -text diff --git a/MAC/Navigator2/panels/Test/test3.pnl b/MAC/Navigator2/panels/Test/test3.pnl new file mode 100644 index 00000000000..9e7ad3fdc6b --- /dev/null +++ b/MAC/Navigator2/panels/Test/test3.pnl @@ -0,0 +1,57 @@ +V 11 +1 +LANG:1 0 +PANEL,-1 -1 500 400 N "_3DFace" 0 +"main() +{ +time resetDate,compareDate; + + resetDate=makeTime(2013,5,27); + compareDate = makeTime(2013,5,20); + + int now = period(resetDate); + int compare = period(compareDate); + DebugN(resetDate); + DebugN(compareDate); + DebugN(\"diff:\"+(now-compare)); +}" 0 + E E E E 1 -1 -1 0 -1 -1 +""0 1 +E E 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 0 +0 +LAYER, 1 +1 +LANG:1 0 +0 +LAYER, 2 +1 +LANG:1 0 +0 +LAYER, 3 +1 +LANG:1 0 +0 +LAYER, 4 +1 +LANG:1 0 +0 +LAYER, 5 +1 +LANG:1 0 +0 +LAYER, 6 +1 +LANG:1 0 +0 +LAYER, 7 +1 +LANG:1 0 +0 +0 diff --git a/MAC/Navigator2/panels/Test/testAlertRowLOFAR.pnl b/MAC/Navigator2/panels/Test/testAlertRowLOFAR.pnl new file mode 100644 index 00000000000..c2eaa03058d --- /dev/null +++ b/MAC/Navigator2/panels/Test/testAlertRowLOFAR.pnl @@ -0,0 +1,131 @@ +V 11 +1 +LANG:1 0 +PANEL,-1 -1 1123 644 N "_3DFace" 0 +E E E E E 1 -1 -1 0 -1 19 +""0 1 +E E 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 0 +29 17 +"sl_gauge" +"" +1 930 200.0000000000001 E E E 1 E 0 E N "_3DText" E N "_3DFace" E E + E E +1 0 0 0 0 0 +E E E +0 +2 +LANG:1 0 +LANG:0 0 + +8 +"invertedAppearance" "bool TRUE" +"maxValue" "int 99" +"tickInterval" "int 1" +"value" "int 50" +"backgroundOrigin" "enum 0" +"lineStep" "int 1" +"minValue" "int 1" +"invertedAppearance" "bool TRUE" +2 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +LANG:0 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 930 200 953 510 +12 SliderWidget +0 +E1 1 0 "0" 18 +0 +1 2 0 "0" 19 +0 +1 3 0 "0" 24 +0 +1 4 0 "0" 8 +0 +1 5 0 "0" 9 +0 +1 6 0 "0" 16 +0 +1 7 0 "0" 12 +0 +1 8 0 "0" 14 +0 +1 9 0 "0" 17 +0 +1 10 0 "0" 25 +0 +1 11 0 "0" 15 +0 +1 12 0 "0" 21 +0 +1 13 0 "0" 22 +0 +1 14 0 "0" 0 +0 +1 15 0 "0" 1 +31 "transform" 0 0 1 0 19.28571428571429 0 -914.2857142857145 +0 +13 16 +"PUSH_BUTTON1" +"" +1 40 10 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E + E E +0 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 38 8 192 42 + +T +1 +LANG:1 12 PUSH_BUTTON1 +"main() +{ + DebugN( \"AES_ACTION_AUTORUN = \" + AES_ACTION_AUTORUN ); + openAES( \"aes_alerts_LOFAR\", \"Alarmen\", AES_ACTION_AUTORUN ); +}" 0 + E E E +0 +LAYER, 1 +1 +LANG:1 0 +0 +LAYER, 2 +1 +LANG:1 0 +0 +LAYER, 3 +1 +LANG:1 0 +0 +LAYER, 4 +1 +LANG:1 0 +0 +LAYER, 5 +1 +LANG:1 0 +0 +LAYER, 6 +1 +LANG:1 0 +0 +LAYER, 7 +1 +LANG:1 0 +0 +3 0 "PANEL_REF0" -1 +"objects\\STD_PANELS\\AESRow.pnl" -1 19 T 0 U +1 +"$AESREGDOLLAR_SCREENTYPE""aes_alertRow_LOFAR" +0 diff --git a/MAC/Navigator2/panels/Test/testDynDynSort.pnl b/MAC/Navigator2/panels/Test/testDynDynSort.pnl new file mode 100644 index 00000000000..16888a82fcd --- /dev/null +++ b/MAC/Navigator2/panels/Test/testDynDynSort.pnl @@ -0,0 +1,72 @@ +V 11 +1 +LANG:1 0 +PANEL,-1 -1 400 400 N "_3DFace" 0 +E E E E E 1 -1 -1 0 200 210 +""0 1 +E E 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 0 +13 0 +"PUSH_BUTTON1" +"" +1 40 40 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E + E E +0 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 34 MS Shell Dlg 2,8,-1,5,50,0,0,0,0,0 +0 38 38 162 64 + +T +1 +LANG:1 12 PUSH_BUTTON1 +E "main() { + string aS = \"CS001:__navObjectState.DPName\"; +// string aS = \"CS001:LOFAR_PIC_Cabinet1_Subrack2.clockBoard.status.state\"; + string System = dpSubStr(aS,DPSUB_SYS); + DebugN(System); + System = dpSubStr(\"\",DPSUB_SYS); + DebugN(System); +}" 0 + E E +0 +LAYER, 1 +1 +LANG:1 0 +0 +LAYER, 2 +1 +LANG:1 0 +0 +LAYER, 3 +1 +LANG:1 0 +0 +LAYER, 4 +1 +LANG:1 0 +0 +LAYER, 5 +1 +LANG:1 0 +0 +LAYER, 6 +1 +LANG:1 0 +0 +LAYER, 7 +1 +LANG:1 0 +0 +0 diff --git a/MAC/Navigator2/panels/Test/testEWO.pnl b/MAC/Navigator2/panels/Test/testEWO.pnl new file mode 100644 index 00000000000..c80b33a258a --- /dev/null +++ b/MAC/Navigator2/panels/Test/testEWO.pnl @@ -0,0 +1,61 @@ +V 11 +1 +LANG:1 0 +PANEL,-1 -1 1274 644 N "_3DFace" 0 +E E E E E 1 -1 -1 0 70 30 +""0 1 +E E 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 0 +29 0 +"Scheduler_ewo1" +"" +1 70 30 E E E 1 E 1 E N "_3DText" E N "_3DFace" E E + E E +0 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 70 30 1170 560 +13 Scheduler.ewo +0 +E0 +LAYER, 1 +1 +LANG:1 0 +0 +LAYER, 2 +1 +LANG:1 0 +0 +LAYER, 3 +1 +LANG:1 0 +0 +LAYER, 4 +1 +LANG:1 0 +0 +LAYER, 5 +1 +LANG:1 0 +0 +LAYER, 6 +1 +LANG:1 0 +0 +LAYER, 7 +1 +LANG:1 0 +0 +0 diff --git a/MAC/Navigator2/panels/Test/testStationSumAlertToMCUalert.pnl b/MAC/Navigator2/panels/Test/testStationSumAlertToMCUalert.pnl new file mode 100644 index 00000000000..0174696c805 --- /dev/null +++ b/MAC/Navigator2/panels/Test/testStationSumAlertToMCUalert.pnl @@ -0,0 +1,177 @@ +V 11 +1 +LANG:1 0 +PANEL,-1 -1 500 400 N "_3DFace" 0 +"main() +{ + Init(); +}" 0 + E E E E 1 -1 -1 0 40 30 +""0 1 +E "string _strCurrentAlertClass; + + +void Init() +{ +// string strQuery = \"SELECT ALERT '_alert_hdl.._direction', '_alert_hdl.._prior', '_alert_hdl.._class' FROM 'CS011_SumAlert'\"; +// dpQueryConnectSingle( \"CallbackAlertMCU\", true, \"\", strQuery ); + + +// string strQuery = \"SELECT ALERT '_alert_hdl.._direction', '_alert_hdl.._prior', '_alert_hdl.._class' FROM 'LOFAR.status.childSumAlert' REMOTE 'CS011:'\"; + string strQuery = \"SELECT ALERT '_alert_hdl.._direction', '_alert_hdl.._prior', '_alert_hdl.._class' FROM 'LOFAR_PIC_Cabinet1_Subrack2.status.childSumAlert' REMOTE 'CS011:'\"; + dpQueryConnectSingle( \"CallbackAlertLCU\", true, \"\", strQuery ); +} + +/* +void CallbackAlertMCU( string strIdent, dyn_dyn_anytype ddaAlerts ) +{ + DebugN( \"CallbackAlert:\" ); + DebugN( ddaAlerts ); + + for( x=2; x<=dynlen(ddaAlerts); x++ ) + { + strClass = ddaAlerts[x][5]; + + // Skip went alerts + if( !bDirection ) + continue; + + // Came alert, if prio heigher then takeover alertclass + if( iPrio >= iHighestPrio ) + { + _strCurrentAlertClass = strClass; + + iHighestPrio = iPrio; + } + } + + +} + +*/ + + +void CallbackAlertLCU( string strIdent, dyn_dyn_anytype ddaAlerts ) +{ + int x, iHighestPrio; + bool bCame, bRetVal; + string strHighestAlertClass; + + DebugN( \"CallbackAlertLCU:\" ); + DebugN( ddaAlerts ); + + for( x=2; x<=dynlen(ddaAlerts); x++ ) + { + bool bDirection = ddaAlerts[x][3]; + int iPrio = ddaAlerts[x][4]; + string strClass = ddaAlerts[x][5]; + + // Skip went alerts + if( !bDirection ) + continue; + + bCame = true; + + // Came alert, if prio heigher then takeover alertclass + if( iPrio >= iHighestPrio ) + { + strHighestAlertClass = strClass; + + iHighestPrio = iPrio; + } + } + + DebugN( \"strHighestAlertClass = \" + strHighestAlertClass ); + + // Now get state of alert + if( bCame ) + { + // Convert class from system to this sytem + strreplace( strHighestAlertClass, \"CS011:\", \"MCU001:\" ); + + DebugTN( \"Setting alert to class: \" + strHighestAlertClass ); + + dpDeactivateAlert( \"MCU001:CS011_SumAlert.\", bRetVal ); + + // Change class and set alert to true + dpSet( \"MCU001:CS011_SumAlert.:_alert_hdl.._class\", strHighestAlertClass, + \"MCU001:CS011_SumAlert.\", true ); + + dpActivateAlert( \"MCU001:CS011_SumAlert.\", bRetVal ); + } + else + { + DebugTN( \"Reset alert\" ); + dpDeactivateAlert( \"MCU001:CS011_SumAlert.\", bRetVal ); + dpSet( \"MCU001:CS011_SumAlert.\", false ); + } +} + + + + +" 0 + 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 0 +13 1 +"PUSH_BUTTON2" +"" +1 100 50 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E + E E +1 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 98 48 332 152 + +T +1 +LANG:1 12 PUSH_BUTTON2 +"main() +{ + int iState; + dpGet( \"MCU001:CS011_SumAlert.:_alert_hdl.._act_state\", iState ); + DebugN( \"iState = \" + iState ); +}" 0 + E E E +0 +LAYER, 1 +1 +LANG:1 0 +0 +LAYER, 2 +1 +LANG:1 0 +0 +LAYER, 3 +1 +LANG:1 0 +0 +LAYER, 4 +1 +LANG:1 0 +0 +LAYER, 5 +1 +LANG:1 0 +0 +LAYER, 6 +1 +LANG:1 0 +0 +LAYER, 7 +1 +LANG:1 0 +0 +0 diff --git a/MAC/Navigator2/panels/Test/testdptypename.pnl b/MAC/Navigator2/panels/Test/testdptypename.pnl new file mode 100644 index 00000000000..de610524830 --- /dev/null +++ b/MAC/Navigator2/panels/Test/testdptypename.pnl @@ -0,0 +1,84 @@ +V 11 +1 +LANG:1 0 +PANEL,-1 -1 500 400 N "_3DFace" 0 +E E E E E 1 -1 -1 0 40 50 +""0 1 +E E 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 0 +14 0 +"TEXT_FIELD1" +"" +1 40 50 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +0 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 38 48 472 82 +3 "0s" 0 0 0 0 0 -1 E E E +13 1 +"PUSH_BUTTON1" +"" +1 120 170 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E + E E +1 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 118 168 242 194 + +T +1 +LANG:1 12 PUSH_BUTTON1 +"main() +{ + TEXT_FIELD1.text = dpTypeName(TEXT_FIELD1.text); +}" 0 + E E E +0 +LAYER, 1 +1 +LANG:1 0 +0 +LAYER, 2 +1 +LANG:1 0 +0 +LAYER, 3 +1 +LANG:1 0 +0 +LAYER, 4 +1 +LANG:1 0 +0 +LAYER, 5 +1 +LANG:1 0 +0 +LAYER, 6 +1 +LANG:1 0 +0 +LAYER, 7 +1 +LANG:1 0 +0 +0 diff --git a/MAC/Navigator2/scripts/readStationConfigs.ctl b/MAC/Navigator2/scripts/readStationConfigs.ctl index 7181cadc85b..bc7be785621 100644 --- a/MAC/Navigator2/scripts/readStationConfigs.ctl +++ b/MAC/Navigator2/scripts/readStationConfigs.ctl @@ -236,6 +236,13 @@ main() } } + if (strpos(dynStr_fileContent[index],"RS.N_POWECS")>-1) { + dyn_string value = strsplit(dynStr_fileContent[index],"="); + if (dynlen(value) > 1) { + dpSet("LOFAR_PIC_StationInfo.nrOfPowerUnits",value[2]); + } + } + if (strpos(dynStr_fileContent[index],"RS.HBA_SPLIT")>-1) { dyn_string value = strsplit(dynStr_fileContent[index],"="); if (dynlen(value) > 1) { diff --git a/MAC/Navigator2/scripts/setSumAlerts.ctl b/MAC/Navigator2/scripts/setSumAlerts.ctl new file mode 100644 index 00000000000..71fbb9d3439 --- /dev/null +++ b/MAC/Navigator2/scripts/setSumAlerts.ctl @@ -0,0 +1,291 @@ + + + +bool bDebug; + +void main() +{ + // connect to debugflag to be able to switch debug on/off during run + if (dpExists("scriptInfo.setSumAlerts.debug")) { + dpConnect("debugCB",true,"scriptInfo.setSumAlerts.debug"); + } else { + DebugTN("setSumAlerts.ctl:main|scriptInfo.setSumAlerts.debugpoint not found in Database"); + } + + delay(0,100); + + if (dpExists("scriptInfo.setSumAlerts.runDone")) { + dpConnect("setSumAlerts",true,"scriptInfo.setSumAlerts.runDone"); + } else { + DebugTN("setSumAlerts.ctl:main|scriptInfo.setSumAlerts.runDone point not found in Database"); + } + + +} + + +private void debugCB(string dp1, bool debug) +{ + bDebug = debug; +} + + +private void setSumAlerts( string strDPE, bool bRunDone ) +{ + int x, y; + string strDPE; + dyn_string dsParentDPEs, dsChilds, dsChildDPEs; + + // If we're already done: do nothing + if( bRunDone ) + return; + + DebugTN("setSumAlerts.ctl:main|start set of sumAlerts"); + + // Get all DPE's with a 'childSumAlert' + dsParentDPEs = dpNames( "*.**.childSumAlert" ); + + // Go through sumalerts + for( x=1; x<=dynlen(dsParentDPEs); x++ ) + { + bool bRetVal1, bRetVal2; + int iRetVal, iAlertHdlType; + string strDPE; + dyn_string dsChilds, dsChildDPEs; + + // Use DPE part, and remove last parts (normally '.state.childSumAlert') + strDPE = dpSubStr( dsParentDPEs[x], DPSUB_DP_EL ); + strDPE = RemoveLastDpeParts(strDPE); + + // Skip master datapoints + if( patternMatch( "*_mp*", strDPE ) ) + { + continue; + } + + if( strDPE == "" ) + { + continue; + } + + // Get childs of this DPE + dsChilds = GetChilds( strDPE ); + + // For each child: get DPE's to add to sumalerts and append to list + for( y=1; y<=dynlen(dsChilds); y++ ) + { + dynAppend( dsChildDPEs, GetChildDPEs( dsChilds[y] ) ); + } + + // Check if this DPE has an alert_hdl of type sumalert + dpGet( dsParentDPEs[x] + ":_alert_hdl.._type", iAlertHdlType ); + + if( iAlertHdlType == DPCONFIG_SUM_ALERT ) + { + // First deactivate the alert + dpDeactivateAlert( dsParentDPEs[x], bRetVal1 ); + if( !bRetVal1 ) + { + DebugTN( "setSumAlerts(): FAILED TO dpDeactivateAlert FOR DPE '" + dsParentDPEs[x] + "'!!" ); + } + + // No change the sumalert dplist + iRetVal = dpSet( dsParentDPEs[x] + ":_alert_hdl.._dp_list", dsChildDPEs, + dsParentDPEs[x] + ":_alert_hdl.._dp_pattern", "" ); + + dyn_errClass derrLastError = getLastError(); + if( dynlen(derrLastError) > 0 ) + { + DebugTN( "setSumAlerts(): FAILED TO dpSet FOR DPE, getLastError():", derrLastError ); + } + else if( iRetVal != 0 ) + { + DebugTN( "setSumAlerts(): FAILED TO dpSet FOR DPE, iRetVal = " + iRetVal ); + } + + // Activate alert again + dpActivateAlert( dsParentDPEs[x], bRetVal2 ); + if( !bRetVal2 ) + { + DebugTN( "setSumAlerts(): FAILED TO dpActivateAlert FOR DPE '" + dsParentDPEs[x] + "'!!" ); + } + + // Show if we're succesfull + if( bRetVal1 && bRetVal2 && ( iRetVal == 0 ) ) + { + if( bDebug ) + { + DebugTN( "setSumAlerts(): SumAlerts for DPE '" + dsParentDPEs[x] + "' succesfully set to " + dynlen(dsChildDPEs) + " child DPEs" ); + } + } + } + + else + { + DebugTN( "setSumAlerts(): DPE '" + dsParentDPEs[x] + "' DOESN'T HAVE AN ALERT-HANDLE OR NOT OF TYPE SUM-ALERT!!" ); + } + + // Small delay to give some time and limit number of events + delay(0,10); + + } + + dpSet( "scriptInfo.setSumAlerts.runDone", true ); + +} + + + + + +private dyn_string GetChilds( string strDPE ) +{ + int x, iLevel; + string strDP, strParent, strChildDPE; + dyn_string dsDPEs, dsChilds; + + strParent = strDPE; + + strDP = dpSubStr( strDPE, DPSUB_DP ); + strDPE = dpSubStr( strDPE, DPSUB_DP_EL ); + + iLevel = GetDpeLevel( strDPE ); + + // Get childs of this DPE + if( patternMatch( "*.*", strDPE ) ) + { + // Only get child DPE's + dynAppend( dsDPEs, dpNames( strDPE + ".**.childSumAlert" ) ); + } + else + { + // Get child DP's and child DPE's + dynAppend( dsDPEs, dpNames( strDP + "_*.*.childSumAlert" ) ); + dynAppend( dsDPEs, dpNames( strDPE + ".**.childSumAlert" ) ); + } + + for( x=dynlen(dsDPEs); x>=1; x-- ) + { + strChildDPE = dpSubStr( dsDPEs[x], DPSUB_DP_EL ); + strChildDPE = RemoveLastDpeParts(strChildDPE); + + // Remove master datapoints + if( patternMatch( "*_mp*", strChildDPE ) ) + { + continue; + } + + + if( GetDpeLevel( strChildDPE ) <= 1 ) + { + // If this DP is not a direct child, but a childs child, skip it + if( patternMatch( strParent + "_*_*", strChildDPE ) ) + { + continue; + } + } + else + { + // Check level of this DPE, if more then 2 deeper then given leven, skip it (so only next level is used) + if( GetDpeLevel( strChildDPE ) > ( iLevel + 1 ) ) + { + continue; + } + } + + // Remove our given DPE + if( strChildDPE == strDPE ) + { + continue; + } + + + dynAppend( dsChilds, strChildDPE ); + + } + + + dynUnique( dsChilds ); + dynSort( dsChilds ); + + return dsChilds; +} + + + +private dyn_string GetChildDPEs( string strDPE ) +{ + int x, iLevel; + dyn_string dsDPEs, dsChildDPEs; + + iLevel = GetDpeLevel(strDPE); + + // Get 'state' and 'childSumAlert' DPE + dynAppend( dsDPEs, dpNames( strDPE + ".*.state" ) ); + dynAppend( dsDPEs, dpNames( strDPE + ".*.childSumAlert" ) ); + + // Only append DPE's if they are 2 levels deeper then given DPE + for( x=1; x<=dynlen(dsDPEs); x++ ) + { + dsDPEs[x] = dpSubStr( dsDPEs[x], DPSUB_DP_EL ); + + if( GetDpeLevel(dsDPEs[x]) == ( iLevel + 2 ) ) + { + dynAppend( dsChildDPEs, dsDPEs[x] ); + } + + } + + return dsChildDPEs; +} + + +private string RemoveLastDpeParts( string strDPE ) +{ + dyn_string dsParts; + + strDPE = dpSubStr( strDPE, DPSUB_DP_EL ); + + // Split into parts + dsParts = strsplit( strDPE, "." ); + + // Remove 'first' last part + if( dynlen( dsParts ) >= 2 ) + { + dynRemove( dsParts, dynlen(dsParts) ); + } + + // Remove 'second' last part + if( dynlen( dsParts ) >= 2 ) + { + dynRemove( dsParts, dynlen(dsParts) ); + } + + // Convert back to DPE + strDPE = dynStringToString( dsParts, "." ); + + return strDPE; +} + + + + + +private int GetDpeLevel( string strDPE ) +{ + dyn_string dsParts; + + strDPE = dpSubStr( strDPE, DPSUB_DP_EL ); + + dsParts = strsplit( strDPE, "." ); + + // This is a DPE? + if( dynlen(dsParts) <= 1 ) + return 0; + + return ( dynlen(dsParts) - 1 ); +} + + + + diff --git a/MAC/Navigator2/scripts/transferMPs.ctl b/MAC/Navigator2/scripts/transferMPs.ctl index e56676752fd..632cbc3f43a 100644 --- a/MAC/Navigator2/scripts/transferMPs.ctl +++ b/MAC/Navigator2/scripts/transferMPs.ctl @@ -35,6 +35,10 @@ void main() } else { DebugTN("transferMPs.ctl:main|set leafpoints to value in mp failed"); } + + // if on a station, determine the ip adres to construct the ip adres of the POEWEC(S) write them in then database + // and set the manager active + } private void debugCB(string dp1, bool debug) { -- GitLab From 1b3ca2faa8b1dd6feae52e069c8b1dfc920fb6bc Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Tue, 12 Jul 2016 12:27:15 +0000 Subject: [PATCH 530/933] Task #9655: changes related to claims --- .gitattributes | 2 + MAC/Navigator2/panels/Test/Claim_Viewer.pnl | 708 ++++++++++++++++++ MAC/Navigator2/panels/Test/CobaltTestStub.pnl | 75 ++ 3 files changed, 785 insertions(+) create mode 100644 MAC/Navigator2/panels/Test/Claim_Viewer.pnl create mode 100644 MAC/Navigator2/panels/Test/CobaltTestStub.pnl diff --git a/.gitattributes b/.gitattributes index 2f7e99346c0..86653f5c150 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3940,6 +3940,8 @@ MAC/Navigator2/panels/Reports/LOFAR_System.pnl -text MAC/Navigator2/panels/Reports/LOFAR_WebBasePanel.pnl -text MAC/Navigator2/panels/Settings/mail.pnl -text MAC/Navigator2/panels/Settings/mail.pnl.bak -text +MAC/Navigator2/panels/Test/Claim_Viewer.pnl -text +MAC/Navigator2/panels/Test/CobaltTestStub.pnl -text MAC/Navigator2/panels/Test/Event_Viewer.pnl -text MAC/Navigator2/panels/Test/stringTest.pnl -text MAC/Navigator2/panels/Test/test[!!-~]colorrange.pnl -text diff --git a/MAC/Navigator2/panels/Test/Claim_Viewer.pnl b/MAC/Navigator2/panels/Test/Claim_Viewer.pnl new file mode 100644 index 00000000000..ee0db472fd4 --- /dev/null +++ b/MAC/Navigator2/panels/Test/Claim_Viewer.pnl @@ -0,0 +1,708 @@ +V 11 +1 +LANG:1 0 +PANEL,-1 -1 1200 814 N "_3DFace" 0 +"main() +{ + // check if the required datapoint for this view are enabled and accessible + string baseDP = MainDBName+\"ClaimManager.cache\"; + if (dpExists(baseDP)) { + if (dpConnect(\"updateCacheClaims\",true, baseDP + \".newObjectNames:_online.._value\", + baseDP + \".DPNames:_online.._value\", + baseDP + \".claimDates:_online.._value\", + baseDP + \".freeDates:_online.._value\", + baseDP + \".newObjectNames:_online.._invalid\") == -1) + { + LOG_DEBUG(\"Claim_Viewer.pnl:main|Couldn't connect to: \"+baseDP); + } + else + { + LOG_DEBUG(\"Claim_Viewer.pnl:main|Connected to: \" + baseDP); + } + } + else + { + if (!isStandalone()) DebugN(\"Claim_Viewer.pnl:main|Couldn't find DP to connect to: \"+baseDP); + } +}" 0 + E E E E 1 -1 -1 0 0 0 +""0 1 +E "#uses \"navigator.ctl\" +#uses \"claimManager.ctl\" + +updateCacheClaims(string dp1, dyn_string objectNames, + string dp2, dyn_string DPNames, + string dp3, dyn_time claimDates, + string dp4, dyn_time freeDates, + string dp5, bool invalid) +{ + cacheTable.deleteAllLines(); + newClaim.foreCol(\"Lofar_broken\"); + if (invalid) + { + LOG_WARN(\"Claim_Viewer.pnl:updateCacheClaims|ClaimManager.cache is invalid\"); + cacheTable.backCol(\"Lofar_invalid\"); + newClaim.foreCol(\"Lofar_operational\"); + return; + } + + int unused = 0; + int claimed = 0; + int freed = 0; + int memInUse = 0; + bool full = false; +// dyn_string claimedMS; + + for (int i=1; i<= dynlen(objectNames);i++) + { + time claim = claimDates[i]; + time free = freeDates[i]; + cacheTable.appendLine(\"temp\", DPNames[i], \"name\", objectNames[i], \"claim\", claim, \"free\", free); + if (period(claim) == 0 && period(free) == 0) + { + unused += 1; + } + else if (period(claim) == 0) + { + freed += 1; + } + else if (period(free) == 0) + { + claimed += 1; + } + else + { + full = true; + } + } + claimTableUnused.text = unused; + claimTableFree.text = freed; + claimTableClaimed.text = claimed; + if (full) + { + claimTableFull.text = \"full\"; + claimTableFullBorder.backCol(\"Lofar_broken\"); + } + else + { + claimTableFull.text = \"free space\"; + claimTableFullBorder.backCol(\"Lofar_operational\"); + } + newClaim.foreCol(\"Lofar_operational\"); + + setValue(\"memInUse\",\"text\",g_usedClaims); + setValue(\"memFree\",\"text\",g_freeClaims); +}" 0 + 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 0 +6 22 +"claimTableFullBorder" +"" +1 610 130 E E E 1 E 1 E N {0,0,0} E N "Lofar_operational" E E + E E +22 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E 1.8 0 1 -516 70 1 E 610 130 650 150 +30 0 +"FRAME2" +"" +1 8 6 E E E 1 E 1 E N "_WindowText" E N {0,0,0} E E + E E +0 0 0 0 0 0 +E E E +1 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 0 1 0 1 E 0.825 0 0.88888888888889 3.4 4.666666666666661 0 E 8 6 409 97 +1 +LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 +0 1 +LANG:1 5 Claim +30 1 +"FRAME3" +"" +1 8 6 E E E 1 E 1 E N "_WindowText" E N {0,0,0} E E + E E +1 0 0 0 0 0 +E E E +1 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 0 1 0 1 E 1.1 0 0.6666666666666674 1.200000000000069 86.00000000000003 0 E 8 6 409 97 +1 +LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 +0 1 +LANG:1 8 Response +2 2 +"PRIMITIVE_TEXT3" +"" +1 -3478.000000000001 -3136 E E E 1 E 1 E N "_WindowText" E N "_Transparent" E E + E E +2 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 2 1 1 2 1 E 0.9999999999999999 0 1 -700 -650 1 E 722 764 802 788 +0 2 2 "0s" 0 0 0 64 0 0 722 764 1 +1 +LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 +0 1 +LANG:1 3 DP: +13 3 +"PUSH_BUTTON16" +"" +1 20 60 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E + E E +3 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 +0 18 58 102 88 + +T +1 +LANG:1 5 claim +"main() +{ + dpSet( + \"ClaimManager.request.typeName\" , \"Observation\", + \"ClaimManager.request.newObjectName\" , NAME.text ); + + dyn_string dpNamesWait = makeDynString(\"ClaimManager.response.newObjectName:_original.._value\"); + dyn_string dpNamesReturn = makeDynString(\"ClaimManager.response.typeName:_original.._value\", + \"ClaimManager.response.newObjectName:_original.._value\", + \"ClaimManager.response.DPName:_original.._value\", + \"ClaimManager.response.claimDate:_original.._value\"); + dyn_anytype conditions=NAME.text; + dyn_anytype returnValues; + + int status = dpWaitForValue( dpNamesWait, conditions, dpNamesReturn, returnValues, 25 ); + + if ( status == -1 ) { + DebugN( \"Event_Viewer.pnl:Error in dpWaitFor Value\" ); + + DebugN( \"Event_Viewer.pnl:dpNamesWait : \" + dpNamesWait ); + + DebugN( \"Event_Viewer.pnl:conditions : \" + conditions ); + + DebugN( \"Event_Viewer.pnl:dpNamesReturn : \" + dpNamesReturn ); + + DebugN( \"returnValues : \" + returnValues ); + } else if ( dynlen(getLastError()) != 0 ) { + + DebugN( \"Event_Viewer.pnl:Error returned in message dpWaitForValue\" ); + + // Reaction: e.g. output + + DebugN( getLastError() ); + + DebugN( \"Event_Viewer.pnl:dpNamesWait : \" + dpNamesWait ); + + DebugN( \"Event_Viewer.pnl:conditions : \" + conditions ); + + DebugN( \"dpNamesReturn : \" + dpNamesReturn ); + + DebugN( \"Event_Viewer.pnl:returnValues : \" + returnValues ); + } else { + DebugN( \"Event_Viewer.pnl:dpWaitForValue : everything ok\" ); + + DebugN( \"Event_Viewer.pnl:dpNamesWait : \" + dpNamesWait ); + + DebugN( \"Event_Viewer.pnl:conditions : \" + conditions ); + + DebugN( \"Event_Viewer.pnl:dpNamesReturn : \" + dpNamesReturn ); + + DebugN( \"Event_Viewer.pnl:returnValues : \" + returnValues ); + + DebugN(\"Set txt_response to: \"+ returnValues[3]); + setValue(\"txt_response\",\"text\",returnValues[3]); + + } +}" 0 + E E E +14 4 +"NAME" +"" +1 120 30 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +4 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 +0 118 28 332 56 +3 "0s" 0 0 0 0 0 -1 E E E +13 5 +"PUSH_BUTTON17" +"" +1 350 20 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E + E E +5 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 +0 348 18 454 92 + +T +1 +LANG:1 29 Display +claims +( LogViewer ) +"main() +{ + // global dyn_string strClaimDPName; // datapoint that was claimed + // global dyn_string strClaimObjectName; // Actual object name + + DebugN( \"*********************************************\" ); + DebugN( \"Our global variable 'strClaimDPName' and 'strClaimObjectName' hold following records\" ); + + if( dynlen( strClaimObjectName )) + for( int t = 1; t <= dynlen( strClaimDPName ); t++) + { + DebugN( strClaimDPName[t] + \",\" + strClaimObjectName[t] ); + } + +}" 0 + E E E +14 6 +"txt_response" +"" +1 69.99999999999977 114 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +6 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 +0 68 112 442 140 +3 "0s" 0 0 0 0 0 -1 E E E +13 7 +"PUSH_BUTTON21" +"" +1 130 60 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E + E E +7 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 +0 128 58 212 88 + +T +1 +LANG:1 4 free +"main() +{ + int ret = dpSet( + \"ClaimManager.reset.typeName\" , \"Observation\", + \"ClaimManager.reset.objectName\" , NAME.text ); + + if (ret < 0) DebugN(\"something went wrong resetting DP \"+NAME.txt+ \" \" + getLastError()); + + +}" 0 + E E E +2 8 +"PRIMITIVE_TEXT2" +"" +1 32 32 E E E 1 E 1 E N "_WindowText" E N "_Transparent" E E + E E +8 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 2 1 1 2 1 E U 1 E 32 32 112 56 +0 2 2 "0s" 0 0 0 64 0 0 32 32 1 +1 +LANG:1 26 Arial,-1,17,5,50,0,0,0,0,0 +0 1 +LANG:1 16 New object name: +25 9 +"cacheTable" +"" +1 -1.261213355974178e-013 249.9999999999999 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +9 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 30 Sans Serif,9,-1,5,50,0,0,0,0,0 +0 -2 248 1172 802 +EE 1 0 1 4 0 "temp" 43 1 0 "s" 1 +LANG:1 4 temp +E +1 +LANG:1 0 + +400 "name" 43 1 0 "s" 1 +LANG:1 4 name +E +1 +LANG:1 0 + +400 "claim" 18 1 0 "s" 1 +LANG:1 5 claim +E +1 +LANG:1 0 + +175 "free" 18 1 0 "s" 1 +LANG:1 4 free +E +1 +LANG:1 0 + +175 +14 14 10 10 +1 +LANG:1 30 Sans Serif,9,-1,5,50,0,0,0,0,0 +0 0 1 1 1 7 +1 0 +2 12 +"PRIMITIVE_TEXT4" +"" +1 602 92 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +12 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 602 92 691 111 +0 2 2 "0s" 0 0 0 192 0 0 602 92 1 +1 +LANG:1 35 MS Shell Dlg 2,12,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 10 claimTable +2 13 +"PRIMITIVE_TEXT5" +"" +1 900 90 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +13 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 900 90 965 109 +0 2 2 "0s" 0 0 0 192 0 0 900 90 1 +1 +LANG:1 35 MS Shell Dlg 2,12,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 6 Memory +2 14 +"PRIMITIVE_TEXT6" +"" +1 522 122 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +14 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 522 122 569 138 +0 2 2 "0s" 0 0 0 192 0 0 522 122 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 6 unused +2 15 +"PRIMITIVE_TEXT7" +"" +1 522 147 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +15 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 522 147 570 163 +0 2 2 "0s" 0 0 0 192 0 0 522 147 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 7 claimed +2 16 +"PRIMITIVE_TEXT8" +"" +1 522 172 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +16 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 522 172 549 188 +0 2 2 "0s" 0 0 0 192 0 0 522 172 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 4 free +2 18 +"claimTableUnused" +"" +1 622 122 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +18 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 622 122 643 138 +0 2 2 "0s" 0 0 0 192 0 0 622 122 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,50,0,0,0,0,0 +0 1 +LANG:1 3 250 +2 19 +"claimTableClaimed" +"" +1 622 147 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +19 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 622 147 643 163 +0 2 2 "0s" 0 0 0 192 0 0 622 147 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,50,0,0,0,0,0 +0 1 +LANG:1 3 250 +2 20 +"claimTableFree" +"" +1 622 172 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +20 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 622 172 643 188 +0 2 2 "0s" 0 0 0 192 0 0 622 172 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,50,0,0,0,0,0 +0 1 +LANG:1 3 250 +2 21 +"claimTableFull" +"" +1 584 202 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +21 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 584 202 653 218 +0 2 2 "0s" 0 0 0 192 0 0 584 202 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 10 free space +2 23 +"newClaim" +"" +1 610 70 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +23 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 610 70 674 86 +0 2 2 "0s" 0 0 0 192 0 0 610 70 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 9 new Claim +2 24 +"PRIMITIVE_TEXT15" +"" +1 812 147 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +24 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 812 147 868 163 +0 2 2 "0s" 0 0 0 192 0 0 812 147 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 9 Nr in use +2 25 +"memInUse" +"" +1 930 147 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +25 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 930 147 951 163 +0 2 2 "0s" 0 0 0 192 0 0 930 147 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,50,0,0,0,0,0 +0 1 +LANG:1 3 250 +2 26 +"PRIMITIVE_TEXT16" +"" +1 812 172 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +26 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 812 172 857 188 +0 2 2 "0s" 0 0 0 192 0 0 812 172 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 7 Nr free +2 27 +"memFree" +"" +1 930 172 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +27 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 930 172 951 188 +0 2 2 "0s" 0 0 0 192 0 0 930 172 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,50,0,0,0,0,0 +0 1 +LANG:1 3 250 +0 +LAYER, 1 +1 +LANG:1 0 +0 +LAYER, 2 +1 +LANG:1 0 +0 +LAYER, 3 +1 +LANG:1 0 +0 +LAYER, 4 +1 +LANG:1 0 +0 +LAYER, 5 +1 +LANG:1 0 +0 +LAYER, 6 +1 +LANG:1 0 +0 +LAYER, 7 +1 +LANG:1 0 +0 +0 diff --git a/MAC/Navigator2/panels/Test/CobaltTestStub.pnl b/MAC/Navigator2/panels/Test/CobaltTestStub.pnl new file mode 100644 index 00000000000..315ecb15490 --- /dev/null +++ b/MAC/Navigator2/panels/Test/CobaltTestStub.pnl @@ -0,0 +1,75 @@ +V 11 +1 +LANG:1 0 +PANEL,-1 -1 500 400 N "_3DFace" 0 +E E E E E 1 -1 -1 0 100 140 +""0 1 +E E 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 0 +13 0 +"PUSH_BUTTON1" +"" +1 100 140 E E E 1 E 1 E N "_ButtonText" E N "_Button" E E + E E +0 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 98 138 222 164 + +T +1 +LANG:1 14 write gpuprocs +"main() +{ + string basedp = \"CCU001:LOFAR_ObsSW_TempObs0127_OSCBT00\"; + string dp; + for (int cbt = 1 ; cbt< 8 ; cbt++) { + for (int gpu = 0 ; gpu< 2 ; gpu++) { + dp = basedp+cbt+\"_CobaltGPUProc0\"+gpu; + dpSet(dp+\".dataProductType\",\"Correlated\"); + } + } +}" 0 + E E E +0 +LAYER, 1 +1 +LANG:1 0 +0 +LAYER, 2 +1 +LANG:1 0 +0 +LAYER, 3 +1 +LANG:1 0 +0 +LAYER, 4 +1 +LANG:1 0 +0 +LAYER, 5 +1 +LANG:1 0 +0 +LAYER, 6 +1 +LANG:1 0 +0 +LAYER, 7 +1 +LANG:1 0 +0 +0 -- GitLab From 8c34e5ba2447554932a56ecd1134061b980005a9 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 12 Jul 2016 13:46:22 +0000 Subject: [PATCH 531/933] Task #9263: Fixed typo: os.mkdirs -> os.makedirs --- SAS/OTDB_Services/TreeStatusEvents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/OTDB_Services/TreeStatusEvents.py b/SAS/OTDB_Services/TreeStatusEvents.py index 1b896e6048c..636dc4b5116 100755 --- a/SAS/OTDB_Services/TreeStatusEvents.py +++ b/SAS/OTDB_Services/TreeStatusEvents.py @@ -136,7 +136,7 @@ if __name__ == "__main__": try: logger.info("creating %s" % (treestatuseventfilename,)) if not os.path.exists(os.path.dirname(treestatuseventfilename)): - os.mkdirs(os.path.dirname(treestatuseventfilename)) + os.makedirs(os.path.dirname(treestatuseventfilename)) with open(treestatuseventfilename, 'w') as f: f.write(start_time.strftime("%Y-%m-%d %H:%M:%S")) -- GitLab From 2489fa110ded8f853c6beef7cf149c5b24936c99 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 12 Jul 2016 13:46:41 +0000 Subject: [PATCH 532/933] Task #9263: Fixed use of str.format for python 2.6 --- SAS/OTDB_Services/test/t_TreeService.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SAS/OTDB_Services/test/t_TreeService.py b/SAS/OTDB_Services/test/t_TreeService.py index d1f7bf17cbf..afae920a6e1 100644 --- a/SAS/OTDB_Services/test/t_TreeService.py +++ b/SAS/OTDB_Services/test/t_TreeService.py @@ -37,18 +37,18 @@ logger = logging.getLogger(__name__) def do_rpc_catch_exception(exc_text, rpc_instance, arg_dict): try: - print "** Executing {}({})...".format(rpc_instance.ServiceName,arg_dict) + print "** Executing {0}({1})...".format(rpc_instance.ServiceName,arg_dict) (data, status) = (rpc_instance)(**arg_dict) - raise Exception("Expected an exception {}, didn't get any".format(exc_text)) + raise Exception("Expected an exception {0}, didn't get any".format(exc_text)) except Exception: - print "Caught expected exception {}".format(exc_text) + print "Caught expected exception {0}".format(exc_text) print "======" def do_rpc(rpc_instance, arg_dict): - print "** Executing {}({})...".format(rpc_instance.ServiceName,arg_dict) + print "** Executing {0}({1})...".format(rpc_instance.ServiceName,arg_dict) (data, status) = (rpc_instance)(**arg_dict) if status != "OK": - raise Exception("Status returned is {}".format(status)) + raise Exception("Status returned is {0}".format(status)) # if isinstance(data, dict): # for key in sorted(data): # print "%s ==> %s" % (key, data[key]) -- GitLab From be9580d26443a0f595a8410113a7de2956a12b0e Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 12 Jul 2016 14:17:36 +0000 Subject: [PATCH 533/933] Task #9652: Fixed use of str.format for python 2.6 --- SAS/OTDB_Services/TreeService.py | 90 ++++++++++++++++---------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/SAS/OTDB_Services/TreeService.py b/SAS/OTDB_Services/TreeService.py index 9713f724d8c..edc84de6e8d 100755 --- a/SAS/OTDB_Services/TreeService.py +++ b/SAS/OTDB_Services/TreeService.py @@ -93,7 +93,7 @@ def TaskGetIDs(input_dict, db_connection, return_tuple=True): if otdb_id is not None: try: (real_otdb_id, real_mom_id,tree_type) =\ - db_connection.query("select treeid,momid,type from getTreeInfo({}, False)".format(otdb_id)).getresult()[0] + db_connection.query("select treeid,momid,type from getTreeInfo({0}, False)".format(otdb_id)).getresult()[0] return (tree_type, real_otdb_id, real_mom_id) if return_tuple else [tree_type, real_otdb_id, real_mom_id] except QUERY_EXCEPTIONS: pass @@ -102,7 +102,7 @@ def TaskGetIDs(input_dict, db_connection, return_tuple=True): if mom_id is not None: try: (real_otdb_id, real_mom_id,tree_type) =\ - db_connection.query("select treeid,momid,type from getTreeInfo({}, True)".format(mom_id)).getresult()[0] + db_connection.query("select treeid,momid,type from getTreeInfo({0}, True)".format(mom_id)).getresult()[0] return (tree_type, real_otdb_id, real_mom_id) if return_tuple else [tree_type, real_otdb_id, real_mom_id] except QUERY_EXCEPTIONS: pass @@ -129,7 +129,7 @@ def TaskGetSpecification(input_dict, db_connection): # if task i not found it is end of story. if task_type is None: - raise FunctionError("Task with OtdbID/MomID {}/{} does not exist".format(otdb_id, mom_id)) + raise FunctionError("Task with OtdbID/MomID {0}/{1} does not exist".format(otdb_id, mom_id)) # Try to get the specification information try: @@ -213,7 +213,7 @@ def TaskCreate(input_dict, db_connection): # when otdb_id = None task is not in the database # if we searched on OtdbID and the task is not found then is it end-of-story if task_type is None and otdb_id is not None: - raise FunctionError("Task with OtdbID/MomID {}/{} does not exist".format(otdb_id, mom_id)) + raise FunctionError("Task with OtdbID/MomID {0}/{1} does not exist".format(otdb_id, mom_id)) # if we searched on MomID and the task is not found that we try to create a task(template) if task_type is None and mom_id is not None: @@ -225,19 +225,19 @@ def TaskCreate(input_dict, db_connection): # template_info is a list with tuples: (treeid,name) all_names = [ name for (_,name) in template_info if name[0] != '#' ] if not selected_template in all_names: - raise AttributeError("DefaultTemplate '{}' not found, available are:{}".format(selected_template, all_names)) + raise AttributeError("DefaultTemplate '{0}' not found, available are:{1}".format(selected_template, all_names)) # Yeah, default template exist, now make a copy of it. template_ids = [ tasknr for (tasknr,name) in template_info if name == selected_template ] if len(template_ids) != 1: - raise FunctionError("Programming error: matching task_ids for template {} are {}".\ + raise FunctionError("Programming error: matching task_ids for template {0} are {1}".\ format(selected_template, template_ids)) - otdb_id = db_connection.query("select copyTree(1,{})".format(template_ids[0])).getresult()[0][0] + otdb_id = db_connection.query("select copyTree(1,{0})".format(template_ids[0])).getresult()[0][0] # give new tree the mom_id when mom_id was specified by the user. campaign_name = input_dict.get('CampaignName','no campaign') if mom_id is None: mom_id = 0 - db_connection.query("select setMomInfo(1,{},{},0,'{}')".format(otdb_id, mom_id, campaign_name)) + db_connection.query("select setMomInfo(1,{0},{1},0,'{2}')".format(otdb_id, mom_id, campaign_name)) except QUERY_EXCEPTIONS, exc_info: - raise FunctionError("Error while create task from template {}: {}".format(selected_template, exc_info)) + raise FunctionError("Error while create task from template {0}: {1}".format(selected_template, exc_info)) # When we are here we always have a task, so do the key updates return TaskSetSpecification({'OtdbID':otdb_id, 'Specification':input_dict['Specification']}, db_connection) @@ -276,7 +276,7 @@ def TaskSetStatus(input_dict, db_connection): # if task i not found it is end of story. if task_type is None: - raise FunctionError("Task with OtdbID/MomID {}/{} does not exist".format(otdb_id, mom_id)) + raise FunctionError("Task with OtdbID/MomID {0}/{1} does not exist".format(otdb_id, mom_id)) try: new_status = input_dict['NewStatus'] @@ -327,9 +327,9 @@ def TaskSetSpecification(input_dict, db_connection): # if task i not found it is end of story. if task_type is None: - raise FunctionError("Task with OtdbID/MomID {}/{} does not exist".format(otdb_id, mom_id)) + raise FunctionError("Task with OtdbID/MomID {0}/{1} does not exist".format(otdb_id, mom_id)) if task_type == HARDWARE_TREE: - raise FunctionError("OtdbID/MomID {}/{} refers to a hardware tree.".format(otdb_id, mom_id)) + raise FunctionError("OtdbID/MomID {0}/{1} refers to a hardware tree.".format(otdb_id, mom_id)) try: update_list = input_dict['Specification'] @@ -344,12 +344,12 @@ def TaskSetSpecification(input_dict, db_connection): for (key, value) in update_list.iteritems(): try: if task_type == TEMPLATE_TREE: - (node_id,name) = db_connection.query("select nodeid,name from getVTitem({},'{}')"\ + (node_id,name) = db_connection.query("select nodeid,name from getVTitem({0},'{1}')"\ .format(otdb_id, key)).getresult()[0] - record_list = db_connection.query("select nodeid,instances,limits from getVTitemlist ({},'{}') where nodeid={}"\ + record_list = db_connection.query("select nodeid,instances,limits from getVTitemlist ({0},'{1}') where nodeid={2}"\ .format(otdb_id, name, node_id)).getresult() else: # VIC_TREE - record_list = db_connection.query("select nodeid,instances,limits from getVHitemlist ({},'{}')"\ + record_list = db_connection.query("select nodeid,instances,limits from getVHitemlist ({0},'{1}')"\ .format(otdb_id, key)).getresult() if len(record_list) == 0: errors[key] = "Not found for tree %d" % otdb_id @@ -395,16 +395,16 @@ def TaskPrepareForScheduling(input_dict, db_connection): # if task i not found it is end of story. if task_type is None: - raise FunctionError("Task with OtdbID/MomID {}/{} does not exist".format(otdb_id, mom_id)) + raise FunctionError("Task with OtdbID/MomID {0}/{1} does not exist".format(otdb_id, mom_id)) if task_type == HARDWARE_TREE: - raise FunctionError("OtdbID/MomID {}/{} refers to a hardware tree.".format(otdb_id, mom_id)) + raise FunctionError("OtdbID/MomID {0}/{1} refers to a hardware tree.".format(otdb_id, mom_id)) # get the information of the task try: - (task_id,task_type,task_state) = db_connection.query("select treeid,type,state from getTreeInfo({},False)"\ + (task_id,task_type,task_state) = db_connection.query("select treeid,type,state from getTreeInfo({0},False)"\ .format(otdb_id)).getresult()[0] except QUERY_EXCEPTIONS, exc_info: - raise FunctionError("TaskPrepareForScheduling: {}".format(exc_info)) + raise FunctionError("TaskPrepareForScheduling: {0}".format(exc_info)) # Get list of defines tree states state_names = {} @@ -414,26 +414,26 @@ def TaskPrepareForScheduling(input_dict, db_connection): state_names[name] = nr state_nrs[nr] = name except QUERY_EXCEPTIONS, exc_info: - raise FunctionError("Error while getting list of task states for tree {}: {}".format(otdb_id, exc_info)) + raise FunctionError("Error while getting list of task states for tree {0}: {1}".format(otdb_id, exc_info)) # If task is of the type VItemplate convert it to a VHtree delete_old_task = False if task_type == TEMPLATE_TREE: try: # create executable task - new_task_id = db_connection.query("select instanciateVHtree(1,{})".format(task_id)).getresult()[0][0] + new_task_id = db_connection.query("select instanciateVHtree(1,{0})".format(task_id)).getresult()[0][0] # get the characteristics - (task_id,task_type,task_state) = db_connection.query("select treeid,type,state from getTreeInfo({},False)"\ + (task_id,task_type,task_state) = db_connection.query("select treeid,type,state from getTreeInfo({0},False)"\ .format(new_task_id)).getresult()[0] delete_old_task = True except QUERY_EXCEPTIONS, exc_info: - raise FunctionError("TaskPrepareForScheduling: failed for task {}: {}".format(otdb_id, exc_info)) + raise FunctionError("TaskPrepareForScheduling: failed for task {0}: {1}".format(otdb_id, exc_info)) # make sure the tree is in the right state if task_state != state_names['approved']: try: - db_connection.query("select setTreeState(1,{},{}::INT2,True)".format(task_id, state_names['approved'])) + db_connection.query("select setTreeState(1,{0},{1}::INT2,True)".format(task_id, state_names['approved'])) except QUERY_EXCEPTIONS, exc_info: - raise FunctionError("Error while setting task {} to 'approved': {}".format(task_id, exc_info)) + raise FunctionError("Error while setting task {0} to 'approved': {1}".format(task_id, exc_info)) if delete_old_task: TaskDelete({'OtdbID':otdb_id}, db_connection) @@ -443,9 +443,9 @@ def TaskPrepareForScheduling(input_dict, db_connection): end_time = input_dict.get("StopTime", "") if start_time != "" or end_time != "": try: - db_connection.query("select setSchedule(1,{},'{}','{}')".format(task_id,start_time,end_time)) + db_connection.query("select setSchedule(1,{0},'{1}','{2}')".format(task_id,start_time,end_time)) except QUERY_EXCEPTIONS, exc_info: - raise FunctionError("Error while setting schedule-times of task {} to '{}'-'{}': {}"\ + raise FunctionError("Error while setting schedule-times of task {0} to '{1}'-'{2}': {3}"\ .format(task_id, start_time, end_time, exc_info)) return {'OtdbID':task_id, 'MomID':mom_id, 'Success':True} @@ -469,13 +469,13 @@ def TaskDelete(input_dict, db_connection): # if task i not found it is end of story. if task_type is None: - raise FunctionError("Task with OtdbID/MomID {}/{} does not exist".format(otdb_id, mom_id)) + raise FunctionError("Task with OtdbID/MomID {0}/{1} does not exist".format(otdb_id, mom_id)) # delete the task try: - db_connection.query("select deleteTree(1,{})".format(otdb_id)) + db_connection.query("select deleteTree(1,{0})".format(otdb_id)) except QUERY_EXCEPTIONS, exc_info: - raise FunctionError("TaskDelete {}: {}".format(otdb_id, exc_info)) + raise FunctionError("TaskDelete {0}: {1}".format(otdb_id, exc_info)) return {'OtdbID':otdb_id, 'MomID':mom_id, 'Success':True} @@ -498,7 +498,7 @@ def GetDefaultTemplates(input_dict, db_connection): if name[0] != '#': Templates[name] = { 'OtdbID':treeid, 'processType':proc_type, 'processSubtype':proc_subtype, 'Strategy':strategy} except QUERY_EXCEPTIONS, exc_info: - raise FunctionError("GetDefaulTemplates: {}".format(exc_info)) + raise FunctionError("GetDefaulTemplates: {0}".format(exc_info)) return { 'DefaultTemplates': Templates } @@ -522,7 +522,7 @@ def GetStations(input_dict, db_connection): if len(level) == 4: Stations[level[3]] = level[2] except QUERY_EXCEPTIONS, exc_info: - raise FunctionError("GetStations: {}".format(exc_info)) + raise FunctionError("GetStations: {0}".format(exc_info)) return { 'Stations': Stations } @@ -549,14 +549,14 @@ def SetProject(input_dict, db_connection): contact = input_dict['contact'] except KeyError, info: raise AttributeError("SetProject: Key %s is missing in the input" % info) - logger.info("SetProject for project: {}".format(project_name)) + logger.info("SetProject for project: {0}".format(project_name)) # get the information Stations = {} try: - project_id = db_connection.query("select saveCampaign(0,'{}','{}','{}','{}','{}')".format(project_name, title, pi, co_i, contact)).getresult()[0][0] + project_id = db_connection.query("select saveCampaign(0,'{0}','{1}','{2}','{3}','{4}')".format(project_name, title, pi, co_i, contact)).getresult()[0][0] except QUERY_EXCEPTIONS, exc_info: - raise FunctionError("SetProject: {}".format(exc_info)) + raise FunctionError("SetProject: {0}".format(exc_info)) return { "projectID": project_id } @@ -609,39 +609,39 @@ class PostgressMessageHandler(MessageHandlerInterface): # The following functions are called from the Service code. def _TaskGetSpecification(self, **kwargs): - logger.info("_TaskGetSpecification({})".format(kwargs)) + logger.info("_TaskGetSpecification({0})".format(kwargs)) return TaskGetSpecification(kwargs, self.connection) def _TaskCreate(self, **kwargs): - logger.info("_TaskCreate({})".format(kwargs)) + logger.info("_TaskCreate({0})".format(kwargs)) return TaskCreate(kwargs, self.connection) def _TaskGetStatus(self, **kwargs): - logger.info("_TaskGetStatus({})".format(kwargs)) + logger.info("_TaskGetStatus({0})".format(kwargs)) return TaskGetStatus(kwargs.get('otdb_id'), self.connection) def _TaskSetStatus(self, **kwargs): - logger.info("_TaskSetStatus({})".format(kwargs)) + logger.info("_TaskSetStatus({0})".format(kwargs)) return TaskSetStatus(kwargs, self.connection) def _TaskGetTreeInfo(self, **kwargs): - logger.info("_TaskGetTreeInfo({})".format(kwargs)) + logger.info("_TaskGetTreeInfo({0})".format(kwargs)) return TaskGetTreeInfo(kwargs.get('otdb_id'), self.connection) def _TaskSetSpecification(self, **kwargs): - logger.info("_TaskSetSpecification({})".format(kwargs)) + logger.info("_TaskSetSpecification({0})".format(kwargs)) return TaskSetSpecification(kwargs, self.connection) def _TaskPrepareForScheduling(self, **kwargs): - logger.info("_TaskPrepareForScheduling({})".format(kwargs)) + logger.info("_TaskPrepareForScheduling({0})".format(kwargs)) return TaskPrepareForScheduling(kwargs, self.connection) def _TaskGetIDs(self, **kwargs): - logger.info("_TaskGetIDs({})".format(kwargs)) + logger.info("_TaskGetIDs({0})".format(kwargs)) return TaskGetIDs(kwargs, self.connection, return_tuple=False) def _TaskDelete(self, **kwargs): - logger.info("_TaskDelete({})".format(kwargs)) + logger.info("_TaskDelete({0})".format(kwargs)) return TaskDelete(kwargs, self.connection) def _GetDefaultTemplates(self, **kwargs): @@ -653,7 +653,7 @@ class PostgressMessageHandler(MessageHandlerInterface): return GetStations(kwargs, self.connection) def _SetProject(self, **kwargs): - logger.info("_SetProject({})".format(kwargs)) + logger.info("_SetProject({0})".format(kwargs)) return SetProject(kwargs, self.connection) -- GitLab From 95ed6920ab21591390e1231bcdc5c239c1ba4b6c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 13 Jul 2016 08:40:04 +0000 Subject: [PATCH 534/933] Task #9623: Use 2 threads/NDPPP for even faster processing (both for demixing and non-demixing preprocessing pipelines) --- CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in index 61c51bf715b..bc8e4f1ef5e 100644 --- a/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in +++ b/CEP/Pipeline/recipes/sip/tasks.cfg.CEP4.in @@ -1,6 +1,6 @@ [ndppp] nproc = 0 -nthreads = 4 +nthreads = 2 [setupparmdb] nproc = 0 @@ -24,7 +24,7 @@ nthreads = 10 [dppp] max_per_node = 0 -nthreads = 4 +nthreads = 2 [awimager] max_per_node = 0 -- GitLab From f7e458847de700dcaf06b7f9566a693f8c0843d8 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 13 Jul 2016 13:19:54 +0000 Subject: [PATCH 535/933] Task #9607: Add Ganglia events for pipeline start/stops --- MAC/Services/src/PipelineControl.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 4115f5f42ce..a3b1775774a 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -460,9 +460,12 @@ function runcmd {{ # print some info echo Running on $SLURM_NODELIST -# notify that we're running +# notify OTDB that we're running runcmd {setStatus_active} +# notify ganglia +wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} ACTIVE&host_regex=" + # pull docker image from repository on all nodes srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull \ --kill-on-bad-exit=0 --wait=0 \ @@ -491,6 +494,12 @@ runcmd sleep 60 # if we reached this point, the pipeline ran succesfully runcmd {setStatus_finished} + +# notify ganglia +wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} FINISHED&host_regex=" + +# return success +exit 0 """.format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, @@ -510,7 +519,13 @@ runcmd {setStatus_finished} # Schedule pipelineAborted.sh logger.info("Scheduling SLURM job for pipelineAborted.sh") slurm_cancel_job_id = self.slurm.submit("%s-abort-trigger" % self._jobName(otdbId), - "{setStatus_aborted}\n" +""" +# notify OTDB +{setStatus_aborted} + +# notify ganglia +wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} ABORTED&host_regex=" +""" .format( setStatus_aborted = setStatus_cmdline("aborted"), ), -- GitLab From 4fd1d01caee2d88f57a4d87dd131472330410bdf Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 14 Jul 2016 11:40:45 +0000 Subject: [PATCH 536/933] Task #9607: Bugfix in scheduling abort-trigger jobs --- MAC/Services/src/PipelineControl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index a3b1775774a..0b9fe8ffa57 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -528,6 +528,7 @@ wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&sta """ .format( setStatus_aborted = setStatus_cmdline("aborted"), + obsid = otdbId, ), sbatch_params=[ -- GitLab From 69a982f7ff6229404182daa16b3e8f78644cb30e Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Thu, 14 Jul 2016 17:29:29 +0000 Subject: [PATCH 537/933] Task #9677: add LCS Thread Barrier class + tBarrier test --- LCS/Common/include/Common/CMakeLists.txt | 1 + LCS/Common/include/Common/Thread/Barrier.h | 78 +++++++++++++++++ LCS/Common/test/CMakeLists.txt | 1 + LCS/Common/test/tBarrier.cc | 98 ++++++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 LCS/Common/include/Common/Thread/Barrier.h create mode 100644 LCS/Common/test/tBarrier.cc diff --git a/LCS/Common/include/Common/CMakeLists.txt b/LCS/Common/include/Common/CMakeLists.txt index 5761963f808..a0843f5c949 100644 --- a/LCS/Common/include/Common/CMakeLists.txt +++ b/LCS/Common/include/Common/CMakeLists.txt @@ -93,6 +93,7 @@ install(FILES DESTINATION include/${PACKAGE_NAME}/shmem) install(FILES + Thread/Barrier.h Thread/Cancellation.h Thread/Condition.h Thread/Mutex.h diff --git a/LCS/Common/include/Common/Thread/Barrier.h b/LCS/Common/include/Common/Thread/Barrier.h new file mode 100644 index 00000000000..c3bb891b540 --- /dev/null +++ b/LCS/Common/include/Common/Thread/Barrier.h @@ -0,0 +1,78 @@ +//# Barrier.h: thread synchronization barrier +//# +//# Copyright (C) 2016 +//# ASTRON (Netherlands Foundation for Research in Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands, softwaresupport@astron.nl +//# +//# This program 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. +//# +//# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//# +//# $Id$ + +#ifndef LOFAR_LCS_COMMON_BARRIER_H +#define LOFAR_LCS_COMMON_BARRIER_H + +#ifdef USE_THREADS + +#include <pthread.h> +#include <Common/LofarLogger.h> +#include <Common/SystemCallException.h> + +namespace LOFAR { + +class Barrier { +public: + explicit Barrier(unsigned count) + { + int rv = pthread_barrier_init(&bar, NULL, count); + if (rv != 0) { + throw SystemCallException("pthread_barrier_init", rv, THROW_ARGS); + } + } + + ~Barrier() + { + int rv = pthread_barrier_destroy(&bar); + if (rv != 0) { + // get backtrace w/out stack unwinding from destr (could be done w/out exc) + try { + throw SystemCallException("pthread_barrier_destroy", rv, THROW_ARGS); + } catch (SystemCallException &exc) { + LOG_ERROR_STR("pthread_barrier_destroy() failed: " << exc.what()); + } + } + } + + void wait() + { + int rv = pthread_barrier_wait(&bar); + if (rv != 0 && rv != PTHREAD_BARRIER_SERIAL_THREAD) { + throw SystemCallException("pthread_barrier_wait", rv, THROW_ARGS); + } + } + +private: + pthread_barrier_t bar; + + // don't use + Barrier(); + Barrier(const Barrier& ); // cannot wait or destroy on a copy + Barrier& operator=(const Barrier& ); // idem +}; + +} // namespace LOFAR + +#endif + +#endif diff --git a/LCS/Common/test/CMakeLists.txt b/LCS/Common/test/CMakeLists.txt index f645d0063d1..95266a86df3 100644 --- a/LCS/Common/test/CMakeLists.txt +++ b/LCS/Common/test/CMakeLists.txt @@ -2,6 +2,7 @@ include(LofarCTest) +lofar_add_test(tBarrier tBarrier.cc) lofar_add_test(tBoostBitset tBoostBitset.cc) lofar_add_test(testLogger testLogger.cc) lofar_add_test(tCasaLogSink tCasaLogSink.cc) diff --git a/LCS/Common/test/tBarrier.cc b/LCS/Common/test/tBarrier.cc new file mode 100644 index 00000000000..774d57065a6 --- /dev/null +++ b/LCS/Common/test/tBarrier.cc @@ -0,0 +1,98 @@ +//# tBarrier.cc: Test program for thread synchronization barrier +//# +//# Copyright (C) 2016 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> + +#include <unistd.h> // alarm(3) +#include <Common/Thread/Thread.h> +#include <Common/Thread/Barrier.h> + +using namespace std; +using namespace LOFAR; + +static void testTrivial() +{ +#ifdef USE_THREADS + Barrier bar(1); + bar.wait(); +#endif +} + +#ifdef USE_THREADS +static Barrier barTestUse(4); // for 3 thread + main thread + +struct Thr { + Thr() : thread(this, &Thr::f) { } + +private: + void f() { + barTestUse.wait(); + barTestUse.wait(); + barTestUse.wait(); + } + + Thread thread; +}; +#endif + +static void testUse() +{ +#ifdef USE_THREADS + Thr t1; + Thr t2; + Thr t3; + + barTestUse.wait(); + barTestUse.wait(); + barTestUse.wait(); +#endif +} + +static void testError() +{ +#ifdef USE_THREADS + int exc = 0; + + // count 0 is invalid + try { + Barrier(0); + } catch (Exception& ) { + exc = 1; + } + + ASSERT(exc == 1); +#endif +} + + +int main() +{ + INIT_LOGGER("tBarrier"); + + alarm(10); // don't wait until ctest timeout on deadlock + + testTrivial(); + testUse(); + testError(); + + return 0; +} -- GitLab From 73086ce8f082aedd40c7683f9e9210091c618ec7 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 15 Jul 2016 11:49:59 +0000 Subject: [PATCH 538/933] Task #9607: check for result of insert in task_predecessors --- .../ResourceAssignmentDatabase/radb.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index b1bf277f74c..e013b2f0377 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -364,13 +364,20 @@ class RADatabase: VALUES (%s, %s) RETURNING id;''' - id = self._executeQuery(query, (task_id, predecessor_id), fetch=_FETCH_ONE)['id'] + result = self._executeQuery(query, (task_id, predecessor_id), fetch=_FETCH_ONE) + if commit: self.commit() - return id + + if result and 'id' in result: + return result['id'] + + return None def insertTaskPredecessors(self, task_id, predecessor_ids, commit=True): ids = [self.insertTaskPredecessor(task_id, predecessor_id, False) for predecessor_id in predecessor_ids] + ids = [x for x in ids if x is not None] + if commit: self.commit() return ids -- GitLab From 90309e38d37b50459516bd77e9859680a051035e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 15 Jul 2016 11:50:56 +0000 Subject: [PATCH 539/933] Task #9607: do not insert predecessor if already predecessor of task --- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index f0e31b9c13b..994a2f5ddd4 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -229,7 +229,7 @@ class ResourceAssigner(): for predecessor_tree in predecessor_trees: pred_otdb_id = predecessor_tree['otdb_id'] predecessor_task = self.radbrpc.getTask(otdb_id=pred_otdb_id) - if predecessor_task: + if predecessor_task and predecessor_task['id'] not in task['predecessor_ids']: self.radbrpc.insertTaskPredecessor(task['id'], predecessor_task['id']) self.processPredecessors(predecessor_tree) -- GitLab From d5f36e8259b5f087fa8b6b12185e5c9cb2075573 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 15 Jul 2016 11:56:07 +0000 Subject: [PATCH 540/933] Task #9681: FindNumpy.cmake improvements by Stephen Bourke --- CMake/FindNumpy.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMake/FindNumpy.cmake b/CMake/FindNumpy.cmake index 2e00bf677f4..4fd18a40704 100644 --- a/CMake/FindNumpy.cmake +++ b/CMake/FindNumpy.cmake @@ -59,7 +59,7 @@ if(NOT NUMPY_FOUND) endif(NOT NUMPY_INCLUDE_DIR) if(NOT NUMPY_MULTIARRAY_LIBRARY) - find_library(NUMPY_MULTIARRAY_LIBRARY multiarray + find_library(NUMPY_MULTIARRAY_LIBRARY NAME multiarray multiarray.${CMAKE_LIBRARY_ARCHITECTURE} HINTS ${NUMPY_PATH} PATH_SUFFIXES core) endif(NOT NUMPY_MULTIARRAY_LIBRARY) @@ -171,9 +171,9 @@ macro (add_f2py_module _name) # Define the command to generate the Fortran to Python interface module. The # output will be a shared library that can be imported by python. add_custom_command(OUTPUT ${_name}.so - COMMAND ${F2PY_EXECUTABLE} --quiet -m ${_name} -h ${_name}.pyf + COMMAND /usr/bin/env -u LDFLAGS ${F2PY_EXECUTABLE} --quiet -m ${_name} -h ${_name}.pyf --include_paths ${_inc_paths} --overwrite-signature ${_abs_srcs} - COMMAND ${F2PY_EXECUTABLE} --quiet -m ${_name} -c ${_name}.pyf + COMMAND /usr/bin/env -u LDFLAGS ${F2PY_EXECUTABLE} --quiet -m ${_name} -c ${_name}.pyf ${_fcompiler_opts} ${_inc_opts} ${_abs_srcs} DEPENDS ${_srcs} COMMENT "[F2PY] Building Fortran to Python interface module ${_name}") -- GitLab From fad5abbd3774afdaa9b5d67d2508d1cbc2475a32 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 15 Jul 2016 12:00:53 +0000 Subject: [PATCH 541/933] Task #9607: made interval at which scheduled/queued pipelines are moved ahead configurable (at default of 300sec) --- SAS/ResourceAssignment/ResourceAssigner/lib/config.py | 2 ++ .../ResourceAssigner/lib/schedulechecker.py | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/config.py b/SAS/ResourceAssignment/ResourceAssigner/lib/config.py index fccb37107e9..761d48d09b1 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/config.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/config.py @@ -9,3 +9,5 @@ DEFAULT_SERVICENAME = 'RAService' DEFAULT_RA_NOTIFICATION_BUSNAME = adaptNameToEnvironment('lofar.ra.notification') DEFAULT_RA_NOTIFICATION_PREFIX = 'ResourceAssigner.' DEFAULT_RA_NOTIFICATION_SUBJECTS=DEFAULT_RA_NOTIFICATION_PREFIX+'*' + +PIPELINE_CHECK_INTERVAL=300 diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index 51a57584046..7f48c21b2ef 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -30,6 +30,8 @@ from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as DEFAULT_RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as DEFAULT_RADB_SERVICENAME +from lofar.sas.resourceassignment.resourceassigner.config import PIPELINE_CHECK_INTERVAL + logger = logging.getLogger(__name__) class ScheduleChecker(): @@ -88,8 +90,8 @@ class ScheduleChecker(): for task in sq_pipelines: if task['starttime'] <= now: - logger.info("Moving ahead scheduled pipeline radb_id=%s otdb_id=%s to 1 minute from now", task['id'], task['otdb_id']) - self._radbrpc.updateTaskAndResourceClaims(task['id'], starttime=now+timedelta(seconds=60), endtime=now+timedelta(seconds=60+task['duration'])) + logger.info("Moving ahead scheduled pipeline radb_id=%s otdb_id=%s to %s seconds from now", task['id'], task['otdb_id'], PIPELINE_CHECK_INTERVAL) + self._radbrpc.updateTaskAndResourceClaims(task['id'], starttime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL), endtime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL+task['duration'])) updated_task = self._radbrpc.getTask(task['id']) if updated_task['status'] != u'scheduled': logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change to %s", updated_task['id'], updated_task['otdb_id'], updated_task['status']) @@ -97,7 +99,7 @@ class ScheduleChecker(): except Exception as e: logger.error("Error while checking scheduled pipelines: %s", e) - for i in range(60): + for i in range(PIPELINE_CHECK_INTERVAL): sleep(1) if not self._running: -- GitLab From 972e90d8864a57558df313b0c4fc9b559e33c44a Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 15 Jul 2016 12:07:23 +0000 Subject: [PATCH 542/933] Task #9607: available_capacity for cep4storage resource is updated regurarly by datamanagement service, so no need anymore to call ssdb --- .../test/t_rotspservice.py | 4 -- .../ResourceAssigner/lib/assignment.py | 46 ------------------- .../ResourceAssigner/lib/raservice.py | 10 ---- .../test/t_resourceassigner.py | 4 -- 4 files changed, 64 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/test/t_rotspservice.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/test/t_rotspservice.py index 66bfa974c64..1e49b01e28d 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/test/t_rotspservice.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/test/t_rotspservice.py @@ -42,10 +42,6 @@ with patch('lofar.sas.resourceassignment.ratootdbtaskspecificationpropagator.rpc #give pre-cooked answer depending on called service if servicename == 'ResourceEstimator': return {'Observation':{'total_data_size':1, 'total_bandwidth':1, 'output_files':1}}, "OK" - elif servicename == 'SSDBService.GetActiveGroupNames': - return {0:'storagenodes', 1:'computenodes', 2:'archivenodes', 3:'locusnodes', 4:'cep4'}, "OK" - elif servicename == 'SSDBService.GetHostForGID': - return {u'groupname': u'cep4', u'nodes': [{u'claimedspace': 0, u'totalspace': 702716, u'statename': u'Active', u'usedspace': 23084, u'id': 1, u'groupname': u'cep4', u'path': u'/lustre', u'hostname': u'lustre001'}]}, "OK" return None, None diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 994a2f5ddd4..bc79c86c91f 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -45,10 +45,6 @@ from lofar.sas.resourceassignment.resourceassignmentestimator.config import DEFA from lofar.sas.otdb.otdbrpc import OTDBRPC from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME -from lofar.sas.systemstatus.service.SSDBrpc import SSDBRPC -from lofar.sas.systemstatus.service.config import DEFAULT_SSDB_BUSNAME -from lofar.sas.systemstatus.service.config import DEFAULT_SSDB_SERVICENAME - from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_BUSNAME from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_PREFIX @@ -60,8 +56,6 @@ class ResourceAssigner(): radb_servicename=RADB_SERVICENAME, re_busname=RE_BUSNAME, re_servicename=RE_SERVICENAME, - ssdb_busname=DEFAULT_SSDB_BUSNAME, - ssdb_servicename=DEFAULT_SSDB_SERVICENAME, otdb_busname=DEFAULT_OTDB_SERVICE_BUSNAME, otdb_servicename=DEFAULT_OTDB_SERVICENAME, ra_notification_busname=DEFAULT_RA_NOTIFICATION_BUSNAME, @@ -73,13 +67,10 @@ class ResourceAssigner(): :param radb_servicename: servicename of the radb service (default: RADBService) :param re_busname: busname on which the resource estimator service listens (default: lofar.ra.command) :param re_servicename: servicename of the resource estimator service (default: ResourceEstimation) - :param ssdb_busname: busname on which the ssdb service listens (default: lofar.system) - :param ssdb_servicename: servicename of the radb service (default: SSDBService) :param broker: Valid Qpid broker host (default: None, which means localhost) """ self.radbrpc = RARPC(servicename=radb_servicename, busname=radb_busname, broker=broker) self.rerpc = RPC(re_servicename, busname=re_busname, broker=broker, ForwardExceptions=True) - self.ssdbrpc = SSDBRPC(servicename=ssdb_servicename, busname=ssdb_busname, broker=broker) self.otdbrpc = OTDBRPC(busname=otdb_busname, servicename=otdb_servicename, broker=broker) ## , ForwardExceptions=True hardcoded in RPCWrapper right now self.ra_notification_bus = ToBus(address=ra_notification_busname, broker=broker) self.ra_notification_prefix = ra_notification_prefix @@ -98,7 +89,6 @@ class ResourceAssigner(): self.radbrpc.open() self.rerpc.open() self.otdbrpc.open() - self.ssdbrpc.open() self.ra_notification_bus.open() def close(self): @@ -106,7 +96,6 @@ class ResourceAssigner(): self.radbrpc.close() self.rerpc.close() self.otdbrpc.close() - self.ssdbrpc.close() self.ra_notification_bus.close() def doAssignment(self, specification_tree): @@ -174,13 +163,6 @@ class ResourceAssigner(): logger.error("no task type %s found in estimator results %s" % (taskType, needed[str(otdb_id)])) return - # make sure the availability in the radb is up to date - # TODO: this should be updated regularly - try: - self.updateAvailableResources('cep4') - except Exception as e: - logger.warning("Exception while updating available resources: %s" % str(e)) - # claim the resources for this task # during the claim inserts the claims are automatically validated # and if not enough resources are available, then they are put to conflict status @@ -271,34 +253,6 @@ class ResourceAssigner(): logger.info('getNeededResouces: %s' % replymessage) return replymessage - def updateAvailableResources(self, cluster): - # find out which resources are available - # and what is their capacity - # For now, only look at CEP4 storage - # Later, also look at stations up/down for short term scheduling - - #get all active groupnames, find id for cluster group - groupnames = self.ssdbrpc.getactivegroupnames() - cluster_group_id = next(k for k,v in groupnames.items() if v == cluster) - - # for CEP4 cluster, do hard codes lookup of first and only node - node_info = self.ssdbrpc.gethostsforgid(cluster_group_id)['nodes'][0] - - storage_resources = self.radbrpc.getResources(resource_types='storage', include_availability=True) - cep4_storage_resource = next(x for x in storage_resources if 'cep4' in x['name']) - active = node_info['statename'] == 'Active' - total_capacity = node_info['totalspace'] - available_capacity = total_capacity - node_info['usedspace'] - - logger.info("Updating resource availability of %s (id=%s) to active=%s available_capacity=%s total_capacity=%s" % - (cep4_storage_resource['name'], cep4_storage_resource['id'], active, available_capacity, total_capacity)) - - self.radbrpc.updateResourceAvailability(cep4_storage_resource['id'], - active=active, - available_capacity=available_capacity, - total_capacity=total_capacity) - - def claimResources(self, needed_resources, task): logger.info('claimResources: task %s needed_resources=%s' % (task, needed_resources)) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py index a8cae557c28..18aa2f27336 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py @@ -83,8 +83,6 @@ def main(): from lofar.sas.resourceassignment.resourceassignmentestimator.config import DEFAULT_BUSNAME as RE_BUSNAME from lofar.sas.resourceassignment.resourceassignmentestimator.config import DEFAULT_SERVICENAME as RE_SERVICENAME from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME - from lofar.sas.systemstatus.service.config import DEFAULT_SSDB_BUSNAME - from lofar.sas.systemstatus.service.config import DEFAULT_SSDB_SERVICENAME from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_BUSNAME from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_PREFIX @@ -114,12 +112,6 @@ def main(): help="Name of the resource estimator service. [default: %default]") parser.add_option("--otdb_busname", dest="otdb_busname", type="string", default=DEFAULT_OTDB_SERVICE_BUSNAME, help="Name of the bus on which the OTDB service listens, default: %default") parser.add_option("--otdb_servicename", dest="otdb_servicename", type="string", default=DEFAULT_OTDB_SERVICENAME, help="Name of the OTDB service, default: %default") - parser.add_option("--ssdb_busname", dest="ssdb_busname", type="string", - default=DEFAULT_SSDB_BUSNAME, - help="Name of the bus on which the ssdb service listens. [default: %default]") - parser.add_option("--ssdb_servicename", dest="ssdb_servicename", type="string", - default=DEFAULT_SSDB_SERVICENAME, - help="Name of the ssdb service. [default: %default]") parser.add_option("--ra_notification_busname", dest="ra_notification_busname", type="string", default=DEFAULT_RA_NOTIFICATION_BUSNAME, help="Name of the notification bus on which the resourceassigner publishes its notifications. [default: %default]") @@ -139,8 +131,6 @@ def main(): re_servicename=options.re_servicename, otdb_busname=options.otdb_busname, otdb_servicename=options.otdb_servicename, - ssdb_busname=options.ssdb_busname, - ssdb_servicename=options.ssdb_servicename, ra_notification_busname=options.ra_notification_busname, ra_notification_prefix=options.ra_notification_prefix, broker=options.broker) as assigner: diff --git a/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py b/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py index 90a9cb2828a..eec15481b8c 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py +++ b/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py @@ -47,10 +47,6 @@ with patch('lofar.sas.resourceassignment.resourceassignmentservice.rpc.RARPC', a #give pre-cooked answer depending on called service if servicename == 'ResourceEstimation': return {'1290472': {'observation': {'bandwidth': {'total_size': 9372800}, 'storage': {'total_size': 140592000, 'output_files': {'is': {'is_nr_stokes': 1, 'is_file_size': 36864000, 'nr_of_is_files': 1}, 'uv': {'nr_of_uv_files': 50, 'uv_file_size': 2074560}, 'saps': [{'sap_nr': 0, 'properties': {'nr_of_uv_files': 50, 'nr_of_is_files': 1}}]}}}}}, "OK" - elif servicename == 'SSDBService.GetActiveGroupNames': - return {0:'storagenodes', 1:'computenodes', 2:'archivenodes', 3:'locusnodes', 4:'cep4'}, "OK" - elif servicename == 'SSDBService.GetHostForGID': - return {u'groupname': u'cep4', u'nodes': [{u'claimedspace': 0, u'totalspace': 702716, u'statename': u'Active', u'usedspace': 23084, u'id': 1, u'groupname': u'cep4', u'path': u'/lustre', u'hostname': u'lustre001'}]}, "OK" return None, None -- GitLab From ffbf2e32d3163e59cb34d2dccb1a8273bd6c005c Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Mon, 18 Jul 2016 09:21:40 +0000 Subject: [PATCH 543/933] Task #9655: changes needed to get aal stations be aware of PowerUnits and sumAlerts --- .gitattributes | 2 + MAC/Deployment/data/PVSS/bin/create_db_files | 45 ++++ MAC/Deployment/data/PVSS/data/CEPbase.dpdef | 19 +- MAC/Deployment/data/PVSS/data/MCUbase.dpdef | 6 - MAC/Deployment/data/PVSS/data/POWEC.list | 2 + .../data/PVSS/data/PVSSDataPoints.base | 1 + MAC/Deployment/data/PVSS/data/PVSSbase.dpdef | 50 +++- MAC/Deployment/data/PVSS/data/PowerUnit.dpl | 90 +++++++ .../data/PVSS/data/StationInfo.dpdef | 2 + .../data/PVSS/data/Stationbase.dpdef | 11 +- .../StaticMetaData/RemoteStation.conf.tmpl | 2 + .../data/StaticMetaData/StationInfo.dat | 228 +++++++++--------- .../data/StaticMetaData/createFiles | 14 +- MAC/Navigator2/config/progs.ccu | 9 +- 14 files changed, 322 insertions(+), 159 deletions(-) create mode 100644 MAC/Deployment/data/PVSS/data/POWEC.list create mode 100644 MAC/Deployment/data/PVSS/data/PowerUnit.dpl diff --git a/.gitattributes b/.gitattributes index 86653f5c150..eae1b7199ed 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3625,7 +3625,9 @@ MAC/Deployment/data/PVSS/data/LocusNode.dpdef -text MAC/Deployment/data/PVSS/data/MCUbase.dpdef -text MAC/Deployment/data/PVSS/data/Observation.dpdef -text MAC/Deployment/data/PVSS/data/ObservationControl.dpdef -text +MAC/Deployment/data/PVSS/data/POWEC.list -text MAC/Deployment/data/PVSS/data/PVSSbase.dpdef -text +MAC/Deployment/data/PVSS/data/PowerUnit.dpl -text MAC/Deployment/data/PVSS/data/RTDBPort.dpdef -text MAC/Deployment/data/PVSS/data/SoftwareMonitor.dpdef -text MAC/Deployment/data/PVSS/data/StationControl.dpdef -text diff --git a/MAC/Deployment/data/PVSS/bin/create_db_files b/MAC/Deployment/data/PVSS/bin/create_db_files index 00f441c0941..777a9923f00 100755 --- a/MAC/Deployment/data/PVSS/bin/create_db_files +++ b/MAC/Deployment/data/PVSS/bin/create_db_files @@ -149,6 +149,22 @@ create_wan_switch_list() rm -f /tmp/wslist } +# +# create_powec_switch_list +# +create_powec_switch_list() +{ + cleanlist ${POWECFILE} | while read powec + do + cleanlist ${powec}POWEC.list | awk -v POWEC=${powec} \ + '{ + print POWEC"_"$1 + }' + done >>/tmp/powlist + concatfile /tmp/powlist + rm -f /tmp/powlist +} + # # labelize somename # @@ -786,6 +802,32 @@ substitute_wan() } ' } +# +# substitute_powec +# +# syntax of the lines must be: dp dpt +# +substitute_powec() +{ + awk -v POWECLIST=${POWECLIST} ' + BEGIN { + nrPow=split(POWECLIST, powname, ":"); + }; + { + hasPow=index($1,"@powec@"); + if (hasPow > 0) { + for (pow in powname) { + dpname=$1; + sub("@powec@", powname[pow], dpname); + print dpname" "$2; + } + } + else { + print $1" "$2; + } + } ' +} + # # substitute_ring_station # @@ -1104,6 +1146,7 @@ create_dp_file() substitute_observation | \ substitute_wan_switch | \ substitute_wan | \ + substitute_powec | \ substitute_Cabinet_SubRack_RSPBoard_RCU | \ substitute_Cabinet_SubRack_RSPBoard | \ substitute_Cabinet_SubRack_TBBoard | \ @@ -1471,6 +1514,7 @@ COMPONENT_FILE=${dpdefdir}/PVSSbase.dpdef CLUSTERFILE=${dpdefdir}/Clusters.list RINGFILE=${dpdefdir}/Rings.list WANFILE=${dpdefdir}/Wan.list +POWECFILE=${dpdefdir}/POWEC.list ERRORFILE=/tmp/Crea.Error INPUTFILE=${dpdefdir}/PVSSDataPoints.base @@ -1486,6 +1530,7 @@ RINGLIST=`concatfile $RINGFILE` RINGSTATIONLIST=`create_ring_station_list` STATIONFIELDLIST=`create_stationfield_list` WANLIST=`concatfile $WANFILE` +POWECLIST = `concatfile $POWECFILE` WANSWITCHLIST=`create_wan_switch_list` # FPGA and URIboard numbers diff --git a/MAC/Deployment/data/PVSS/data/CEPbase.dpdef b/MAC/Deployment/data/PVSS/data/CEPbase.dpdef index f9b391333f2..5d6f8df5477 100644 --- a/MAC/Deployment/data/PVSS/data/CEPbase.dpdef +++ b/MAC/Deployment/data/PVSS/data/CEPbase.dpdef @@ -1,19 +1,4 @@ # CEP PVSS Database base types +# If CEP need some extra DPT DP DPE defines or powerconfig settings +# they can be defined here -# Create missing CtrlDbg internals and DP for remoteStation -# Datapoint/DpId -DpName TypeName -_CtrlDebug_CTRL_5 _CtrlDebug -_CtrlDebug_CTRL_6 _CtrlDebug -_CtrlDebug_CTRL_7 _CtrlDebug -_CtrlDebug_CTRL_8 _CtrlDebug -_CtrlDebug_CTRL_9 _CtrlDebug - -#Fill some defaults -# DpValue -ElementName TypeName _original.._value -scriptInfo.transferMPs.runDone ScriptInfo 0 -_ValueArchive_2.size.maxDpElGet _ValueArchive 15000 -_ValueArchive_2.size.maxDpElSet _ValueArchive 15000 -_ValueArchive_2.size.maxValuesSet _ValueArchive 1250 -_ValueArchive_2.size.maxValuesGet _ValueArchive 1250 diff --git a/MAC/Deployment/data/PVSS/data/MCUbase.dpdef b/MAC/Deployment/data/PVSS/data/MCUbase.dpdef index 882c7937116..b7eb95f5b40 100644 --- a/MAC/Deployment/data/PVSS/data/MCUbase.dpdef +++ b/MAC/Deployment/data/PVSS/data/MCUbase.dpdef @@ -173,18 +173,12 @@ __navigator Navigator root NavPanelConfig __gcf_cwd GCFWatchDog rootSaves NavigatorUserSaves -_CtrlDebug_CTRL_5 _CtrlDebug -_CtrlDebug_CTRL_6 _CtrlDebug -_CtrlDebug_CTRL_7 _CtrlDebug -_CtrlDebug_CTRL_8 _CtrlDebug #Fill some defaults # DpValue ElementName TypeName _original.._value scriptInfo.transferMPs.runDone ScriptInfo 0 -_ValueArchive_2.size.maxDpElGet _ValueArchive 1250 -_ValueArchive_2.size.maxDpElSet _ValueArchive 1250 __navigator.alarmSettings.emails Navigator "observer@astron.nl" root.LOFAR_Processes NavPanelConfig "Processes/MainCU_Processes.pnl" root.LOFAR_Observations NavPanelConfig "Observations/Observations.pnl" diff --git a/MAC/Deployment/data/PVSS/data/POWEC.list b/MAC/Deployment/data/PVSS/data/POWEC.list new file mode 100644 index 00000000000..1ffd0eff63d --- /dev/null +++ b/MAC/Deployment/data/PVSS/data/POWEC.list @@ -0,0 +1,2 @@ +POWEC1 +POWEC2 diff --git a/MAC/Deployment/data/PVSS/data/PVSSDataPoints.base b/MAC/Deployment/data/PVSS/data/PVSSDataPoints.base index 0a77f21d7a8..812ba8dd5cb 100644 --- a/MAC/Deployment/data/PVSS/data/PVSSDataPoints.base +++ b/MAC/Deployment/data/PVSS/data/PVSSDataPoints.base @@ -92,6 +92,7 @@ URIBoard URI S Y LOFAR_PIC_@uriboard@ LBAAntenna LBA S Y LOFAR_PIC_@lbaantenna@ HBAAntenna HBA S N LOFAR_PIC_@hbaantenna@ StationInfo STI S Y LOFAR_PIC_StationInfo +PowerUnit POW S Y LOFAR_PIC_@powec@ StnPermSW - S N LOFAR_PermSW # Note: the next 2 lines are neccesary for PVSS2SAS to create the PIC tree. Cluster - S N LOFAR_PermSW_@cluster@ diff --git a/MAC/Deployment/data/PVSS/data/PVSSbase.dpdef b/MAC/Deployment/data/PVSS/data/PVSSbase.dpdef index 25c6a73f1d2..f2aa9db4e37 100644 --- a/MAC/Deployment/data/PVSS/data/PVSSbase.dpdef +++ b/MAC/Deployment/data/PVSS/data/PVSSbase.dpdef @@ -82,7 +82,8 @@ ObjectStatus.ObjectStatus 1# childState 21# message 25# leaf 23# - + childSumAlert 25# + TypeName ProcessStatus.ProcessStatus 1# processID 21# @@ -115,20 +116,63 @@ ScriptInfo.ScriptInfo 1# transferMPs 1# debug 23# runDone 23# + setSumAlerts 1# + debug 23# + runDone 23# + # create mp for ProcessStatus and attach an archive +# create SNMP manager, Agent and Pollgroup DpName TypeName _mp_ProcessStatus ProcessStatus _dt_ProcessStatus _DynamicDatapoints +_mp_ObjectStatus ObjectStatus +_dt_ObjectStatus _DynamicDatapoints +_2_SNMPManager _SNMPManager +_SNMP _PollGroup +DummyBit ExampleDP_Bit # create datapoints for ClaimManager,NCFObjectState and lofarSpeedTest +# create missing _CtrlDebug points (needed if more then 4 ctl scripts are running) DpName TypeName ClaimManager ClaimManager +lofarSpeedTest LofarSpeedTest +scriptInfo ScriptInfo +_CtrlDebug_CTRL_5 _CtrlDebug +_CtrlDebug_CTRL_6 _CtrlDebug +_CtrlDebug_CTRL_7 _CtrlDebug +_CtrlDebug_CTRL_8 _CtrlDebug +_CtrlDebug_CTRL_9 _CtrlDebug +_CtrlDebug_CTRL_10 _CtrlDebug +_CtrlDebug_CTRL_11 _CtrlDebug __navObjectState NCFObjectState __resetObjectState NCFObjectState __navObjectStates NCFObjectStates __resetObjectStates NCFObjectStates -lofarSpeedTest LofarSpeedTest -scriptInfo ScriptInfo +#Fill some defaults +# DpValue +ElementName TypeName _original.._value +scriptInfo.transferMPs.runDone ScriptInfo 0 +_ValueArchive_2.size.maxDpElGet _ValueArchive 15000 +_ValueArchive_2.size.maxDpElSet _ValueArchive 15000 +_ValueArchive_2.size.maxValuesSet _ValueArchive 1250 +_ValueArchive_2.size.maxValuesGet _ValueArchive 1250 +_SNMP.Active _PollGroup 1 +_SNMP.PollInterval _PollGroup 5000 +_dt_ObjectStatus.Leaf _DynamicDatapoints "_mp_ObjectStatus.state:_alert_hdl", "_mp_ObjectStatus.childSumAlert:_alert_hdl" +_dt_ObjectStatus.DynamicAttribute _DynamicDatapoints "_da_none", "_da_alert_hdl_sum" + +# AlertValue +ElementName TypeName DetailNr _alert_hdl.._type _alert_hdl.._l_limit _alert_hdl.._u_limit _alert_hdl.._l_incl _alert_hdl.._u_incl _alert_hdl.._panel _alert_hdl.._panel_param _alert_hdl.._help _alert_hdl.._min_prio _alert_hdl.._class _alert_hdl.._text _alert_hdl.._active _alert_hdl.._orig_hdl _alert_hdl.._ok_range _alert_hdl.._hyst_type _alert_hdl.._hyst_time _alert_hdl.._multi_instance _alert_hdl.._l_hyst_limit _alert_hdl.._u_hyst_limit _alert_hdl.._text1 _alert_hdl.._text0 _alert_hdl.._ack_has_prio _alert_hdl.._order _alert_hdl.._dp_pattern _alert_hdl.._dp_list _alert_hdl.._prio_pattern _alert_hdl.._abbr_pattern _alert_hdl.._ack_deletes _alert_hdl.._non_ack _alert_hdl.._came_ack _alert_hdl.._pair_ack _alert_hdl.._both_ack _alert_hdl.._impulse _alert_hdl.._filter_threshold _alert_hdl.._went_text _alert_hdl.._add_text _alert_hdl.._status64_pattern _alert_hdl.._neg _alert_hdl.._status64_match _alert_hdl.._match _alert_hdl.._set +_mp_ObjectStatus.state ObjectStatus 13 "" "" lt:1 LANG:1 "" \0 1 1 0 0 +_mp_ObjectStatus.state ObjectStatus 1 4 -2147483648 0 1 0 lt:1 LANG:1 "" 0 01.01.1970 00:00:00.000 -2147483648 0 lt:1 LANG:1 "" 0x0 +_mp_ObjectStatus.state ObjectStatus 2 4 0 10 1 0 LOFAR_AlertClass_Off. lt:1 LANG:1 "Off" 0 01.01.1970 00:00:00.000 0 10 lt:1 LANG:1 "" 0x0 +_mp_ObjectStatus.state ObjectStatus 3 4 10 20 1 0 LOFAR_AlertClass_Operational. lt:1 LANG:1 "Operational" 0 01.01.1970 00:00:00.000 10 20 lt:1 LANG:1 "" 0x0 +_mp_ObjectStatus.state ObjectStatus 4 4 20 30 1 0 LOFAR_AlertClass_Maintenance. lt:1 LANG:1 "Maintenance" 0 01.01.1970 00:00:00.000 20 30 lt:1 LANG:1 "" 0x0 +_mp_ObjectStatus.state ObjectStatus 5 4 30 40 1 0 LOFAR_AlertClass_Test. lt:1 LANG:1 "Test" 0 01.01.1970 00:00:00.000 30 40 lt:1 LANG:1 "" 0x0 +_mp_ObjectStatus.state ObjectStatus 6 4 40 50 1 0 LOFAR_AlertClass_Suspicious. lt:1 LANG:1 "Suspicious" 0 01.01.1970 00:00:00.000 40 50 lt:1 LANG:1 "" 0x0 +_mp_ObjectStatus.state ObjectStatus 7 4 50 60 1 0 LOFAR_AlertClass_Broken. lt:1 LANG:1 "Broken" 0 01.01.1970 00:00:00.000 50 60 lt:1 LANG:1 "" 0x0 +_mp_ObjectStatus.state ObjectStatus 8 4 60 2147483647 1 1 LOFAR_AlertClass_Off. lt:1 LANG:1 "DP Offline" 0 01.01.1970 00:00:00.000 60 2147483647 lt:1 LANG:1 "" 0x0 +_mp_ObjectStatus.childSumAlert ObjectStatus 59 "" lt:1 LANG:1 "" 1 lt:1 LANG:1 "" lt:1 LANG:1 "" 1 0 "" DummyBit. "" "" 1 1 1 1 1 0 diff --git a/MAC/Deployment/data/PVSS/data/PowerUnit.dpl b/MAC/Deployment/data/PVSS/data/PowerUnit.dpl new file mode 100644 index 00000000000..6f1facc176c --- /dev/null +++ b/MAC/Deployment/data/PVSS/data/PowerUnit.dpl @@ -0,0 +1,90 @@ +# description of the POWEC power unit in the LOFAR stations + +# DpType +TypeName +PowerUnit.PowerUnit 1# + status 41#2:ObjectStatus + OK 5# + nrOfModules 21# + voltage 5# + current 5# + temperature 5# + +# +# Next points are needed for dataparameterization Do not alter them unless you know what you are doing. tabs are essential in these +# + +# Datapoint/DpId +!DpName TypeName +!LOFAR_PIC_POWEC1 PowerUnit +!LOFAR_PIC_POWEC2 PowerUnit +!_2_SNMPAgent_1 _SNMPAgent +!_2_SNMPAgent_2 _SNMPAgent + +# Aliases/Comments +!AliasId AliasName CommentName +!_2_SNMPAgent_1. "" lt:1 LANG:1 "POWEC1@@" +!_2_SNMPAgent_2. "" lt:1 LANG:1 "POWEC2@@" + +# DpValue +!ElementName TypeName _original.._value +!LOFAR_PIC_POWEC1.status.leaf PowerUnit 1 +!LOFAR_PIC_POWEC2.status.leaf PowerUnit 1 +!_2_SNMPAgent_1.Access.ReadCommunity _SNMPAgent "public" +!_2_SNMPAgent_1.Access.WriteCommunity _SNMPAgent "public" +!_2_SNMPAgent_1.Access.Timeout _SNMPAgent 100 +!_2_SNMPAgent_1.Access.Retries _SNMPAgent 5 +!_2_SNMPAgent_1.Access.Protocol _SNMPAgent 1 +!_2_SNMPAgent_1.Access.Port _SNMPAgent 161 +!_2_SNMPAgent_1.Status.Timeout _SNMPAgent 1 +!_2_SNMPAgent_1.Redundancy.ReduAgent _SNMPAgent 0 +!_2_SNMPAgent_1.Redundancy.FallBack _SNMPAgent 0 +!_2_SNMPAgent_2.Access.ReadCommunity _SNMPAgent "public" +!_2_SNMPAgent_2.Access.WriteCommunity _SNMPAgent "public" +!_2_SNMPAgent_2.Access.Timeout _SNMPAgent 100 +!_2_SNMPAgent_2.Access.Retries _SNMPAgent 5 +!_2_SNMPAgent_2.Access.Protocol _SNMPAgent 1 +!_2_SNMPAgent_2.Access.Port _SNMPAgent 161 +!_2_SNMPAgent_2.Status.Timeout _SNMPAgent 1 +!_2_SNMPAgent_2.Redundancy.ReduAgent _SNMPAgent 0 +!_2_SNMPAgent_2.Redundancy.FallBack _SNMPAgent 0 + +# DistributionInfo +!ElementName TypeName _distrib.._type _distrib.._driver +!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.voltage PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.current PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.temperature PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.OK PowerUnit 56 \2 +!LOFAR_PIC_POWEC2.nrOfModules PowerUnit 56 \2 +!LOFAR_PIC_POWEC2.voltage PowerUnit 56 \2 +!LOFAR_PIC_POWEC2.current PowerUnit 56 \2 +!LOFAR_PIC_POWEC2.temperature PowerUnit 56 \2 +!LOFAR_PIC_POWEC2.OK PowerUnit 56 \2 + +# DpSmoothMain +!ElementName TypeName _smooth.._type _smooth.._std_type +!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 48 4 +!LOFAR_PIC_POWEC1.voltage PowerUnit 48 4 +!LOFAR_PIC_POWEC1.current PowerUnit 48 4 +!LOFAR_PIC_POWEC1.temperature PowerUnit 48 4 +!LOFAR_PIC_POWEC1.OK PowerUnit 48 4 +!LOFAR_PIC_POWEC2.nrOfModules PowerUnit 48 4 +!LOFAR_PIC_POWEC2.voltage PowerUnit 48 4 +!LOFAR_PIC_POWEC2.current PowerUnit 48 4 +!LOFAR_PIC_POWEC2.temperature PowerUnit 48 4 +!LOFAR_PIC_POWEC2.OK PowerUnit 48 4 + +# PeriphAddrMain +!ElementName TypeName _address.._type _address.._reference _address.._poll_group _address.._offset _address.._subindex _address.._direction _address.._internal _address.._lowlevel _address.._active _address.._start _address.._interval _address.._reply _address.._datatype _address.._drv_ident +!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.1.0" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.voltage PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.3B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.current PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.4B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.temperature PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.6B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.OK PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.2B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC2.nrOfModules PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.1.0" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 660 "SNMP" +!LOFAR_PIC_POWEC2.voltage PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.3B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC2.current PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.4B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC2.temperature PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.6B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC2.OK PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.2B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" + diff --git a/MAC/Deployment/data/PVSS/data/StationInfo.dpdef b/MAC/Deployment/data/PVSS/data/StationInfo.dpdef index 04137534875..b4666510273 100644 --- a/MAC/Deployment/data/PVSS/data/StationInfo.dpdef +++ b/MAC/Deployment/data/PVSS/data/StationInfo.dpdef @@ -1,4 +1,5 @@ stationID int +stationIP string N_RSPBoards int N_TBBoards int N_LBAS int @@ -8,6 +9,7 @@ wide_LBAS bool AARTFAAC bool power48On bool power220On bool +nrOfPowerUnits int datastream0 bool datastream1 bool Cabinet.X float diff --git a/MAC/Deployment/data/PVSS/data/Stationbase.dpdef b/MAC/Deployment/data/PVSS/data/Stationbase.dpdef index 5aaa57289ba..e5356910199 100644 --- a/MAC/Deployment/data/PVSS/data/Stationbase.dpdef +++ b/MAC/Deployment/data/PVSS/data/Stationbase.dpdef @@ -153,26 +153,17 @@ HBAElement.HBAElement 1# -# Create missing CtrlDbg internals and DP for remoteStation +# Create missing CtrlDbg internals # Datapoint/DpId DpName TypeName __navigator Navigator standalone NavPanelConfig rootSaves NavigatorUserSaves -_CtrlDebug_CTRL_5 _CtrlDebug -_CtrlDebug_CTRL_6 _CtrlDebug -_CtrlDebug_CTRL_7 _CtrlDebug -_CtrlDebug_CTRL_8 _CtrlDebug -_CtrlDebug_CTRL_9 _CtrlDebug -_CtrlDebug_CTRL_10 _CtrlDebug -_CtrlDebug_CTRL_11 _CtrlDebug #Fill some defaults # DpValue ElementName TypeName _original.._value scriptInfo.transferMPs.runDone ScriptInfo 0 -_ValueArchive_2.size.maxDpElGet _ValueArchive 1250 -_ValueArchive_2.size.maxDpElSet _ValueArchive 1250 __navigator.alarmSettings.emails Navigator "observer@astron.nl" standalone.StnLOFAR_Hardware NavPanelConfig "Hardware/Station.pnl" standalone.StnPIC_Hardware NavPanelConfig "Hardware/Station_Cabinet.pnl" diff --git a/MAC/Deployment/data/StaticMetaData/RemoteStation.conf.tmpl b/MAC/Deployment/data/StaticMetaData/RemoteStation.conf.tmpl index ac028c12671..5531af9e33e 100644 --- a/MAC/Deployment/data/StaticMetaData/RemoteStation.conf.tmpl +++ b/MAC/Deployment/data/StaticMetaData/RemoteStation.conf.tmpl @@ -7,6 +7,7 @@ # RS.STATION_ID = @STATION_ID@ +RS.STATION_IP = @STATION_IP@ RS.N_RSPBOARDS = @NR_RSP@ RS.N_TBBOARDS = @NR_TBB@ RS.N_LBAS = @NR_LBA@ @@ -14,4 +15,5 @@ RS.N_HBAS = @NR_HBA@ RS.HBA_SPLIT = @HBA_SPLIT@ RS.WIDE_LBAS = @LBA_WIDE@ RS.AARTFAAC = @AARTFAAC@ +RS.N_POWECS = @NR_POWECS@ diff --git a/MAC/Deployment/data/StaticMetaData/StationInfo.dat b/MAC/Deployment/data/StaticMetaData/StationInfo.dat index e942a82ad2c..e63b634c81f 100644 --- a/MAC/Deployment/data/StaticMetaData/StationInfo.dat +++ b/MAC/Deployment/data/StaticMetaData/StationInfo.dat @@ -16,51 +16,51 @@ # HBAsplit indicates split HBA field (core stations only) # LBAcal # -# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA HBAsplit LBAcal Aartfaac -#-------------------------------------------------------------------------------------------------------------- +# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA nrPowec HBAsplit LBAcal Aartfaac +#--------------------------------------------------------------------------------------------------------------- ## core (1-59) -CS001 1 C 6.8680483 52.9110414 54.43 12 6 96 48 Yes Yes Yes -CS002 2 C 6.8693028 52.9148347 55.90 12 6 96 48 Yes Yes Yes -CS003 3 C 6.8693283 52.9159339 55.19 12 6 96 48 Yes Yes Yes -CS004 4 C 6.8680911 52.9147486 55.94 12 6 96 48 Yes Yes Yes -CS005 5 C 6.8696561 52.9141017 57.58 12 6 96 48 Yes Yes Yes -CS006 6 C 6.87108883 52.9144497 57.78 12 6 96 48 Yes Yes Yes -CS007 7 C 6.8715772 52.9157881 56.56 12 6 96 48 Yes Yes Yes -CS008 8 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS009 9 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS010 10 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS011 11 C 6.8737881 52.9141903 53.68 12 6 96 48 Yes Yes Yes -CS012 12 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS013 13 C 6.8672969 52.9177144 54.98 12 6 96 48 Yes Yes Yes -CS014 14 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS015 15 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS016 16 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS017 17 C 6.8778067 52.9158697 59.23 12 6 96 48 Yes Yes Yes -CS018 18 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS019 19 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS020 20 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS021 21 C 6.8622447 52.9176275 52.73 12 6 96 48 Yes Yes Yes -CS022 22 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS023 23 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS024 24 C 6.8738103 52.9080003 55.80 12 6 96 48 Yes Yes No -CS025 25 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS026 26 C 6.8821933 52.9163728 55.42 12 6 96 48 Yes Yes No -CS027 27 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS028 28 C 6.8755528 52.9254167 52.66 12 6 96 48 Yes Yes No -CS029 29 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS030 30 C 6.8609850 52.9225878 58.01 12 6 96 48 Yes Yes No -CS031 31 C 6.8597219 52.9177706 53.61 12 6 96 48 Yes Yes No -CS032 32 C 6.8603878 52.9121611 59.31 12 6 96 48 Yes Yes Yes +CS001 1 C 6.8680483 52.9110414 54.43 12 6 96 48 1 Yes Yes Yes +CS002 2 C 6.8693028 52.9148347 55.90 12 6 96 48 1 Yes Yes Yes +CS003 3 C 6.8693283 52.9159339 55.19 12 6 96 48 1 Yes Yes Yes +CS004 4 C 6.8680911 52.9147486 55.94 12 6 96 48 1 Yes Yes Yes +CS005 5 C 6.8696561 52.9141017 57.58 12 6 96 48 1 Yes Yes Yes +CS006 6 C 6.87108883 52.9144497 57.78 12 6 96 48 1 Yes Yes Yes +CS007 7 C 6.8715772 52.9157881 56.56 12 6 96 48 1 Yes Yes Yes +CS008 8 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS009 9 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS010 10 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS011 11 C 6.8737881 52.9141903 53.68 12 6 96 48 1 Yes Yes Yes +CS012 12 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS013 13 C 6.8672969 52.9177144 54.98 12 6 96 48 1 Yes Yes Yes +CS014 14 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS015 15 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS016 16 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS017 17 C 6.8778067 52.9158697 59.23 12 6 96 48 1 Yes Yes Yes +CS018 18 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS019 19 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS020 20 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS021 21 C 6.8622447 52.9176275 52.73 12 6 96 48 1 Yes Yes Yes +CS022 22 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS023 23 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS024 24 C 6.8738103 52.9080003 55.80 12 6 96 48 1 Yes Yes No +CS025 25 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS026 26 C 6.8821933 52.9163728 55.42 12 6 96 48 1 Yes Yes No +CS027 27 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS028 28 C 6.8755528 52.9254167 52.66 12 6 96 48 1 Yes Yes No +CS029 29 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS030 30 C 6.8609850 52.9225878 58.01 12 6 96 48 1 Yes Yes No +CS031 31 C 6.8597219 52.9177706 53.61 12 6 96 48 1 Yes Yes No +CS032 32 C 6.8603878 52.9121611 59.31 12 6 96 48 1 Yes Yes Yes # The Remote stations with core station layout (i.e., with HBASplit set to 'yes') -CS101 101 C 6.8805014 52.9223181 57.78 12 6 96 48 Yes Yes No -CS102 102 C 0.0 0.0 0 12 6 96 48 Yes Yes No -CS103 103 C 6.8963725 52.9160403 56.52 12 6 96 48 Yes Yes No -CS201 121 C 6.8829719 52.9128547 56.08 12 6 96 48 Yes Yes No -CS301 141 C 6.8677119 52.9054208 58.75 12 6 96 48 Yes Yes No -CS302 142 C 6.8487681 52.90186 67.32 12 6 96 48 Yes Yes No -CS401 161 C 6.8556800 52.9138667 57.08 12 6 96 48 Yes Yes No -CS501 181 C 6.8665950 52.9263631 56.53 12 6 96 48 Yes Yes No +CS101 101 C 6.8805014 52.9223181 57.78 12 6 96 48 1 Yes Yes No +CS102 102 C 0.0 0.0 0 12 6 96 48 1 Yes Yes No +CS103 103 C 6.8963725 52.9160403 56.52 12 6 96 48 1 Yes Yes No +CS201 121 C 6.8829719 52.9128547 56.08 12 6 96 48 1 Yes Yes No +CS301 141 C 6.8677119 52.9054208 58.75 12 6 96 48 1 Yes Yes No +CS302 142 C 6.8487681 52.90186 67.32 12 6 96 48 1 Yes Yes No +CS401 161 C 6.8556800 52.9138667 57.08 12 6 96 48 1 Yes Yes No +CS501 181 C 6.8665950 52.9263631 56.53 12 6 96 48 1 Yes Yes No ## 33 .. 59 ## @@ -68,7 +68,7 @@ CS501 181 C 6.8665950 52.9263631 56.53 12 6 96 48 Ye ## 60 #MCU001 61 #MCU099 62 -#MCU100 1063 +#MCU100 63 # No numbers above 255 ## 61 .. 69 ## wan (70-79) @@ -80,7 +80,7 @@ CS501 181 C 6.8665950 52.9263631 56.53 12 6 96 48 Ye ## 80 #CCU001 81 #CCU099 82 -#CCU100 1083 +#CCU100 83 # No numbers above 255 ## 82 .. 89 ## spare (90-99) @@ -90,121 +90,121 @@ CS501 181 C 6.8665950 52.9263631 56.53 12 6 96 48 Ye ## arm 1 (100-119) ## 100 -# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA HBAsplit LBAcal Aartfaac -#-------------------------------------------------------------------------------------------------------------- -RS104 104 R 6.929932 52.943271 0 12 6 96 48 No Yes No -RS105 105 R 0.0 0.0 0 12 6 96 48 No Yes No -RS106 106 R 6.9854983 52.8744053 29.82 12 6 96 48 No Yes No -RS107 107 R 7.103304 52.928932 0 12 6 96 48 No Yes No -RS108 108 R 0.0 0.0 0 12 6 96 48 No Yes No -RS109 109 R 0.0 0.0 0 12 6 96 48 No Yes No +# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA nrPowec HBAsplit LBAcal Aartfaac +#----------------------------------------------------------------------------------------------------------------- +RS104 104 R 6.929932 52.943271 0 12 6 96 48 1 No Yes No +RS105 105 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS106 106 R 6.9854983 52.8744053 29.82 12 6 96 48 1 No Yes No +RS107 107 R 7.103304 52.928932 0 12 6 96 48 1 No Yes No +RS108 108 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS109 109 R 0.0 0.0 0 12 6 96 48 1 No Yes No # 110 .. 119 # # arm 2 (120-139) # 120 -# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA HBAsplit LBAcal Aartfaac -#-------------------------------------------------------------------------------------------------------------- -RS202 122 R 0.0 0.0 0 12 6 96 48 No Yes No -RS203 123 R 0.0 0.0 0 12 6 96 48 No Yes No -RS204 124 R 0.0 0.0 0 12 6 96 48 No Yes No -RS205 125 R 6.8965411 52.8571108 59.79 12 6 96 48 No Yes No -RS206 126 R 6.819885 52.808927 0 12 6 96 48 No Yes No -RS207 127 R 6.974889 52.757463 0 12 6 96 48 No Yes No -RS208 128 R 6.9196231 52.6695617 60.66 12 6 96 48 No Yes No -RS209 129 R 0.0 0.0 0 12 6 96 48 No Yes No -RS210 130 R 6.8742114 52.3310447 69.59 12 6 96 48 No Yes No +# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA nrPowec HBAsplit LBAcal Aartfaac +#---------------------------------------------------------------------------------------------------------------- +RS202 122 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS203 123 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS204 124 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS205 125 R 6.8965411 52.8571108 59.79 12 6 96 48 1 No Yes No +RS206 126 R 6.819885 52.808927 0 12 6 96 48 1 No Yes No +RS207 127 R 6.974889 52.757463 0 12 6 96 48 1 No Yes No +RS208 128 R 6.9196231 52.6695617 60.66 12 6 96 48 1 No Yes No +RS209 129 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS210 130 R 6.8742114 52.3310447 69.59 12 6 96 48 1 No Yes No ## 130 .. 139 ## ## arm 3 (140-159) ## 140 -# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA HBAsplit LBAcal Aartfaac +# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA nrPowec HBAsplit LBAcal Aartfaac #-------------------------------------------------------------------------------------------------------------- -RS303 143 R 0.0 0.0 0 12 6 96 48 No Yes No -RS304 144 R 0.0 0.0 0 12 6 96 48 No Yes No -RS305 145 R 6.7732369 52.8995903 61.69 12 6 96 48 No Yes No -RS306 146 R 6.7430722 52.8905881 61.76 12 6 96 48 No Yes No -RS307 147 R 6.6816903 52.8033092 65.55 12 6 96 48 No Yes No -RS308 148 R 6.539006 52.82659 0 12 6 96 48 No Yes No -RS309 149 R 6.52464 52.532 0 12 6 96 48 No Yes No -RS310 150 R 6.1386128 52.7648403 40.91 12 6 96 48 No Yes No +RS303 143 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS304 144 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS305 145 R 6.7732369 52.8995903 61.69 12 6 96 48 1 No Yes No +RS306 146 R 6.7430722 52.8905881 61.76 12 6 96 48 1 No Yes No +RS307 147 R 6.6816903 52.8033092 65.55 12 6 96 48 1 No Yes No +RS308 148 R 6.539006 52.82659 0 12 6 96 48 1 No Yes No +RS309 149 R 6.52464 52.532 0 12 6 96 48 1 No Yes No +RS310 150 R 6.1386128 52.7648403 40.91 12 6 96 48 1 No Yes No ## 150 .. 159 ## ## arm 4 (160-179) ## 160 -# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA HBAsplit LBAcal Aartfaac +# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA nrPowec HBAsplit LBAcal Aartfaac #-------------------------------------------------------------------------------------------------------------- -RS402 162 R 0.0 0.0 0 12 6 96 48 No Yes No -RS403 163 R 0.0 0.0 0 12 6 96 48 No Yes No -RS404 164 R 6.808886111 52.93291944 0 12 6 96 48 No Yes No -RS405 165 R 0.0 0.0 0 12 6 96 48 No Yes No -RS406 166 R 6.7505064 53.0183578 65.62 12 6 96 48 No Yes No -RS407 167 R 6.7848725 53.0923619 49.64 12 6 96 48 No Yes No -RS408 168 R 0.0 0.0 0 12 6 96 48 No Yes No -RS409 169 R 6.3574842 52.9804722 59.65 12 6 96 48 No Yes No -RS410 170 R 5.83021 52.99421 0 12 6 96 48 No Yes No -RS411 171 R 6.692146 53.040486 0 12 6 96 48 No Yes No -RS412 172 R 0.0 0.0 0 12 6 96 48 No Yes No -RS413 173 R 6.106016 52.99816 0 12 6 96 48 No Yes No +RS402 162 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS403 163 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS404 164 R 6.808886111 52.93291944 0 12 6 96 48 1 No Yes No +RS405 165 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS406 166 R 6.7505064 53.0183578 65.62 12 6 96 48 1 No Yes No +RS407 167 R 6.7848725 53.0923619 49.64 12 6 96 48 1 No Yes No +RS408 168 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS409 169 R 6.3574842 52.9804722 59.65 12 6 96 48 1 No Yes No +RS410 170 R 5.83021 52.99421 0 12 6 96 48 1 No Yes No +RS411 171 R 6.692146 53.040486 0 12 6 96 48 1 No Yes No +RS412 172 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS413 173 R 6.106016 52.99816 0 12 6 96 48 1 No Yes No ## 170 .. 179 ## ## arm 5 (180-199) ## 180 -# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA HBAsplit LBAcal Aartfaac -#-------------------------------------------------------------------------------------------------------------- -RS502 182 R 0.0 0.0 0 12 6 96 48 No Yes No -RS503 183 R 6.8506333 52.9445961 58.65 12 6 96 48 No Yes No -RS504 184 R 0.0 0.0 0 12 6 96 48 No Yes No -RS505 185 R 0.0 0.0 0 12 6 96 48 No Yes No -RS506 186 R 7.03114 53.008596 0 12 6 96 48 No Yes No -RS507 187 R 7.02318 53.0722 0 12 6 96 48 No Yes No -RS508 188 R 6.9536942 53.2403583 45.51 12 6 96 48 No Yes No -RS509 189 R 6.7852089 53.4093164 51.53 12 6 96 48 No Yes No +# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA nrPowec HBAsplit LBAcal Aartfaac +#---------------------------------------------------------------------------------------------------------------- +RS502 182 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS503 183 R 6.8506333 52.9445961 58.65 12 6 96 48 1 No Yes No +RS504 184 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS505 185 R 0.0 0.0 0 12 6 96 48 1 No Yes No +RS506 186 R 7.03114 53.008596 0 12 6 96 48 1 No Yes No +RS507 187 R 7.02318 53.0722 0 12 6 96 48 1 No Yes No +RS508 188 R 6.9536942 53.2403583 45.51 12 6 96 48 1 No Yes No +RS509 189 R 6.7852089 53.4093164 51.53 12 6 96 48 1 No Yes No # 190 .. 199 # ## international (200-255) ## 200 -# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA HBAsplit LBAcal -#-------------------------------------------------------------------------------------------------------------- +# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA nrPowec HBAsplit LBAcal Aartfaac +#---------------------------------------------------------------------------------------------------------------- # effelsberg -DE601 201 E 6.8835692 50.5229575 396.29 24 12 96 96 No No No +DE601 201 E 6.8835692 50.5229575 396.29 24 12 96 96 1 No No No # garching -DE602 202 E 11.2876647 48.5014758 515.20 24 12 96 96 No No No +DE602 202 E 11.2876647 48.5014758 515.20 24 12 96 96 1 No No No # Tautenburg -DE603 203 E 11.7106958 50.9796781 385.41 24 12 96 96 No No No +DE603 203 E 11.7106958 50.9796781 385.41 24 12 96 96 1 No No No # Potsdam -DE604 204 E 13.0158897 52.4381292 87.02 24 12 96 96 No No No +DE604 204 E 13.0158897 52.4381292 87.02 24 12 96 96 1 No No No # Juelich -DE605 205 E 6.4241444 50.8972719 147.28 24 12 96 96 No No No +DE605 205 E 6.4241444 50.8972719 147.28 24 12 96 96 1 No No No # Nancay -FR606 206 E 2.1926264 47.3759658 192.45 24 12 96 96 No No No +FR606 206 E 2.1926264 47.3759658 192.45 24 12 96 96 1 No No No # Onsala -SE607 207 E 11.9302386 57.3990617 50.34 24 12 96 96 No No No +SE607 207 E 11.9302386 57.3990617 50.34 24 12 96 96 1 No No No # Chillbolton -UK608 208 E -1.4342539 51.1439472 137.66 24 12 96 96 No No No +UK608 208 E -1.4342539 51.1439472 137.66 24 12 96 96 1 No No No # Norderstedt (H=67.246568409726024) # WARNING: JUMP IN STATIONNUMBERS!!!! FI609 HAS NUMBER 209!!!! -DE609 210 E 9.9692322 53.6988092 69.27 24 12 96 96 No No No +DE609 210 E 9.9692322 53.6988092 69.27 24 12 96 96 1 No No No # Borowiec -PL610 211 E 17.0741606 52.2759328 122.29 24 12 96 96 No No No +PL610 211 E 17.0741606 52.2759328 122.29 24 12 96 96 2 No No No # Lazy -PL611 212 E 20.4896131 49.9649386 305.42 24 12 96 48 No No No +PL611 212 E 20.4896131 49.9649386 305.42 24 12 96 48 2 No No No # Baldy -PL612 213 E 20.5897506 53.5939042 178.38 24 12 96 96 No No No +PL612 213 E 20.5897506 53.5939042 178.38 24 12 96 96 2 No No No ## Non-ILT international ## 900 # Finland (to be renamed when it becomes part of ILT) -FI609 901 E 20.7609103 69.0714225 533.25 12 6 48 48 No No No +FI609 901 E 20.7609103 69.0714225 533.25 12 6 48 48 1 No No No ## Test systems -# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA HBAsplit LBAcal -#-------------------------------------------------------------------------------------------------------------- -CS100 230 C 6.3952067 52.8121013 0 6 12 4 4 Yes No No +# name ID ring long lat height nrRSP nrTBB nrLBA nrHBA nrPowec HBAsplit LBAcal Aartfaac +#---------------------------------------------------------------------------------------------------------------- +CS100 230 C 6.3952067 52.8121013 0 6 12 4 4 1 Yes No No #MCU099 231 #CCU099 232 -#RS002 102 R 6.0000000 52.00000000 50.000 4 0 16 16 Yes No +#RS002 102 R 6.0000000 52.00000000 50.000 4 0 16 16 1 Yes No No diff --git a/MAC/Deployment/data/StaticMetaData/createFiles b/MAC/Deployment/data/StaticMetaData/createFiles index 447e0c995d1..19778b473fd 100755 --- a/MAC/Deployment/data/StaticMetaData/createFiles +++ b/MAC/Deployment/data/StaticMetaData/createFiles @@ -279,7 +279,7 @@ def createRSPDriverFile(resultDir, stationName, dataDir,int_local,is_Cobalt): rspDestNode = findRSPDestNodes(stationName, dataDir) print stationName,"matches:",rspDestNode - (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, HBAsplit, LBAcal, Aartfaac ) = findStationInfo(stationName, dataDir) + (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, nrPOWEC, HBAsplit, LBAcal, Aartfaac ) = findStationInfo(stationName, dataDir) # Substitute MAC and IP address of destination nodes RSPfile = open(dataDir+"/RSPDriver.conf.tmpl") @@ -476,7 +476,7 @@ def createRSPDriverFile_Test(resultDir, stationName, dataDir, alias): rspDestNode = findRSPDestNodes(stationName, dataDir) #print stationName,"matches:",rspDestNode - (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, HBAsplit, LBAcal, Aartfaac ) = findStationInfo(stationName, dataDir) + (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, nrPOWECS, HBAsplit, LBAcal, Aartfaac ) = findStationInfo(stationName, dataDir) # Substitute MAC and IP address of destination nodes RSPfile = open(dataDir+"/RSPDriver.conf.tmpl") @@ -623,7 +623,7 @@ def createTBBDriverFile(resultDir, stationName, dataDir): tbbDestNode = findTBBDestNodes(stationName, dataDir) #print stationName,"matches:",tbbDestNode - (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, HBAsplit, LBAcal, Aartfaac ) = findStationInfo(stationName, dataDir) + (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, nrPOWECS, HBAsplit, LBAcal, Aartfaac ) = findStationInfo(stationName, dataDir) #print stationName," has ",nrTBB," TBBoards" # Substitute MAC and IP address of destination nodes @@ -685,14 +685,20 @@ def createRemoteStationFile(resultDir, stationName, dataDir): """ Fills in the markers in the RemoteStation.conf file to match the values for the given station. """ - (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, HBAsplit, LBAcal, Aartfaac ) = findStationInfo(stationName, dataDir) + (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, nrPOWECS, HBAsplit, LBAcal, Aartfaac ) = findStationInfo(stationName, dataDir) + + # Find Ip number station + ipnumber = findIPNumber(name) + RSconfig = open(dataDir+"/RemoteStation.conf.tmpl").read() RSconfig = RSconfig.replace("@STATION_NAME@", stationName.upper()) RSconfig = RSconfig.replace("@STATION_ID@", stationID) + RSconfig = RSconfig.replace("@STATION_IP@", ipnumber) RSconfig = RSconfig.replace("@NR_RSP@", nrRSP) RSconfig = RSconfig.replace("@NR_TBB@", nrTBB) RSconfig = RSconfig.replace("@NR_LBA@", nrLBA) RSconfig = RSconfig.replace("@NR_HBA@", nrHBA) + RSconfig = RSconfig.replace("@NR_POWECS@", nrPOWECS) RSconfig = RSconfig.replace("@HBA_SPLIT@", HBAsplit) RSconfig = RSconfig.replace("@LBA_WIDE@", LBAcal) RSconfig = RSconfig.replace("@AARTFAAC@", Aartfaac) diff --git a/MAC/Navigator2/config/progs.ccu b/MAC/Navigator2/config/progs.ccu index 44065159a51..417f70293f2 100644 --- a/MAC/Navigator2/config/progs.ccu +++ b/MAC/Navigator2/config/progs.ccu @@ -5,11 +5,8 @@ auth "" "" PVSS00pmon | manual | 30 | 3 | 1 | PVSS00data | always | 30 | 3 | 1 | PVSS00valarch | always | 30 | 3 | 1 |-num 0 -PVSS00valarch | manual | 30 | 3 | 1 |-num 1 PVSS00valarch | always | 30 | 3 | 1 |-num 2 -PVSS00valarch | manual | 30 | 3 | 1 |-num 3 -PVSS00valarch | manual | 30 | 3 | 1 |-num 4 -PVSS00valarch | manual | 30 | 3 | 1 |-num 5 +PVSS00valarch | always | 30 | 3 | 1 |-num 3 PVSS00event | always | 30 | 3 | 1 | PVSS00ctrl | always | 30 | 3 | 1 |-f pvss_scripts.lst PVSS00sim | always | 30 | 3 | 1 | @@ -19,4 +16,6 @@ PVSS00ctrl | always | 30 | 2 | 2 |monitorStateChanges.c PVSS00ctrl | once | 30 | 2 | 2 |readStationConnections.ctl PVSS00ctrl | always | 30 | 2 | 2 |monitorStateReset.ctl PVSS00ctrl | always | 30 | 2 | 2 |transferMPs.ctl -PVSS00ui | manual | 30 | 2 | 2 |-m para +PVSS00ctrl | always | 30 | 2 | 2 |setSumAlerts.ctl +PVSS00snmp | always | 30 | 2 | 2 |-num 2 +PVSS00ui | manual | 30 | 2 | 2 |-m para -display localhost:10.0 -- GitLab From 35eb952dfc73cd980970ac1d40f6e49a06dd6c8a Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 19 Jul 2016 07:27:41 +0000 Subject: [PATCH 544/933] Task #9682: only set completting and finished state when pipeline ran successfully --- MAC/Services/src/PipelineControl.py | 35 ++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 0b9fe8ffa57..74d5d04f40b 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -486,20 +486,34 @@ runcmd docker run --rm --net=host \ {image} \ runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -# notify that we're tearing down -runcmd {setStatus_completing} +if [ $? -eq 0 ]; then + # notify that we're tearing down + runcmd {setStatus_completing} -# wait for MoM to pick up feedback before we set finished status -runcmd sleep 60 + # wait for MoM to pick up feedback before we set finished status + runcmd sleep 60 -# if we reached this point, the pipeline ran succesfully -runcmd {setStatus_finished} + # if we reached this point, the pipeline ran succesfully + runcmd {setStatus_finished} -# notify ganglia -wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} FINISHED&host_regex=" + # notify ganglia + wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} FINISHED&host_regex=" + + # return success + exit 0 +else + # aborted state is already set by pipeline framework. + # Why? why does the framework set the aborted state, but not the finished state? questions questions.... + # so, do not set aborted state here. + # {setStatus_aborted} + + # notify ganglia + wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} ABORTED&host_regex=" + + # return failure + exit 1 +fi -# return success -exit 0 """.format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, @@ -510,6 +524,7 @@ exit 0 setStatus_active = setStatus_cmdline("active"), setStatus_completing = setStatus_cmdline("completing"), setStatus_finished = setStatus_cmdline("finished"), + setStatus_aborted = setStatus_cmdline("aborted"), ), sbatch_params=sbatch_params -- GitLab From f8a761cd8d77348be2a49c70ced2015d83b3de3d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 19 Jul 2016 07:31:17 +0000 Subject: [PATCH 545/933] Task #9683: fixed race condition. set status to queue before we hand over the control of the pipeline to slurm --- MAC/Services/src/PipelineControl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 74d5d04f40b..95e265bd310 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -437,6 +437,9 @@ class PipelineControl(OTDBBusListener): status_bus = self.otdb_service_busname, )) + logger.info("Handing over pipeline %s to SLURM, setting status to QUEUED", otdbId) + self._setStatus(otdbId, "queued") + # Schedule runPipeline.sh logger.info("Scheduling SLURM job for runPipeline.sh") slurm_job_id = self.slurm.submit(self._jobName(otdbId), @@ -558,9 +561,6 @@ wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&sta ) logger.info("Scheduled SLURM job %s", slurm_cancel_job_id) - logger.info("Setting status to QUEUED") - self._setStatus(otdbId, "queued") - def _stopPipeline(self, otdbId): # Cancel corresponding SLURM job, but first the abort-trigger # to avoid setting ABORTED as a side effect. -- GitLab From 343f0c9bcad2af911aa12ea1c801efb322e00c00 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 19 Jul 2016 07:50:41 +0000 Subject: [PATCH 546/933] Task #9607: minor fix: shift running pipelines end time by configured PIPELINE_CHECK_INTERVAL --- SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index 7f48c21b2ef..c56572dfb8c 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -77,7 +77,7 @@ class ScheduleChecker(): for task in active_pipelines: if task['endtime'] <= now: - new_endtime=now+timedelta(minutes=1) + new_endtime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL) logger.info("Extending endtime to %s for pipeline radb_id=%s otdb_id=%s", new_endtime, task['id'], task['otdb_id']) self._radbrpc.updateTaskAndResourceClaims(task['id'], endtime=new_endtime) except Exception as e: -- GitLab From a209860844463798e43aa405e6a347fe61ead003 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Tue, 19 Jul 2016 07:57:53 +0000 Subject: [PATCH 547/933] Task #9667: Fixes for correct number of output files when using beamformed with tab rings --- .../lib/propagator.py | 10 ++++ .../lib/rotspservice.py | 8 +++ .../sql/add_resource_allocation_statics.sql | 2 +- .../resource_estimators/observation.py | 40 +++++++++++---- .../ResourceAssignmentEstimator/service.py | 51 +++++++++++-------- 5 files changed, 78 insertions(+), 33 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py index 081a83859ed..edfb3b1c37a 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py @@ -109,6 +109,16 @@ class RAtoOTDBPropagator(): except Exception as e: logger.error(e) + def doTaskError(self, otdb_id): + logger.info('doTaskError: otdb_id=%s' % (otdb_id,)) + if not otdb_id: + logger.warning('doTaskError no valid otdb_id: otdb_id=%s' % (otdb_id,)) + return + try: + self.otdbrpc.taskSetStatus(otdb_id, 'error') + except Exception as e: + logger.error(e) + def doTaskScheduled(self, ra_id, otdb_id, mom_id): try: logger.info('doTaskScheduled: ra_id=%s otdb_id=%s mom_id=%s' % (ra_id, otdb_id, mom_id)) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py index bc3359afa3d..36acbcfefab 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py @@ -80,6 +80,14 @@ class RATaskStatusChangedListener(RABusListener): self.propagator.doTaskConflict(otdb_id) + def onTaskError(self, task_ids): + radb_id = task_ids.get('radb_id') + otdb_id = task_ids.get('otdb_id') #Does this work if one of the Id's is not set? + mom_id = task_ids.get('mom_id') + logger.info('onTaskError: radb_id=%s otdb_id=%s mom_id=%s', radb_id, otdb_id, mom_id) + + self.propagator.doTaskError(otdb_id) + __all__ = ["RATaskStatusChangedListener"] diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql index 512e5634beb..6e8af1e2ecc 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql @@ -7,7 +7,7 @@ INSERT INTO resource_allocation.task_status VALUES (200, 'prepared'), (300, 'app (1150, 'error'), (1200, 'obsolete'); -- This is the list from OTDB, we'll need to merge it with the list from MoM in the future, might use different indexes? INSERT INTO resource_allocation.task_type VALUES (0, 'observation'),(1, 'pipeline'); -- We'll need more types INSERT INTO resource_allocation.resource_claim_status VALUES (0, 'claimed'), (1, 'allocated'), (2, 'conflict'); -INSERT INTO resource_allocation.resource_claim_property_type VALUES (0, 'nr_of_is_files'),(1, 'nr_of_cs_files'),(2, 'nr_of_uv_files'),(3, 'nr_of_im_files'),(4, 'nr_of_img_files'),(5, 'nr_of_pulp_files'),(6, 'nr_of_cs_stokes'),(7, 'nr_of_is_stokes'),(8, 'is_file_size'),(9, 'cs_file_size'),(10, 'uv_file_size'),(11, 'im_file_size'),(12, 'img_file_size'),(13, 'nr_of_pulp_files'),(14, 'nr_of_tabs'),(15, 'start_sb_nr'),(16,'uv_otdb_id'),(17,'cs_otdb_id'),(18,'is_otdb_id'),(19,'im_otdb_id'),(20,'img_otdb_id'),(21,'pulp_otdb_id'); +INSERT INTO resource_allocation.resource_claim_property_type VALUES (0, 'nr_of_is_files'),(1, 'nr_of_cs_files'),(2, 'nr_of_uv_files'),(3, 'nr_of_im_files'),(4, 'nr_of_img_files'),(5, 'nr_of_pulp_files'),(6, 'nr_of_cs_stokes'),(7, 'nr_of_is_stokes'),(8, 'is_file_size'),(9, 'cs_file_size'),(10, 'uv_file_size'),(11, 'im_file_size'),(12, 'img_file_size'),(13, 'nr_of_pulp_files'),(14, 'nr_of_tabs'),(15, 'start_sb_nr'),(16,'uv_otdb_id'),(17,'cs_otdb_id'),(18,'is_otdb_id'),(19,'im_otdb_id'),(20,'img_otdb_id'),(21,'pulp_otdb_id'),(22, 'is_tab_nr'); INSERT INTO resource_allocation.resource_claim_property_io_type VALUES (0, 'output'),(1, 'input'); INSERT INTO resource_allocation.config VALUES (0, 'max_fill_percentage_cep4', '85.00'), (1, 'claim_timeout', '172800'), (2, 'min_inter_task_delay', '60'); -- Just some values 172800 is two days in seconds INSERT INTO resource_allocation.conflict_reason diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py index f94857845ae..cdcca21fce5 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py @@ -73,7 +73,7 @@ class ObservationResourceEstimator(BaseResourceEstimator): logger.info('parset: %s ' % parset) duration = self._getDuration(parset.getString('Observation.startTime'), parset.getString('Observation.stopTime')) - result = {} + result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} output_files = {} correlated_size, correlated_bandwidth, output_files_uv, correlated_saps = self.correlated(parset, duration) coherentstokes_size, coherentstokes_bandwidth, output_files_cs, coherentstokes_saps = self.coherentstokes(parset, duration) @@ -97,14 +97,27 @@ class ObservationResourceEstimator(BaseResourceEstimator): sap['properties'].update(incoherentstokes_saps[sap_nr]) output_files['saps'].append(sap) + total_data_size = correlated_size + coherentstokes_size + incoherentstokes_size - result['storage'] = {'total_size': total_data_size, 'output_files': output_files} - result['bandwidth'] = {'total_size': correlated_bandwidth + coherentstokes_bandwidth + incoherentstokes_bandwidth} + if total_data_size and output_files: + result['storage'] = {'total_size': total_data_size, 'output_files': output_files} + result['bandwidth'] = {'total_size': correlated_bandwidth + coherentstokes_bandwidth + incoherentstokes_bandwidth} + else: + if not total_data_size: + result['errors'].append('Total data size is zero!') + logger.warning('ERROR: A datasize of zero was calculated!') + if not output_files: + result['errors'].append('No output files!') + logger.warning('ERROR: No output files were calculated!') return result def correlated(self, parset, duration): """ Estimate number of files, file size and bandwidth needed for correlated data """ + if not parset.getBool('Observation.DataProducts.Output_Correlated.enabled'): + logger.info("No correlated data") + return (0,0, {}, {}) + logger.info("calculating correlated datasize") size_of_header = 512 #TODO More magic numbers (probably from Alwin). ScS needs to check these. They look ok though. size_of_overhead = 600000 @@ -142,6 +155,7 @@ class ObservationResourceEstimator(BaseResourceEstimator): """ Estimate number of files, file size and bandwidth needed for coherent stokes """ if not parset.getBool('Observation.DataProducts.Output_CoherentStokes.enabled'): + logger.info("No coherent stokes data") return (0,0, {}, {}) logger.info("calculate coherentstokes datasize") @@ -194,9 +208,10 @@ class ObservationResourceEstimator(BaseResourceEstimator): nr_stokes = nr_tabs * nr_coherent total_nr_stokes += nr_stokes nr_files += int(nr_stokes * ceil(nr_subbands / float(subbands_per_file))) - - sap_files[sap_nr]= {'nr_of_cs_files': nr_files, 'nr_of_tabs': total_nr_tabs} - total_files += nr_files + + if nr_files: + sap_files[sap_nr]= {'nr_of_cs_files': nr_files, 'nr_of_tabs': total_nr_tabs} + total_files += nr_files nr_subbands_per_file = min(subbands_per_file, max_nr_subbands) size_per_file = int(nr_subbands_per_file * size_per_subband) @@ -212,6 +227,7 @@ class ObservationResourceEstimator(BaseResourceEstimator): """ Estimate number of files, file size and bandwidth needed for incoherentstokes """ if not parset.getBool('Observation.DataProducts.Output_IncoherentStokes.enabled'): + logger.info("No incoherent stokes data") return (0,0, {}, {}) logger.info("calculate incoherentstokes data size") @@ -238,13 +254,15 @@ class ObservationResourceEstimator(BaseResourceEstimator): total_nr_tabs = parset.getInt('Observation.Beam[%d].nrTiedArrayBeams' % sap_nr) for tab_nr in xrange(total_nr_tabs): logger.info("checking TAB {}".format(tab_nr)) - if not parset.getBool("Observation.Beam[%d].TiedArrayBeam[%d].coherent" % (sap_nr, tab_nr)): - logger.info("adding incoherentstokes size") + if not parset.getBool("Observation.Beam[%d].TiedArrayBeam[%d].coherent" % (sap_nr, tab_nr)): #not coherent is incoherent + logger.info("Found incoherent stokes TAB: %i" % tab_nr) + is_tab_nr = tab_nr total_nr_stokes += nr_incoherent nr_files += int(nr_incoherent * ceil(nr_subbands / float(subbands_per_file))) - - sap_files[sap_nr]= {'nr_of_is_files': nr_files, 'nr_of_tabs': total_nr_tabs} - total_files += nr_files + + if nr_files: + sap_files[sap_nr] = {'nr_of_is_files': nr_files, 'nr_of_tabs': total_nr_tabs, 'is_tab_nr': is_tab_nr} + total_files += nr_files if incoherent_channels_per_subband > 0: channel_integration_factor = channels_per_subband / incoherent_channels_per_subband diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index 55cb19d86e5..3914ac7fc92 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -5,7 +5,7 @@ Simple Service listening ''' -import logging +import logging, pprint from lofar.messaging import Service from lofar.messaging.Service import MessageHandlerInterface @@ -44,43 +44,52 @@ class ResourceEstimatorHandler(MessageHandlerInterface): if specification_tree['task_type'] == 'observation': return {str(otdb_id): self.add_id(self.observation.verify_and_estimate(parset), otdb_id)} elif specification_tree['task_type'] == 'pipeline': + if not specification_tree['predecessors']: + logger.warning("Could not estimate %s because the pipeline has no predecessors" % (otdb_id)) + return {str(otdb_id): {'errors': ["Could not estimate %s because the pipeline has no predecessors" % (otdb_id)]}} + branch_estimates = {} for branch in specification_tree['predecessors']: - branch_estimates.update(self.get_subtree_estimate(branch)) - logger.info(str(branch_estimates)) + subtree_estimate = self.get_subtree_estimate(branch) + if subtree_estimate[str(branch)]['errors']: + logger.warning("Could not estimate %s because predecessor %s has errors" % (otdb_id, branch)) + return {str(otdb_id): {'errors': ["Could not estimate %s because predecessor %s has errors" % (otdb_id, branch)]}} + branch_estimates.update(subtree_estimate) + logger.info(("Branch estimates for %s\n" % otdb_id) + pprint.pformat(branch_estimates)) + if specification_tree['task_subtype'] in ['averaging pipeline', 'calibration pipeline']: for id, estimate in branch_estimates.iteritems(): input_files = {} - if not 'im' in estimate.values()[0]['storage']['output_files'] and 'uv' in estimate.values()[0]['storage']['output_files']: # Not a calibrator pipeline + predecessor_output = estimate.values()[0]['storage']['output_files'] + if not 'im' in predecessor_output and 'uv' in predecessor_output: # Not a calibrator pipeline logger.info('found %s as the target of pipeline %s' % (id, otdb_id)) - input_files['uv'] = estimate.values()[0]['storage']['output_files']['uv'] - if 'saps' in estimate.values()[0]['storage']['output_files']: - input_files['saps'] = estimate.values()[0]['storage']['output_files']['saps'] - elif 'im' in estimate.values()[0]['storage']['output_files']: - input_files['im'] = estimate.values()[0]['storage']['output_files']['im'] + input_files['uv'] = predecessor_output['uv'] + if 'saps' in predecessor_output: + input_files['saps'] = predecessor_output['saps'] + elif 'im' in predecessor_output: + input_files['im'] = predecessor_output['im'] return {str(otdb_id): self.add_id(self.calibration_pipeline.verify_and_estimate(parset, input_files), otdb_id)} - + + if len(branch_estimates) > 1: + logger.warning('Pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys())) + return {str(otdb_id): {'errors': ['Pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys())]}} + predecessor_output = branch_estimates.values()[0].values()[0]['storage']['output_files'] + if specification_tree['task_subtype'] in ['imaging pipeline', 'imaging pipeline msss']: - if len(branch_estimates) > 1: - logger.error('Imaging pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) - input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] + input_files = predecessor_output return {str(otdb_id): self.add_id(self.imaging_pipeline.verify_and_estimate(parset, input_files), otdb_id)} if specification_tree['task_subtype'] in ['long baseline pipeline']: - if len(branch_estimates) > 1: - logger.error('Long baseline pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) - input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] + input_files = predecessor_output return {str(otdb_id): self.add_id(self.longbaseline_pipeline.verify_and_estimate(parset, input_files), otdb_id)} if specification_tree['task_subtype'] in ['pulsar pipeline']: - if len(branch_estimates) > 1: - logger.error('Pulsar pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys() ) ) - input_files = branch_estimates.values()[0].values()[0]['storage']['output_files'] + input_files = predecessor_output return {str(otdb_id): self.add_id(self.pulsar_pipeline.verify_and_estimate(parset, input_files), otdb_id)} else: # reservation, maintenance, system tasks? - logger.info("It's not a pipeline or observation: %s" % otdb_id) - return {str(otdb_id): {}} + logger.warning("ID %s is not a pipeline or observation." % otdb_id) + return {str(otdb_id): {'errors': ["ID %s is not a pipeline or observation." % otdb_id]}} def _get_estimated_resources(self, specification_tree): """ Input is like: -- GitLab From 61bb054510295f7c79d89efa576fcd9d5489d4bf Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 19 Jul 2016 08:19:17 +0000 Subject: [PATCH 548/933] Task #9607: set task status to error if estimator found errors. Send TaskError notification --- .../ResourceAssigner/lib/assignment.py | 47 +++++++++++-------- .../ResourceAssigner/lib/rabuslistener.py | 7 +++ 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index bc79c86c91f..3ba55bd0842 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -169,31 +169,40 @@ class ResourceAssigner(): # also, if any claim is in conflict state, then the task is put to conflict status as well main_needed = needed[str(otdb_id)] task = self.radbrpc.getTask(taskId) - claimed, claim_ids = self.claimResources(main_needed, task) - if claimed: - conflictingClaims = self.radbrpc.getResourceClaims(task_ids=taskId, status='conflict') - - if conflictingClaims: - # radb set's task status to conflict automatically - logger.warning('doAssignment: %s conflicting claims detected. Task cannot be scheduled. %s' % - (len(conflictingClaims), conflictingClaims)) - self._sendNotification(task, 'conflict') - else: - logger.info('doAssignment: all claims for task %s were succesfully claimed. Setting task status to scheduled' % (taskId,)) - self.radbrpc.updateTaskAndResourceClaims(taskId, task_status='scheduled', claim_status='allocated') - self._sendNotification(task, 'scheduled') - self.processPredecessors(specification_tree) + if 'errors' in main_needed and main_needed['errors']: + for error in main_needed['errors']: + logger.error("Error in estimator: %s", error) + + logger.error("Error(s) in estimator for otdb_id=%s radb_id=%s, setting task status to 'error'", otdb_id, taskId) + self.radbrpc.updateTask(taskId, task_status='error') + self._sendNotification(task, 'error') else: - logger.warning('doAssignment: Not all claims could be inserted. Setting task %s status to conflict' % (taskId)) - self.radbrpc.updateTask(taskId, status='conflict') - self._sendNotification(task, 'conflict') + claimed, claim_ids = self.claimResources(main_needed, task) + if claimed: + conflictingClaims = self.radbrpc.getResourceClaims(task_ids=taskId, status='conflict') + + if conflictingClaims: + # radb set's task status to conflict automatically + logger.warning('doAssignment: %s conflicting claims detected. Task cannot be scheduled. %s' % + (len(conflictingClaims), conflictingClaims)) + self._sendNotification(task, 'conflict') + else: + logger.info('doAssignment: all claims for task %s were succesfully claimed. Setting task status to scheduled' % (taskId,)) + self.radbrpc.updateTaskAndResourceClaims(taskId, task_status='scheduled', claim_status='allocated') + self._sendNotification(task, 'scheduled') + + self.processPredecessors(specification_tree) + else: + logger.warning('doAssignment: Not all claims could be inserted. Setting task %s status to conflict' % (taskId)) + self.radbrpc.updateTask(taskId, task_status='conflict') + self._sendNotification(task, 'conflict') def _sendNotification(self, task, status): try: - if status == 'scheduled' or status == 'conflict': + if status == 'scheduled' or status == 'conflict' or status == 'error': content={'radb_id': task['id'], 'otdb_id':task['otdb_id'], 'mom_id': task['mom_id']} - subject= 'TaskScheduled' if status == 'scheduled' else 'TaskConflict' + subject= 'Task' + status[0].upper() + status[1:] msg = EventMessage(context=self.ra_notification_prefix + subject, content=content) logger.info('Sending notification %s: %s' % (subject, str(content).replace('\n', ' '))) self.ra_notification_bus.send(msg) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/rabuslistener.py b/SAS/ResourceAssignment/ResourceAssigner/lib/rabuslistener.py index 573b791a8e0..bbeec250129 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/rabuslistener.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/rabuslistener.py @@ -64,6 +64,8 @@ class RABusListener(AbstractBusListener): self.onTaskScheduled(msg.content) elif msg.subject == '%sTaskConflict' % self.subject_prefix: self.onTaskConflict(msg.content) + elif msg.subject == '%sTaskError' % self.subject_prefix: + self.onTaskError(msg.content) else: logger.error("RABusListener.handleMessage: unknown subject: %s" %str(msg.subject)) @@ -77,6 +79,11 @@ class RABusListener(AbstractBusListener): :param task_ids: a dict containing radb_id, mom_id and otdb_id''' pass + def onTaskError(self, task_ids): + '''onTaskError is called upon receiving a TaskError message. + :param task_ids: a dict containing radb_id, mom_id and otdb_id''' + pass + if __name__ == '__main__': with RABusListener(broker=None) as listener: waitForInterrupt() -- GitLab From 81e85fa9cb394dbd8c20c85eeee534992b0366a0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 19 Jul 2016 08:28:29 +0000 Subject: [PATCH 549/933] Task #9607: typo's --- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 3ba55bd0842..9344f5b7bba 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -175,7 +175,7 @@ class ResourceAssigner(): logger.error("Error in estimator: %s", error) logger.error("Error(s) in estimator for otdb_id=%s radb_id=%s, setting task status to 'error'", otdb_id, taskId) - self.radbrpc.updateTask(taskId, task_status='error') + self.radbrpc.updateTask(taskId, status='error') self._sendNotification(task, 'error') else: claimed, claim_ids = self.claimResources(main_needed, task) @@ -195,7 +195,7 @@ class ResourceAssigner(): self.processPredecessors(specification_tree) else: logger.warning('doAssignment: Not all claims could be inserted. Setting task %s status to conflict' % (taskId)) - self.radbrpc.updateTask(taskId, task_status='conflict') + self.radbrpc.updateTask(taskId, status='conflict') self._sendNotification(task, 'conflict') def _sendNotification(self, task, status): -- GitLab From d5cb7843f7724c68bd43e8dd96709f989f1caeb4 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Tue, 19 Jul 2016 08:49:57 +0000 Subject: [PATCH 550/933] Task #9667: Fixes for correct number of output files when using beamformed with tab rings --- .../lib/translator.py | 21 ++++++++++--------- .../calibration_pipeline.py | 12 +++++++++-- .../resource_estimators/image_pipeline.py | 12 +++++++++-- .../longbaseline_pipeline.py | 12 +++++++++-- .../resource_estimators/observation.py | 21 +++++++++++++------ .../resource_estimators/pulsar_pipeline.py | 12 +++++++++-- 6 files changed, 66 insertions(+), 24 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index e1d1e2cd4e5..8cbd4ac388f 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -95,9 +95,11 @@ class RAtoOTDBTranslator(): result = {} nr_stokes = storage_properties['nr_of_cs_stokes'] for sap in storage_properties["saps"]: ##We might need to sort saps? - if "nr_of_cs_files" in sap['properties']: + if 'nr_of_cs_files' in sap['properties']: nr_files = sap['properties']['nr_of_cs_files'] nr_tabs = sap['properties']['nr_of_tabs'] + if 'is_tab_nr' in sap['properties']: + nr_tabs -= 1 nr_parts = int(ceil(nr_files/float(nr_tabs * nr_stokes))) for tab in xrange(nr_tabs): for stokes in xrange(nr_stokes): @@ -118,15 +120,14 @@ class RAtoOTDBTranslator(): nr_stokes = storage_properties['nr_of_is_stokes'] for sap in storage_properties["saps"]: ##We might need to sort saps? if "nr_of_is_files" in sap['properties']: - nr_files = sap['properties']['nr_of_is_files'] - nr_tabs = sap['properties']['nr_of_tabs'] - nr_parts = int(ceil(nr_files/float(nr_tabs * nr_stokes))) - for tab in xrange(nr_tabs): - for stokes in xrange(nr_stokes): - for part in xrange(nr_parts): - locations.append(self.locationPath(project_name, otdb_id) + '/is') - filenames.append("L%d_SAP%03d_B%03d_S%d_P%03d_bf.h5" % (otdb_id, sap['sap_nr'], tab, stokes, part)) - skip.append("0") + nr_files = sap['properties']['nr_of_is_files'] + is_tab_nr = sap['properties']['is_tab_nr'] + nr_parts = int(ceil(nr_files/float(nr_tabs * nr_stokes))) + for stokes in xrange(nr_stokes): + for part in xrange(nr_parts): + locations.append(self.locationPath(project_name, otdb_id) + '/is') + filenames.append("L%d_SAP%03d_B%03d_S%d_P%03d_bf.h5" % (otdb_id, sap['sap_nr'], is_tab_nr, stokes, part)) + skip.append("0") result[PREFIX + 'DataProducts.%s_IncoherentStokes.locations' % (io_type)] = '[' + to_csv_string(locations) + ']' result[PREFIX + 'DataProducts.%s_IncoherentStokes.filenames' % (io_type)] = '[' + to_csv_string(filenames) + ']' result[PREFIX + 'DataProducts.%s_IncoherentStokes.skip' % (io_type)] = '[' + to_csv_string(skip) + ']' diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py index 5d7cb3a07ee..524a0356c25 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py @@ -115,7 +115,15 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): #total_data_size = result['storage']['output_files']['uv']['nr_of_uv_files'] * result['storage']['output_files']['uv']['uv_file_size'] + \ # result['storage']['output_files']['im']['nr_of_im_files'] * result['storage']['output_files']['im']['im_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - result['storage']['total_size'] = total_data_size - result['bandwidth']['total_size'] = total_bandwidth + if total_data_size and output_files: + result['storage']['total_size'] = total_data_size + result['bandwidth']['total_size'] = total_bandwidth + else: + if not total_data_size: + result['errors'].append('Total data size is zero!') + logger.warning('ERROR: A datasize of zero was calculated!') + if not output_files: + result['errors'].append('No output files!') + logger.warning('ERROR: No output files were calculated!') return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py index 5314e3b81ec..3050fd40134 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py @@ -98,7 +98,15 @@ class ImagePipelineResourceEstimator(BaseResourceEstimator): # count total data size total_data_size = result['storage']['output_files']['img']['nr_of_img_files'] * result['storage']['output_files']['img']['img_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - result['storage']['total_size'] = total_data_size - result['bandwidth']['total_size'] = total_bandwidth + if total_data_size and output_files: + result['storage']['total_size'] = total_data_size + result['bandwidth']['total_size'] = total_bandwidth + else: + if not total_data_size: + result['errors'].append('Total data size is zero!') + logger.warning('ERROR: A datasize of zero was calculated!') + if not output_files: + result['errors'].append('No output files!') + logger.warning('ERROR: No output files were calculated!') return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py index 6014e46e9fc..2be090a2cab 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py @@ -99,6 +99,14 @@ class LongBaselinePipelineResourceEstimator(BaseResourceEstimator): # count total data size total_data_size = result['storage']['output_files']['uv']['nr_of_uv_files'] * result['storage']['output_files']['uv']['uv_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - result['storage']['total_size'] = total_data_size - result['bandwidth']['total_size'] = total_bandwidth + if total_data_size and output_files: + result['storage']['total_size'] = total_data_size + result['bandwidth']['total_size'] = total_bandwidth + else: + if not total_data_size: + result['errors'].append('Total data size is zero!') + logger.warning('ERROR: A datasize of zero was calculated!') + if not output_files: + result['errors'].append('No output files!') + logger.warning('ERROR: No output files were calculated!') return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py index cdcca21fce5..a893e1bac20 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/observation.py @@ -95,6 +95,10 @@ class ObservationResourceEstimator(BaseResourceEstimator): sap['properties'].update(coherentstokes_saps[sap_nr]) if sap_nr in incoherentstokes_saps: sap['properties'].update(incoherentstokes_saps[sap_nr]) + if 'nr_of_tabs' in sap['properties']: # These are coherent TABs + sap['properties']['nr_of_tabs'] = sap['properties']['nr_of_tabs'] + 1 + else: + sap['properties']['nr_of_tabs'] = 1 # Only an incoherent TAB for this SAP output_files['saps'].append(sap) @@ -187,7 +191,7 @@ class ObservationResourceEstimator(BaseResourceEstimator): logger.info("checking TAB {}".format(tab_nr)) if parset.getBool("Observation.Beam[%d].TiedArrayBeam[%d].coherent" % (sap_nr, tab_nr)): logger.info("adding coherentstokes size") - nr_stokes = nr_coherent #TODO what does min mean here? + nr_stokes = nr_coherent # TODO, there used to be a function with min() here? total_nr_tabs += 1 total_nr_stokes += nr_stokes nr_files += int(nr_stokes * ceil(nr_subbands / float(subbands_per_file))) @@ -238,7 +242,7 @@ class ObservationResourceEstimator(BaseResourceEstimator): channels_per_subband = parset.getInt(COBALT + 'Correlator.nrChannelsPerSubband', 64) #TODO should these have defaults? incoherent_channels_per_subband = parset.getInt(COBALT + 'BeamFormer.IncoherentStokes.nrChannelsPerSubband', 0) - nr_incoherent = 4 if incoherent_type in ('IQUV',) else 1 + nr_incoherent = 4 if incoherent_type in ('IQUV',) else 1 # Should this also include XXYY ? total_nr_stokes = 0 total_files = 0 @@ -251,17 +255,22 @@ class ObservationResourceEstimator(BaseResourceEstimator): nr_subbands = len(subbandList) max_nr_subbands = max(nr_subbands, max_nr_subbands) nr_files = 0 + is_tab_nr = -1 total_nr_tabs = parset.getInt('Observation.Beam[%d].nrTiedArrayBeams' % sap_nr) for tab_nr in xrange(total_nr_tabs): logger.info("checking TAB {}".format(tab_nr)) if not parset.getBool("Observation.Beam[%d].TiedArrayBeam[%d].coherent" % (sap_nr, tab_nr)): #not coherent is incoherent logger.info("Found incoherent stokes TAB: %i" % tab_nr) - is_tab_nr = tab_nr - total_nr_stokes += nr_incoherent - nr_files += int(nr_incoherent * ceil(nr_subbands / float(subbands_per_file))) + if is_tab_nr >= 0: + logger.warning("TAB nr %i can't be incoherent as %i already is!" % (tab_nr, is_tab_nr)) + # TODO We need to generate an error here, or preferably check before we get here + else: + is_tab_nr = tab_nr + total_nr_stokes += nr_incoherent + nr_files += int(nr_incoherent * ceil(nr_subbands / float(subbands_per_file))) if nr_files: - sap_files[sap_nr] = {'nr_of_is_files': nr_files, 'nr_of_tabs': total_nr_tabs, 'is_tab_nr': is_tab_nr} + sap_files[sap_nr] = {'nr_of_is_files': nr_files, 'is_tab_nr': is_tab_nr} total_files += nr_files if incoherent_channels_per_subband > 0: diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py index a024a9db3c3..7dbe0a2b790 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py @@ -99,6 +99,14 @@ class PulsarPipelineResourceEstimator(BaseResourceEstimator): # count total data size total_data_size = result['storage']['output_files']['pulp']['nr_of_pulp_files'] * result['storage']['output_files']['pulp']['pulp_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - result['storage']['total_size'] = total_data_size - result['bandwidth']['total_size'] = total_bandwidth + if total_data_size and output_files: + result['storage']['total_size'] = total_data_size + result['bandwidth']['total_size'] = total_bandwidth + else: + if not total_data_size: + result['errors'].append('Total data size is zero!') + logger.warning('ERROR: A datasize of zero was calculated!') + if not output_files: + result['errors'].append('No output files!') + logger.warning('ERROR: No output files were calculated!') return result -- GitLab From 9fb10fa71e7e3ccab9f140d14f31b2fb63a4d9d3 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Tue, 19 Jul 2016 09:01:29 +0000 Subject: [PATCH 551/933] Task #9667: Fixes for correct number of output files when using beamformed with tab rings --- .../lib/propagator.py | 13 +++++++------ .../lib/translator.py | 5 +++-- .../ResourceAssignmentEstimator/service.py | 3 ++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py index edfb3b1c37a..42cf5c22b21 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/propagator.py @@ -28,6 +28,7 @@ reads the info from the RA DB and sends it to OTDB in the correct format. import logging import datetime import time +import pprint from lofar.messaging.RPC import RPC, RPCException from lofar.parameterset import parameterset @@ -147,11 +148,10 @@ class RAtoOTDBPropagator(): project_name = 'unknown' otdb_info = self.translator.CreateParset(otdb_id, ra_info, project_name) - logger.info("Parset info for OTDB: %s" %otdb_info) self.setOTDBinfo(otdb_id, otdb_info, 'scheduled') except Exception as e: logger.error(e) - self.doTaskConflict(otdb_id) + self.doTaskError(otdb_id) #FIXME should be to the RADB also or instead? def ParseStorageProperties(self, storage_claim): """input something like: @@ -200,7 +200,7 @@ class RAtoOTDBPropagator(): result['output_files'][p['type_name']] = p['value'] if p['io_type_name'] == 'input': result['input_files'][p['type_name']] = p['value'] - logging.info(result) + logger.info(pprint.pformat(result)) return result def getRAinfo(self, ra_id): @@ -209,7 +209,7 @@ class RAtoOTDBPropagator(): task = self.radbrpc.getTask(ra_id) claims = self.radbrpc.getResourceClaims(task_ids=ra_id, extended=True, include_properties=True) for claim in claims: - logger.debug("Processing claim: %s" % claim) + logger.info("Processing claim: %s" % claim) if claim['resource_type_name'] == 'storage': ## TODO we will need to check for different storage names/types in the future info['storage'] = self.ParseStorageProperties(claim) info["starttime"] = task["starttime"] @@ -220,11 +220,12 @@ class RAtoOTDBPropagator(): def setOTDBinfo(self, otdb_id, otdb_info, otdb_status): try: - logger.info('Setting specticication for otdb_id %s: %s' % (otdb_id, otdb_info)) + logger.info('Setting specticication for otdb_id %s:\n' % (otdb_id,)) + logger.info(pprint.pformat(otdb_info)) self.otdbrpc.taskSetSpecification(otdb_id, otdb_info) self.otdbrpc.taskPrepareForScheduling(otdb_id, otdb_info["LOFAR.ObsSW.Observation.startTime"], otdb_info["LOFAR.ObsSW.Observation.stopTime"]) logger.info('Setting status (%s) for otdb_id %s' % (otdb_status, otdb_id)) self.otdbrpc.taskSetStatus(otdb_id, otdb_status) except Exception as e: logger.error(e) - self.doTaskConflict(otdb_id) + self.doTaskError(otdb_id) #FIXME should be to the RADB also or instead? diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 8cbd4ac388f..aa8b6566a38 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -26,6 +26,7 @@ reads the info from the RA DB and sends it to OTDB in the correct format. """ import logging +import pprint from lofar.common.util import to_csv_string from math import ceil, floor @@ -199,7 +200,7 @@ class RAtoOTDBTranslator(): return result def ProcessStorageInfo(self, otdb_id, storage_info, project_name): - logging.debug('processing the storage for %i with ' % (otdb_id) + str(storage_info)) + logging.info('processing the storage for %i' % (otdb_id)) result = {} if 'input_files' in storage_info: result.update(self.CreateStorageKeys(otdb_id, storage_info['input_files'], project_name, "Input")) @@ -216,7 +217,7 @@ class RAtoOTDBTranslator(): parset[PREFIX+'stopTime'] = ra_info['endtime'].strftime('%Y-%m-%d %H:%M:%S') if 'storage' in ra_info: - logging.info("Adding storage claims to parset: " + str(ra_info['storage'])) + logging.info("Adding storage claims to parset\n" + pprint.pformat(ra_info['storage']))) parset.update(self.ProcessStorageInfo(otdb_id, ra_info['storage'], project_name)) if 'stations' in ra_info: logging.info("Adding stations to parset: " + str(ra_info["stations"])) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index 3914ac7fc92..20453311d0e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -5,7 +5,8 @@ Simple Service listening ''' -import logging, pprint +import logging +import pprint from lofar.messaging import Service from lofar.messaging.Service import MessageHandlerInterface -- GitLab From b755cf7623b9872b602f25682d5b6baf4aa69377 Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Tue, 19 Jul 2016 11:24:25 +0000 Subject: [PATCH 552/933] Task #9655: extra content for PowerUnits --- .gitattributes | 2 +- MAC/Deployment/data/PVSS/data/POWEC.list | 2 +- MAC/Deployment/data/PVSS/data/PowerUnit.dpdef | 128 ++++++++++++++++++ MAC/Deployment/data/PVSS/data/PowerUnit.dpl | 90 ------------ .../data/StaticMetaData/createFiles | 4 +- 5 files changed, 132 insertions(+), 94 deletions(-) create mode 100644 MAC/Deployment/data/PVSS/data/PowerUnit.dpdef delete mode 100644 MAC/Deployment/data/PVSS/data/PowerUnit.dpl diff --git a/.gitattributes b/.gitattributes index eae1b7199ed..4a0935e794d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3627,7 +3627,7 @@ MAC/Deployment/data/PVSS/data/Observation.dpdef -text MAC/Deployment/data/PVSS/data/ObservationControl.dpdef -text MAC/Deployment/data/PVSS/data/POWEC.list -text MAC/Deployment/data/PVSS/data/PVSSbase.dpdef -text -MAC/Deployment/data/PVSS/data/PowerUnit.dpl -text +MAC/Deployment/data/PVSS/data/PowerUnit.dpdef -text MAC/Deployment/data/PVSS/data/RTDBPort.dpdef -text MAC/Deployment/data/PVSS/data/SoftwareMonitor.dpdef -text MAC/Deployment/data/PVSS/data/StationControl.dpdef -text diff --git a/MAC/Deployment/data/PVSS/data/POWEC.list b/MAC/Deployment/data/PVSS/data/POWEC.list index 1ffd0eff63d..d237fba19c2 100644 --- a/MAC/Deployment/data/PVSS/data/POWEC.list +++ b/MAC/Deployment/data/PVSS/data/POWEC.list @@ -1,2 +1,2 @@ +POWEC0 POWEC1 -POWEC2 diff --git a/MAC/Deployment/data/PVSS/data/PowerUnit.dpdef b/MAC/Deployment/data/PVSS/data/PowerUnit.dpdef new file mode 100644 index 00000000000..e50ca72a355 --- /dev/null +++ b/MAC/Deployment/data/PVSS/data/PowerUnit.dpdef @@ -0,0 +1,128 @@ +# description of the POWEC power unit in the LOFAR stations +nrOfModules int +voltage intArr +current intArr +temperature intArr +OK intArr +nrOfAlarms int +alarmType intArr +alarmReason intArr +alarmTime stringArr +alarmText stringArr +clearAlarmHistory int + +# +# Next points are needed for dataparameterization Do not alter them unless you know what you are doing. tabs are essential in these +# + +# Datapoint/DpId +!DpName TypeName +!LOFAR_PIC_POWEC0 PowerUnit +!LOFAR_PIC_POWEC1 PowerUnit +!_2_SNMPAgent_1 _SNMPAgent +!_2_SNMPAgent_2 _SNMPAgent + +# Aliases/Comments +!AliasId AliasName CommentName +!_2_SNMPAgent_1. "" lt:1 LANG:1 "POWEC0@@" +!_2_SNMPAgent_2. "" lt:1 LANG:1 "POWEC1@@" + +# DpValue +!ElementName TypeName _original.._value +!LOFAR_PIC_POWEC0.status.leaf PowerUnit 1 +!LOFAR_PIC_POWEC1.status.leaf PowerUnit 1 +!_2_SNMPAgent_1.Access.ReadCommunity _SNMPAgent "public" +!_2_SNMPAgent_1.Access.WriteCommunity _SNMPAgent "public" +!_2_SNMPAgent_1.Access.Timeout _SNMPAgent 100 +!_2_SNMPAgent_1.Access.Retries _SNMPAgent 5 +!_2_SNMPAgent_1.Access.Protocol _SNMPAgent 1 +!_2_SNMPAgent_1.Access.Port _SNMPAgent 161 +!_2_SNMPAgent_1.Status.Timeout _SNMPAgent 1 +!_2_SNMPAgent_1.Redundancy.ReduAgent _SNMPAgent 0 +!_2_SNMPAgent_1.Redundancy.FallBack _SNMPAgent 0 +!_2_SNMPAgent_2.Access.ReadCommunity _SNMPAgent "public" +!_2_SNMPAgent_2.Access.WriteCommunity _SNMPAgent "public" +!_2_SNMPAgent_2.Access.Timeout _SNMPAgent 100 +!_2_SNMPAgent_2.Access.Retries _SNMPAgent 5 +!_2_SNMPAgent_2.Access.Protocol _SNMPAgent 1 +!_2_SNMPAgent_2.Access.Port _SNMPAgent 161 +!_2_SNMPAgent_2.Status.Timeout _SNMPAgent 1 +!_2_SNMPAgent_2.Redundancy.ReduAgent _SNMPAgent 0 +!_2_SNMPAgent_2.Redundancy.FallBack _SNMPAgent 0 + + +!# DistributionInfo +!ElementName TypeName _distrib.._type _distrib.._driver +!LOFAR_PIC_POWEC0.nrOfModules PowerUnit 56 \2 +!LOFAR_PIC_POWEC0.voltage PowerUnit 56 \2 +!LOFAR_PIC_POWEC0.current PowerUnit 56 \2 +!LOFAR_PIC_POWEC0.temperature PowerUnit 56 \2 +!LOFAR_PIC_POWEC0.OK PowerUnit 56 \2 +!LOFAR_PIC_POWEC0.nrOfAlarms PowerUnit 56 \2 +!LOFAR_PIC_POWEC0.clearAlarmHistory PowerUnit 56 \2 +!LOFAR_PIC_POWEC0.alarmType PowerUnit 56 \2 +!LOFAR_PIC_POWEC0.alarmReason PowerUnit 56 \2 +!LOFAR_PIC_POWEC0.alarmTime PowerUnit 56 \2 +!LOFAR_PIC_POWEC0.alarmText PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.voltage PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.current PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.temperature PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.OK PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.nrOfAlarms PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.clearAlarmHistory PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.alarmType PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.alarmReason PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.alarmTime PowerUnit 56 \2 +!LOFAR_PIC_POWEC1.alarmText PowerUnit 56 \2 + +!# DpSmoothMain +!ElementName TypeName _smooth.._type _smooth.._std_type +!LOFAR_PIC_POWEC0.nrOfModules PowerUnit 48 4 +!LOFAR_PIC_POWEC0.voltage PowerUnit 48 4 +!LOFAR_PIC_POWEC0.current PowerUnit 48 4 +!LOFAR_PIC_POWEC0.temperature PowerUnit 48 4 +!LOFAR_PIC_POWEC0.OK PowerUnit 48 4 +!LOFAR_PIC_POWEC0.nrOfAlarms PowerUnit 48 4 +!LOFAR_PIC_POWEC0.clearAlarmHistory PowerUnit 48 4 +!LOFAR_PIC_POWEC0.alarmType PowerUnit 48 4 +!LOFAR_PIC_POWEC0.alarmReason PowerUnit 48 4 +!LOFAR_PIC_POWEC0.alarmTime PowerUnit 48 4 +!LOFAR_PIC_POWEC0.alarmText PowerUnit 48 4 +!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 48 4 +!LOFAR_PIC_POWEC1.voltage PowerUnit 48 4 +!LOFAR_PIC_POWEC1.current PowerUnit 48 4 +!LOFAR_PIC_POWEC1.temperature PowerUnit 48 4 +!LOFAR_PIC_POWEC1.OK PowerUnit 48 4 +!LOFAR_PIC_POWEC1.nrOfAlarms PowerUnit 48 4 +!LOFAR_PIC_POWEC1.clearAlarmHistory PowerUnit 48 4 +!LOFAR_PIC_POWEC1.alarmType PowerUnit 48 4 +!LOFAR_PIC_POWEC1.alarmReason PowerUnit 48 4 +!LOFAR_PIC_POWEC1.alarmTime PowerUnit 48 4 +!LOFAR_PIC_POWEC1.alarmText PowerUnit 48 4 + +!# PeriphAddrMain +!ElementName TypeName _address.._type _address.._reference _address.._poll_group _address.._offset _address.._subindex _address.._direction _address.._internal _address.._lowlevel _address.._active _address.._start _address.._interval _address.._reply _address.._datatype _address.._drv_ident +!LOFAR_PIC_POWEC0.nrOfModules PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.1.0" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC0.voltage PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.3B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC0.current PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.4B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC0.temperature PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.6B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC0.nrOfAlarms PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.1.0" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC0.clearAlarmHistory PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.3.0" "_SNMP" 0 0 \5 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC0.alarmReason PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.3B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC0.alarmText PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.5B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" +!LOFAR_PIC_POWEC0.alarmType PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.2B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC0.alarmTime PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.4B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" +!LOFAR_PIC_POWEC0.OK PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.2B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.1.0" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.voltage PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.3B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.current PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.4B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.temperature PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.6B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.OK PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.2B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.nrOfAlarms PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.1.0" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.clearAlarmHistory PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.3.0" "_SNMP" 0 0 \5 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.alarmReason PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.3B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.alarmText PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.5B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" +!LOFAR_PIC_POWEC1.alarmType PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.2B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +!LOFAR_PIC_POWEC1.alarmTime PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.4B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" + diff --git a/MAC/Deployment/data/PVSS/data/PowerUnit.dpl b/MAC/Deployment/data/PVSS/data/PowerUnit.dpl deleted file mode 100644 index 6f1facc176c..00000000000 --- a/MAC/Deployment/data/PVSS/data/PowerUnit.dpl +++ /dev/null @@ -1,90 +0,0 @@ -# description of the POWEC power unit in the LOFAR stations - -# DpType -TypeName -PowerUnit.PowerUnit 1# - status 41#2:ObjectStatus - OK 5# - nrOfModules 21# - voltage 5# - current 5# - temperature 5# - -# -# Next points are needed for dataparameterization Do not alter them unless you know what you are doing. tabs are essential in these -# - -# Datapoint/DpId -!DpName TypeName -!LOFAR_PIC_POWEC1 PowerUnit -!LOFAR_PIC_POWEC2 PowerUnit -!_2_SNMPAgent_1 _SNMPAgent -!_2_SNMPAgent_2 _SNMPAgent - -# Aliases/Comments -!AliasId AliasName CommentName -!_2_SNMPAgent_1. "" lt:1 LANG:1 "POWEC1@@" -!_2_SNMPAgent_2. "" lt:1 LANG:1 "POWEC2@@" - -# DpValue -!ElementName TypeName _original.._value -!LOFAR_PIC_POWEC1.status.leaf PowerUnit 1 -!LOFAR_PIC_POWEC2.status.leaf PowerUnit 1 -!_2_SNMPAgent_1.Access.ReadCommunity _SNMPAgent "public" -!_2_SNMPAgent_1.Access.WriteCommunity _SNMPAgent "public" -!_2_SNMPAgent_1.Access.Timeout _SNMPAgent 100 -!_2_SNMPAgent_1.Access.Retries _SNMPAgent 5 -!_2_SNMPAgent_1.Access.Protocol _SNMPAgent 1 -!_2_SNMPAgent_1.Access.Port _SNMPAgent 161 -!_2_SNMPAgent_1.Status.Timeout _SNMPAgent 1 -!_2_SNMPAgent_1.Redundancy.ReduAgent _SNMPAgent 0 -!_2_SNMPAgent_1.Redundancy.FallBack _SNMPAgent 0 -!_2_SNMPAgent_2.Access.ReadCommunity _SNMPAgent "public" -!_2_SNMPAgent_2.Access.WriteCommunity _SNMPAgent "public" -!_2_SNMPAgent_2.Access.Timeout _SNMPAgent 100 -!_2_SNMPAgent_2.Access.Retries _SNMPAgent 5 -!_2_SNMPAgent_2.Access.Protocol _SNMPAgent 1 -!_2_SNMPAgent_2.Access.Port _SNMPAgent 161 -!_2_SNMPAgent_2.Status.Timeout _SNMPAgent 1 -!_2_SNMPAgent_2.Redundancy.ReduAgent _SNMPAgent 0 -!_2_SNMPAgent_2.Redundancy.FallBack _SNMPAgent 0 - -# DistributionInfo -!ElementName TypeName _distrib.._type _distrib.._driver -!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.voltage PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.current PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.temperature PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.OK PowerUnit 56 \2 -!LOFAR_PIC_POWEC2.nrOfModules PowerUnit 56 \2 -!LOFAR_PIC_POWEC2.voltage PowerUnit 56 \2 -!LOFAR_PIC_POWEC2.current PowerUnit 56 \2 -!LOFAR_PIC_POWEC2.temperature PowerUnit 56 \2 -!LOFAR_PIC_POWEC2.OK PowerUnit 56 \2 - -# DpSmoothMain -!ElementName TypeName _smooth.._type _smooth.._std_type -!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 48 4 -!LOFAR_PIC_POWEC1.voltage PowerUnit 48 4 -!LOFAR_PIC_POWEC1.current PowerUnit 48 4 -!LOFAR_PIC_POWEC1.temperature PowerUnit 48 4 -!LOFAR_PIC_POWEC1.OK PowerUnit 48 4 -!LOFAR_PIC_POWEC2.nrOfModules PowerUnit 48 4 -!LOFAR_PIC_POWEC2.voltage PowerUnit 48 4 -!LOFAR_PIC_POWEC2.current PowerUnit 48 4 -!LOFAR_PIC_POWEC2.temperature PowerUnit 48 4 -!LOFAR_PIC_POWEC2.OK PowerUnit 48 4 - -# PeriphAddrMain -!ElementName TypeName _address.._type _address.._reference _address.._poll_group _address.._offset _address.._subindex _address.._direction _address.._internal _address.._lowlevel _address.._active _address.._start _address.._interval _address.._reply _address.._datatype _address.._drv_ident -!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.1.0" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.voltage PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.3B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.current PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.4B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.temperature PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.6B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.OK PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.2B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC2.nrOfModules PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.1.0" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 660 "SNMP" -!LOFAR_PIC_POWEC2.voltage PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.3B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC2.current PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.4B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC2.temperature PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.6B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC2.OK PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.2B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" - diff --git a/MAC/Deployment/data/StaticMetaData/createFiles b/MAC/Deployment/data/StaticMetaData/createFiles index 19778b473fd..b90dd143b44 100755 --- a/MAC/Deployment/data/StaticMetaData/createFiles +++ b/MAC/Deployment/data/StaticMetaData/createFiles @@ -33,7 +33,7 @@ def setWarning(message): # # Find host IP number from DNS # -def findIPNumber(stationName): +def POWEC: """ Returns the IPnumber for a station LCU. In case the station is not known, it will return -1. @@ -279,7 +279,7 @@ def createRSPDriverFile(resultDir, stationName, dataDir,int_local,is_Cobalt): rspDestNode = findRSPDestNodes(stationName, dataDir) print stationName,"matches:",rspDestNode - (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, nrPOWEC, HBAsplit, LBAcal, Aartfaac ) = findStationInfo(stationName, dataDir) + (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, nrPOWECS, HBAsplit, LBAcal, Aartfaac ) = findStationInfo(stationName, dataDir) # Substitute MAC and IP address of destination nodes RSPfile = open(dataDir+"/RSPDriver.conf.tmpl") -- GitLab From 4d310c5900f6c61f0687770159dc3a7d2547b423 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 19 Jul 2016 13:11:03 +0000 Subject: [PATCH 553/933] Task #9607: set max nodes to 23, so we always have at least 50-2*23=4 nodes available for vlad pulp --- MAC/Services/src/PipelineControl.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 95e265bd310..692e6ba7b89 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -146,24 +146,29 @@ class Parset(dict): """ Parse the number of nodes to allocate from "Observation.Cluster.ProcessingCluster.numberOfTasks", which can have either the format "{number}" or "{min}-{max}". """ - defaultValue = "12-25" + #JS 2016-07-19: set max nodes to 23, so we always have at least 50-2*23=4 nodes available for vlad pulp + + defaultValue = "12-23" parsetValue = self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfTasks"].strip() if "-" in parsetValue: # min,max _min, _max = parsetValue.split("-") + if _max > 23: + _max = 23 + # collapse if not min <= max if _min > _max: result = _min else: - result = "%s,%s" % (_min, _max) + result = "%s-%s" % (_min, _max) else: # plain number result = int(parsetValue) # apply bound - if result < 1 or result > 50: + if result < 1 or result > 23: result = defaultValue if result != parsetValue: -- GitLab From cb26b92ab35701d5e20a3a91ed7a10370226466a Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Tue, 19 Jul 2016 13:55:51 +0000 Subject: [PATCH 554/933] Task #9667: Fixes for correct number of output files when using beamformed with tab rings --- .../RAtoOTDBTaskSpecificationPropagator/lib/translator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index aa8b6566a38..c331ee73997 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -217,7 +217,7 @@ class RAtoOTDBTranslator(): parset[PREFIX+'stopTime'] = ra_info['endtime'].strftime('%Y-%m-%d %H:%M:%S') if 'storage' in ra_info: - logging.info("Adding storage claims to parset\n" + pprint.pformat(ra_info['storage']))) + logging.info("Adding storage claims to parset\n" + pprint.pformat(ra_info['storage'])) parset.update(self.ProcessStorageInfo(otdb_id, ra_info['storage'], project_name)) if 'stations' in ra_info: logging.info("Adding stations to parset: " + str(ra_info["stations"])) -- GitLab From 8ca38d4ae3259b2bd581369623416ded6b985c3e Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Tue, 19 Jul 2016 15:11:15 +0000 Subject: [PATCH 555/933] Task #9667: Fixes for correct number of output files when using beamformed with tab rings --- .../RAtoOTDBTaskSpecificationPropagator/lib/translator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index c331ee73997..543a9b6aa67 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -123,7 +123,7 @@ class RAtoOTDBTranslator(): if "nr_of_is_files" in sap['properties']: nr_files = sap['properties']['nr_of_is_files'] is_tab_nr = sap['properties']['is_tab_nr'] - nr_parts = int(ceil(nr_files/float(nr_tabs * nr_stokes))) + nr_parts = int(ceil(nr_files/float(nr_stokes))) for stokes in xrange(nr_stokes): for part in xrange(nr_parts): locations.append(self.locationPath(project_name, otdb_id) + '/is') -- GitLab From 0c1caeb717409f678a2280036626db83f6c9f175 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 20 Jul 2016 08:08:27 +0000 Subject: [PATCH 556/933] Task #9607: added context menu entry for inspection plots --- .../static/app/controllers/gridcontroller.js | 12 ++++++++++- .../angular-gantt-contextmenu-plugin.js | 21 +++++++++++++++---- .../lib/webservice.py | 3 ++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index bd0811d42b0..6f13c5ac69b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -284,7 +284,7 @@ $scope.columns = [ $scope.$watch('dataService.selected_task_id', jumpToSelectedTaskRow);} ]); -gridControllerMod.directive('contextMenu', ['$document', function($document) { +gridControllerMod.directive('contextMenu', ['$document', '$window', function($document, $window) { return { restrict: 'A', scope: { @@ -329,6 +329,16 @@ gridControllerMod.directive('contextMenu', ['$document', function($document) { // dataService.copyTask(task); // }); + if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { + var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + var url = dataService.config.inspection_plots_base_url + '/' + task.otdb_id; + $window.open(url, '_blank'); + }); + } + var liElement = angular.element('<li><a href="#">Show disk usage</a></li>'); ulElement.append(liElement); liElement.on('click', function() { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index e7394102af7..cf1c495d70b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -1,6 +1,6 @@ (function(){ 'use strict'; - angular.module('gantt.contextmenu', ['gantt', 'gantt.contextmenu.templates']).directive('ganttContextmenu', ['$compile', '$document', function($compile, $document) { + angular.module('gantt.contextmenu', ['gantt', 'gantt.contextmenu.templates']).directive('ganttContextmenu', ['$compile', '$document', '$window', function($compile, $document, $window) { return { restrict: 'E', require: '^gantt', @@ -30,9 +30,12 @@ var cleanupCtrl = dScope.scope.$parent.cleanupCtrl; var docElement = angular.element($document); - if(dScope.task.model.raTask) { - dataService.selected_task_id = dScope.task.model.raTask.id; - } + var task = dScope.task.model.raTask; + + if(!task) + return; + + dataService.selected_task_id = task.id; //search for already existing contextmenu element while($document.find('#gantt-context-menu').length) { @@ -59,6 +62,16 @@ // dataService.copyTask(dScope.task.model.raTask); // }); + if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { + var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + var url = dataService.config.inspection_plots_base_url + '/' + task.otdb_id; + $window.open(url, '_blank'); + }); + } + var liElement = angular.element('<li><a href="#">Show disk usage</a></li>'); ulElement.append(liElement); liElement.on('click', function() { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index fe31dbc97b4..24bf2d58bb5 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -118,7 +118,8 @@ def index(): @app.route('/rest/config') @gzipped def config(): - config = {'mom_base_url':''} + config = {'mom_base_url':'', + 'inspection_plots_base_url':'https://proxy.lofar.eu/inspect/HTML/'} if isProductionEnvironment(): config['mom_base_url'] = 'https://lofar.astron.nl/mom3' -- GitLab From 162b391c83d052111f22b1c1c2add2583882b867 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Wed, 20 Jul 2016 09:10:12 +0000 Subject: [PATCH 557/933] Task #9667: Fixes for correct number of output files when using beamformed with tab rings --- .../lib/translator.py | 6 ++++-- .../resource_estimators/base_resource_estimator.py | 8 ++++---- .../ResourceAssignmentEstimator/service.py | 12 ++++++------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 543a9b6aa67..1f6fd4701a3 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -99,10 +99,12 @@ class RAtoOTDBTranslator(): if 'nr_of_cs_files' in sap['properties']: nr_files = sap['properties']['nr_of_cs_files'] nr_tabs = sap['properties']['nr_of_tabs'] - if 'is_tab_nr' in sap['properties']: - nr_tabs -= 1 + skip_tab = 'is_tab_nr' in sap['properties'] nr_parts = int(ceil(nr_files/float(nr_tabs * nr_stokes))) for tab in xrange(nr_tabs): + if skip_tab: + if tab == sap['properties']['is_tab_nr']: + continue for stokes in xrange(nr_stokes): for part in xrange(nr_parts): locations.append(self.locationPath(project_name, otdb_id) + '/cs') diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py index adc887b7751..5776a821ee7 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py @@ -24,6 +24,7 @@ """ import logging import copy +import pprint from datetime import datetime from lofar.common.datetimeutils import totalSeconds from datetime import datetime, timedelta @@ -109,8 +110,7 @@ class BaseResourceEstimator(object): estimates = self._calculate(parameterset(parset), input_files) else: raise ValueError('The parset is incomplete') - result = {} - result[self.name] = {} - result[self.name]['storage'] = estimates['storage'] - result[self.name]['bandwidth'] = estimates['bandwidth'] + result[self.name] = estimates + logger.info('Estimates for %s:' % self.name) + logger.info(pprint.pformat(result)) return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index 20453311d0e..930f1e9aecf 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -45,16 +45,16 @@ class ResourceEstimatorHandler(MessageHandlerInterface): if specification_tree['task_type'] == 'observation': return {str(otdb_id): self.add_id(self.observation.verify_and_estimate(parset), otdb_id)} elif specification_tree['task_type'] == 'pipeline': - if not specification_tree['predecessors']: + if not 'predecessors' in specification_tree or not specification_tree['predecessors']: logger.warning("Could not estimate %s because the pipeline has no predecessors" % (otdb_id)) - return {str(otdb_id): {'errors': ["Could not estimate %s because the pipeline has no predecessors" % (otdb_id)]}} + return {str(otdb_id): {'pipeline': {'errors': ["Could not estimate %s because the pipeline has no predecessors" % (otdb_id)]}}} branch_estimates = {} for branch in specification_tree['predecessors']: subtree_estimate = self.get_subtree_estimate(branch) - if subtree_estimate[str(branch)]['errors']: + if subtree_estimate[str(branch)].values()[0]['errors']: logger.warning("Could not estimate %s because predecessor %s has errors" % (otdb_id, branch)) - return {str(otdb_id): {'errors': ["Could not estimate %s because predecessor %s has errors" % (otdb_id, branch)]}} + return {str(otdb_id): {'pipeline': {'errors': ["Could not estimate %s because predecessor %s has errors" % (otdb_id, branch)]}}} branch_estimates.update(subtree_estimate) logger.info(("Branch estimates for %s\n" % otdb_id) + pprint.pformat(branch_estimates)) @@ -73,7 +73,7 @@ class ResourceEstimatorHandler(MessageHandlerInterface): if len(branch_estimates) > 1: logger.warning('Pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys())) - return {str(otdb_id): {'errors': ['Pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys())]}} + return {str(otdb_id): {'pipeline': {'errors': ['Pipeline %d should not have multiple predecessors: %s' % (otdb_id, branch_estimates.keys())]}}} predecessor_output = branch_estimates.values()[0].values()[0]['storage']['output_files'] if specification_tree['task_subtype'] in ['imaging pipeline', 'imaging pipeline msss']: @@ -90,7 +90,7 @@ class ResourceEstimatorHandler(MessageHandlerInterface): else: # reservation, maintenance, system tasks? logger.warning("ID %s is not a pipeline or observation." % otdb_id) - return {str(otdb_id): {'errors': ["ID %s is not a pipeline or observation." % otdb_id]}} + return {str(otdb_id): {specification_tree['task_type']: {'errors': ["ID %s is not a pipeline or observation." % otdb_id]}}} def _get_estimated_resources(self, specification_tree): """ Input is like: -- GitLab From 279ce3bceef6fda7df2ac2efd155fb4387c51a54 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 20 Jul 2016 09:44:17 +0000 Subject: [PATCH 558/933] Task #9351 #9353 #9355: disabled legend. handle undefined disk_usage. show alert when no disk_usage found --- .../static/app/controllers/cleanupcontroller.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js index 68d18145ecf..ae9abc53292 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -154,6 +154,9 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h type: 'pie', animation: { duration: 200 + }, + legend: { + enabled: false } }, plotOptions: { @@ -163,7 +166,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h dataLabels: { enabled: true }, - showInLegend: true + showInLegend: false }, series: { point: { @@ -203,7 +206,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h sub_directory_names.sort(function(a, b) { return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0)); }); for(var sub_dir of sub_directory_names) { var sub_dir_result = result.sub_directories[sub_dir]; - $scope.diskUsageChartSeries[0].data.push({name:sub_dir_result.name + ' ' + sub_dir_result.disk_usage_readable,y:sub_dir_result.disk_usage}); + $scope.diskUsageChartSeries[0].data.push({name:sub_dir_result.name + ' ' + sub_dir_result.disk_usage_readable,y:sub_dir_result.disk_usage || 0}); if(sub_dir_result.cache_timestamp < $scope.leastRecentCacheTimestamp) { $scope.leastRecentCacheTimestamp = sub_dir_result.cache_timestamp; @@ -212,6 +215,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h $scope.leastRecentCacheTimestamp = dataService.convertDatestringToLocalUTCDate($scope.leastRecentCacheTimestamp); }else { $scope.ok(); + $scope.$evalAsync(function() { alert("Could not find disk usage for task " + otdb_id); }); } }); }; @@ -230,7 +234,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h for(var sub_dir of sub_directory_names) { var sub_dir_result = result.sub_directories[sub_dir]; $scope.diskUsageChartSeries[0].data.push({name:sub_dir_result.name + ' ' + sub_dir_result.disk_usage_readable, - y:sub_dir_result.disk_usage, + y:sub_dir_result.disk_usage || 0, otdb_id: parseInt(sub_dir_result.name.slice(1)) }); if(sub_dir_result.cache_timestamp < $scope.leastRecentCacheTimestamp) { @@ -240,6 +244,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h $scope.leastRecentCacheTimestamp = dataService.convertDatestringToLocalUTCDate($scope.leastRecentCacheTimestamp); }else { $scope.ok(); + $scope.$evalAsync(function() { alert("Could not find disk usage for project " + project_name); }); } }); }; @@ -258,7 +263,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h for(var sub_dir of sub_directory_names) { var sub_dir_result = result.sub_directories[sub_dir]; $scope.diskUsageChartSeries[0].data.push({name:sub_dir_result.name + ' ' + sub_dir_result.disk_usage_readable, - y:sub_dir_result.disk_usage, + y:sub_dir_result.disk_usage || 0, project_name: sub_dir_result.name }); if(sub_dir_result.cache_timestamp < $scope.leastRecentCacheTimestamp) { @@ -268,6 +273,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h $scope.leastRecentCacheTimestamp = dataService.convertDatestringToLocalUTCDate($scope.leastRecentCacheTimestamp); }else { $scope.ok(); + $scope.$evalAsync(function() { alert("Could not find disk usage for all projects"); }); } }); }; -- GitLab From 51659e11c7e0e15058eee315347968786d80f869 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 20 Jul 2016 09:45:30 +0000 Subject: [PATCH 559/933] Task #9351 #9353 #9355: start service with initial project tree scan --- .../StorageQueryService/cache.py | 74 +++++++++++++++---- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index 666ceb44dec..5916749cafd 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -89,16 +89,18 @@ class CacheManager: def _readCacheFromDisk(self): # maybe this cache on disk is slow, if so, revert to proper db solution try: - with open('.du_cache.py', 'r') as file: - with self._cacheLock: - self._cache = eval(file.read()) - if not isinstance(self._cache, dict) or 'paths' not in self._cache or 'otdb_ids' not in self._cache: - self._cache = {} + if os.path.exists('.du_cache.py'): + with open('.du_cache.py', 'r') as file: + with self._cacheLock: + self._cache = eval(file.read().strip()) + if not isinstance(self._cache, dict): + self._cache = {} except Exception as e: logger.error("Error while reading in du cache: %s", e) with self._cacheLock: self._cache = {} + def _writeCacheToDisk(self): try: with open('.du_cache.py', 'w') as file: @@ -138,7 +140,46 @@ class CacheManager: if path in self._cache: self._cache[path]['cache_timestamp'] = self._cache[path]['cache_timestamp'] - MAX_CACHE_ENTRY_AGE + def _scanProjectsTree(self): + try: + def addSubDirectoriesToCache(directory): + depth = len(directory.replace(self.disk_usage.path_resolver.projects_path, '').strip('/').split('/')) + if depth > 2: + return + + with self._cacheLock: + logger.info('tree scan: %s %s', directory, self._cache.keys()) + if directory not in self._cache: + logger.info('tree scan: adding \'%s\' with empty disk_usage to cache which will be du\'ed later', directory) + empty_du_result = {'found': True, 'disk_usage': None, 'path': directory, 'name': directory.split('/')[-1]} + self._updateCache(empty_du_result) + + if directory in self._cache: + # make cache entry for directory 'old', so it will be du'ed in _updateOldEntriesInCache immediately + self._cache[directory]['cache_timestamp'] -= MAX_CACHE_ENTRY_AGE + + if not self._updateCacheThreadRunning: + return + + if depth < 2: + logger.info('tree scan: scanning \'%s\'', directory) + sd_result = self.disk_usage.path_resolver.getSubDirectories(directory) + + if sd_result['found']: + subdir_paths = [os.path.join(directory,sd) for sd in sd_result['sub_directories']] + + for subdir_path in subdir_paths: + # recurse + addSubDirectoriesToCache(subdir_path) + + addSubDirectoriesToCache(self.disk_usage.path_resolver.projects_path) + logger.info('tree scan complete') + + except Exception as e: + logger.error(str(e)) + def _updateOldEntriesInCache(self): + logger.info('starting updating old cache entries') while self._updateCacheThreadRunning: try: now = datetime.datetime.utcnow() @@ -183,6 +224,10 @@ class CacheManager: except Exception as e: logger.error(str(e)) + def _scanProjectsTreeAndUpdateOldEntriesInCache(self): + self._scanProjectsTree() + self._updateOldEntriesInCache() + def _updateProjectsDiskUsageInRADB(self): try: projects_du_result = self.getDiskUsageForPath(self.disk_usage.path_resolver.projects_path) @@ -192,16 +237,17 @@ class CacheManager: storage_resources = radbrpc.getResources(resource_types='storage', include_availability=True) cep4_storage_resource = next(x for x in storage_resources if 'cep4' in x['name']) - total_capacity = cep4_storage_resource['total_capacity'] - used_capacity = projects_du_result['disk_usage'] - available_capacity = total_capacity - used_capacity + total_capacity = cep4_storage_resource.get('total_capacity') + used_capacity = projects_du_result.get('disk_usage') + if total_capacity != None and used_capacity != None: + available_capacity = total_capacity - used_capacity - logger.info('updating availability capacity for %s (id=%s) to %s in the RADB', - cep4_storage_resource['name'], - cep4_storage_resource['id'], - humanreadablesize(available_capacity)) + logger.info('updating availability capacity for %s (id=%s) to %s in the RADB', + cep4_storage_resource['name'], + cep4_storage_resource['id'], + humanreadablesize(available_capacity)) - radbrpc.updateResourceAvailability(cep4_storage_resource['id'], available_capacity=available_capacity) + radbrpc.updateResourceAvailability(cep4_storage_resource['id'], available_capacity=available_capacity) except Exception as e: logger.error(e) @@ -209,7 +255,7 @@ class CacheManager: self.disk_usage.open() self.event_bus.open() - self._updateCacheThread = Thread(target=self._updateOldEntriesInCache) + self._updateCacheThread = Thread(target=self._scanProjectsTreeAndUpdateOldEntriesInCache) self._updateCacheThread.daemon = True self._updateCacheThreadRunning = True self._updateCacheThread.start() -- GitLab From 17950ec3a3488ed39cff2bac87c2a98bc9f9e4f2 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Wed, 20 Jul 2016 10:25:40 +0000 Subject: [PATCH 560/933] Task #9667: Fixes for correct number of output files when using beamformed with tab rings --- .../lib/translator.py | 2 +- .../resource_estimators/base_resource_estimator.py | 1 + .../resource_estimators/calibration_pipeline.py | 10 +++------- .../resource_estimators/image_pipeline.py | 10 +++------- .../resource_estimators/longbaseline_pipeline.py | 10 +++------- .../resource_estimators/pulsar_pipeline.py | 10 +++------- .../ResourceAssignmentEstimator/service.py | 2 +- 7 files changed, 15 insertions(+), 30 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 1f6fd4701a3..60a68de5a17 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -104,7 +104,7 @@ class RAtoOTDBTranslator(): for tab in xrange(nr_tabs): if skip_tab: if tab == sap['properties']['is_tab_nr']: - continue + continue for stokes in xrange(nr_stokes): for part in xrange(nr_parts): locations.append(self.locationPath(project_name, otdb_id) + '/cs') diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py index 5776a821ee7..4a1519bbb4e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py @@ -110,6 +110,7 @@ class BaseResourceEstimator(object): estimates = self._calculate(parameterset(parset), input_files) else: raise ValueError('The parset is incomplete') + result = {} result[self.name] = estimates logger.info('Estimates for %s:' % self.name) logger.info(pprint.pformat(result)) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py index 524a0356c25..1e8cb50a5a3 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py @@ -115,15 +115,11 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): #total_data_size = result['storage']['output_files']['uv']['nr_of_uv_files'] * result['storage']['output_files']['uv']['uv_file_size'] + \ # result['storage']['output_files']['im']['nr_of_im_files'] * result['storage']['output_files']['im']['im_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - if total_data_size and output_files: + if total_data_size: result['storage']['total_size'] = total_data_size result['bandwidth']['total_size'] = total_bandwidth else: - if not total_data_size: - result['errors'].append('Total data size is zero!') - logger.warning('ERROR: A datasize of zero was calculated!') - if not output_files: - result['errors'].append('No output files!') - logger.warning('ERROR: No output files were calculated!') + result['errors'].append('Total data size is zero!') + logger.warning('ERROR: A datasize of zero was calculated!') return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py index 3050fd40134..3dfaaa10eea 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py @@ -98,15 +98,11 @@ class ImagePipelineResourceEstimator(BaseResourceEstimator): # count total data size total_data_size = result['storage']['output_files']['img']['nr_of_img_files'] * result['storage']['output_files']['img']['img_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - if total_data_size and output_files: + if total_data_size: result['storage']['total_size'] = total_data_size result['bandwidth']['total_size'] = total_bandwidth else: - if not total_data_size: - result['errors'].append('Total data size is zero!') - logger.warning('ERROR: A datasize of zero was calculated!') - if not output_files: - result['errors'].append('No output files!') - logger.warning('ERROR: No output files were calculated!') + result['errors'].append('Total data size is zero!') + logger.warning('ERROR: A datasize of zero was calculated!') return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py index 2be090a2cab..07779a61e5c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py @@ -99,14 +99,10 @@ class LongBaselinePipelineResourceEstimator(BaseResourceEstimator): # count total data size total_data_size = result['storage']['output_files']['uv']['nr_of_uv_files'] * result['storage']['output_files']['uv']['uv_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - if total_data_size and output_files: + if total_data_size: result['storage']['total_size'] = total_data_size result['bandwidth']['total_size'] = total_bandwidth else: - if not total_data_size: - result['errors'].append('Total data size is zero!') - logger.warning('ERROR: A datasize of zero was calculated!') - if not output_files: - result['errors'].append('No output files!') - logger.warning('ERROR: No output files were calculated!') + result['errors'].append('Total data size is zero!') + logger.warning('ERROR: A datasize of zero was calculated!') return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py index 7dbe0a2b790..c6ae0475d83 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py @@ -99,14 +99,10 @@ class PulsarPipelineResourceEstimator(BaseResourceEstimator): # count total data size total_data_size = result['storage']['output_files']['pulp']['nr_of_pulp_files'] * result['storage']['output_files']['pulp']['pulp_file_size'] # bytes total_bandwidth = int(ceil((total_data_size * 8) / duration)) # bits/second - if total_data_size and output_files: + if total_data_size: result['storage']['total_size'] = total_data_size result['bandwidth']['total_size'] = total_bandwidth else: - if not total_data_size: - result['errors'].append('Total data size is zero!') - logger.warning('ERROR: A datasize of zero was calculated!') - if not output_files: - result['errors'].append('No output files!') - logger.warning('ERROR: No output files were calculated!') + result['errors'].append('Total data size is zero!') + logger.warning('ERROR: A datasize of zero was calculated!') return result diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index 930f1e9aecf..0dbadd795c0 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -52,7 +52,7 @@ class ResourceEstimatorHandler(MessageHandlerInterface): branch_estimates = {} for branch in specification_tree['predecessors']: subtree_estimate = self.get_subtree_estimate(branch) - if subtree_estimate[str(branch)].values()[0]['errors']: + if subtree_estimate[str(branch['otdb_id'])][branch['task_type']]['errors']: logger.warning("Could not estimate %s because predecessor %s has errors" % (otdb_id, branch)) return {str(otdb_id): {'pipeline': {'errors': ["Could not estimate %s because predecessor %s has errors" % (otdb_id, branch)]}}} branch_estimates.update(subtree_estimate) -- GitLab From daf0d7c31862a01927dfb57f7016a77fa2ffd1a8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 20 Jul 2016 12:40:16 +0000 Subject: [PATCH 561/933] Task #9351 #9353 #9355: multi select of tasks --- .../static/app/controllers/datacontroller.js | 74 ++++++++++++++----- .../app/controllers/ganttprojectcontroller.js | 51 ++----------- .../controllers/ganttresourcecontroller.js | 15 ++-- .../static/app/controllers/gridcontroller.js | 35 ++++++--- .../angular-gantt-contextmenu-plugin.js | 4 +- 5 files changed, 100 insertions(+), 79 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 31d14181326..9f7d79c7c86 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -33,7 +33,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.selected_resource_id; self.selected_resourceGroup_id; - self.selected_task_id; + self.selected_task_ids = []; self.selected_project_id; self.selected_resourceClaim_id; @@ -48,6 +48,36 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.viewTimeSpan = {from: new Date(), to: new Date() }; self.autoFollowNow = true; + self.isTaskIdSelected = function(task_id) { + return self.selected_task_ids.indexOf(task_id) != -1; + } + + self.toggleTaskSelection = function(task_id) { + if(self.isTaskIdSelected(task_id)) { + self.removeSelectedTaskId(task_id); + } else { + self.addSelectedTaskId(task_id); + } + } + + self.addSelectedTaskId = function(task_id) { + if(self.selected_task_ids.indexOf(task_id) == -1) { + self.selected_task_ids.push(task_id); + } + } + + self.removeSelectedTaskId = function(task_id) { + var idx = self.selected_task_ids.indexOf(task_id); + if(idx != -1) { + self.selected_task_ids.splice(idx, 1); + } + } + + self.setSelectedTaskId = function(task_id) { + self.selected_task_ids.splice(0, self.selected_task_ids.length); + self.selected_task_ids.push(task_id); + } + self.floorDate = function(date, hourMod=1, minMod=1) { var min = date.getMinutes(); min = date.getMinutes()/minMod; @@ -246,25 +276,27 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, if(initialTaskLoad && self.tasks.length > 0) { setTimeout(function() { + self.selected_task_ids.splice(0,self.selected_task_ids.length); + //try to select current task var currentTasks = self.tasks.filter(function(t) { return t.starttime <= self.lofarTime && t.endtime >= self.lofarTime; }); if(currentTasks.length > 0) { - self.selected_task_id = currentTasks[0].id; + self.selected_task_ids.push(currentTasks[0].id); } else { //try to select next task var nextTasks = self.tasks.filter(function(t) { return t.starttime >= self.lofarTime; }).sort(); if(nextTasks.length > 0) { - self.selected_task_id = nextTasks[0].id; + self.selected_task_ids.push(nextTasks[0].id); } else { //try to select most recent task var prevTasks = self.tasks.filter(function(t) { return t.endtime <= self.lofarTime; }).sort(); if(prevTasks.length > 0) { - self.selected_task_id = prevTasks[prevTasks.length-1].id; + self.selected_task_ids.push(prevTasks[prevTasks.length-1].id); } else { - self.selected_task_id = self.tasks[0].id; + self.selected_task_ids.push(self.tasks[0].id); } } } @@ -730,24 +762,27 @@ dataControllerMod.controller('DataController', $scope.selectCurrentTask = function() { var currentTasks = dataService.tasks.filter(function(t) { return t.starttime <= dataService.viewTimeSpan.to && t.endime >= dataService.viewTimeSpan.from; }); if(currentTasks.lenght > 0) { - dataService.selected_task_id = currentTasks[0].id; + dataService.setSelectedTaskId(currentTasks[0].id); } }; $scope.jumpToSelectedTask = function() { - if(dataService.selected_task_id == undefined) + if(dataService.selected_task_ids == undefined) return; - var task = dataService.taskDict[dataService.selected_task_id]; + var tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); - if(task == undefined) + if(tasks.lenght == 0) return; - var taskDurationInmsec = task.endtime.getTime() - task.starttime.getTime(); - var taskDurationInMinutes = taskDurationInmsec/60000; - var viewSpanInMinutes = taskDurationInMinutes; + var minStarttime = new Date(Math.min.apply(null, tasks.map(function(t) { return t.starttime; }))); + var maxEndtime = new Date(Math.max.apply(null, tasks.map(function(t) { return t.endtime; }))); - var fittingSpans = $scope.zoomTimespans.filter(function(w) { return w.value >= taskDurationInMinutes; }); + var selectedTasksDurationInmsec = maxEndtime.getTime() - minStarttime.getTime(); + var selectedTasksDurationInMinutes = selectedTasksDurationInmsec/60000; + var viewSpanInMinutes = selectedTasksDurationInMinutes; + + var fittingSpans = $scope.zoomTimespans.filter(function(w) { return w.value >= selectedTasksDurationInMinutes; }); if(fittingSpans.length > 0) { $scope.zoomTimespan = fittingSpans[0]; //select one span larger if possible @@ -756,7 +791,7 @@ dataControllerMod.controller('DataController', viewSpanInMinutes = $scope.zoomTimespan.value; } - var focusTime = new Date(task.starttime.getTime() + 0.5*taskDurationInmsec); + var focusTime = new Date(minStarttime.getTime() + 0.5*selectedTasksDurationInmsec); dataService.viewTimeSpan = { from: dataService.floorDate(new Date(focusTime.getTime() - 0.4*viewSpanInMinutes*60*1000), 1, 5), @@ -788,11 +823,14 @@ dataControllerMod.controller('DataController', if(dataService.autoFollowNow) { focusTime = dataService.floorDate(dataService.lofarTime, 1, 5); - } else if(dataService.selected_task_id != undefined) { - var task = dataService.taskDict[dataService.selected_task_id]; + } else { + var tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); + + if(tasks.lenght > 0) { + var minStarttime = new Date(Math.min.apply(null, tasks.map(function(t) { return t.starttime; }))); + var maxEndtime = new Date(Math.max.apply(null, tasks.map(function(t) { return t.endtime; }))); - if(task) { - focusTime = dataService.floorDate(task.starttime, 1, 5); + focusTime = dataService.floorDate(new Date(0.5*(minStarttime.getTime() + maxEndtime.getTime())), 1, 5); } } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index ef5752e6ffe..878ab4b7f9a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -59,53 +59,19 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS } else if (directiveName === 'ganttTask') { directiveElement.bind('click', function(event) { if(directiveScope.task.model.raTask) { - $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; + if(event.ctrlKey) { + $scope.dataService.toggleTaskSelection(directiveScope.task.model.raTask.id); + } else { + $scope.dataService.setSelectedTaskId(directiveScope.task.model.raTask.id); + } } }); directiveElement.bind('dblclick', function(event) { if(directiveScope.task.model.raTask) { - $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; + $scope.dataService.setSelectedTaskId(directiveScope.task.model.raTask.id); $scope.jumpToSelectedTask(); } }); -// directiveElement.bind('contextmenu', function(event) { -// if(directiveScope.task.model.raTask) { -// $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; -// } -// -// //search for already existing contextmenu element -// if(directiveElement.find('#gantt-project-context-menu').length) { -// //found, remove it, so we can create a fresh one -// directiveElement.find('#gantt-project-context-menu')[0].remove(); -// } -// -// //create contextmenu element -// //with list of menu items, -// //each with it's own action -// var contextmenuElement = angular.element('<div id="gantt-project-context-menu"></div>'); -// ulElement = angular.element('<ul style="z-index:10000; position:fixed; top:initial; left:initial; display:block;" role="menu" class="dropdown-menu"></ul>'); -// contextmenuElement.append(ulElement); -// liElement = angular.element('<li><a href="#">Copy Task</a></li>'); -// ulElement.append(liElement); -// liElement.on('click', function() { -// $scope.dataService.copyTask(directiveScope.task.model.raTask); -// closeContextMenu(); -// }); -// -// var closeContextMenu = function() { -// contextmenuElement.remove(); -// angular.element(document).unbind('click', closeContextMenu); -// }; -// -// //click anywhere to remove the contextmenu -// angular.element(document).bind('click', closeContextMenu); -// -// //add contextmenu to clicked element -// directiveElement.append(contextmenuElement); -// -// //prevent bubbling event upwards -// return false; -// }); } }); @@ -115,7 +81,6 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS } if (directiveName === 'ganttTask') { directiveElement.unbind('dblclick'); -// directiveElement.unbind('contextmenu'); } }); } @@ -235,7 +200,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS movable: $.inArray(task.status_id, editableTaskStatusIds) > -1 }; - if(task.id == dataService.selected_task_id) { + if(dataService.isTaskIdSelected(task.id)) { rowTask.classes += ' task-selected-task'; } @@ -262,7 +227,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS }; $scope.$watch('dataService.initialLoadComplete', function() { $scope.$evalAsync(updateGanttData); }); - $scope.$watch('dataService.selected_task_id', function() { $scope.$evalAsync(updateGanttData); }); + $scope.$watch('dataService.selected_task_ids', function() { $scope.$evalAsync(updateGanttData); }, true); $scope.$watch('dataService.viewTimeSpan', function() { $scope.$evalAsync(updateGanttData); }, true); $scope.$watch('dataService.filteredTaskChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); $scope.$watch('dataService.lofarTime', function() { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js index 9ec685de16e..de24e59997d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js @@ -58,17 +58,17 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat } else if (directiveName === 'ganttTask') { directiveElement.bind('click', function(event) { if(directiveScope.task.model.raTask) { - $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; + if(event.ctrlKey) { + $scope.dataService.toggleTaskSelection(directiveScope.task.model.raTask.id); + } else { + $scope.dataService.setSelectedTaskId(directiveScope.task.model.raTask.id); + } } if(directiveScope.task.model.claim) { $scope.dataService.selected_resourceClaim_id = directiveScope.task.model.claim.id; } }); directiveElement.bind('contextmenu', function(event) { - if(directiveScope.task.model.raTask) { - $scope.dataService.selected_task_id = directiveScope.task.model.raTask.id; - } - //search for already existing contextmenu element if(directiveElement.find('#gantt-resource-context-menu').length) { //found, remove it, so we can create a fresh one @@ -335,10 +335,9 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat movable: $.inArray(task.status_id, editableTaskStatusIds) > -1 }; - if(claim.id == dataService.selected_resourceClaim_id) { claimTask.classes += ' claim-selected-claim'; - } else if(task.id == dataService.selected_task_id) { + } else if(dataService.isTaskIdSelected(task.id)) { claimTask.classes += ' claim-selected-task'; } @@ -469,7 +468,7 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat }; $scope.$watch('dataService.initialLoadComplete', function() { $scope.$evalAsync(updateGanttData); }); - $scope.$watch('dataService.selected_task_id', function() { $scope.$evalAsync(updateGanttData); }); + $scope.$watch('dataService.selected_task_ids', function() { $scope.$evalAsync(updateGanttData); }, true); $scope.$watch('dataService.viewTimeSpan', function() { $scope.$evalAsync(updateGanttData); }, true); $scope.$watch('dataService.claimChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); $scope.$watch('dataService.filteredTaskChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index a431de9c97c..a79e11891ed 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -113,8 +113,9 @@ $scope.columns = [ enableRowSelection: true, enableRowHeaderSelection: true, enableFullRowSelection: false, + modifierKeysToMultiSelect: true, + multiSelect:true, enableSelectionBatchEvent:false, - multiSelect:false, gridMenuShowHideColumns: false, columnDefs: $scope.columns, data: [], @@ -135,10 +136,13 @@ $scope.columns = [ var row = rows[i]; if(row.visible) { - var task = taskDict[row.entity.id]; + var task_id = row.entity.id; + var task = taskDict[task_id]; if(task) { $scope.dataService.filteredTasks.push(task); } + + row.setSelected($scope.dataService.selected_task_ids.indexOf(task_id) != -1); } } @@ -154,8 +158,12 @@ $scope.columns = [ }); gridApi.selection.on.rowSelectionChanged($scope,function(row){ - if(row.entity.id && row.isSelected) { - $scope.dataService.selected_task_id = row.entity.id; + if(row.entity.id) { + if(row.isSelected) { + $scope.dataService.addSelectedTaskId(row.entity.id); + } else if(!row.isSelected) { + $scope.dataService.removeSelectedTaskId(row.entity.id); + } } }); } @@ -222,11 +230,20 @@ $scope.columns = [ }; function jumpToSelectedTaskRow() { - var taskIdx = $scope.gridOptions.data.findIndex(function(row) {return row.id == dataService.selected_task_id}); +// var taskIdx = $scope.gridOptions.data.findIndex(function(row) {return row.id == dataService.selected_task_id}); +// +// if(taskIdx > -1) { +// $scope.gridApi.selection.selectRow($scope.gridOptions.data[taskIdx]); +// $scope.gridApi.core.scrollTo($scope.gridOptions.data[taskIdx], null); +// } + }; + + function onSelectedTaskIdsChanged() { + var selected_task_ids = $scope.dataService.selected_task_ids; + var rows = $scope.gridApi.grid.rows; - if(taskIdx > -1) { - $scope.gridApi.selection.selectRow($scope.gridOptions.data[taskIdx]); - $scope.gridApi.core.scrollTo($scope.gridOptions.data[taskIdx], null); + for(var row of rows) { + row.setSelected(selected_task_ids.indexOf(row.entity.id) != -1); } }; @@ -281,7 +298,7 @@ $scope.columns = [ fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[7]); }; - $scope.$watch('dataService.selected_task_id', jumpToSelectedTaskRow);} + $scope.$watch('dataService.selected_task_ids', onSelectedTaskIdsChanged, true);} ]); gridControllerMod.directive('contextMenu', ['$document', function($document) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index ed879020df5..ad9e1fbac9d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -31,7 +31,9 @@ var docElement = angular.element($document); if(dScope.task.model.raTask) { - dataService.selected_task_id = dScope.task.model.raTask.id; + if(!dataService.isTaskIdSelected(dScope.task.model.raTask.id)) { + dataService.setSelectedTaskId(dScope.task.model.raTask.id); + } } //search for already existing contextmenu element -- GitLab From bcb2d9633ed39ff9ba8d69af7392ac35639635d4 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 20 Jul 2016 12:55:01 +0000 Subject: [PATCH 562/933] Task #9607: Made RAServices dependent on DataManagement --- SubSystems/RAServices/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SubSystems/RAServices/CMakeLists.txt b/SubSystems/RAServices/CMakeLists.txt index 50dda7052a1..9bc5419dca4 100644 --- a/SubSystems/RAServices/CMakeLists.txt +++ b/SubSystems/RAServices/CMakeLists.txt @@ -12,7 +12,8 @@ lofar_package(RAServices ResourceAssignmentEstimator ResourceAssignmentService SystemStatusDatabase - SystemStatusService) + SystemStatusService + DataManagement) # supervisord config files install(FILES -- GitLab From d980ffab14a892b62f54c2d3b07ef89e8eceded9 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Wed, 20 Jul 2016 14:24:45 +0000 Subject: [PATCH 563/933] Task #9697: optionally update weights in DPPP applybeam --- .gitattributes | 2 + CEP/DP3/DPPP/include/DPPP/ApplyBeam.h | 6 +- CEP/DP3/DPPP/include/DPPP/ApplyBeam.tcc | 8 ++- CEP/DP3/DPPP/include/DPPP/ApplyCal.h | 12 ++++ CEP/DP3/DPPP/src/ApplyBeam.cc | 16 +++++- CEP/DP3/DPPP/src/ApplyCal.cc | 76 ++++++++++++------------- CEP/DP3/DPPP/src/Predict.cc | 7 ++- CEP/DP3/DPPP/test/CMakeLists.txt | 1 + CEP/DP3/DPPP/test/tApplyBeam.run | 6 ++ CEP/DP3/DPPP/test/tApplyCal2.run | 43 ++++++++++++++ CEP/DP3/DPPP/test/tApplyCal2.sh | 2 + 11 files changed, 131 insertions(+), 48 deletions(-) create mode 100755 CEP/DP3/DPPP/test/tApplyCal2.run create mode 100755 CEP/DP3/DPPP/test/tApplyCal2.sh diff --git a/.gitattributes b/.gitattributes index 4a0935e794d..025ea37510b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -895,6 +895,8 @@ CEP/DP3/DPPP/test/tApplyCal.cc -text CEP/DP3/DPPP/test/tApplyCal.in_parmdb.tgz -text svneol=unset#application/x-gzip CEP/DP3/DPPP/test/tApplyCal.run -text CEP/DP3/DPPP/test/tApplyCal.sh -text +CEP/DP3/DPPP/test/tApplyCal2.run -text +CEP/DP3/DPPP/test/tApplyCal2.sh -text CEP/DP3/DPPP/test/tApplyCal_parmdbscript -text CEP/DP3/DPPP/test/tDemix.in_MS.tgz -text CEP/DP3/DPPP/test/tDemix.run -text diff --git a/CEP/DP3/DPPP/include/DPPP/ApplyBeam.h b/CEP/DP3/DPPP/include/DPPP/ApplyBeam.h index 2f80b2f860f..c27c34e2af0 100644 --- a/CEP/DP3/DPPP/include/DPPP/ApplyBeam.h +++ b/CEP/DP3/DPPP/include/DPPP/ApplyBeam.h @@ -82,13 +82,14 @@ namespace LOFAR { template<typename T> static void applyBeam( - const DPInfo& info, double time, T* data0, + const DPInfo& info, double time, T* data0, float* weight0, const StationResponse::vector3r_t& srcdir, const StationResponse::vector3r_t& refdir, const StationResponse::vector3r_t& tiledir, const vector<StationResponse::Station::Ptr>& antBeamInfo, vector<StationResponse::matrix22c_t>& beamValues, - bool useChannelFreq, bool invert, int mode=DEFAULT); + bool useChannelFreq, bool invert, int mode, + bool doUpdateWeights=false); private: StationResponse::vector3r_t dir2Itrf( @@ -100,6 +101,7 @@ namespace LOFAR { string itsName; DPBuffer itsBuffer; bool itsInvert; + bool itsUpdateWeights; bool itsUseChannelFreq; Position itsPhaseRef; BeamMode itsMode; diff --git a/CEP/DP3/DPPP/include/DPPP/ApplyBeam.tcc b/CEP/DP3/DPPP/include/DPPP/ApplyBeam.tcc index ecaeef750c8..d241f43c03c 100644 --- a/CEP/DP3/DPPP/include/DPPP/ApplyBeam.tcc +++ b/CEP/DP3/DPPP/include/DPPP/ApplyBeam.tcc @@ -66,13 +66,13 @@ namespace LOFAR { // applyBeam is templated on the type of the data, could be double or float template<typename T> void ApplyBeam::applyBeam( - const DPInfo& info, double time, T* data0, + const DPInfo& info, double time, T* data0, float* weight0, const StationResponse::vector3r_t& srcdir, const StationResponse::vector3r_t& refdir, const StationResponse::vector3r_t& tiledir, const vector<StationResponse::Station::Ptr>& antBeamInfo, vector<StationResponse::matrix22c_t>& beamValues, bool useChannelFreq, - bool invert, int mode) + bool invert, int mode, bool doUpdateWeights) { // Get the beam values for each station. uint nCh = info.chanFreqs().size(); @@ -162,6 +162,10 @@ void ApplyBeam::applyBeam( data[1] = tmp[0] * r[1] + tmp[1] * r[3]; data[2] = tmp[2] * r[0] + tmp[3] * r[2]; data[3] = tmp[2] * r[1] + tmp[3] * r[3]; + + if (doUpdateWeights) { + ApplyCal::applyWeights(l, r, weight0 + bl * 4 * nCh + ch * 4); + } } } } diff --git a/CEP/DP3/DPPP/include/DPPP/ApplyCal.h b/CEP/DP3/DPPP/include/DPPP/ApplyCal.h index 113b39c2a5e..b827eaa81d4 100644 --- a/CEP/DP3/DPPP/include/DPPP/ApplyCal.h +++ b/CEP/DP3/DPPP/include/DPPP/ApplyCal.h @@ -92,6 +92,18 @@ namespace LOFAR { uint bl, uint chan, bool updateWeights, FlagCounter& flagCounter); + // Do the same as the combination of BBS + python script + // covariance2weight.py (cookbook), except it stores weights per freq. + // The diagonal of covariance matrix is transferred to the weights. + // Note that the real covariance (mixing of noise terms after which they + // are not independent anymore) is not stored. + // The input covariance matrix C is assumed to be diagonal with elements + // w_i (the weights), the result the diagonal of + // (gainA kronecker gainB^H).C.(gainA kronecker gainB^H)^H + static void applyWeights (const casa::DComplex* gainA, + const casa::DComplex* gainB, + float* weight); + private: // Read parameters from the associated parmdb and store them in itsParms void updateParms (const double bufStartTime); diff --git a/CEP/DP3/DPPP/src/ApplyBeam.cc b/CEP/DP3/DPPP/src/ApplyBeam.cc index 7f9b139c69c..8d1484bb1a0 100644 --- a/CEP/DP3/DPPP/src/ApplyBeam.cc +++ b/CEP/DP3/DPPP/src/ApplyBeam.cc @@ -54,10 +54,11 @@ namespace LOFAR { namespace DPPP { ApplyBeam::ApplyBeam(DPInput* input, const ParameterSet& parset, - const string& prefix, bool substep) + const string& prefix, bool substep) : itsInput(input), itsName(prefix), + itsUpdateWeights(parset.getBool(prefix + "updateweights", false)), itsUseChannelFreq(parset.getBool(prefix + "usechannelfreq", true)), itsDebugLevel(parset.getInt(prefix + "debuglevel", 0)) { @@ -94,6 +95,9 @@ namespace LOFAR { info() = infoIn; info().setNeedVisData(); info().setWriteData(); + if (itsUpdateWeights) { + info().setWriteWeights(); + } MDirection dirJ2000( MDirection::Convert(infoIn.phaseCenter(), MDirection::J2000)()); @@ -136,6 +140,7 @@ namespace LOFAR { os << endl; os << " use channelfreq: " << boolalpha << itsUseChannelFreq << endl; os << " invert: " << boolalpha << itsInvert << endl; + os << " update weights: " << boolalpha << itsUpdateWeights << endl; } void ApplyBeam::showTimings(std::ostream& os, double duration) const @@ -151,6 +156,11 @@ namespace LOFAR { itsBuffer.copy (bufin); Complex* data=itsBuffer.getData().data(); + if (itsUpdateWeights) { + itsInput->fetchWeights (bufin, itsBuffer, itsTimer); + } + float* weight = itsBuffer.getWeights().data(); + double time = itsBuffer.getTime(); //Set up directions for beam evaluation @@ -168,9 +178,9 @@ namespace LOFAR { uint thread = OpenMP::threadNum(); StationResponse::vector3r_t srcdir = refdir; - applyBeam(info(), time, data, srcdir, refdir, tiledir, + applyBeam(info(), time, data, weight, srcdir, refdir, tiledir, itsAntBeamInfo[thread], itsBeamValues[thread], - itsUseChannelFreq, itsInvert, itsMode); + itsUseChannelFreq, itsInvert, itsMode, itsUpdateWeights); itsTimer.stop(); getNextStep()->process(itsBuffer); diff --git a/CEP/DP3/DPPP/src/ApplyCal.cc b/CEP/DP3/DPPP/src/ApplyCal.cc index 0e4f28cdca3..08f0dc16ea9 100644 --- a/CEP/DP3/DPPP/src/ApplyCal.cc +++ b/CEP/DP3/DPPP/src/ApplyCal.cc @@ -554,7 +554,7 @@ namespace LOFAR { void ApplyCal::applyFull (const DComplex* gainA, const DComplex* gainB, Complex* vis, float* weight, bool* flag, - uint bl, uint chan, bool updateWeights, + uint bl, uint chan, bool doUpdateWeights, FlagCounter& flagCounter) { DComplex gainAxvis[4]; @@ -595,46 +595,44 @@ namespace LOFAR { } } - // The code below does the same as the combination of BBS + python script - // covariance2weight.py (cookbook), except it stores weights per freq. - // The diagonal of covariance matrix is transferred to the weights. - // Note that the real covariance (mixing of noise terms after which they - // are not independent anymore) is not stored. - // The input covariance matrix C is assumed to be diagonal with elements - // w_i (the weights), the result the diagonal of - // (gainA kronecker gainB^H).C.(gainA kronecker gainB^H)^H - if (updateWeights) { - float cov[4], normGainA[4], normGainB[4]; - for (uint i=0;i<4;++i) { - cov[i]=1./weight[i]; - normGainA[i]=norm(gainA[i]); - normGainB[i]=norm(gainB[i]); - } + if (doUpdateWeights) { + applyWeights(gainA, gainB, weight); + } + } - weight[0]=cov[0]*(normGainA[0]*normGainB[0]) - +cov[1]*(normGainA[0]*normGainB[1]) - +cov[2]*(normGainA[1]*normGainB[0]) - +cov[3]*(normGainA[1]*normGainB[1]); - weight[0]=1./weight[0]; - - weight[1]=cov[0]*(normGainA[0]*normGainB[2]) - +cov[1]*(normGainA[0]*normGainB[3]) - +cov[2]*(normGainA[1]*normGainB[2]) - +cov[3]*(normGainA[1]*normGainB[3]); - weight[1]=1./weight[1]; - - weight[2]=cov[0]*(normGainA[2]*normGainB[0]) - +cov[1]*(normGainA[2]*normGainB[1]) - +cov[2]*(normGainA[3]*normGainB[0]) - +cov[3]*(normGainA[3]*normGainB[1]); - weight[2]=1./weight[2]; - - weight[3]=cov[0]*(normGainA[2]*normGainB[2]) - +cov[1]*(normGainA[2]*normGainB[3]) - +cov[2]*(normGainA[3]*normGainB[2]) - +cov[3]*(normGainA[3]*normGainB[3]); - weight[3]=1./weight[3]; + void ApplyCal::applyWeights(const DComplex* gainA, + const DComplex* gainB, + float* weight) { + float cov[4], normGainA[4], normGainB[4]; + for (uint i=0;i<4;++i) { + cov[i]=1./weight[i]; + normGainA[i]=norm(gainA[i]); + normGainB[i]=norm(gainB[i]); } + + weight[0]=cov[0]*(normGainA[0]*normGainB[0]) + +cov[1]*(normGainA[0]*normGainB[1]) + +cov[2]*(normGainA[1]*normGainB[0]) + +cov[3]*(normGainA[1]*normGainB[1]); + weight[0]=1./weight[0]; + + weight[1]=cov[0]*(normGainA[0]*normGainB[2]) + +cov[1]*(normGainA[0]*normGainB[3]) + +cov[2]*(normGainA[1]*normGainB[2]) + +cov[3]*(normGainA[1]*normGainB[3]); + weight[1]=1./weight[1]; + + weight[2]=cov[0]*(normGainA[2]*normGainB[0]) + +cov[1]*(normGainA[2]*normGainB[1]) + +cov[2]*(normGainA[3]*normGainB[0]) + +cov[3]*(normGainA[3]*normGainB[1]); + weight[2]=1./weight[2]; + + weight[3]=cov[0]*(normGainA[2]*normGainB[2]) + +cov[1]*(normGainA[2]*normGainB[3]) + +cov[2]*(normGainA[3]*normGainB[2]) + +cov[3]*(normGainA[3]*normGainB[3]); + weight[3]=1./weight[3]; } void ApplyCal::showCounts (std::ostream& os) const diff --git a/CEP/DP3/DPPP/src/Predict.cc b/CEP/DP3/DPPP/src/Predict.cc index 64db694225c..e8b318a82fc 100644 --- a/CEP/DP3/DPPP/src/Predict.cc +++ b/CEP/DP3/DPPP/src/Predict.cc @@ -337,9 +337,12 @@ namespace LOFAR { MDirection::J2000); StationResponse::vector3r_t srcdir = dir2Itrf(dir,itsMeasConverters[thread]); - ApplyBeam::applyBeam(info(), time, data0, srcdir, refdir, + float* dummyweight = 0; + + ApplyBeam::applyBeam(info(), time, data0, dummyweight, srcdir, refdir, tiledir, itsAntBeamInfo[thread], - itsBeamValues[thread], itsUseChannelFreq, false, itsBeamMode); + itsBeamValues[thread], itsUseChannelFreq, false, + itsBeamMode, false); //Add temporary buffer to itsModelVis std::transform(itsModelVis[thread].data(), diff --git a/CEP/DP3/DPPP/test/CMakeLists.txt b/CEP/DP3/DPPP/test/CMakeLists.txt index cbe86586311..f5e4caa66ae 100644 --- a/CEP/DP3/DPPP/test/CMakeLists.txt +++ b/CEP/DP3/DPPP/test/CMakeLists.txt @@ -17,6 +17,7 @@ lofar_add_test(tPhaseShift tPhaseShift.cc) lofar_add_test(tStationAdder tStationAdder.cc) lofar_add_test(tScaleData tScaleData.cc) lofar_add_test(tApplyCal tApplyCal.cc) +lofar_add_test(tApplyCal2) lofar_add_test(tFilter tFilter.cc) #lofar_add_test(tDemixer tDemixer.cc) lofar_add_test(tNDPPP tNDPPP.cc) diff --git a/CEP/DP3/DPPP/test/tApplyBeam.run b/CEP/DP3/DPPP/test/tApplyBeam.run index 8878b0ad9ec..19daa40a2d1 100755 --- a/CEP/DP3/DPPP/test/tApplyBeam.run +++ b/CEP/DP3/DPPP/test/tApplyBeam.run @@ -58,3 +58,9 @@ echo; echo "Test with beammode=ELEMENT"; echo # Compare the DATA column of the output MS with the BBS reference output. $taqlexe 'select from outinv.ms t1, tApplyBeam.tab t2 where not all(near(t1.DATA,t2.DATA_ELEMENT,1e-5) || (isnan(t1.DATA) && isnan(t2.DATA_ELEMENT)))' > taql.out diff taql.out taql.ref || exit 1 + +echo; echo "Test with updateweights=true"; echo +../../src/NDPPP msin=tNDPPP-generic.MS msout=. steps=[applybeam] applybeam.updateweights=truue msout.weightcolumn=NEW_WEIGHT_SPECTRUM +# Check that the weights have changed +$taqlexe 'select from tNDPPP-generic.MS where all(near(WEIGHT_SPECTRUM, NEW_WEIGHT_SPECTRUM))' > taql.out +diff taql.out taql.ref || exit 1 diff --git a/CEP/DP3/DPPP/test/tApplyCal2.run b/CEP/DP3/DPPP/test/tApplyCal2.run new file mode 100755 index 00000000000..3e460f1ffa5 --- /dev/null +++ b/CEP/DP3/DPPP/test/tApplyCal2.run @@ -0,0 +1,43 @@ +#!/bin/bash + +# Get the taql executable and srcdir (script created by cmake's CONFIGURE_FILE). +source findenv.run_script +echo "srcdirx=$rt_srcdir" + +# Set srcdir if not defined (in case run by hand). +if test "$srcdir" = ""; then + srcdir="$rt_srcdir" +fi + +if test ! -f ${srcdir}/tNDPPP-generic.in_MS.tgz; then + exit 3 # untested +fi + +rm -rf tApplyCal2_tmp +mkdir -p tApplyCal2_tmp +# Unpack the MS and other files and do the DPPP run. +cd tApplyCal2_tmp +tar zxf ${srcdir}/tNDPPP-generic.in_MS.tgz + +# Create expected taql output. +echo " select result of 0 rows" > taql.ref + +echo; echo "Creating parmdb with defvalues 3" +../../../../ParmDB/src/parmdbm <<EOL +open table="tApplyCal.parmdb" +adddef Gain:0:0:Real values=3. +adddef Gain:1:1:Real values=3. +EOL + +echo; echo "Testing without updateweights" +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DATA3 msout.weightcolumn=WEIGHTS_NEW steps=[applycal] applycal.parmdb=tApplyCal.parmdb showcounts=false +$taqlexe 'select from tNDPPP-generic.MS where not(all(DATA~=9*DATA3))' > taql.out +diff taql.out taql.ref || exit 1 +$taqlexe 'select from tNDPPP-generic.MS where not(all(WEIGHTS_NEW~=WEIGHT_SPECTRUM))' > taql.out +diff taql.out taql.ref || exit 1 + +echo; echo "Testing with updateweights" +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DATA3 msout.weightcolumn=WEIGHTS_NEW steps=[applycal] applycal.parmdb=tApplyCal.parmdb showcounts=false applycal.updateweights=true +$taqlexe 'select from tNDPPP-generic.MS where not(all(WEIGHTS_NEW~=81*WEIGHT_SPECTRUM))' > taql.out +diff taql.out taql.ref || exit 1 + diff --git a/CEP/DP3/DPPP/test/tApplyCal2.sh b/CEP/DP3/DPPP/test/tApplyCal2.sh new file mode 100755 index 00000000000..2c166e188f8 --- /dev/null +++ b/CEP/DP3/DPPP/test/tApplyCal2.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh tApplyCal2 -- GitLab From 491b8d7726030cf4ac360972060275d35f38c0f7 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 21 Jul 2016 08:45:05 +0000 Subject: [PATCH 564/933] Task #9351 #9353 #9355: loglevel --- SAS/DataManagement/DataManagementCommon/path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index 390c0ac5d24..4cbf7afe621 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -181,7 +181,7 @@ class PathResolver: dir_names = [l.split(' ')[-1].strip() for l in dir_lines] result = {'found': True, 'path': path, 'sub_directories': dir_names} - logger.info('getSubDirectories(%s) result: %s', path, result) + logger.debug('getSubDirectories(%s) result: %s', path, result) return result def pathExists(self, path): -- GitLab From b4d3fd13d9637afad0e495a5af41de3fe54bbad6 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 21 Jul 2016 08:45:41 +0000 Subject: [PATCH 565/933] Task #9351 #9353 #9355: scan 3 levels deep --- SAS/DataManagement/StorageQueryService/cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index 5916749cafd..ad82f4ea834 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -144,7 +144,7 @@ class CacheManager: try: def addSubDirectoriesToCache(directory): depth = len(directory.replace(self.disk_usage.path_resolver.projects_path, '').strip('/').split('/')) - if depth > 2: + if depth > 3: return with self._cacheLock: @@ -161,7 +161,7 @@ class CacheManager: if not self._updateCacheThreadRunning: return - if depth < 2: + if depth < 3: logger.info('tree scan: scanning \'%s\'', directory) sd_result = self.disk_usage.path_resolver.getSubDirectories(directory) -- GitLab From 16127919b514ed08d790b511168865b79d2bfa7f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 21 Jul 2016 08:46:44 +0000 Subject: [PATCH 566/933] Task #9351 #9353 #9355: implemented multi task selection. Delete data for multiple selected tasks --- .../app/controllers/cleanupcontroller.js | 104 +++++++++++++----- .../static/app/controllers/datacontroller.js | 21 +++- .../controllers/ganttresourcecontroller.js | 2 +- .../static/app/controllers/gridcontroller.js | 40 ++++--- .../angular-gantt-contextmenu-plugin.js | 24 ++-- 5 files changed, 139 insertions(+), 52 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js index ae9abc53292..ecb4601b290 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -18,14 +18,28 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h return defer.promise; }; - self.deleteTaskDataWithConfirmation = function(task) { - dataService.getTaskDiskUsageByOTDBId(task.otdb_id).then(function(du_result) { - if(du_result.found) { - openDeleteConfirmationDialog(task, du_result); - } else { - alert(du_result.message); - } - }); + self.deleteSelectedTasksDataWithConfirmation = function() { + var tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); + self.deleteTasksDataWithConfirmation(tasks); + } + + self.deleteTasksDataWithConfirmation = function(tasks) { + du_results = []; + for(var task of tasks) { + dataService.getTaskDiskUsage(task).then(function(du_result) { + if(du_result.found) { + du_results.push(du_result); + + if(du_results.length == tasks.length) { + openDeleteConfirmationDialog(du_results); + } + + console.log(du_results); + } else { + alert(du_result.message); + } + }); + } }; function deleteTaskData(task, delete_is, delete_cs, delete_uv, delete_im, delete_img, delete_pulp, delete_scratch) { @@ -37,17 +51,16 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h }); }; - function openDeleteConfirmationDialog(task, du_result) { - var path = du_result.task_directory.path; - + function openDeleteConfirmationDialog(du_results) { var modalInstance = $uibModal.open({ animation: false, template: '<div class="modal-header">\ <h3 class="modal-title">Are you sure?</h3>\ </div>\ <div class="modal-body">\ - <p>This will delete all selected data in ' + path + '<br>\ - Are you sure?</p>\ + <p>This will delete all selected data in: \ + <ul><li ng-repeat="path in paths">{{path}}</li></ul>\ + <br>Are you sure?</p>\ <label ng-if="has_is" style="margin-left:24px">IS: {{amount_is}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_is"></label>\ <label ng-if="has_cs" style="margin-left:24px">CS: {{amount_cs}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_cs"></label>\ <label ng-if="has_uv" style="margin-left:24px">UV: {{amount_uv}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_uv"></label>\ @@ -61,19 +74,53 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h <button class="btn btn-warning" type="button" autofocus ng-click="cancel()">Cancel</button>\ </div>', controller: function ($scope, $uibModalInstance) { - $scope.has_is = du_result.sub_directories.hasOwnProperty(path + '/is'); - $scope.has_cs = du_result.sub_directories.hasOwnProperty(path + '/cs'); - $scope.has_uv = du_result.sub_directories.hasOwnProperty(path + '/uv'); - $scope.has_im = du_result.sub_directories.hasOwnProperty(path + '/im'); - $scope.has_img = du_result.sub_directories.hasOwnProperty(path + '/img'); - $scope.has_pulp = du_result.sub_directories.hasOwnProperty(path + '/pulp'); - $scope.has_scratch = du_result.task_directory.hasOwnProperty('scratch_paths');; - $scope.amount_is = $scope.has_is ? du_result.sub_directories[path + '/is'].disk_usage_readable : ''; - $scope.amount_cs = $scope.has_cs ? du_result.sub_directories[path + '/cs'].disk_usage_readable : ''; - $scope.amount_uv = $scope.has_uv ? du_result.sub_directories[path + '/uv'].disk_usage_readable : ''; - $scope.amount_im = $scope.has_im ? du_result.sub_directories[path + '/im'].disk_usage_readable : ''; - $scope.amount_img = $scope.has_img ? du_result.sub_directories[path + '/img'].disk_usage_readable : ''; - $scope.amount_pulp = $scope.has_pulp ? du_result.sub_directories[path + '/pulp'].disk_usage_readable : ''; + $scope.paths = du_results.map(function(r) { return r.task_directory.path; }); + $scope.has_is = false; + $scope.has_cs = false; + $scope.has_uv = false; + $scope.has_im = false; + $scope.has_img = false; + $scope.has_pulp = false; + $scope.has_scratch = false; + $scope.amount_is = 0; + $scope.amount_cs = 0; + $scope.amount_uv = 0; + $scope.amount_im = 0; + $scope.amount_img = 0; + $scope.amount_pulp = 0; + + for(var du_result of du_results) { + var path = du_result.task_directory.path; + var has_is = du_result.sub_directories.hasOwnProperty(path + '/is'); + var has_cs = du_result.sub_directories.hasOwnProperty(path + '/cs'); + var has_uv = du_result.sub_directories.hasOwnProperty(path + '/uv'); + var has_im = du_result.sub_directories.hasOwnProperty(path + '/im'); + var has_img = du_result.sub_directories.hasOwnProperty(path + '/img'); + var has_pulp = du_result.sub_directories.hasOwnProperty(path + '/pulp'); + var has_scratch = du_result.sub_directories.hasOwnProperty('scratch_paths'); + + $scope.has_is |= has_is; + $scope.has_cs |= has_cs; + $scope.has_uv |= has_uv; + $scope.has_im |= has_im; + $scope.has_img |= has_img; + $scope.has_pulp |= has_pulp; + $scope.has_scratch |= has_scratch; + + $scope.amount_is += has_is ? du_result.sub_directories[path + '/is'].disk_usage : 0; + $scope.amount_cs += has_cs ? du_result.sub_directories[path + '/cs'].disk_usage : 0; + $scope.amount_uv += has_uv ? du_result.sub_directories[path + '/uv'].disk_usage : 0; + $scope.amount_im += has_im ? du_result.sub_directories[path + '/im'].disk_usage : 0; + $scope.amount_img += has_img ? du_result.sub_directories[path + '/img'].disk_usage : 0; + $scope.amount_pulp += has_pulp ? du_result.sub_directories[path + '/pulp'].disk_usage : 0; + } + + $scope.amount_is = dataService.humanreadablesize($scope.amount_is); + $scope.amount_cs = dataService.humanreadablesize($scope.amount_cs); + $scope.amount_uv = dataService.humanreadablesize($scope.amount_uv); + $scope.amount_im = dataService.humanreadablesize($scope.amount_im); + $scope.amount_img = dataService.humanreadablesize($scope.amount_img); + $scope.amount_pulp = dataService.humanreadablesize($scope.amount_pulp); $scope.delete_is = true; $scope.delete_cs = true; @@ -85,7 +132,10 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h $scope.ok = function () { $uibModalInstance.close(); - deleteTaskData(task, $scope.delete_is, $scope.delete_cs, $scope.delete_uv, $scope.delete_im, $scope.delete_img, $scope.delete_pulp, $scope.delete_scratch); + for(var du_result of du_results) { + var task = du_result.task; + deleteTaskData(task, $scope.delete_is, $scope.delete_cs, $scope.delete_uv, $scope.delete_im, $scope.delete_img, $scope.delete_pulp, $scope.delete_scratch); + } }; $scope.cancel = function () { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 9f7d79c7c86..b9a002dd8d0 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -48,6 +48,18 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.viewTimeSpan = {from: new Date(), to: new Date() }; self.autoFollowNow = true; + + self.humanreadablesize = function(num) { + var units = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']; + for(unit of units) { + if(Math.abs(num) < 1000.0) { + return num.toPrecision(4).toString() + unit; + } + num /= 1000.0; + } + return num.toPrecision(5).toString() + 'Y'; + } + self.isTaskIdSelected = function(task_id) { return self.selected_task_ids.indexOf(task_id) != -1; } @@ -326,9 +338,14 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }; self.getTaskDiskUsageByOTDBId = function(otdb_id) { + var task = self.tasks.find(function(t) { return t.otdb_id == otdb_id; }); + return self.getTaskDiskUsage(task); + }; + + self.getTaskDiskUsage = function(task) { var defer = $q.defer(); - $http.get('/rest/tasks/otdb/' + otdb_id + '/diskusage').success(function(result) { - console.log(result); + $http.get('/rest/tasks/otdb/' + task.otdb_id + '/diskusage').success(function(result) { + result.task = task; defer.resolve(result); }).error(function(result) { defer.resolve({found:false}); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js index de24e59997d..4ce9888a4e6 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js @@ -426,7 +426,7 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat movable: $.inArray(task.status_id, editableTaskStatusIds) > -1 }; - if(task.id == dataService.selected_task_id) { + if(dataService.isTaskIdSelected(task.id)) { claimTask.classes += ' claim-selected-task'; } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index a79e11891ed..3453ee2b01c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -229,13 +229,13 @@ $scope.columns = [ $scope.$evalAsync(fillGroupsColumFilterSelectOptions); }; - function jumpToSelectedTaskRow() { -// var taskIdx = $scope.gridOptions.data.findIndex(function(row) {return row.id == dataService.selected_task_id}); -// -// if(taskIdx > -1) { -// $scope.gridApi.selection.selectRow($scope.gridOptions.data[taskIdx]); -// $scope.gridApi.core.scrollTo($scope.gridOptions.data[taskIdx], null); -// } + function jumpToSelectedTaskRows() { + var rowIndices = dataService.selected_task_ids.map(function(t_id) { return $scope.gridOptions.data.findIndex(function(row) {return row.id == t_id; } ); }); + rowIndices = rowIndices.filter(function(idx) {return idx > -1;}).sort(); + + for(var rowIndex of rowIndices) { + $scope.gridApi.core.scrollTo($scope.gridOptions.data[rowIndex], null); + } }; function onSelectedTaskIdsChanged() { @@ -245,12 +245,14 @@ $scope.columns = [ for(var row of rows) { row.setSelected(selected_task_ids.indexOf(row.entity.id) != -1); } + + $scope.$evalAsync(jumpToSelectedTaskRows); }; $scope.$watch('dataService.taskChangeCntr', function() { $scope.$evalAsync(populateList); }); $scope.$watch('dataService.viewTimeSpan', function() { $scope.$evalAsync(populateList); - $scope.$evalAsync(jumpToSelectedTaskRow); + $scope.$evalAsync(jumpToSelectedTaskRows); }, true); $scope.$watch('dataService.initialLoadComplete', function() { @@ -318,7 +320,12 @@ gridControllerMod.directive('contextMenu', ['$document', function($document) { var taskId = rowEntity.id; var task = dataService.taskDict[taskId]; - dataService.selected_task_id = taskId; + if(!task) + return true; + + if(!dataService.isTaskIdSelected(taskId)) { + dataService.setSelectedTaskId(taskId); + } var docElement = angular.element($document); @@ -350,15 +357,18 @@ gridControllerMod.directive('contextMenu', ['$document', function($document) { ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - cleanupCtrl.deleteTaskDataWithConfirmation(task); + cleanupCtrl.deleteSelectedTasksDataWithConfirmation(); }); - var liElement = angular.element('<li><a href="#">Show disk usage</a></li>'); + var liContent = dataService.selected_task_ids.length == 1 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' + var liElement = angular.element(liContent); ulElement.append(liElement); - liElement.on('click', function() { - closeContextMenu(); - cleanupCtrl.showTaskDiskUsage(task); - }); + if(dataService.selected_task_ids.length == 1) { + liElement.on('click', function() { + closeContextMenu(); + cleanupCtrl.showTaskDiskUsage(task); + }); + } var closeContextMenu = function(cme) { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index ad9e1fbac9d..af055ba52dc 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -53,13 +53,13 @@ var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>'); contextmenuElement.append(ulElement); - var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); - ulElement.append(liElement); - liElement.on('click', function() { - closeContextMenu(); - //TODO: remove link to dataService in this generic plugin - dataService.copyTask(dScope.task.model.raTask); - }); +// var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); +// ulElement.append(liElement); +// liElement.on('click', function() { +// closeContextMenu(); +// //TODO: remove link to dataService in this generic plugin +// dataService.copyTask(dScope.task.model.raTask); +// }); var liElement = angular.element('<li><a href="#">Delete data</a></li>'); ulElement.append(liElement); @@ -68,6 +68,16 @@ cleanupCtrl.deleteTaskDataWithConfirmation(dScope.task.model.raTask); }); + var liContent = dataService.selected_task_ids.length == 1 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + if(dataService.selected_task_ids.length == 1) { + liElement.on('click', function() { + closeContextMenu(); + cleanupCtrl.showTaskDiskUsage(dScope.task.model.raTask); + }); + } + var closeContextMenu = function() { contextmenuElement.remove(); -- GitLab From aba309cf9770937499b253b1897571a039ee10b8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 21 Jul 2016 10:25:55 +0000 Subject: [PATCH 567/933] Task #9351 #9353 #9355: logging --- .../ResourceAssignmentEditor/lib/webservice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index fe31dbc97b4..e7386979280 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -276,13 +276,13 @@ def cleanupTaskData(task_id): if 'Content-Type' in request.headers and (request.headers['Content-Type'].startswith('application/json') or request.headers['Content-Type'].startswith('text/plain')): delete_params = json.loads(request.data) - print 'delete_params:', delete_params - task = rarpc.getTask(task_id) if not task: abort(404, 'No such task (id=%s)' % task_id) + logger.info("cleanup task data id=%s otdb_id=%s delete_params=%s", task_id, task['otdb_id'], delete_params) + result = curpc.removeTaskData(task['otdb_id'], delete_is=delete_params.get('delete_is', True), delete_cs=delete_params.get('delete_cs', True), -- GitLab From ce71a20b16cf87af5ebb676577fc583350436cf2 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 21 Jul 2016 10:26:20 +0000 Subject: [PATCH 568/933] Task #9351 #9353 #9355: show used/free bar --- .../app/controllers/cleanupcontroller.js | 75 ++++++++++++++++++- .../lib/templates/index.html | 10 ++- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js index ecb4601b290..1e51df29558 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -145,6 +145,9 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h }); }; + self.showAllProjectsDiskUsage = function() { + self.showTaskDiskUsage(undefined); + } self.showTaskDiskUsage = function(task) { var modalInstance = $uibModal.open({ @@ -153,6 +156,8 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h <h3 class="modal-title">Disk usage</h3>\ </div>\ <div class="modal-body" style="text-align:right">\ + <highchart id="chart_total_disk_usage" config="totalDiskUsageChartConfig" style="width: 960px; height: 120px; margin-bottom: 20px;" ></highchart>\ + <hr>\ <highchart id="chart_disk_usage" config="diskUsageChartConfig" style="width: 960px; height: 720px;" ></highchart>\ <p>\ <span style="margin-right:50px">Last updated at: {{leastRecentCacheTimestamp | date }}</span>\ @@ -241,6 +246,70 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h loading: false } + $scope.totalDiskUsageChartSeries = []; + + var cep4storage_resource = dataService.resources.find(function(r) { return r.name == 'cep4storage'; }); + if(cep4storage_resource) { + $scope.totalDiskUsageChartSeries = [{name:'Free', data:[100.0*cep4storage_resource.available_capacity/cep4storage_resource.total_capacity], color:'#a3f75c'}, + {name:'Used', data:[100.0*cep4storage_resource.used_capacity/cep4storage_resource.total_capacity], color:'#f45b5b'}]; + } + + $scope.totalDiskUsageChartConfig = { + options: { + chart: { + type: 'bar', + animation: { + duration: 200 + }, + legend: { + enabled: false + } + }, + navigation: { + buttonOptions: { + enabled: false + } + + }, + plotOptions: { + bar: { + allowPointSelect: false, + cursor: 'pointer', + dataLabels: { + enabled: false + }, + showInLegend: false, + }, + series: { + stacking: 'normal', + pointWidth: 32 + }, + }, + yAxis: { + visible: true, + title: {text:'Percentage'}, + min: 0, + max: 100, + endOnTick: false + }, + xAxis: { + visible: false + }, + tooltip: { + headerFormat: '{series.name}<br/>', + pointFormat: '{point.name}: <b>{point.percentage:.1f}%</b>' + }, + }, + series: $scope.totalDiskUsageChartSeries, + title: { + text: 'CEP4 total disk usage' + }, + credits: { + enabled: false + }, + loading: false + } + var loadTaskDiskUsage = function(otdb_id) { dataService.getTaskDiskUsageByOTDBId(otdb_id).then(function(result) { if(result.found) { @@ -328,7 +397,11 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h }); }; - loadTaskDiskUsage(task.otdb_id); + if(task) { + loadTaskDiskUsage(task.otdb_id); + } else { + loadAllProjectsDiskUsage(); + } } }); }; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index f577115eff0..61024abac9b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -75,12 +75,12 @@ <uib-timepicker ng-model="dataService.viewTimeSpan.to" ng-change="onViewTimeSpanToChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> </p> </div> - <div class="col-md-2"> + <div class="col-md-1"> <label>Scroll:</label> <p class="input-group"> <label title="Automatically scroll 'From' and 'To' to watch live events" style="padding-right: 4px; vertical-align: top;">Live <input type="checkbox" ng-model="dataService.autoFollowNow"></label> <button title="Scroll back in time" type="button" class="btn btn-default" ng-click="scrollBack()"><i class="glyphicon glyphicon-step-backward"></i></button> - <button title="Scroll forward in time"type="button" class="btn btn-default" ng-click="scrollForward()"><i class="glyphicon glyphicon-step-forward"></i></button> + <button title="Scroll forward in time" type="button" class="btn btn-default" ng-click="scrollForward()"><i class="glyphicon glyphicon-step-forward"></i></button> </p> </div> <div class="col-md-2"> @@ -89,6 +89,12 @@ <select class="form-control" ng-model=zoomTimespan ng-options="option.name for option in zoomTimespans track by option.value" ng-change="onZoomTimespanChanged(span)"></select> </p> </div> + <div class="col-md-1"> + <label></label> + <p class="input-group"> + <button title="Show disk usage by project" type="button" class="btn btn-default" ng-click="cleanupCtrl.showAllProjectsDiskUsage()"><i class="glyphicon glyphicon-floppy-disk"></i></button> + </p> + </div> </div> <div class="top-stretch" ui-layout options="{flow: 'column'}"> -- GitLab From ec175f196b2f0caa614fc95722ca95a84aeffd61 Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Thu, 21 Jul 2016 10:46:53 +0000 Subject: [PATCH 569/933] Task #9655: extra content for PowerUnits --- .gitattributes | 1 + MAC/Navigator2/panels/main.pnl | 28 +- .../objects/Hardware/Station_mainView.pnl | 24 +- .../objects/Hardware/powerUnit_small.pnl | 268 ++++++++++++++++++ MAC/Navigator2/panels/objects/baseLine.pnl | 10 +- .../panels/objects/genericOnOffView.pnl | 4 +- .../objects/navigator_viewSelection.pnl | 4 +- MAC/Navigator2/scripts/readStationConfigs.ctl | 42 ++- 8 files changed, 344 insertions(+), 37 deletions(-) create mode 100644 MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl diff --git a/.gitattributes b/.gitattributes index 025ea37510b..e315d6c8001 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4049,6 +4049,7 @@ MAC/Navigator2/panels/objects/Hardware/diskuse_small.pnl -text MAC/Navigator2/panels/objects/Hardware/lofar_HW_state.pnl -text MAC/Navigator2/panels/objects/Hardware/memuse_small.pnl -text MAC/Navigator2/panels/objects/Hardware/observationFlow_stations.pnl -text +MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl -text MAC/Navigator2/panels/objects/Hardware/superterpStatusView.pnl -text MAC/Navigator2/panels/objects/Observations/Observation_small.pnl -text MAC/Navigator2/panels/objects/Observations/Pipeline_small.pnl -text diff --git a/MAC/Navigator2/panels/main.pnl b/MAC/Navigator2/panels/main.pnl index 5c68aea3f97..aeefb440392 100644 --- a/MAC/Navigator2/panels/main.pnl +++ b/MAC/Navigator2/panels/main.pnl @@ -523,26 +523,26 @@ LANG:1 5 T H 2 46 "PRIMITIVE_TEXT18" "" -1 747 10 E E E 1 E 1 E N "_WindowText" E N "_Window" E E +1 750 10 E E E 1 E 1 E N "_WindowText" E N "_Window" E E E E 31 0 0 0 0 0 E E E 0 1 -LANG:1 27 power 48V and TBB.recording +LANG:1 39 power 48V power unit, and TBB.recording 1 "dashclr"N "_Transparent" -E E 0 1 1 2 1 E U 0 E 747 10 785 23 -0 2 2 "0s" 0 0 0 192 0 0 747 10 1 +E E 0 1 1 2 1 E U 0 E 750 10 820 23 +0 2 2 "0s" 0 0 0 192 0 0 750 10 1 1 LANG:1 34 MS Shell Dlg 2,8,-1,5,75,0,0,0,0,0 0 1 -LANG:1 6 48 TBB +LANG:1 12 48 Pow TBB 2 47 "PRIMITIVE_TEXT19" "" -1 830 10 E E E 1 E 1 E N "_WindowText" E N "_Window" E E +1 870 10 E E E 1 E 1 E N "_WindowText" E N "_Window" E E E E 32 0 0 0 0 0 E E E @@ -552,8 +552,8 @@ LANG:1 26 #faulty HBA & LBA Antennas 1 "dashclr"N "_Transparent" -E E 0 1 1 2 1 E U 0 E 830 10 877 23 -0 2 2 "0s" 0 0 0 192 0 0 830 10 1 +E E 0 1 1 2 1 E U 0 E 870 10 917 23 +0 2 2 "0s" 0 0 0 192 0 0 870 10 1 1 LANG:1 34 MS Shell Dlg 2,8,-1,5,75,0,0,0,0,0 0 1 @@ -732,7 +732,7 @@ LANG:1 14 processSubtype 2 117 "PRIMITIVE_TEXT39" "" -1 940 10 E E E 1 E 1 E N "_WindowText" E N "_Window" E E +1 970 10 E E E 1 E 1 E N "_WindowText" E N "_Window" E E E E 54 0 0 0 0 0 E E E @@ -742,8 +742,8 @@ LANG:1 26 #faulty HBA & LBA Antennas 1 "dashclr"N "_Transparent" -E E 0 1 1 2 1 E U 0 E 940 10 1019 23 -0 2 2 "0s" 0 0 0 192 0 0 940 10 1 +E E 0 1 1 2 1 E U 0 E 970 10 1049 23 +0 2 2 "0s" 0 0 0 192 0 0 970 10 1 1 LANG:1 34 MS Shell Dlg 2,8,-1,5,75,0,0,0,0,0 0 1 @@ -751,7 +751,7 @@ LANG:1 13 input streams 2 172 "PRIMITIVE_TEXT40" "" -1 790 10 E E E 1 E 1 E N "_WindowText" E N "_Window" E E +1 830 10 E E E 1 E 1 E N "_WindowText" E N "_Window" E E E E 65 0 0 0 0 0 E E E @@ -761,8 +761,8 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 1 1 2 1 E U 0 E 790 10 823 23 -0 2 2 "0s" 0 0 0 192 0 0 790 10 1 +E E 0 1 1 2 1 E U 0 E 830 10 863 23 +0 2 2 "0s" 0 0 0 192 0 0 830 10 1 1 LANG:1 34 MS Shell Dlg 2,8,-1,5,75,0,0,0,0,0 0 1 diff --git a/MAC/Navigator2/panels/objects/Hardware/Station_mainView.pnl b/MAC/Navigator2/panels/objects/Hardware/Station_mainView.pnl index 62cc8cdb057..2d103d58362 100644 --- a/MAC/Navigator2/panels/objects/Hardware/Station_mainView.pnl +++ b/MAC/Navigator2/panels/objects/Hardware/Station_mainView.pnl @@ -1,7 +1,7 @@ V 11 1 LANG:1 0 -PANEL,-1 -1 380 26 N "_3DFace" 1 +PANEL,-1 -1 380 22 N "_3DFace" 1 "$station" "main() { @@ -81,6 +81,8 @@ LANG:1 0 0 1 37 13 "" 0 0 +1 38 14 "" 0 +0 0 LAYER, 1 1 @@ -120,17 +122,17 @@ LANG:1 0 "$fullDP""LOFAR_PIC_StationInfo.power48On" "$station""$station" 3 4 "PANEL_REF4" -1 -"objects\\Hardware\\antennaBroken_Small.pnl" 80 0 T 37 1 0 1 82 0 +"objects\\Hardware\\antennaBroken_Small.pnl" 80 0 T 37 1 0 1 112 0 2 "$antennaType""HBA" "$station""$station" 3 5 "PANEL_REF5" -1 -"objects\\Hardware\\antennaBroken_Small.pnl" 100 0 T 38 1 0 1 90 0 +"objects\\Hardware\\antennaBroken_Small.pnl" 100 0 T 38 1 0 1 120 0 2 "$antennaType""LBA" "$station""$station" 3 6 "PANEL_REF6" -1 -"objects\\Hardware\\TBBmode_small.pnl" 60 0 T 39 1 0 1 40 0 +"objects\\Hardware\\TBBmode_small.pnl" 60 0 T 39 1 0 1 70 0 1 "$station""$station" 3 7 "PANEL_REF7" -1 @@ -142,27 +144,31 @@ LANG:1 0 1 "$station""$station" 3 9 "PANEL_REF9" -1 -"objects\\Processes\\stationStreamView_small.pnl" 250 0 T 41 1 0 1 20 0 +"objects\\Processes\\stationStreamView_small.pnl" 250 0 T 41 1 0 1 50 0 2 "$station""$station" "$streamNr""0" 3 10 "PANEL_REF10" -1 -"objects\\Processes\\stationStreamView_small.pnl" 270 0 T 42 1 0 1 20 0 +"objects\\Processes\\stationStreamView_small.pnl" 270 0 T 42 1 0 1 50 0 2 "$station""$station" "$streamNr""1" 3 11 "PANEL_REF11" -1 -"objects\\Processes\\stationStreamView_small.pnl" 290 0 T 43 1 0 1 20 0 +"objects\\Processes\\stationStreamView_small.pnl" 290 0 T 43 1 0 1 50 0 2 "$station""$station" "$streamNr""2" 3 12 "PANEL_REF12" -1 -"objects\\Processes\\stationStreamView_small.pnl" 310 0 T 44 1 0 1 20 0 +"objects\\Processes\\stationStreamView_small.pnl" 310 0 T 44 1 0 1 50 0 2 "$station""$station" "$streamNr""3" 3 13 "PANEL_REF13" -1 -"objects\\Hardware\\AARTFAAC_small.pnl" 130 0 T 44 U +"objects\\Hardware\\AARTFAAC_small.pnl" 130 0 T 44 1 0 1 30 0 +1 +"$station""$station" +3 14 "PANEL_REF14" -1 +"objects/Hardware\\powerUnit_small.pnl" 100 0 T 44 U 1 "$station""$station" 0 diff --git a/MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl b/MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl new file mode 100644 index 00000000000..d8281fed8cb --- /dev/null +++ b/MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl @@ -0,0 +1,268 @@ +V 11 +1 +LANG:1 0 +PANEL,-1 -1 388 166 N "_3DFace" 1 +"$station" +"main() +{ + dyn_dyn_anytype tab; + + station = $station+\":\"; + + if (dpExists(station+\"LOFAR_PIC_StationInfo.nrOfPowerUnits\")) + { + dpGet(station+\"LOFAR_PIC_StationInfo.nrOfPowerUnits\",nrOfPowerUnits); + reload(); + } + else + { + setValue(\"powerUnit\", \"backCol\", \"Lofar_dpdoesnotexist\"); + } +} + +private void reload() +{ + + // check if the required datapoint for this view are enabled and accessible + if (navFunct_dpReachable(station+\"LOFAR_PIC_POWEC0\")) + { + dyn_string connectpoints; + dynAppend(connectpoints,station+\"LOFAR_PIC_POWEC0.nrOfModules:_online.._invalid\"); + dynAppend(connectpoints,station+\"LOFAR_PIC_POWEC0.nrOfModules:_online.._value\"); + dynAppend(connectpoints,station+\"LOFAR_PIC_POWEC0.OK:_online.._value\"); + if (nrOfPowerUnits == 2) + { + dynAppend(connectpoints,station+\"LOFAR_PIC_POWEC1.nrOfModules:_online.._value\"); + dynAppend(connectpoints,station+\"LOFAR_PIC_POWEC1.OK:_online.._value\"); + dpConnect(\"update2Powecs\",connectpoints); + } + else + { + dpConnect(\"updatePowec\",connectpoints); + } + } + else + { + if (dpExists(\"LOFAR_PIC_POWEC0\")) + { + setValue(\"powerUnit\", \"backCol\", \"Lofar_dpOffline\"); + } + else + { + setValue(\"powerUnit\", \"backCol\", \"Lofar_dpdoesnotexist\"); + } + } +} + +void updatePowec(string dp1, bool invalid, + string dp2, int nrOfModules, + string dp3, dyn_int OK) +{ + + string tooltip= station + \" PowerUnit <br>\"; + string color = \"Lofar_operational\"; + + if (invalid) + { + color = \"Lofar_broken\"; + tooltip += \"POWEC0: invalid, unit might be turned off?\"; + } + else + { + + // Sometimes the boards doesn't give an answer anymore, in that case nrOfModules = 0, so thats an error, + // For now only a powercycle is known to resolve this issue + if (nrOfModules < 1) { + tooltip += \"POWEC0: nrOfModules = 0, unit might be stalled\"; + color = \"Lofar_suspicious\"; + } + else if (nrOfModules != dynlen(OK)) + { + tooltip += \"POWEC0: nrOfModules and OKArray length differ\"; + color = \"Lofar_broken\"; + } + else + { + tooltip += \"POWEC0: <br>\"; + bool ok = true; + for (int i = 1; i <= nrOfModules; i++) + { + if (!OK[i]) + { + ok = false; + tooltip += \" Module [\"+i+\"] not OK <br>\"; + color = \"Lofar_broken\"; + } + } + if (ok) tooltip += \"All Modules OK\"; + } + } + setValue(\"powerUnit\",\"toolTipText\",tooltip); + setValue(\"powerUnit\", \"backCol\", color); +} + +void update2Powecs(string dp1, int nrOfModules1, + string dp2, dyn_int OK1, + string dp3, int nrOfModules2, + string dp4, dyn_int OK2) +{ + + string tooltip= station + \" PowerUnit <br>\"; + string color = \"Lofar_operational\"; + + // Sometimes the boards doesn't give an answer anymore, in that case nrOfModules = 0, so thats an error, + // For now only a powercycle is known to resolve this issue + if (nrOfModules1 < 1) { + tooltip += \"POWEC0: nrOfModules = 0, unit might be stalled\"; + color = \"Lofar_suspicious\"; + } + else if (nrOfModules2 < 1) + { + tooltip += \"POWEC1: nrOfModules = 0, unit might be stalled\"; + color = \"Lofar_suspicious\"; + } + else if (nrOfModules1 != dynlen(OK1)) + { + tooltip += \"POWEC0: nrOfModules and OKArray length differ\"; + color = \"Lofar_broken\"; + } + else if (nrOfModules2 != dynlen(OK2)) + { + tooltip += \"POWEC1: nrOfModules and OKArray length differ\"; + color = \"Lofar_broken\"; + } + else + { + tooltip += \"POWEC0: </br>\"; + bool ok = true; + for (int i = 1; i <= nrOfModules1; i++) + { + if (!OK1[i]) + { + ok = false; + tooltip += \" Module [\"+i+\"] not OK </br>\"; + } + } + if (ok) tooltip += \"All Modules OK <br>\"; + + ok = true; + tooltip += \"POWEC1: </br>\"; + + for (int i = 1; i <= nrOfModules2; i++) + { + if (!OK2[i]) + { + ok = false; + tooltip += \" Module [\"+i+\"] not OK </br>\"; + } + } + if (ok) tooltip += \"All Modules OK <br>\"; + } + + setValue(\"powerUnit\",\"toolTipText\",tooltip); + setValue(\"powerUnit\", \"backCol\", color); + +} + +" 0 + E E E E 1 -1 -1 0 0 0 +""0 1 +E "#uses \"navPanel.ctl\" +string station = \"\"; +string baseDP=\"\"; +int nrOfPowerUnits = 0; + +bool bDoubleClicked = false; + +// routine for single mouse click +void click() { + // set delay in case double click was meant + delay(0, 100); + if (!bDoubleClicked) { + navPanel_setEvent(station,\"EventClick\"); + } +} + +// routine for double mouse click +void dblClick() { + // indicate this is a doubleClick + bDoubleClicked = true; + + if (dpExists(baseDP) ) { + LOG_DEBUG(\"BPTemp_small.pnl:DoubleClick|Setting currentDatapoint from : \"+g_currentDatapoint+\" to \"+baseDP); + g_currentDatapoint=baseDP; + navPanel_setEvent(station,\"ChangePanel\"); + } + + // set delay to avoid click event will be triggered + delay(0, 500); + bDoubleClicked = false; +} + +// routine for right mouse click +void rClick() { +}" 0 + 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 0 +6 0 +"powerUnit" +"" +1 230 150 E E E 1 E 1 E N {0,0,0} E N {255,255,255} E E + "main() +{ + rClick(); +}" 0 + "main() +{ + dblClick(); +}" 0 + +0 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E "main() +{ + click(); +}" 0 + 0 1 1 2 1 E 1 0 1 0 -10 1 E 0 10 14 19 +0 +LAYER, 1 +1 +LANG:1 0 +0 +LAYER, 2 +1 +LANG:1 0 +0 +LAYER, 3 +1 +LANG:1 0 +0 +LAYER, 4 +1 +LANG:1 0 +0 +LAYER, 5 +1 +LANG:1 0 +0 +LAYER, 6 +1 +LANG:1 0 +0 +LAYER, 7 +1 +LANG:1 0 +0 +0 diff --git a/MAC/Navigator2/panels/objects/baseLine.pnl b/MAC/Navigator2/panels/objects/baseLine.pnl index 6167e5c119a..ad48ada04cb 100644 --- a/MAC/Navigator2/panels/objects/baseLine.pnl +++ b/MAC/Navigator2/panels/objects/baseLine.pnl @@ -190,7 +190,7 @@ void swlevel_stationCallback(string dp1, bool aTrig) { { click(); }" 0 - 0 1 1 2 1 E U 1 E 0 0 931 15 + 0 1 1 2 1 E U 1 E 0 0 961 15 6 0 "bar" "" @@ -204,11 +204,11 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 0 1 2 1 E U 1 E 3 3 928 12 +E E 0 0 1 2 1 E U 1 E 3 3 958 12 2 4 "station_text" "" -1 890 0 E E E 1 E 1 E N "_WindowText" E N "_Transparent" E E +1 928 0 E E E 1 E 1 E N "_WindowText" E N "_Transparent" E E "main() { rClick(station); @@ -230,8 +230,8 @@ E "main(int x, int y) { click(station); }" 0 - 0 1 1 2 1 E U 0 E 890 0 918 13 -0 2 2 "0s" 0 0 0 192 0 0 890 0 1 + 0 1 1 2 1 E U 0 E 928 0 956 13 +0 2 2 "0s" 0 0 0 192 0 0 928 0 1 1 LANG:1 34 MS Shell Dlg 2,8,-1,5,50,0,0,0,0,0 0 1 diff --git a/MAC/Navigator2/panels/objects/genericOnOffView.pnl b/MAC/Navigator2/panels/objects/genericOnOffView.pnl index 01fb943a6ba..210c9e22403 100644 --- a/MAC/Navigator2/panels/objects/genericOnOffView.pnl +++ b/MAC/Navigator2/panels/objects/genericOnOffView.pnl @@ -18,7 +18,6 @@ private void reload() { if (navFunct_dpReachable(fullDP)) { string dp = dpSubStr(fullDP,DPSUB_DP); - setValue(\"viewObject\",\"toolTipText\",fullDP ); if ( dp != \"\") { baseDP = station+dp; @@ -47,7 +46,8 @@ updateViewObject(string dp1, bool on, string color = \"Lofar_operational\"; if (!on) { color = \"Lofar_broken\"; - } + } + setValue(\"viewObject\",\"toolTipText\",\"Power: \" + on ); setValue(\"viewObject\",\"backCol\", color); }" 0 E E E E 1 -1 -1 0 0 0 diff --git a/MAC/Navigator2/panels/objects/navigator_viewSelection.pnl b/MAC/Navigator2/panels/objects/navigator_viewSelection.pnl index c71f7e5d026..7c53e2c4164 100644 --- a/MAC/Navigator2/panels/objects/navigator_viewSelection.pnl +++ b/MAC/Navigator2/panels/objects/navigator_viewSelection.pnl @@ -139,7 +139,7 @@ LANG:1 6 Alerts 22 14 "panelChoice" "" -1 1043 36 E E E 1 E 0 E N "_WindowText" E N "_Window" E E +1 1060 36 E E E 1 E 0 E N "_WindowText" E N "_Window" E E E E 15 0 0 0 0 0 E E E @@ -150,7 +150,7 @@ LANG:1 0 0 1 LANG:1 33 MS Shell Dlg,-1,11,5,50,0,0,0,0,0 -0 1041 34 1195 55 +0 1058 34 1195 55 0 E diff --git a/MAC/Navigator2/scripts/readStationConfigs.ctl b/MAC/Navigator2/scripts/readStationConfigs.ctl index bc7be785621..5b11a8801c6 100644 --- a/MAC/Navigator2/scripts/readStationConfigs.ctl +++ b/MAC/Navigator2/scripts/readStationConfigs.ctl @@ -50,6 +50,8 @@ global bool bDebug = false; global int nr_HBA=0; global int nr_LBA=0; + global int nr_POWECS=0; + global string stationIP = ""; global bool norVecLBAFound=false; global bool norVecHBAFound=false; global bool norVecHBA0Found=false; @@ -208,6 +210,14 @@ main() } } + if (strpos(dynStr_fileContent[index],"RS.STATION_IP")>-1) { + dyn_string value = strsplit(dynStr_fileContent[index],"="); + if (dynlen(value) > 1) { + stationIP = value[2]; + dpSet("LOFAR_PIC_StationInfo.stationIP",stationIP); + } + } + if (strpos(dynStr_fileContent[index],"RS.N_RSPBOARDS")>-1) { dyn_string value = strsplit(dynStr_fileContent[index],"="); if (dynlen(value) > 1) { @@ -239,7 +249,8 @@ main() if (strpos(dynStr_fileContent[index],"RS.N_POWECS")>-1) { dyn_string value = strsplit(dynStr_fileContent[index],"="); if (dynlen(value) > 1) { - dpSet("LOFAR_PIC_StationInfo.nrOfPowerUnits",value[2]); + nr_POWECS = value[2]; + dpSet("LOFAR_PIC_StationInfo.nrOfPowerUnits",nr_POWECS); } } @@ -285,9 +296,32 @@ main() } } } - - } + + // set POWEC IP address for all available POWECS. + dyn_string ipparts = strsplit(stationIP,"."); + string ip = ipparts[1] + "." + ipparts[2] + "." + ipparts[3] + "."; + + dpSet("_2_SNMPAgent_1.Access.IPAddress",ip+"5"); + dpSet("LOFAR_PIC_POWEC0.nrOfModules:_address.._active",1); + dpSet("LOFAR_PIC_POWEC0.voltage:_address.._active",1); + dpSet("LOFAR_PIC_POWEC0.current:_address.._active",1); + dpSet("LOFAR_PIC_POWEC0.temperature:_address.._active",1); + dpSet("LOFAR_PIC_POWEC0.OK:_address.._active",1); + if (nr_POWECS == 2) { + dpSet("_2_SNMPAgent_2.Access.IPAddress",ip+"7"); + dpSet("LOFAR_PIC_POWEC1.nrOfModules:_address.._active",1); + dpSet("LOFAR_PIC_POWEC1.voltage:_address.._active",1); + dpSet("LOFAR_PIC_POWEC1.current:_address.._active",1); + dpSet("LOFAR_PIC_POWEC1.temperature:_address.._active",1); + dpSet("LOFAR_PIC_POWEC1.OK:_address.._active",1); + } else if (nr_POWECS == 1) { + dpSet("LOFAR_PIC_POWEC1.nrOfModules:_address.._active",0); + dpSet("LOFAR_PIC_POWEC1.voltage:_address.._active",0); + dpSet("LOFAR_PIC_POWEC1.current:_address.._active",0); + dpSet("LOFAR_PIC_POWEC1.temperature:_address.._active",0); + dpSet("LOFAR_PIC_POWEC1.OK:_address.._active",0); + } } @@ -382,8 +416,6 @@ void processRotationMatrix(string aS) { "LOFAR_PIC_StationInfo.HBA.HBA0.RotationMatrix.Y",fY, "LOFAR_PIC_StationInfo.HBA.HBA0.RotationMatrix.Z",fZ); } - - } void processFieldCenter(string aS) { -- GitLab From 74f0f721435fe6f70487da0a696a3c8dfb227f29 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 21 Jul 2016 12:51:40 +0000 Subject: [PATCH 570/933] Task #9607: logging in processPredecessors --- .../ResourceAssigner/lib/assignment.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 9344f5b7bba..b0c49e49459 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -215,14 +215,27 @@ class ResourceAssigner(): if predecessor_trees: otdb_id = specification_tree['otdb_id'] + pred_otdb_ids = [pt['otdb_id'] for pt in predecessor_trees] + logger.info('proccessing predecessors otdb_ids=%s for otdb_id=%s', pred_otdb_ids, otdb_id) + task = self.radbrpc.getTask(otdb_id=otdb_id) - for predecessor_tree in predecessor_trees: - pred_otdb_id = predecessor_tree['otdb_id'] - predecessor_task = self.radbrpc.getTask(otdb_id=pred_otdb_id) - if predecessor_task and predecessor_task['id'] not in task['predecessor_ids']: - self.radbrpc.insertTaskPredecessor(task['id'], predecessor_task['id']) - self.processPredecessors(predecessor_tree) + if task: + for predecessor_tree in predecessor_trees: + #first process the predecessor's predecessors + self.processPredecessors(predecessor_tree) + + #then check if the predecessor needs to be linked to this task + pred_otdb_id = predecessor_tree['otdb_id'] + predecessor_task = self.radbrpc.getTask(otdb_id=pred_otdb_id) + if predecessor_task: + if predecessor_task['id'] not in task['predecessor_ids']: + logger.info('connecting predecessor task with otdb_id=%s to it\'s successor with otdb_id=%s', pred_otdb_id, otdb_id) + self.radbrpc.insertTaskPredecessor(task['id'], predecessor_task['id']) + else: + logger.warning('could not find predecessor task with otdb_id=%s in radb for task otdb_id=%s', pred_otdb_id, otdb_id) + else: + logger.info('no predecessors for otdb_id=%s', specification_tree['otdb_id']) except Exception as e: logger.error(e) -- GitLab From ce01c783cdcf2bff28c3d886047bf37ed0d53c6f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 21 Jul 2016 12:52:16 +0000 Subject: [PATCH 571/933] Task #9351 #9353 #9355: select by group --- .../lib/static/app/controllers/datacontroller.js | 8 ++++++++ .../lib/static/app/controllers/gridcontroller.js | 7 +++++++ .../app/gantt-plugins/angular-gantt-contextmenu-plugin.js | 7 +++++++ 3 files changed, 22 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index e9af7da1c89..c9a34a74b63 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -90,6 +90,14 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.selected_task_ids.push(task_id); } + self.selectTasksInSameGroup = function(task) { + self.selected_task_ids.splice(0, self.selected_task_ids.length); + var groupTasks = self.filteredTasks.filter(function(t) { return t.mom_object_group_id == task.mom_object_group_id; }); + for(var t of groupTasks) { + self.selected_task_ids.push(t.id); + } + } + self.floorDate = function(date, hourMod=1, minMod=1) { var min = date.getMinutes(); min = date.getMinutes()/minMod; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index e64b3574dbb..8d8bccda7d8 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -346,6 +346,13 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>'); contextmenuElement.append(ulElement); + var liElement = angular.element('<li><a href="#">Select group</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + dataService.selectTasksInSameGroup(task); + }); + if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>'); ulElement.append(liElement); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index 33c7cb4d434..464606c3221 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -64,6 +64,13 @@ // dataService.copyTask(task); // }); + var liElement = angular.element('<li><a href="#">Select group</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + dataService.selectTasksInSameGroup(task); + }); + if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>'); ulElement.append(liElement); -- GitLab From 81b1aae2c4da20d6d1519eb4f58f6f9a72a45a8b Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Thu, 21 Jul 2016 13:26:27 +0000 Subject: [PATCH 572/933] Task #9667: Fixes for correct number of output files when using beamformed with tab rings --- SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index 0dbadd795c0..0f2031fc96d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -59,8 +59,8 @@ class ResourceEstimatorHandler(MessageHandlerInterface): logger.info(("Branch estimates for %s\n" % otdb_id) + pprint.pformat(branch_estimates)) if specification_tree['task_subtype'] in ['averaging pipeline', 'calibration pipeline']: + input_files = {} for id, estimate in branch_estimates.iteritems(): - input_files = {} predecessor_output = estimate.values()[0]['storage']['output_files'] if not 'im' in predecessor_output and 'uv' in predecessor_output: # Not a calibrator pipeline logger.info('found %s as the target of pipeline %s' % (id, otdb_id)) -- GitLab From b049a9ae540b04416c50d813f7f0b09e0959581c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 21 Jul 2016 13:33:31 +0000 Subject: [PATCH 573/933] Task #9351 #9353 #9355: only enable context menu items for cep4 --- .../static/app/controllers/gridcontroller.js | 36 ++++++++++++++----- .../angular-gantt-contextmenu-plugin.js | 28 ++++++++++----- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 8d8bccda7d8..7f2833ef4ac 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -346,6 +346,20 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>'); contextmenuElement.append(ulElement); + var selected_tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); + var selected_cep4_tasks = selected_tasks.filter(function(t) { + var task_claims = dataService.resourceClaims.filter(function(rc) { return rc.task_id == t.id;}); + return task_claims.length > 0; + }); + +// var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); +// ulElement.append(liElement); +// liElement.on('click', function() { +// closeContextMenu(); +// //TODO: remove link to dataService in this generic plugin +// dataService.copyTask(task); +// }); + var liElement = angular.element('<li><a href="#">Select group</a></li>'); ulElement.append(liElement); liElement.on('click', function() { @@ -358,8 +372,7 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - var tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); - for(var t of tasks) { + for(var t of selected_tasks) { if(t) { var url = dataService.config.inspection_plots_base_url + '/' + t.otdb_id; $window.open(url, '_blank'); @@ -368,22 +381,27 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } - var liContent = dataService.selected_task_ids.length == 1 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' + var liContent = selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); - if(dataService.selected_task_ids.length == 1) { + if(selected_cep4_tasks.length == selected_tasks.length) { liElement.on('click', function() { closeContextMenu(); cleanupCtrl.showTaskDiskUsage(task); }); } - var liElement = angular.element('<li><a href="#">Delete data</a></li>'); + var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); + + var liContent = completed_selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>' + var liElement = angular.element(liContent); ulElement.append(liElement); - liElement.on('click', function() { - closeContextMenu(); - cleanupCtrl.deleteSelectedTasksDataWithConfirmation(); - }); + if(completed_selected_cep4_tasks.length == selected_tasks.length) { + liElement.on('click', function() { + closeContextMenu(); + cleanupCtrl.deleteSelectedTasksDataWithConfirmation(); + }); + } var closeContextMenu = function(cme) { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index 464606c3221..a984d944720 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -56,6 +56,12 @@ var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>'); contextmenuElement.append(ulElement); + var selected_tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); + var selected_cep4_tasks = selected_tasks.filter(function(t) { + var task_claims = dataService.resourceClaims.filter(function(rc) { return rc.task_id == t.id;}); + return task_claims.length > 0; + }); + // var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); // ulElement.append(liElement); // liElement.on('click', function() { @@ -76,8 +82,7 @@ ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - var tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); - for(var t of tasks) { + for(var t of selected_tasks) { if(t) { var url = dataService.config.inspection_plots_base_url + '/' + t.otdb_id; $window.open(url, '_blank'); @@ -86,22 +91,27 @@ }); } - var liContent = dataService.selected_task_ids.length == 1 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' + var liContent = selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); - if(dataService.selected_task_ids.length == 1) { + if(selected_cep4_tasks.length == selected_tasks.length) { liElement.on('click', function() { closeContextMenu(); cleanupCtrl.showTaskDiskUsage(task); }); } - var liElement = angular.element('<li><a href="#">Delete data</a></li>'); + var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); + + var liContent = completed_selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>' + var liElement = angular.element(liContent); ulElement.append(liElement); - liElement.on('click', function() { - closeContextMenu(); - cleanupCtrl.deleteSelectedTasksDataWithConfirmation(); - }); + if(completed_selected_cep4_tasks.length == selected_tasks.length) { + liElement.on('click', function() { + closeContextMenu(); + cleanupCtrl.deleteSelectedTasksDataWithConfirmation(); + }); + } var closeContextMenu = function() { contextmenuElement.remove(); -- GitLab From cacce2ff82b930642aaf28f6c8556350aad597d8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 22 Jul 2016 06:35:40 +0000 Subject: [PATCH 574/933] Task #9607: fill project and group filters with entries from filtered tasks --- .../static/app/controllers/gridcontroller.js | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 7f2833ef4ac..461b7892185 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -78,7 +78,7 @@ $scope.columns = [ { field: 'mom_object_group_id', displayName: 'Group', enableCellEdit: false, - cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity.mom_object_group_name}} {{row.entity.mom_object_group_id}}</a>', + cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity.mom_object_group_id}} {{row.entity.mom_object_group_name}}</a>', width: '13%', filter: { condition: uiGridConstants.filter.EXACT, @@ -255,6 +255,11 @@ $scope.columns = [ $scope.$evalAsync(jumpToSelectedTaskRows); }, true); + $scope.$watch('dataService.filteredTaskChangeCntr', function() { + $scope.$evalAsync(fillProjectsColumFilterSelectOptions) + $scope.$evalAsync(fillGroupsColumFilterSelectOptions); + }); + $scope.$watch('dataService.initialLoadComplete', function() { $scope.$evalAsync(function() { taskstatustypenames = $scope.dataService.taskstatustypes.map(function(x) { return x.name; }); @@ -271,10 +276,10 @@ $scope.columns = [ function fillProjectsColumFilterSelectOptions() { var projectNames = []; var momProjectsDict = $scope.dataService.momProjectsDict; - var tasks = $scope.dataService.tasks; + var tasks = $scope.dataService.filteredTasks; //get unique projectIds from tasks var task_project_ids = tasks.map(function(t) { return t.project_mom_id; }); - task_project_ids = task_project_ids.filter(function(value, index, arr) { return arr.indexOf(value) == index;}) + task_project_ids = task_project_ids.filter(function(value, index) { return task_project_ids.indexOf(value) == index;}) for(var project_id of task_project_ids) { if(momProjectsDict.hasOwnProperty(project_id)) { @@ -289,13 +294,22 @@ $scope.columns = [ }; function fillGroupsColumFilterSelectOptions() { - var tasks = $scope.dataService.tasks; + var tasks = $scope.dataService.filteredTasks; //get unique groupNames from tasks - var groupNamesAndIds = tasks.map(function(t) { return { name: t.mom_object_group_name, id: t.mom_object_group_id }; }); - groupNamesAndIds = groupNamesAndIds.filter(function(value, index, arr) { return arr.indexOf(value) == index;}) - groupNamesAndIds.sort(); + var groupId2Name = {}; + var groupIds = []; + + for(var task of tasks) { + if(task.mom_object_group_id) { + if(!groupId2Name.hasOwnProperty(task.mom_object_group_id)) { + groupId2Name[task.mom_object_group_id] = task.mom_object_group_name; + groupIds.push(task.mom_object_group_id); + } + } + } - var groupSelectOptions = groupNamesAndIds.map(function(obj) { return { value: obj.id, label: obj.name + ' ' + obj.id }; }); + var groupSelectOptions = groupIds.map(function(gid) { return { value: gid, label: gid + ' ' + groupId2Name[gid]}; }); + groupSelectOptions.sort(function(a,b) { return a.value - b.value; }); fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[7]); }; -- GitLab From 1a5dcb61b114d5b97ee9422b0870dd52eb8150a5 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 22 Jul 2016 07:14:37 +0000 Subject: [PATCH 575/933] Task #9607: some minor performance improvements --- .../chartresourceusagecontroller.js | 32 +++++++++++-------- .../static/app/controllers/datacontroller.js | 8 +---- .../app/controllers/ganttprojectcontroller.js | 14 +++++--- .../controllers/ganttresourcecontroller.js | 16 ++++++---- 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/chartresourceusagecontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/chartresourceusagecontroller.js index 2164ee9da8a..e1c1b062cff 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/chartresourceusagecontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/chartresourceusagecontroller.js @@ -61,17 +61,24 @@ chartResourceUsageControllerMod.controller('ChartResourceUsageController', ['$sc function updateChartLofarTime() { var lofarTime = $scope.dataService.lofarTime; - if(lofarTime.getSeconds() % 5 == 0) { - $scope.chartConfig.xAxis.plotLines = [{ - width: 3, - color: '#222222', - zIndex: 100, - value: lofarTime.getTime() - }]; - } + $scope.chartConfig.xAxis.plotLines = [{ + width: 3, + color: '#222222', + zIndex: 100, + value: lofarTime.getTime() + }]; }; - $scope.$watch('dataService.lofarTime', updateChartLofarTime); + $scope.$watch('dataService.lofarTime', function() { + var lofarTime = $scope.dataService.lofarTime; + if(lofarTime.getSeconds() % 10 == 0) { + $scope.$evalAsync(updateChartLofarTime); + } + }); + + function updateChartDataAsync() { + $scope.$evalAsync(updateChartData); + }; function updateChartData() { var selected_resource_id = $scope.dataService.selected_resource_id; @@ -237,9 +244,8 @@ chartResourceUsageControllerMod.controller('ChartResourceUsageController', ['$sc } }; - $scope.$watch('dataService.selected_resource_id', updateChartData); - $scope.$watch('dataService.resources', updateChartData, true); - $scope.$watch('dataService.resourceUsagesChangeCntr', function() { $scope.$evalAsync(updateChartData); }); - $scope.$watch('dataService.viewTimeSpan', updateChartData, true); + $scope.$watch('dataService.selected_resource_id', updateChartDataAsync); + $scope.$watch('dataService.resourceUsagesChangeCntr', updateChartDataAsync); + $scope.$watch('dataService.viewTimeSpan', updateChartDataAsync, true); } ]); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index c9a34a74b63..9cfefb6a589 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -908,13 +908,7 @@ dataControllerMod.controller('DataController', $scope.$evalAsync(function() { dataService.getTasksAndClaimsForViewSpan(); }); }, true); - $scope.$watch('dataService.filteredTasks', dataService.computeMinMaxTaskTimes); - - $scope.$watch('dataService.lofarTime', function() { - if(dataService.autoFollowNow && (Math.round(dataService.lofarTime.getTime()/1000))%5==0) { - $scope.jumpToNow(); - } - }); + $scope.$watch('dataService.filteredTaskChangeCntr', dataService.computeMinMaxTaskTimes); $scope.$watch('dataService.lofarTime', function() { if(dataService.autoFollowNow && (Math.round(dataService.lofarTime.getTime()/1000))%5==0) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index 09fd23f3e00..526e3c87849 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -101,6 +101,10 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS } }; + function updateGanttDataAsync() { + $scope.$evalAsync(updateGanttData); + }; + function updateGanttData() { if(!dataService.initialLoadComplete) { return; @@ -229,13 +233,13 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS $scope.ganttData = ganntRows; }; - $scope.$watch('dataService.initialLoadComplete', function() { $scope.$evalAsync(updateGanttData); }); - $scope.$watch('dataService.selected_task_ids', function() { $scope.$evalAsync(updateGanttData); }, true); - $scope.$watch('dataService.viewTimeSpan', function() { $scope.$evalAsync(updateGanttData); }, true); - $scope.$watch('dataService.filteredTaskChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); + $scope.$watch('dataService.initialLoadComplete', function() { updateGanttDataAsync(); }); + $scope.$watch('dataService.selected_task_ids', function() { updateGanttDataAsync(); }, true); + $scope.$watch('dataService.viewTimeSpan', function() { updateGanttDataAsync(); }, true); + $scope.$watch('dataService.filteredTaskChangeCntr', function() { updateGanttDataAsync(); }); $scope.$watch('dataService.lofarTime', function() { $scope.$evalAsync(function() { - if($scope.dataService.lofarTime.getSeconds() % 5 == 0) { + if($scope.dataService.lofarTime.getSeconds() % 10 == 0) { $scope.options.currentDateValue= $scope.dataService.lofarTime;} }); }); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js index 4ce9888a4e6..85eb5f313d9 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js @@ -125,6 +125,10 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat $scope.dataService.putTask(updatedTask); }; + function updateGanttDataAsync() { + $scope.$evalAsync(updateGanttData); + }; + function updateGanttData() { if(!dataService.initialLoadComplete) { return; @@ -467,14 +471,14 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat } }; - $scope.$watch('dataService.initialLoadComplete', function() { $scope.$evalAsync(updateGanttData); }); - $scope.$watch('dataService.selected_task_ids', function() { $scope.$evalAsync(updateGanttData); }, true); - $scope.$watch('dataService.viewTimeSpan', function() { $scope.$evalAsync(updateGanttData); }, true); - $scope.$watch('dataService.claimChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); - $scope.$watch('dataService.filteredTaskChangeCntr', function() { $scope.$evalAsync(updateGanttData); }); + $scope.$watch('dataService.initialLoadComplete', function() { updateGanttDataAsync(); }); + $scope.$watch('dataService.selected_task_ids', function() { updateGanttDataAsync(); }, true); + $scope.$watch('dataService.viewTimeSpan', function() { updateGanttDataAsync(); }, true); + $scope.$watch('dataService.claimChangeCntr', function() { updateGanttDataAsync(); }); + $scope.$watch('dataService.filteredTaskChangeCntr', function() { updateGanttDataAsync(); }); $scope.$watch('dataService.lofarTime', function() { $scope.$evalAsync(function() { - if($scope.dataService.lofarTime.getSeconds() % 5 == 0) { + if($scope.dataService.lofarTime.getSeconds() % 10 == 0) { $scope.options.currentDateValue= $scope.dataService.lofarTime;} }); }); -- GitLab From 62ba021334bc2a37d274a0aafa63f51a99cb9e49 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 22 Jul 2016 09:12:58 +0000 Subject: [PATCH 576/933] Task #9607: check for obsolete radb tasks and delete them --- SAS/MoM/MoMQueryService/momqueryservice.py | 7 +- .../ResourceAssigner/lib/raservice.py | 11 +++ .../ResourceAssigner/lib/schedulechecker.py | 90 +++++++++++++------ 3 files changed, 78 insertions(+), 30 deletions(-) diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index 179d568706e..af8fdb6b26e 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -92,7 +92,7 @@ class MoMDatabaseWrapper: def getProjectDetails(self, mom_ids): ''' get the project details (project_mom2id, project_name, project_description, object_mom2id, object_name, object_description, - object_type, object_group_id, object_group_name) for given mom object mom_ids + object_type, object_group_id, object_group_name, object_status) for given mom object mom_ids :param mixed mom_ids comma seperated string of mom2object id's, or list of ints :rtype list of dict's key value pairs with the project details ''' @@ -114,10 +114,13 @@ class MoMDatabaseWrapper: # TODO: make a view for this query in momdb! query = '''SELECT project.mom2id as project_mom2id, project.id as project_mom2objectid, project.name as project_name, project.description as project_description, - object.mom2id as object_mom2id, object.id as object_mom2objectid, object.name as object_name, object.description as object_description, object.mom2objecttype as object_type, object.group_id as object_group_id, grp.id as object_group_mom2objectid, grp.name as object_group_name + object.mom2id as object_mom2id, object.id as object_mom2objectid, object.name as object_name, object.description as object_description, object.mom2objecttype as object_type, object.group_id as object_group_id, grp.id as object_group_mom2objectid, grp.name as object_group_name, + status.code as object_status FROM mom2object as object left join mom2object as project on project.id = object.ownerprojectid left join mom2object as grp on grp.mom2id = object.group_id + left join mom2objectstatus as mostatus on object.currentstatusid = mostatus.id + inner join status on mostatus.statusid = status.id where object.mom2id in (%s) order by project_mom2id ''' % (ids_str,) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py index 18aa2f27336..775d484b03e 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py @@ -85,6 +85,7 @@ def main(): from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_BUSNAME from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_PREFIX + from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME # Check the invocation arguments parser = OptionParser("%prog [options]", @@ -118,6 +119,12 @@ def main(): parser.add_option("--ra_notification_prefix", dest="ra_notification_prefix", type="string", default=DEFAULT_RA_NOTIFICATION_PREFIX, help="Prefix for the subject of the by the resourceassigner published notification messages. [default: %default]") + parser.add_option('--mom_query_busname', dest='mom_query_busname', type='string', + default=DEFAULT_MOMQUERY_BUSNAME, + help='Name of the bus exchange on the qpid broker on which the momqueryservice listens, default: %default') + parser.add_option('--mom_query_servicename', dest='mom_query_servicename', type='string', + default=DEFAULT_MOMQUERY_SERVICENAME, + help='Name of the momqueryservice, default: %default') parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() @@ -133,6 +140,8 @@ def main(): otdb_servicename=options.otdb_servicename, ra_notification_busname=options.ra_notification_busname, ra_notification_prefix=options.ra_notification_prefix, + mom_busname=options.mom_query_busname, + mom_servicename=options.mom_query_servicename, broker=options.broker) as assigner: with SpecifiedTaskListener(busname=options.notification_busname, subject=options.notification_subject, @@ -140,6 +149,8 @@ def main(): assigner=assigner) as listener: with ScheduleChecker(radb_busname=options.radb_busname, radb_servicename=options.radb_servicename, + mom_busname=options.mom_query_busname, + mom_servicename=options.mom_query_servicename, broker=options.broker) as schedulechecker: waitForInterrupt() diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index c56572dfb8c..5a52e3e14a4 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -30,6 +30,9 @@ from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as DEFAULT_RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as DEFAULT_RADB_SERVICENAME +from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC +from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME + from lofar.sas.resourceassignment.resourceassigner.config import PIPELINE_CHECK_INTERVAL logger = logging.getLogger(__name__) @@ -38,12 +41,15 @@ class ScheduleChecker(): def __init__(self, radb_busname=DEFAULT_RADB_BUSNAME, radb_servicename=DEFAULT_RADB_SERVICENAME, + mom_busname=DEFAULT_MOMQUERY_BUSNAME, + mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, broker=None): """ """ self._thread = None self._running = False self._radbrpc = RARPC(servicename=radb_servicename, busname=radb_busname, broker=broker) + self._momrpc = MoMQueryRPC(servicename=mom_servicename, busname=mom_busname, broker=broker) def __enter__(self): """Internal use only. (handles scope 'with')""" @@ -57,6 +63,7 @@ class ScheduleChecker(): def start(self): """Open rpc connections to radb service and resource estimator service""" self._radbrpc.open() + self._momrpc.open() self._running = True self._thread = Thread(target=self._check_loop) self._thread.daemon = True @@ -65,39 +72,66 @@ class ScheduleChecker(): def stop(self): """Close rpc connections to radb service and resource estimator service""" self._radbrpc.close() + self._momrpc.close() self._running = False self._thread.join(60) + + def checkRunningPipelines(self): + try: + now = datetime.utcnow() + + active_pipelines = self._radbrpc.getTasks(task_status='active', task_type='pipeline') + + for task in active_pipelines: + if task['endtime'] <= now: + new_endtime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL) + logger.info("Extending endtime to %s for pipeline radb_id=%s otdb_id=%s", new_endtime, task['id'], task['otdb_id']) + self._radbrpc.updateTaskAndResourceClaims(task['id'], endtime=new_endtime) + except Exception as e: + logger.error("Error while checking running pipelines: %s", e) + + def checkScheduledAndQueuedPipelines(self): + try: + now = datetime.utcnow() + + scheduled_pipelines = self._radbrpc.getTasks(task_status='scheduled', task_type='pipeline') + queued_pipelines = self._radbrpc.getTasks(task_status='queued', task_type='pipeline') + sq_pipelines = scheduled_pipelines + queued_pipelines + + for task in sq_pipelines: + if task['starttime'] <= now: + logger.info("Moving ahead scheduled pipeline radb_id=%s otdb_id=%s to %s seconds from now", task['id'], task['otdb_id'], PIPELINE_CHECK_INTERVAL) + self._radbrpc.updateTaskAndResourceClaims(task['id'], starttime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL), endtime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL+task['duration'])) + updated_task = self._radbrpc.getTask(task['id']) + if updated_task['status'] != u'scheduled': + logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change to %s", updated_task['id'], updated_task['otdb_id'], updated_task['status']) + #TODO: automatically resolve conflict status by moved pipeline in first free time slot. + except Exception as e: + logger.error("Error while checking scheduled pipelines: %s", e) + + def checkApprovedTasks(self): + try: + now = datetime.utcnow() + + approved_tasks = self._radbrpc.getTasks(task_status='approved') + + for task in approved_tasks: + mom_task_details = self._momrpc.getProjectDetails(task['mom_id']) + + if (not mom_task_details or + str(task['mom_id']) not in mom_task_details or + mom_task_details[str(task['mom_id'])]['object_status'] == 'opened'): + logger.info('task %s mom_id=%s otdb_id=%s was removed or set to status opened. removing task from rabd', task['id'], task['mom_id'], task['otdb_id']) + self._radbrpc.deleteSpecification(task['specification_id']) + + except Exception as e: + logger.error("Error while checking scheduled pipelines: %s", e) + def _check_loop(self): while self._running: - try: - now = datetime.utcnow() - - active_pipelines = self._radbrpc.getTasks(task_status='active', task_type='pipeline') - - for task in active_pipelines: - if task['endtime'] <= now: - new_endtime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL) - logger.info("Extending endtime to %s for pipeline radb_id=%s otdb_id=%s", new_endtime, task['id'], task['otdb_id']) - self._radbrpc.updateTaskAndResourceClaims(task['id'], endtime=new_endtime) - except Exception as e: - logger.error("Error while checking running pipelines: %s", e) - - try: - scheduled_pipelines = self._radbrpc.getTasks(task_status='scheduled', task_type='pipeline') - queued_pipelines = self._radbrpc.getTasks(task_status='queued', task_type='pipeline') - sq_pipelines = scheduled_pipelines + queued_pipelines - - for task in sq_pipelines: - if task['starttime'] <= now: - logger.info("Moving ahead scheduled pipeline radb_id=%s otdb_id=%s to %s seconds from now", task['id'], task['otdb_id'], PIPELINE_CHECK_INTERVAL) - self._radbrpc.updateTaskAndResourceClaims(task['id'], starttime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL), endtime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL+task['duration'])) - updated_task = self._radbrpc.getTask(task['id']) - if updated_task['status'] != u'scheduled': - logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change to %s", updated_task['id'], updated_task['otdb_id'], updated_task['status']) - #TODO: automatically resolve conflict status by moved pipeline in first free time slot. - except Exception as e: - logger.error("Error while checking scheduled pipelines: %s", e) + self.checkRunningPipelines() + self.checkScheduledAndQueuedPipelines() for i in range(PIPELINE_CHECK_INTERVAL): sleep(1) -- GitLab From 255253462ee4feae6f38cbe5f1128f658a44b53b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 22 Jul 2016 09:44:55 +0000 Subject: [PATCH 577/933] Task #9607: take predecessors into account when moving pipelines ahead --- .../ResourceAssigner/lib/schedulechecker.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index 5a52e3e14a4..3a26c6edcfb 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -100,11 +100,18 @@ class ScheduleChecker(): sq_pipelines = scheduled_pipelines + queued_pipelines for task in sq_pipelines: - if task['starttime'] <= now: - logger.info("Moving ahead scheduled pipeline radb_id=%s otdb_id=%s to %s seconds from now", task['id'], task['otdb_id'], PIPELINE_CHECK_INTERVAL) - self._radbrpc.updateTaskAndResourceClaims(task['id'], starttime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL), endtime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL+task['duration'])) + predecessor_tasks = self._radbrpc.getTasks(task_ids=task['predecessor_ids']) + predecessor_endtimes = [t['endtime'] for t in predecessor_tasks] + predecessor_endtimes.append(now + timedelta(seconds=PIPELINE_CHECK_INTERVAL)) + + max_pred_endtime = max(predecessor_endtimes) + + if task['starttime'] < max_pred_endtime: + shift = max_pred_endtime - task['starttime'] + logger.info("Moving ahead %s pipeline radb_id=%s otdb_id=%s by %s", task['status'], task['id'], task['otdb_id'], shift) + self._radbrpc.updateTaskAndResourceClaims(task['id'], starttime=task['starttime']+shift, endtime=task['endtime']+shift) updated_task = self._radbrpc.getTask(task['id']) - if updated_task['status'] != u'scheduled': + if updated_task['status'] != u'scheduled' or updated_task['status'] != u'queued': logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change to %s", updated_task['id'], updated_task['otdb_id'], updated_task['status']) #TODO: automatically resolve conflict status by moved pipeline in first free time slot. except Exception as e: -- GitLab From fc45fa7c0f49f5c443c824ecc487ebba9c5e684b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 22 Jul 2016 10:31:48 +0000 Subject: [PATCH 578/933] Task #9607: logging --- SAS/ResourceAssignment/ResourceAssignmentService/rpc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py index 0952869f376..7f8e3f22e7b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py @@ -46,8 +46,8 @@ class RARPC(RPCWrapper): extended=extended, include_properties=include_properties) - logger.info("found %s claims for claim_ids=%s, lower_bound=%s, upper_bound=%s, task_ids=%s, status=%s, resource_type=%s" % - (len(claims), claim_ids, lower_bound, upper_bound, task_ids, status, resource_type)) + logger.debug("found %s claims for claim_ids=%s, lower_bound=%s, upper_bound=%s, task_ids=%s, status=%s, resource_type=%s", + len(claims), claim_ids, lower_bound, upper_bound, task_ids, status, resource_type) for claim in claims: claim['starttime'] = claim['starttime'].datetime() -- GitLab From ae48887a016475d8513866134820412b077f3aff Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 22 Jul 2016 10:32:06 +0000 Subject: [PATCH 579/933] Task #9607: typo --- SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py index 775d484b03e..d00f7945c7d 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py @@ -140,8 +140,6 @@ def main(): otdb_servicename=options.otdb_servicename, ra_notification_busname=options.ra_notification_busname, ra_notification_prefix=options.ra_notification_prefix, - mom_busname=options.mom_query_busname, - mom_servicename=options.mom_query_servicename, broker=options.broker) as assigner: with SpecifiedTaskListener(busname=options.notification_busname, subject=options.notification_subject, -- GitLab From 7d60e78ef007cbbe53e804ad5e9714fd64d9f797 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 22 Jul 2016 10:32:29 +0000 Subject: [PATCH 580/933] Task #9607: logging and fix --- .../ResourceAssigner/lib/schedulechecker.py | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index 3a26c6edcfb..8975c0838fd 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -83,6 +83,9 @@ class ScheduleChecker(): active_pipelines = self._radbrpc.getTasks(task_status='active', task_type='pipeline') + if active_pipelines: + logger.info('checking endtime of running pipelines') + for task in active_pipelines: if task['endtime'] <= now: new_endtime=now+timedelta(seconds=PIPELINE_CHECK_INTERVAL) @@ -99,36 +102,39 @@ class ScheduleChecker(): queued_pipelines = self._radbrpc.getTasks(task_status='queued', task_type='pipeline') sq_pipelines = scheduled_pipelines + queued_pipelines - for task in sq_pipelines: - predecessor_tasks = self._radbrpc.getTasks(task_ids=task['predecessor_ids']) - predecessor_endtimes = [t['endtime'] for t in predecessor_tasks] - predecessor_endtimes.append(now + timedelta(seconds=PIPELINE_CHECK_INTERVAL)) - - max_pred_endtime = max(predecessor_endtimes) - - if task['starttime'] < max_pred_endtime: - shift = max_pred_endtime - task['starttime'] - logger.info("Moving ahead %s pipeline radb_id=%s otdb_id=%s by %s", task['status'], task['id'], task['otdb_id'], shift) - self._radbrpc.updateTaskAndResourceClaims(task['id'], starttime=task['starttime']+shift, endtime=task['endtime']+shift) - updated_task = self._radbrpc.getTask(task['id']) - if updated_task['status'] != u'scheduled' or updated_task['status'] != u'queued': - logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change to %s", updated_task['id'], updated_task['otdb_id'], updated_task['status']) - #TODO: automatically resolve conflict status by moved pipeline in first free time slot. + if sq_pipelines: + logger.info('checking starttime of scheduled/queued cep4 pipelines') + + for task in sq_pipelines: + #only reschedule pipelines which have resourceclaims, and hence run on cep4 + if self._radbrpc.getResourceClaims(task_ids=task['id']): + predecessor_tasks = self._radbrpc.getTasks(task_ids=task['predecessor_ids']) + predecessor_endtimes = [t['endtime'] for t in predecessor_tasks] + predecessor_endtimes.append(now + timedelta(seconds=PIPELINE_CHECK_INTERVAL)) + + max_pred_endtime = max(predecessor_endtimes) + + if task['starttime'] < max_pred_endtime: + shift = max_pred_endtime - task['starttime'] + logger.info("Moving ahead %s pipeline radb_id=%s otdb_id=%s by %s", task['status'], task['id'], task['otdb_id'], shift) + self._radbrpc.updateTaskAndResourceClaims(task['id'], starttime=task['starttime']+shift, endtime=task['endtime']+shift) + updated_task = self._radbrpc.getTask(task['id']) + if updated_task['status'] not in [u'scheduled', u'queued']: + logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change to %s", updated_task['id'], updated_task['otdb_id'], updated_task['status']) + #TODO: automatically resolve conflict status by moved pipeline in first free time slot. except Exception as e: logger.error("Error while checking scheduled pipelines: %s", e) def checkApprovedTasks(self): try: - now = datetime.utcnow() - + logger.info('checking approved tasks for status in mom') approved_tasks = self._radbrpc.getTasks(task_status='approved') + mom_ids = [t['mom_id'] for t in approved_tasks] + mom_details = self._momrpc.getProjectDetails(mom_ids) for task in approved_tasks: - mom_task_details = self._momrpc.getProjectDetails(task['mom_id']) - - if (not mom_task_details or - str(task['mom_id']) not in mom_task_details or - mom_task_details[str(task['mom_id'])]['object_status'] == 'opened'): + if (str(task['mom_id']) not in mom_details or + mom_details[str(task['mom_id'])]['object_status'] == 'opened'): logger.info('task %s mom_id=%s otdb_id=%s was removed or set to status opened. removing task from rabd', task['id'], task['mom_id'], task['otdb_id']) self._radbrpc.deleteSpecification(task['specification_id']) @@ -139,6 +145,7 @@ class ScheduleChecker(): while self._running: self.checkRunningPipelines() self.checkScheduledAndQueuedPipelines() + self.checkApprovedTasks() for i in range(PIPELINE_CHECK_INTERVAL): sleep(1) -- GitLab From 9a05de629a64bb1bcde11725d6abfa1c559edd24 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 22 Jul 2016 13:35:39 +0000 Subject: [PATCH 581/933] Task #9607: automatically load task when searching for mom/otdb id which is not in scope, select loaded task and adjust scope to view the loaded task --- .../static/app/controllers/datacontroller.js | 54 +++++++++++++++++++ .../static/app/controllers/gridcontroller.js | 17 ++++++ .../lib/webservice.py | 32 +++++++++++ 3 files changed, 103 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 9cfefb6a589..6a41d756ed7 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -338,6 +338,36 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }) }; + self.getTaskByOTDBId = function(otdb_id) { + var defer = $q.defer(); + $http.get('/rest/tasks/otdb/' + otdb_id).success(function(result) { + var task = result.task; + if(task) { + task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); + task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); + } + defer.resolve(task); + }).error(function(result) { + defer.resolve(undefined); + }) + return defer.promise; + }; + + self.getTaskByMoMId = function(mom_id) { + var defer = $q.defer(); + $http.get('/rest/tasks/mom/' + mom_id).success(function(result) { + var task = result.task; + if(task) { + task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); + task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); + } + defer.resolve(task); + }).error(function(result) { + defer.resolve(undefined); + }) + return defer.promise; + }; + self.copyTask = function(task) { $http.put('/rest/tasks/' + task.id + '/copy').error(function(result) { console.log("Error. Could not copy task. " + result); @@ -767,6 +797,7 @@ dataControllerMod.controller('DataController', function($scope, dataService) { var self = this; $scope.dataService = dataService; + dataService.dataCtrl = this; $scope.dateOptions = { formatYear: 'yyyy', @@ -790,6 +821,28 @@ dataControllerMod.controller('DataController', $scope.jumpToNow(); + $scope.loadTaskByOTDBIdSelectAndJumpIntoView = function(otdb_id) { + $scope.dataService.getTaskByOTDBId(otdb_id).then(function(task) { + if(task) { + $scope.dataService.tasks.push(task); + $scope.dataService.taskDict[task.id] = task; + $scope.dataService.setSelectedTaskId(task.id); + $scope.jumpToSelectedTask(); + } + }); + }; + + $scope.loadTaskByMoMIdSelectAndJumpIntoView = function(mom_id) { + $scope.dataService.getTaskByMoMId(mom_id).then(function(task) { + if(task) { + $scope.dataService.tasks.push(task); + $scope.dataService.taskDict[task.id] = task; + $scope.dataService.setSelectedTaskId(task.id); + $scope.jumpToSelectedTask(); + } + }); + }; + $scope.selectCurrentTask = function() { var currentTasks = dataService.tasks.filter(function(t) { return t.starttime <= dataService.viewTimeSpan.to && t.endime >= dataService.viewTimeSpan.from; }); if(currentTasks.lenght > 0) { @@ -828,6 +881,7 @@ dataControllerMod.controller('DataController', from: dataService.floorDate(new Date(focusTime.getTime() - 0.4*viewSpanInMinutes*60*1000), 1, 5), to: dataService.floorDate(new Date(focusTime.getTime() + 0.6*viewSpanInMinutes*60*1000), 1, 5) }; + dataService.autoFollowNow = false; }; $scope.scrollBack = function() { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 461b7892185..e54e9c8b7ff 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -148,6 +148,23 @@ $scope.columns = [ $scope.dataService.filteredTaskDict = $scope.dataService.toIdBasedDict($scope.dataService.filteredTasks); $scope.dataService.filteredTaskChangeCntr++; + + if($scope.dataService.filteredTasks.length == 0) { + var otdb_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'otdb_id'; }); + if(otdb_col && otdb_col.filters.length && otdb_col.filters[0].hasOwnProperty('term')) { + var otdb_id = otdb_col.filters[0].term; + $scope.$parent.$parent.loadTaskByOTDBIdSelectAndJumpIntoView(otdb_id); + otdb_col.filters[0].term = null; + } else { + var mom_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_id'; }); + + if(mom_col && mom_col.filters.length && mom_col.filters[0].hasOwnProperty('term')) { + var mom_id = mom_col.filters[0].term; + $scope.$parent.$parent.loadTaskByMoMIdSelectAndJumpIntoView(mom_id); + mom_col.filters[0].term = null; + } + } + } }); }); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index d2492c6dd73..7a040d9c6bf 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -233,6 +233,38 @@ def getTask(task_id): return jsonify({'task': None}) +@app.route('/rest/tasks/otdb/<int:otdb_id>', methods=['GET']) +def getTaskByOTDBId(otdb_id): + try: + task = rarpc.getTask(otdb_id=otdb_id) + + if not task: + abort(404) + + task['name'] = 'Task %d' % task['id'] + updateTaskMomDetails(task, momqueryrpc) + return jsonify({'task': task}) + except Exception as e: + abort(404) + + return jsonify({'task': None}) + +@app.route('/rest/tasks/mom/<int:mom_id>', methods=['GET']) +def getTaskByMoMId(mom_id): + try: + task = rarpc.getTask(mom_id=mom_id) + + if not task: + abort(404) + + task['name'] = 'Task %d' % task['id'] + updateTaskMomDetails(task, momqueryrpc) + return jsonify({'task': task}) + except Exception as e: + abort(404) + + return jsonify({'task': None}) + @app.route('/rest/tasks/<int:task_id>', methods=['PUT']) def putTask(task_id): if isProductionEnvironment(): -- GitLab From 66ef1f00c532fedffce5aca61d629728cae253b8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 22 Jul 2016 14:00:07 +0000 Subject: [PATCH 582/933] Task #9607: changed past/future ratio --- .../lib/static/app/controllers/datacontroller.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 6a41d756ed7..9b03ade3d06 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -814,8 +814,8 @@ dataControllerMod.controller('DataController', $scope.jumpToNow = function() { var floorLofarTime = dataService.floorDate(dataService.lofarTime, 1, 5); dataService.viewTimeSpan = { - from: dataService.floorDate(new Date(floorLofarTime.getTime() - 0.4*$scope.zoomTimespan.value*60*1000), 1, 5), - to: dataService.floorDate(new Date(floorLofarTime.getTime() + 0.6*$scope.zoomTimespan.value*60*1000), 1, 5) + from: dataService.floorDate(new Date(floorLofarTime.getTime() - 0.25*$scope.zoomTimespan.value*60*1000), 1, 5), + to: dataService.floorDate(new Date(floorLofarTime.getTime() + 0.75*$scope.zoomTimespan.value*60*1000), 1, 5) }; }; @@ -878,8 +878,8 @@ dataControllerMod.controller('DataController', var focusTime = new Date(minStarttime.getTime() + 0.5*selectedTasksDurationInmsec); dataService.viewTimeSpan = { - from: dataService.floorDate(new Date(focusTime.getTime() - 0.4*viewSpanInMinutes*60*1000), 1, 5), - to: dataService.floorDate(new Date(focusTime.getTime() + 0.6*viewSpanInMinutes*60*1000), 1, 5) + from: dataService.floorDate(new Date(focusTime.getTime() - 0.25*viewSpanInMinutes*60*1000), 1, 5), + to: dataService.floorDate(new Date(focusTime.getTime() + 0.75*viewSpanInMinutes*60*1000), 1, 5) }; dataService.autoFollowNow = false; }; @@ -920,8 +920,8 @@ dataControllerMod.controller('DataController', } dataService.viewTimeSpan = { - from: dataService.floorDate(new Date(focusTime.getTime() - 0.4*$scope.zoomTimespan.value*60*1000)), - to: dataService.floorDate(new Date(focusTime.getTime() + 0.6*$scope.zoomTimespan.value*60*1000)) + from: dataService.floorDate(new Date(focusTime.getTime() - 0.25*$scope.zoomTimespan.value*60*1000)), + to: dataService.floorDate(new Date(focusTime.getTime() + 0.75*$scope.zoomTimespan.value*60*1000)) }; }; -- GitLab From b64a05a93c82d8bceb8b02ed4808413610a2a012 Mon Sep 17 00:00:00 2001 From: Stefan Froehlich <s.froehlich@fz-juelich.de> Date: Tue, 26 Jul 2016 08:49:38 +0000 Subject: [PATCH 583/933] Task #9709: Added environment variable expansion in string replacement feature of the generic pipeline. --- CEP/Pipeline/recipes/sip/bin/genericpipeline.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/genericpipeline.py b/CEP/Pipeline/recipes/sip/bin/genericpipeline.py index c094d6d2cd0..5ac1f058b54 100755 --- a/CEP/Pipeline/recipes/sip/bin/genericpipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/genericpipeline.py @@ -519,8 +519,10 @@ class GenericPipeline(control): replacedict[str(check).lstrip('!').lstrip(' ')] = str(self.parset[check]) if str(check).startswith('pipeline.replace.'): replacedict[str(check).replace('pipeline.replace.', '').lstrip(' ')] = str(self.parset[check]) - #self.logger.info( 'REPLACEDICT: ') - #self.logger.info(replacedict) + #expand environment variables + for k, v in replacedict.items(): + replacedict[k] = os.path.expandvars(v) + for check in self._keys(self.parset): for k, v in reversed(replacedict.items()): if '{{ '+k+' }}' in str(self.parset[check]): -- GitLab From 5eec631f3b3bb232c123819c67aafde043da2ef9 Mon Sep 17 00:00:00 2001 From: Stefan Froehlich <s.froehlich@fz-juelich.de> Date: Tue, 26 Jul 2016 14:23:31 +0000 Subject: [PATCH 584/933] Task #9159: Generic pipeline: added possibility to split the steplist in different sublists (pipeline.steps.<sublist>). --- CEP/Pipeline/recipes/sip/bin/genericpipeline.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/bin/genericpipeline.py b/CEP/Pipeline/recipes/sip/bin/genericpipeline.py index 5ac1f058b54..72a207f0a09 100755 --- a/CEP/Pipeline/recipes/sip/bin/genericpipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/genericpipeline.py @@ -139,11 +139,19 @@ class GenericPipeline(control): # the names will be the prefix for parset subsets pipeline_args = self.parset.makeSubset( self.parset.fullModuleName('pipeline') + '.') - + pipeline_steps = self.parset.makeSubset( + self.parset.fullModuleName('steps') + '.') # ********************************************************************* # forward declaration of things. just for better overview and understanding whats in here. # some of this might be removed in upcoming iterations, or stuff gets added. step_name_list = pipeline_args.getStringVector('steps') + # construct the step name list if there were pipeline.steps.<subset> + for item in pipeline_steps.keys(): + if item in step_name_list: + loc = step_name_list.index(item) + step_name_list[loc:loc] = pipeline_steps.getStringVector(item) + step_name_list.remove(item) + step_control_dict = {} step_parset_files = {} step_parset_obj = {} -- GitLab From 3b4cefd91982f6c4f93e09bf0a3b71ab64ffacae Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 2 Aug 2016 08:01:31 +0000 Subject: [PATCH 585/933] Task #9717: Prevent target pipeline from moving its input files --- CEP/Pipeline/recipes/sip/bin/msss_target_pipeline.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/msss_target_pipeline.py b/CEP/Pipeline/recipes/sip/bin/msss_target_pipeline.py index f628424d523..1d8cee64e7e 100755 --- a/CEP/Pipeline/recipes/sip/bin/msss_target_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/msss_target_pipeline.py @@ -146,7 +146,8 @@ class msss_target_pipeline(control): mapfile_source=source_path, mapfile_target=target_path, mapfiles_dir=copier_map_path, - mapfile=copied_files_path)['mapfile_target_copied'] + mapfile=copied_files_path, + allow_move=False)['mapfile_target_copied'] # Some copy action might fail; the skip fields in the other map-files # need to be updated these to reflect this. @@ -308,7 +309,8 @@ class msss_target_pipeline(control): mapfile_source=bbs_mapfile, mapfile_target=corrected_mapfile, mapfiles_dir=mapfile_dir, - mapfile=corrected_mapfile + mapfile=corrected_mapfile, + allow_move=True ) # ********************************************************************* -- GitLab From 9b6abd8de8a087d61d9012a17a5d55951f3a5d0f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 2 Aug 2016 08:05:37 +0000 Subject: [PATCH 586/933] Task #9718: Target of ln command is hosting directory, not symlink name --- CEP/Pipeline/recipes/sip/nodes/imager_prepare.py | 4 ++-- CEP/Pipeline/recipes/sip/nodes/long_baseline.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py b/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py index eb4dd79e1fa..b590cc80680 100644 --- a/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py +++ b/CEP/Pipeline/recipes/sip/nodes/imager_prepare.py @@ -182,8 +182,8 @@ class imager_prepare(LOFARnodeTCP): "{0}".format(processed_ms_dir)] if self.globalfs or input_item.host == "localhost": # symlinking is enough - command = ["ln", "-sfT", "{0}".format(input_item.file), - "{0}".format(processed_ms_dir)] + command = ["ln", "-sf", "{0}".format(input_item.file), + "-t", "{0}".format(processed_ms_dir)] self.logger.debug("executing: " + " ".join(command)) diff --git a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py index 42f667a80e3..282255861a7 100644 --- a/CEP/Pipeline/recipes/sip/nodes/long_baseline.py +++ b/CEP/Pipeline/recipes/sip/nodes/long_baseline.py @@ -200,8 +200,8 @@ class long_baseline(LOFARnodeTCP): "{0}".format(processed_ms_dir)] if self.globalfs or input_item.host == "localhost": # symlinking is enough - command = ["ln", "-sfT", "{0}".format(input_item.file), - "{0}".format(processed_ms_dir)] + command = ["ln", "-sf", "{0}".format(input_item.file), + "-t", "{0}".format(processed_ms_dir)] self.logger.debug("executing: " + " ".join(command)) -- GitLab From 1310746384ad0aec1f7022b95243b910eae94bc9 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Tue, 2 Aug 2016 08:46:39 +0000 Subject: [PATCH 587/933] Task #9640: Fixes for handing Instrument Model input files --- .../resource_estimators/base_resource_estimator.py | 3 +++ .../resource_estimators/calibration_pipeline.py | 7 +++++-- .../ResourceAssignmentEstimator/service.py | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py index 4a1519bbb4e..59fe0bbc049 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py @@ -78,11 +78,13 @@ class BaseResourceEstimator(object): output_files = {} if 'saps' in input_files: output_files['saps'] = [] + logger.info('parsing input for identifications: %s' % (identifications,)) for identification in identifications: for data_type, data_properties in input_files.items(): if data_type == 'saps': continue if identification in data_properties['identifications']: #This data_type matches an input identification + logger.info('Found input identification matching %s' % (identification,)) output_files[data_type] = copy.deepcopy(data_properties) if 'SAP' in identification: #Special case for identifications that contain a SAP number # We would need to make this smarter if we can have the data from multiple SAPs as input. @@ -101,6 +103,7 @@ class BaseResourceEstimator(object): output_files['saps'].append({'sap_nr': sap_nr, 'properties': sap['properties']}) if sap_data_type == 'start_sb_nr': output_files[data_type]['start_sb_nr'] = sap_data_value + logger.info('filtered input files down to: \n' + pprint.pformat(output_files)) return output_files diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py index 1e8cb50a5a3..7dac29c602c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py @@ -21,6 +21,7 @@ # $Id: base_resource_estimator.py 33534 2016-02-08 14:28:26Z schaap $ import logging +import pprint from math import ceil from base_resource_estimator import BaseResourceEstimator @@ -64,9 +65,11 @@ class CalibrationPipelineResourceEstimator(BaseResourceEstimator): }} """ logger.debug("start estimate '{}'".format(self.name)) - logger.info('parset: %s ' % parset) + logger.info('parset:\n%s' % (parset,)) + logger.info('input_files: \n' + pprint.pformat(input_files)) result = {'errors': [], 'storage': {'total_size': 0}, 'bandwidth': {'total_size': 0}} - input_files = self._filterInputs(input_files, parset.getStringVector(DATAPRODUCTS + 'Input_Correlated.identifications')) + identifications = parset.getStringVector(DATAPRODUCTS + 'Input_Correlated.identifications') + parset.getStringVector(DATAPRODUCTS + 'Input_InstrumentModel.identifications') + input_files = self._filterInputs(input_files, identifications) result['storage']['input_files'] = input_files duration = self._getDuration(parset.getString('Observation.startTime'), parset.getString('Observation.stopTime')) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index 0f2031fc96d..fd1f1fd6e65 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -61,6 +61,7 @@ class ResourceEstimatorHandler(MessageHandlerInterface): if specification_tree['task_subtype'] in ['averaging pipeline', 'calibration pipeline']: input_files = {} for id, estimate in branch_estimates.iteritems(): + logger.info('Looking at predecessor %s' % id) predecessor_output = estimate.values()[0]['storage']['output_files'] if not 'im' in predecessor_output and 'uv' in predecessor_output: # Not a calibrator pipeline logger.info('found %s as the target of pipeline %s' % (id, otdb_id)) @@ -68,6 +69,7 @@ class ResourceEstimatorHandler(MessageHandlerInterface): if 'saps' in predecessor_output: input_files['saps'] = predecessor_output['saps'] elif 'im' in predecessor_output: + logger.info('found %s as the calibrator of pipeline %s' % (id, otdb_id)) input_files['im'] = predecessor_output['im'] return {str(otdb_id): self.add_id(self.calibration_pipeline.verify_and_estimate(parset, input_files), otdb_id)} -- GitLab From 1183a660b832cfe6556c67750f64df65a3897c8a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 3 Aug 2016 08:28:05 +0000 Subject: [PATCH 588/933] Task #9678: Use select.poll instead of select.select to support fd > 1024 --- .../lofarpipe/support/subprocessgroup.py | 26 ++++++++++--------- .../test/support/subprocessgroup_test.py | 12 ++++++++- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index d20f67aaa99..0cdf9223bd9 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -96,12 +96,11 @@ class SubProcess(object): def fds(self): return self.output_streams.values() - def read(self, fds): - for fd in fds: - if fd == self.process.stdout: - self._addoutput(self.STDOUT, fd.read(4096)) - if fd == self.process.stderr: - self._addoutput(self.STDERR, fd.read(4096)) + def read(self, fileno): + if fileno == self.process.stdout.fileno(): + self._addoutput(self.STDOUT, self.process.stdout.read(4096)) + if fileno == self.process.stderr.fileno(): + self._addoutput(self.STDERR, self.process.stderr.read(4096)) def _addoutput(self, stdtype, output, flush=False): buf = self.output_buffers[stdtype] + output @@ -215,17 +214,20 @@ class SubProcessGroup(object): for process in processes: process.kill() - # collect fds we need to poll - fds = [] + # collect fds we need to poll -- we need select.poll to support fd > 1024 + poller = select.poll() + fd_lookup = {} for process in processes: - fds.extend(process.fds()) + for fd in process.fds(): + poller.register(fd, select.POLLIN) + fd_lookup[fd.fileno()] = process # poll for data - rlist, _, _ = select.select(fds, [], [], self.polling_interval) + events = poller.poll(self.polling_interval) # let processed read their data - for process in processes: - process.read(rlist) + for (fileno, _) in events: + fd_lookup[fileno].read(fileno) # check all the running processes for completion for process in self.process_group: diff --git a/CEP/Pipeline/test/support/subprocessgroup_test.py b/CEP/Pipeline/test/support/subprocessgroup_test.py index 816d7fe4628..3a792b8fc86 100644 --- a/CEP/Pipeline/test/support/subprocessgroup_test.py +++ b/CEP/Pipeline/test/support/subprocessgroup_test.py @@ -57,7 +57,17 @@ class SubProcessGroupTest(unittest.TestCase): process_group.wait_for_finish() end_time = time.time() self.assertTrue((end_time - start_time) > 3) - + + + def test_fd_bigger_than_1024(self): + process_group = SubProcessGroup(polling_interval=1, max_concurrent_processes=1000) + + cmd = "sleep 2" + for idx in range(513): # each process uses 2 fds, so we only need 513 processes to ensure fds > 1024 + process_group.run(cmd) + + process_group.wait_for_finish() + def test_start_without_jobs(self): process_group = SubProcessGroup(polling_interval=1) -- GitLab From 84557373c77684cc945ee3dc03cc14b22154a3c6 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 3 Aug 2016 16:56:08 +0000 Subject: [PATCH 589/933] Task #9746: Put both source and dest node in result filename --- .../Online_Cobalt/validation/cluster/infiniband/osu_bw.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SubSystems/Online_Cobalt/validation/cluster/infiniband/osu_bw.test b/SubSystems/Online_Cobalt/validation/cluster/infiniband/osu_bw.test index c20b136df5c..8e34035ac13 100755 --- a/SubSystems/Online_Cobalt/validation/cluster/infiniband/osu_bw.test +++ b/SubSystems/Online_Cobalt/validation/cluster/infiniband/osu_bw.test @@ -46,7 +46,7 @@ for target in $HOSTS do [ "$host" == "$target" ] && continue; echo "$host --> $target" - OUT_FILE=osu_bw.$target.out + OUT_FILE=osu_bw.$host.$target.out COMMAND="$MPIRUN --prefix $OPENMPI_DIR -H $host,$target bash -l -c $OSU_BW" run_command "$COMMAND" > "$OUT_FILE" 2> /dev/null || error "$COMMAND FAILED" -- GitLab From bcd82ec75f481629bd377e443f6c78f3d0634d12 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 4 Aug 2016 09:26:45 +0000 Subject: [PATCH 590/933] Task #9678: Replace select.select by select.poll to support fd > 1024 --- CEP/Pipeline/framework/lofarpipe/support/jobserver.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/jobserver.py b/CEP/Pipeline/framework/lofarpipe/support/jobserver.py index 1e2f724730f..10182fe2890 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/jobserver.py +++ b/CEP/Pipeline/framework/lofarpipe/support/jobserver.py @@ -122,11 +122,13 @@ class JobSocketReceiver(SocketServer.ThreadingTCPServer): # until the queue is empty, thereby avoiding the problem of falling # out of the logger threads before all log messages have been handled. def loop_in_thread(): + poller = select.poll() + poller.register(self.socket.fileno(), select.POLLIN) + while True: - rd, wr, ex = select.select( - [self.socket.fileno()], [], [], self.timeout - ) - if rd: + events = poller.poll(self.timeout) + + if events: self.handle_request() elif self.abort: break -- GitLab From 16686a9f7ae66b7db6cab86930badc6aa43f8d61 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 4 Aug 2016 09:29:19 +0000 Subject: [PATCH 591/933] Task #9678: More robust test --- CEP/Pipeline/test/support/subprocessgroup_test.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CEP/Pipeline/test/support/subprocessgroup_test.py b/CEP/Pipeline/test/support/subprocessgroup_test.py index 816d7fe4628..15e61b8f2c2 100644 --- a/CEP/Pipeline/test/support/subprocessgroup_test.py +++ b/CEP/Pipeline/test/support/subprocessgroup_test.py @@ -60,16 +60,14 @@ class SubProcessGroupTest(unittest.TestCase): def test_start_without_jobs(self): - process_group = SubProcessGroup(polling_interval=1) + process_group = SubProcessGroup(polling_interval=10) - # wait for 5 seconds start_time = time.time() - process_group.wait_for_finish() end_time = time.time() # The wait should complete without a polling interfal - self.assertTrue((end_time - start_time) < 1) + self.assertTrue((end_time - start_time) < 10) if __name__ == '__main__': import xmlrunner -- GitLab From 24910da97cb82168a40aefeb4a5b7786c820dfd2 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 4 Aug 2016 09:52:46 +0000 Subject: [PATCH 592/933] Task #9678: More robust test --- CEP/Pipeline/test/support/subprocessgroup_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/test/support/subprocessgroup_test.py b/CEP/Pipeline/test/support/subprocessgroup_test.py index 15e61b8f2c2..bf82916b2e1 100644 --- a/CEP/Pipeline/test/support/subprocessgroup_test.py +++ b/CEP/Pipeline/test/support/subprocessgroup_test.py @@ -26,7 +26,7 @@ class SubProcessGroupTest(unittest.TestCase): def test_alternating_output(self): - process_group = SubProcessGroup(polling_interval=1) + process_group = SubProcessGroup(polling_interval=10) # print a lot of numbers cmd = '%s/output_stderr_stdout.sh' % (os.path.dirname(__file__) or ".",) @@ -38,7 +38,7 @@ class SubProcessGroupTest(unittest.TestCase): process_group.wait_for_finish() end_time = time.time() - self.assertTrue((end_time - start_time) < 1) + self.assertTrue((end_time - start_time) < 10) def test_limit_number_of_proc(self): process_group = SubProcessGroup(polling_interval=1) -- GitLab From fe11325537a9588ab855926d087e51b67bc40347 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 4 Aug 2016 12:58:12 +0000 Subject: [PATCH 593/933] Task #9742: Log an ERROR if budget timers required >100% budget on average --- RTCP/Cobalt/CoInterface/src/BudgetTimer.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RTCP/Cobalt/CoInterface/src/BudgetTimer.cc b/RTCP/Cobalt/CoInterface/src/BudgetTimer.cc index 497604004d3..324bcfdb84b 100644 --- a/RTCP/Cobalt/CoInterface/src/BudgetTimer.cc +++ b/RTCP/Cobalt/CoInterface/src/BudgetTimer.cc @@ -45,7 +45,11 @@ namespace LOFAR { const double realTimePerc = 100.0 * getAverage() / budget; if (log_on_destruction) { - LOG_INFO_STR(std::left << std::setw(25) << itsName << ": ran at " << realTimePerc << "% of run-time budget"); + if (realTimePerc >= 100.0) { + LOG_ERROR_STR(std::left << std::setw(25) << itsName << ": ran at " << realTimePerc << "% of run-time budget"); + } else { + LOG_INFO_STR(std::left << std::setw(25) << itsName << ": ran at " << realTimePerc << "% of run-time budget"); + } } else { // TODO } -- GitLab From 92e8f1f8b4ae4b970d5c15c5fa306742d9b93b4c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 4 Aug 2016 13:28:08 +0000 Subject: [PATCH 594/933] Task #9678: Use subprocess27 module to use poll() in Popen as well --- .../framework/lofarpipe/support/subprocessgroup.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index 0cdf9223bd9..b0f3f978fee 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -1,4 +1,3 @@ -import subprocess import select import os import signal @@ -6,6 +5,14 @@ import fcntl import time from lofarpipe.support.lofarexceptions import PipelineException +# subprocess is broken in python <=2.6. It does not work for fds > 1024 for example. +try: + import subprocess27 as subprocess + print >> sys.stderr, __file__, ": Using Python 2.7 subprocess module!" +except ImportError: + import subprocess + print >> sys.stderr, __file__, ": Using default subprocess module!" + class SubProcess(object): STDOUT = 1 STDERR = 2 -- GitLab From aa936eef7634b76b62837c4f7d262d81a7f8866c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 4 Aug 2016 14:00:30 +0000 Subject: [PATCH 595/933] Task #9744: Prevent outputProc from waiting forever for a parset to arive --- RTCP/Cobalt/OutputProc/src/GPUProcIO.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc index 7cb62c7b0c8..39e92216519 100644 --- a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc +++ b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc @@ -100,7 +100,12 @@ size_t getMaxRunTime(const Parset &parset) bool process(Stream &controlStream) { bool success(true); + + // obtain the parset but don't wait forever. Note that we do not know whether + // we're running in real time mode or not. + alarm(3600); Parset parset(&controlStream); + alarm(0); string myHostName = myHostname(false); -- GitLab From 21f57b592fcec372048d0be59c695f3466498a1c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 4 Aug 2016 14:06:54 +0000 Subject: [PATCH 596/933] Task #9744: Disable elevated priviledges and memory locking/limiting --- RTCP/Cobalt/OutputProc/src/GPUProcIO.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc index 39e92216519..039f03423da 100644 --- a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc +++ b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc @@ -114,6 +114,11 @@ bool process(Stream &controlStream) * Real-time observation */ + /* + * Disabled elevated priviledges on CEP4 due to issues with Docker. + * We don't seem to need the priorities anyway, and memory limits will + * need to be enforced by Docker or SLURM, not us. + // Acquire elevated IO and CPU priorities setIOpriority(); setRTpriority(); @@ -121,6 +126,9 @@ bool process(Stream &controlStream) // Prevent swapping of our buffers lockInMemory(16UL * 1024UL * 1024UL * 1024UL); // limit memory to 16 GB + * + */ + // Deadline for outputProc, in seconds. const time_t outputProcTimeout = parset.ParameterSet::getTime("Cobalt.Tuning.outputProcTimeout", -- GitLab From d637bd2e626d9d892519906bd82cf5ab3e8da1ef Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 5 Aug 2016 07:52:38 +0000 Subject: [PATCH 597/933] Task #9678: Use subprocess27 module to use poll() in Popen as well --- CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index b0f3f978fee..27df5a47390 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -1,5 +1,6 @@ import select import os +import sys import signal import fcntl import time -- GitLab From 7d830a2ab1ede9c89f7052f0337bcbd7c0f95091 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 5 Aug 2016 10:36:01 +0000 Subject: [PATCH 598/933] Task #9682: set aborted after runPipeline.sh as well, to avoid having to wait for the follow-up job to be scheduled in case of a graceful abort --- MAC/Services/src/PipelineControl.py | 33 +++++++++++++++-------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 692e6ba7b89..a5ca637d157 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -30,13 +30,16 @@ The execution chains are as follows: [SCHEDULED] -> PipelineControl schedules - runPipeline.sh <obsid> || setOTDBTreeStatus -o <obsid> -s aborted + runPipeline.sh <obsid> + + and + + setOTDBTreeStatus -o <obsid> -s aborted using two SLURM jobs, guaranteeing that pipelineAborted.sh is called in the following circumstances: - - runPipeline.sh exits with failure - - runPipeline.sh is killed by SLURM + - runPipeline.sh wrapper cannot finish (bash bugs, etc) - runPipeline.sh job is cancelled in the queue State is set to [QUEUED]. @@ -45,9 +48,12 @@ The execution chains are as follows: - state <- [ACTIVE] - getParset - (run pipeline) - - state <- [COMPLETING] - - (wrap up) - - state <- [FINISHED] + - success: + - state <- [COMPLETING] + - (wrap up) + - state <- [FINISHED] + - failure: + - state <- [ABORTED] (setOTDBTreeStatus) -> Calls - state <- [ABORTED] @@ -506,22 +512,17 @@ if [ $? -eq 0 ]; then # notify ganglia wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} FINISHED&host_regex=" - - # return success - exit 0 else - # aborted state is already set by pipeline framework. - # Why? why does the framework set the aborted state, but not the finished state? questions questions.... - # so, do not set aborted state here. - # {setStatus_aborted} + # notify system that we've aborted + {setStatus_aborted} # notify ganglia wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} ABORTED&host_regex=" - - # return failure - exit 1 fi +# if we've reached this point, the process (not the pipeline) ran ok. +# we do not want to trigger the $OBSID-aborted job. +exit 0 """.format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, -- GitLab From a110eecb6184082fee63394280c509c097866c0c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 10 Aug 2016 13:17:37 +0000 Subject: [PATCH 599/933] Task #9764: Initialise succesfull_job variable --- CEP/Pipeline/recipes/sip/master/imager_source_finding.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CEP/Pipeline/recipes/sip/master/imager_source_finding.py b/CEP/Pipeline/recipes/sip/master/imager_source_finding.py index aaa7aa00111..bba8b49a5d0 100644 --- a/CEP/Pipeline/recipes/sip/master/imager_source_finding.py +++ b/CEP/Pipeline/recipes/sip/master/imager_source_finding.py @@ -124,6 +124,9 @@ class imager_source_finding(BaseRecipe, RemoteCommandRecipeMixIn): source_dbs_from_nodes.iterator = \ catalog_output_path_from_nodes.iterator = DataMap.SkipIterator + # Job is succesfull if at least one source_db is found + succesfull_job = False + for job, sourcedb_item, catalog_item in zip(jobs, source_dbs_from_nodes, catalog_output_path_from_nodes): -- GitLab From 32a5215b9aecab0f5cb4c0c244e5df928e220b1f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 11 Aug 2016 09:10:59 +0000 Subject: [PATCH 600/933] Task #9678: Fix killing of subprocess --- CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index 27df5a47390..b3ac6193700 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -1,7 +1,6 @@ import select import os import sys -import signal import fcntl import time from lofarpipe.support.lofarexceptions import PipelineException @@ -98,7 +97,7 @@ class SubProcess(object): if self.killed: return - os.signal(self.pid, signal.SIGTERM) + self.process.kill() # sends SIGKILL self.killed = True def fds(self): -- GitLab From f09ff8030619f497a257faaa0ce9ec64d6b4da10 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 11 Aug 2016 09:28:58 +0000 Subject: [PATCH 601/933] Task #8475: Use LOFAR_TAG as default docker tag if none specified in softwareVersion key --- MAC/Services/src/PipelineControl.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index a5ca637d157..efef3e65f8d 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -190,10 +190,22 @@ class Parset(dict): def defaultDockerImage(): return runCommand("docker-template", "lofar-pipeline:${LOFAR_TAG}") + @staticmethod + def defaultDockerTag(): + return runCommand("docker-template", "${LOFAR_TAG}") + def dockerImage(self): # Return the version set in the parset, and fall back to our own version. - return (self[PARSET_PREFIX + "Observation.ObservationControl.PythonControl.softwareVersion"] or - self.defaultDockerImage()) + image = self[PARSET_PREFIX + "Observation.ObservationControl.PythonControl.softwareVersion"] + + if not image: + return self.defaultDockerImage() + + if ":" in image: + return image + + # Insert our tag by default + return "%s:%s" % (image, self.defaultDockerTag()) def otdbId(self): return int(self[PARSET_PREFIX + "Observation.otdbID"]) -- GitLab From 75d4d7993f4282f0aeefa4e2e78d1d9b321fc3eb Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 11 Aug 2016 14:39:08 +0000 Subject: [PATCH 602/933] Task #8475: Update pulp command-line parameters for CEP4 --- CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py index 53d16d41dee..773efd73292 100755 --- a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py @@ -113,6 +113,7 @@ class pulsar_pipeline(control): self._get_io_product_specs() self.job_dir = self.config.get("layout", "job_directory") + self.globalfs = self.config.has_option("remote", "globalfs") and self.config.getboolean("remote", "globalfs") parset_dir = os.path.join(self.job_dir, "parsets") mapfile_dir = os.path.join(self.job_dir, "mapfiles") @@ -146,6 +147,10 @@ class pulsar_pipeline(control): # --auto = automatic run from framework # -q = quiet mode, no user interaction sys.argv = ['pulp.py', '--auto', '-q'] + + if self.globalfs: + project = self.parset.getString(self.parset.fullModuleName('Campaign') + '.name') + sys.argv.extend(['--slurm', '--globalfs', '--docker', '--docker-container=lofar-pulp:LOFAR-Release-2_17', '--raw=/data/projects/%s' % project]) if (not self.coherentStokesEnabled): sys.argv.extend(["--noCS", "--noCV", "--noFE"]) -- GitLab From 4c2e0375cf42933bc8ae57f9725dea48abdaf9f9 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 11 Aug 2016 14:42:51 +0000 Subject: [PATCH 603/933] Task #8475: Read correct LOFAR image tag from environment\ --- CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py index 773efd73292..2e9d98ede11 100755 --- a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py @@ -150,7 +150,7 @@ class pulsar_pipeline(control): if self.globalfs: project = self.parset.getString(self.parset.fullModuleName('Campaign') + '.name') - sys.argv.extend(['--slurm', '--globalfs', '--docker', '--docker-container=lofar-pulp:LOFAR-Release-2_17', '--raw=/data/projects/%s' % project]) + sys.argv.extend(['--slurm', '--globalfs', '--docker', '--docker-container=lofar-pulp:%s' % os.environ.get("LOFAR_TAG"), '--raw=/data/projects/%s' % project]) if (not self.coherentStokesEnabled): sys.argv.extend(["--noCS", "--noCV", "--noFE"]) -- GitLab From 7bca9893ba868a06ae36d88a34b2235ce9a3eaa9 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 11 Aug 2016 15:17:35 +0000 Subject: [PATCH 604/933] Task #8475: Added lofar-pulp Docker image to build on top of pulp:latest --- .gitattributes | 1 + Docker/CMakeLists.txt | 2 ++ Docker/lofar-pulp/Dockerfile.tmpl | 36 +++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 Docker/lofar-pulp/Dockerfile.tmpl diff --git a/.gitattributes b/.gitattributes index e315d6c8001..cc9b9b5a66d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2369,6 +2369,7 @@ Docker/lofar-base/bashrc.d/50-lofar -text Docker/lofar-base/chuser.sh -text Docker/lofar-outputproc/Dockerfile.tmpl -text Docker/lofar-pipeline/Dockerfile.tmpl -text +Docker/lofar-pulp/Dockerfile.tmpl -text Docker/lofar-tbbwriter/Dockerfile -text Docker/lofar-tbbwriter/bashrc -text Docker/lofar-tbbwriter/chuser.sh -text diff --git a/Docker/CMakeLists.txt b/Docker/CMakeLists.txt index 3bb17c3f7ba..22298021a53 100644 --- a/Docker/CMakeLists.txt +++ b/Docker/CMakeLists.txt @@ -25,6 +25,7 @@ lofar_add_bin_program(versiondocker versiondocker.cc) set(DOCKER_TEMPLATE_DIRS lofar-base lofar-pipeline + lofar-pulp lofar-outputproc) # Note: "docker-template" only works as long as the sources are still around, @@ -59,6 +60,7 @@ endforeach() install(DIRECTORY lofar-base lofar-pipeline + lofar-pulp lofar-outputproc lofar-tbbwriter DESTINATION share/docker diff --git a/Docker/lofar-pulp/Dockerfile.tmpl b/Docker/lofar-pulp/Dockerfile.tmpl new file mode 100644 index 00000000000..72dc993fb6f --- /dev/null +++ b/Docker/lofar-pulp/Dockerfile.tmpl @@ -0,0 +1,36 @@ +# +# base +# +FROM pulp:latest + +# Run-time dependencies +RUN sudo apt-get update && sudo apt-get install -y python-xmlrunner liblog4cplus-1.0-4 libxml2 libxml++2.6-2 openssh-client gettext-base rsync python-matplotlib + +# +# ******************* +# LOFAR +# ******************* +# + +# Tell image build information +ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ + LOFAR_TAG=${LOFAR_TAG} \ + LOFAR_REVISION=${LOFAR_REVISION} \ + LOFAR_BUILDVARIANT=gnu_optarch + +# Install +RUN sudo apt-get update && sudo apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev python-dev libxml2-dev pkg-config libunittest++-dev libxml++2.6-dev binutils-dev && \ + mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ + cd ${INSTALLDIR}/lofar && \ + svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ + svn --non-interactive -q up src/CMake && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Pipeline -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ + bash -c "mkdir -p ${INSTALLDIR}/lofar/var/{log,run}" && \ + bash -c "chmod a+rwx ${INSTALLDIR}/lofar/var/{log,run}" && \ + bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ + bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ + sudo apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev python-dev libxml2-dev pkg-config libunittest++-dev libxml++2.6-dev binutils-dev && \ + sudo apt-get autoremove -y + -- GitLab From 349140e460fcf847fb7ffa105a6d3d33595e2ac5 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 11 Aug 2016 19:30:07 +0000 Subject: [PATCH 605/933] Task #8475: Fix dependencies of Pipeline-recipes --- CEP/Pipeline/recipes/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/CMakeLists.txt b/CEP/Pipeline/recipes/CMakeLists.txt index 8951603a883..aa2b46cb431 100644 --- a/CEP/Pipeline/recipes/CMakeLists.txt +++ b/CEP/Pipeline/recipes/CMakeLists.txt @@ -1,6 +1,6 @@ # $Id$ -lofar_package(Pipeline-Recipes 0.1 DEPENDS Docker) +lofar_package(Pipeline-Recipes 0.1 DEPENDS Docker pyparameterset) # The pipeline.cfg needs to know whether QPID is installed include(LofarFindPackage) -- GitLab From c6e46b66d8b4e10c54dcfe15201a1ecfb7d63932 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 12 Aug 2016 06:16:13 +0000 Subject: [PATCH 606/933] Task #8475: Fixes for lofar-pulp: added QPID, MessageBus, fixed dependencies --- Docker/lofar-pulp/Dockerfile.tmpl | 32 ++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/Docker/lofar-pulp/Dockerfile.tmpl b/Docker/lofar-pulp/Dockerfile.tmpl index 72dc993fb6f..2b7754aa969 100644 --- a/Docker/lofar-pulp/Dockerfile.tmpl +++ b/Docker/lofar-pulp/Dockerfile.tmpl @@ -3,8 +3,32 @@ # FROM pulp:latest +COPY ["sudoers", "/etc/"] + # Run-time dependencies RUN sudo apt-get update && sudo apt-get install -y python-xmlrunner liblog4cplus-1.0-4 libxml2 libxml++2.6-2 openssh-client gettext-base rsync python-matplotlib +# +# ******************* +# QPID client +# ******************* +# + +ENV BOOST_VERSION=1.54 + +# Run-time dependencies +# QPID daemon legacy store would require: libaio1 libdb5.1++ +RUN sudo apt-get update && sudo apt-get install -y sasl2-bin libuuid1 libnss3 libnspr4 xqilla + +# Install +# QPID daemon legacy store would require: libaio-dev libdb5.1++-dev +RUN sudo apt-get update && sudo apt-get install -y ruby ruby-dev libsasl2-dev uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man libsslcommon2-dev libxqilla-dev && \ + mkdir /opt/qpid && \ + svn --non-interactive -q co https://svn.astron.nl/LOFAR/trunk/LCS/MessageBus/qpid/ /opt/qpid; \ + bash -c "HOME=/tmp /opt/qpid/local/sbin/build_qpid" && \ + bash -c "strip /opt/qpid/{bin,lib}/* || true" && \ + bash -c "rm -rf /tmp/sources" && \ + sudo apt-get purge -y ruby ruby-dev libsasl2-dev uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man libsslcommon2-dev libxqilla-dev && \ + sudo apt-get autoremove -y # # ******************* @@ -19,18 +43,20 @@ ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ LOFAR_BUILDVARIANT=gnu_optarch # Install -RUN sudo apt-get update && sudo apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev python-dev libxml2-dev pkg-config libunittest++-dev libxml++2.6-dev binutils-dev && \ +#some are already installed, but we need boost, and RUN sudo apt-get update && sudo apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev python-dev libxml2-dev pkg-config libunittest++-dev libxml++2.6-dev binutils-dev && \ +RUN sudo apt-get update && sudo apt-get install -y liblog4cplus-dev libhdf5-dev libblitz0-dev libunittest++-dev libxml++2.6-dev binutils-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES=Pipeline -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES="Pipeline MessageBus" -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ bash -c "mkdir -p ${INSTALLDIR}/lofar/var/{log,run}" && \ bash -c "chmod a+rwx ${INSTALLDIR}/lofar/var/{log,run}" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ - sudo apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev python-dev libxml2-dev pkg-config libunittest++-dev libxml++2.6-dev binutils-dev && \ + sudo apt-get purge -y liblog4cplus-dev libhdf5-dev libblitz0-dev libunittest++-dev libxml++2.6-dev binutils-dev && \ sudo apt-get autoremove -y +COPY ["bashrc", "/opt/"] -- GitLab From ac130b7837a2cdd5108d9b4a2cf10993d824446f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 12 Aug 2016 06:16:40 +0000 Subject: [PATCH 607/933] Task #8475: Fixes for lofar-pulp: added missing bashrc and sudoers overrides --- .gitattributes | 2 ++ Docker/lofar-pulp/bashrc | 11 +++++++++++ Docker/lofar-pulp/sudoers | 1 + 3 files changed, 14 insertions(+) create mode 100644 Docker/lofar-pulp/bashrc create mode 100644 Docker/lofar-pulp/sudoers diff --git a/.gitattributes b/.gitattributes index cc9b9b5a66d..6dbcab00441 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2370,6 +2370,8 @@ Docker/lofar-base/chuser.sh -text Docker/lofar-outputproc/Dockerfile.tmpl -text Docker/lofar-pipeline/Dockerfile.tmpl -text Docker/lofar-pulp/Dockerfile.tmpl -text +Docker/lofar-pulp/bashrc -text +Docker/lofar-pulp/sudoers -text Docker/lofar-tbbwriter/Dockerfile -text Docker/lofar-tbbwriter/bashrc -text Docker/lofar-tbbwriter/chuser.sh -text diff --git a/Docker/lofar-pulp/bashrc b/Docker/lofar-pulp/bashrc new file mode 100644 index 00000000000..7c9e948ce7e --- /dev/null +++ b/Docker/lofar-pulp/bashrc @@ -0,0 +1,11 @@ +#!/bin/bash + +# lofar +[ -r ${INSTALLDIR}/lofar/lofarinit.sh ] && source ${INSTALLDIR}/lofar/lofarinit.sh +export PATH PYTHONPATH LD_LIBRARY_PATH LOFARROOT + +# qpid +source ${INSTALLDIR}/qpid/.profile + +# lofarsoft +source ${LOFARSOFT}/devel_common/scripts/init.sh diff --git a/Docker/lofar-pulp/sudoers b/Docker/lofar-pulp/sudoers new file mode 100644 index 00000000000..24c18523f4b --- /dev/null +++ b/Docker/lofar-pulp/sudoers @@ -0,0 +1 @@ +ALL ALL=(ALL) NOPASSWD:ALL -- GitLab From af1b2de06783098a72877241613b6f206328d1e0 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 12 Aug 2016 12:03:46 +0000 Subject: [PATCH 608/933] Task #8475: Added OTDB_Services package to provide getOTDBParset --- Docker/lofar-pulp/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-pulp/Dockerfile.tmpl b/Docker/lofar-pulp/Dockerfile.tmpl index 2b7754aa969..f214fa8d765 100644 --- a/Docker/lofar-pulp/Dockerfile.tmpl +++ b/Docker/lofar-pulp/Dockerfile.tmpl @@ -49,7 +49,7 @@ RUN sudo apt-get update && sudo apt-get install -y liblog4cplus-dev libhdf5-dev cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ svn --non-interactive -q up src/CMake && \ - cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES="Pipeline MessageBus" -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES="Pipeline MessageBus OTDB_Services" -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ bash -c "mkdir -p ${INSTALLDIR}/lofar/var/{log,run}" && \ -- GitLab From d31361cb228f84f0faaa78bd6c3a1205874c701b Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 12 Aug 2016 12:51:46 +0000 Subject: [PATCH 609/933] Task #9678: Read POLLPRI data as well, and wait for stdout/stderr to signal partial close before calling communicate(), instead of waiting for the PID to exit --- CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py index b3ac6193700..cf966a224dd 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py +++ b/CEP/Pipeline/framework/lofarpipe/support/subprocessgroup.py @@ -77,7 +77,7 @@ class SubProcess(object): if self.completed: return True - if self.process.poll() is None: + if self.output_streams: return False # Process is finished, read remaining data and exit code @@ -226,7 +226,7 @@ class SubProcessGroup(object): fd_lookup = {} for process in processes: for fd in process.fds(): - poller.register(fd, select.POLLIN) + poller.register(fd, select.POLLIN | select.POLLPRI) fd_lookup[fd.fileno()] = process # poll for data -- GitLab From 60cbfd3f38ad11adcd16a02f934a0df644f54e5e Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 12 Aug 2016 19:08:54 +0000 Subject: [PATCH 610/933] Task #8475: Disable pulp auto mode for CEP4 --- CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py index 2e9d98ede11..926d07151ff 100755 --- a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py @@ -146,11 +146,13 @@ class pulsar_pipeline(control): # Rebuilding sys.argv without the options given automatically by framework # --auto = automatic run from framework # -q = quiet mode, no user interaction - sys.argv = ['pulp.py', '--auto', '-q'] + sys.argv = ['pulp.py', '-q'] if self.globalfs: project = self.parset.getString(self.parset.fullModuleName('Campaign') + '.name') sys.argv.extend(['--slurm', '--globalfs', '--docker', '--docker-container=lofar-pulp:%s' % os.environ.get("LOFAR_TAG"), '--raw=/data/projects/%s' % project]) + else: + sys.argv.append("--auto") if (not self.coherentStokesEnabled): sys.argv.extend(["--noCS", "--noCV", "--noFE"]) -- GitLab From 831162d224f7bbcb02446070ed89abd799a23ec6 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sun, 14 Aug 2016 12:24:46 +0000 Subject: [PATCH 611/933] Task #9678: Fix use of socket.recv in jobserver and lofarnode in case of closed connections and interrupts --- .../framework/lofarpipe/support/jobserver.py | 15 ++++++----- .../framework/lofarpipe/support/lofarnode.py | 20 +++++++++----- .../framework/lofarpipe/support/utilities.py | 26 +++++++++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/jobserver.py b/CEP/Pipeline/framework/lofarpipe/support/jobserver.py index 10182fe2890..a88a795a422 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/jobserver.py +++ b/CEP/Pipeline/framework/lofarpipe/support/jobserver.py @@ -21,7 +21,7 @@ import cPickle as pickle from lofarpipe.support.lofarexceptions import PipelineQuit from lofarpipe.support.pipelinelogging import log_process_output -from lofarpipe.support.utilities import spawn_process +from lofarpipe.support.utilities import spawn_process, socket_recv class JobStreamHandler(SocketServer.StreamRequestHandler): """ @@ -41,17 +41,20 @@ class JobStreamHandler(SocketServer.StreamRequestHandler): Each request is expected to be a 4-bute length followed by either a GET/PUT request or a pickled LogRecord. """ + while True: - chunk = self.request.recv(4) + # Read message length try: + chunk = socket_recv(self.request, 4) slen = struct.unpack(">L", chunk)[0] except: break - chunk = self.connection.recv(slen) - while len(chunk) < slen: - chunk = chunk + self.connection.recv(slen - len(chunk)) - input_msg = chunk.split(" ", 2) + + # Read message + chunk = socket_recv(self.request, slen) try: + input_msg = chunk.split(" ", 2) + # Can we handle this message type? if input_msg[0] == "GET": self.send_arguments(int(input_msg[1])) diff --git a/CEP/Pipeline/framework/lofarpipe/support/lofarnode.py b/CEP/Pipeline/framework/lofarpipe/support/lofarnode.py index ab028f856f4..c8127e4db51 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/lofarnode.py +++ b/CEP/Pipeline/framework/lofarpipe/support/lofarnode.py @@ -16,6 +16,7 @@ import logging.handlers import cPickle as pickle from lofarpipe.support.usagestats import UsageStats +from lofarpipe.support.utilities import socket_recv def run_node(*args): """ @@ -128,18 +129,25 @@ class LOFARnodeTCP(LOFARnode): while True: tries -= 1 try: + # connect s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.__try_connect(s) + + # send request message = "GET %d" % self.job_id s.sendall(struct.pack(">L", len(message)) + message) - chunk = s.recv(4) + + # receive response length + chunk = socket_recv(s, 4) slen = struct.unpack(">L", chunk)[0] - chunk = s.recv(slen) - while len(chunk) < slen: - chunk += s.recv(slen - len(chunk)) + + # receive response + chunk = socket_recv(s, slen) + + # parse response self.arguments = pickle.loads(chunk) - except socket.error, e: - print "Failed to get recipe arguments from server" + except (IOError, socket.error) as e: + print "Failed to get recipe arguments from server: %s" % (e,) if tries > 0: timeout = random.uniform(min_timeout, max_timeout) print("Retrying in %f seconds (%d more %s)." % diff --git a/CEP/Pipeline/framework/lofarpipe/support/utilities.py b/CEP/Pipeline/framework/lofarpipe/support/utilities.py index c445578c03c..31bf7c21a6b 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/utilities.py +++ b/CEP/Pipeline/framework/lofarpipe/support/utilities.py @@ -299,3 +299,29 @@ def catch_segfaults(cmd, cwd, env, logger, max = 1, cleanup = lambda: None, logger.error("Too many segfaults from %s; aborted" % (cmd[0])) raise subprocess.CalledProcessError(process.returncode, cmd[0]) return process + +def socket_recv(socket, numbytes): + """ + Read numbytes from the given socket. + + Raises IOError if connection has closed before all data could be read. + """ + + data = "" + while numbytes > 0: + try: + chunk = socket.recv(numbytes) + except IOError, e: + if e.errno == errno.EINTR: + continue + else: + raise + + if not chunk: + raise IOError("Connection closed. Received '%s', need %d more bytes" % (data,numbytes)) + + data += chunk + numbytes -= len(chunk) + + return data + -- GitLab From 5a8840ce207b4e3179d286f9db705de991415b90 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sun, 14 Aug 2016 12:30:29 +0000 Subject: [PATCH 612/933] Task #8475: Reenable auto mode for CEP4, but replace "CEP4:" hostnames with localhost --- .../recipes/sip/bin/pulsar_pipeline.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py index 926d07151ff..e13692ba7c8 100755 --- a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py @@ -46,12 +46,24 @@ class pulsar_pipeline(control): dps = self.parset.makeSubset( self.parset.fullModuleName('DataProducts') + '.' ) + + def fix_locations(locations): + """ Hack to turn CEP4:/xxxx paths into valid host:path combinations. """ + + def fix(loc): + host,path = loc.split(':') + if host == "CEP4": + host = "localhost" + return "%s:%s" % (host,path) + + return [fix(loc) for loc in locations] + # Coherent Stokes input data self.coherentStokesEnabled = dps.getBool('Input_CoherentStokes.enabled', False) self.input_data['coherent'] = DataMap([ tuple(os.path.join(location, filename).split(':')) + (skip,) for location, filename, skip in zip( - dps.getStringVector('Input_CoherentStokes.locations'), + fix_locations(dps.getStringVector('Input_CoherentStokes.locations')), dps.getStringVector('Input_CoherentStokes.filenames'), dps.getBoolVector('Input_CoherentStokes.skip')) ]) @@ -62,7 +74,7 @@ class pulsar_pipeline(control): self.input_data['incoherent'] = DataMap([ tuple(os.path.join(location, filename).split(':')) + (skip,) for location, filename, skip in zip( - dps.getStringVector('Input_IncoherentStokes.locations'), + fix_locations(dps.getStringVector('Input_IncoherentStokes.locations')), dps.getStringVector('Input_IncoherentStokes.filenames'), dps.getBoolVector('Input_IncoherentStokes.skip')) ]) @@ -71,7 +83,7 @@ class pulsar_pipeline(control): self.output_data['data'] = DataMap([ tuple(os.path.join(location, filename).split(':')) + (skip,) for location, filename, skip in zip( - dps.getStringVector('Output_Pulsar.locations'), + fix_locations(dps.getStringVector('Output_Pulsar.locations')), dps.getStringVector('Output_Pulsar.filenames'), dps.getBoolVector('Output_Pulsar.skip')) ]) @@ -146,7 +158,7 @@ class pulsar_pipeline(control): # Rebuilding sys.argv without the options given automatically by framework # --auto = automatic run from framework # -q = quiet mode, no user interaction - sys.argv = ['pulp.py', '-q'] + sys.argv = ['pulp.py', '--auto', '-q'] if self.globalfs: project = self.parset.getString(self.parset.fullModuleName('Campaign') + '.name') -- GitLab From fb9b506df8a9c90f57d8a58fab3d05dbcea3a8ee Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 15 Aug 2016 08:09:27 +0000 Subject: [PATCH 613/933] Task #8475: Removed location fixes for CEP4, as pulp does not need them anymore --- CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py index e13692ba7c8..988d03222f7 100755 --- a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py @@ -47,23 +47,12 @@ class pulsar_pipeline(control): self.parset.fullModuleName('DataProducts') + '.' ) - def fix_locations(locations): - """ Hack to turn CEP4:/xxxx paths into valid host:path combinations. """ - - def fix(loc): - host,path = loc.split(':') - if host == "CEP4": - host = "localhost" - return "%s:%s" % (host,path) - - return [fix(loc) for loc in locations] - # Coherent Stokes input data self.coherentStokesEnabled = dps.getBool('Input_CoherentStokes.enabled', False) self.input_data['coherent'] = DataMap([ tuple(os.path.join(location, filename).split(':')) + (skip,) for location, filename, skip in zip( - fix_locations(dps.getStringVector('Input_CoherentStokes.locations')), + dps.getStringVector('Input_CoherentStokes.locations'), dps.getStringVector('Input_CoherentStokes.filenames'), dps.getBoolVector('Input_CoherentStokes.skip')) ]) @@ -74,7 +63,7 @@ class pulsar_pipeline(control): self.input_data['incoherent'] = DataMap([ tuple(os.path.join(location, filename).split(':')) + (skip,) for location, filename, skip in zip( - fix_locations(dps.getStringVector('Input_IncoherentStokes.locations')), + dps.getStringVector('Input_IncoherentStokes.locations'), dps.getStringVector('Input_IncoherentStokes.filenames'), dps.getBoolVector('Input_IncoherentStokes.skip')) ]) @@ -83,7 +72,7 @@ class pulsar_pipeline(control): self.output_data['data'] = DataMap([ tuple(os.path.join(location, filename).split(':')) + (skip,) for location, filename, skip in zip( - fix_locations(dps.getStringVector('Output_Pulsar.locations')), + dps.getStringVector('Output_Pulsar.locations'), dps.getStringVector('Output_Pulsar.filenames'), dps.getBoolVector('Output_Pulsar.skip')) ]) -- GitLab From 1078b0604992c3e867bc9ea6046b3b7787f0924e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 15 Aug 2016 09:48:02 +0000 Subject: [PATCH 614/933] Task #9778: added logging --- LTA/LTAIngest/ingestpipeline.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index 32ae589a94f..eb4cdde201e 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -116,6 +116,7 @@ class IngestPipeline(): def GetStorageTicket(self): try: + self.logger.debug("calling GetStorageTicket(%s, %s, %s, %s, %s, %s, True, %s)", self.Project, self.FileName, self.FileSize, self.ArchiveId, self.JobId, self.ObsId, self.Type) start = time.time() result = self.ltaClient.GetStorageTicket(self.Project, self.FileName, self.FileSize, self.ArchiveId, self.JobId, self.ObsId, True, self.Type) self.logger.debug("GetStorageTicket for %s took %ds" % (self.JobId, time.time() - start)) @@ -174,11 +175,13 @@ class IngestPipeline(): def TransferFile(self): self.logger.debug('Starting file transfer') hostname = socket.getfqdn() + self.logger.debug('%s: hostname=%s HostLocation=%s', self.JobId, hostname, self.HostLocation) javacmd = "java" if "lexar001" in hostname or "lexar002" in hostname: javacmd = "/data/java7/jdk1.7.0_55/bin/java" ltacppath = "/globalhome/%s/ltacp" % ("ingesttest" if self.ltacpport == 8801 else "ingest") + use_shell = False if self.HostLocation == 'localhost': # copy data with ltacp client from HostLocation localhost, to ltacpserver at localhost @@ -190,10 +193,11 @@ class IngestPipeline(): if "lexar003" in hostname or "lexar004" in hostname: #lexar003/004 are not aware of cep2, and cep2 is not aware of lexar003/004, #so make sure we can handle the proper hostnames and ips - use_shell=True if self.HostLocation.startswith('locus') and not self.HostLocation.endswith('cep2.lofar'): hostname = '10.178.1.3' if "lexar003" in hostname else '10.178.1.4' self.HostLocation += '.cep2.lofar' + else: + use_shell=True # copy data with ltacp from a remote host, so use ssh if self.PrimaryUri: @@ -201,6 +205,7 @@ class IngestPipeline(): else: cmd = ["ssh", "-T", "ingest@" + self.HostLocation, "cd %s;%s -Xmx256m -cp %s/qpid-properties/lexar001.offline.lofar:%s/ltacp.jar nl.astron.ltacp.client.LtaCp %s %s %s/%s %s" % (self.LocationDir, javacmd, ltacppath, ltacppath, hostname, self.ltacpport, self.tempPrimary, self.FileName, self.Source)] + self.logger.debug('%s: hostname=%s HostLocation=%s, use_shell=%s', self.JobId, hostname, self.HostLocation, use_shell) ## SecondaryUri handling not implemented self.logger.debug(' '.join(cmd)) start = time.time() @@ -541,6 +546,7 @@ class IngestPipeline(): ## function raised PipelineError itself. Assume retries not useful raise except Exception as e: + self.logger.error('Exception for %s in %s: %s', self.JobId, func.__name__, e) error += '\n' + str(e) else: if retry: -- GitLab From 3291850c4d314d6c7f8d74f5a0463429a1200ced Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 15 Aug 2016 12:51:32 +0000 Subject: [PATCH 615/933] Task #9607: only clean mom/otdb id filter box when task found --- .../lib/static/app/controllers/datacontroller.js | 6 ++++++ .../lib/static/app/controllers/gridcontroller.js | 10 ++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 9b03ade3d06..c35c2c02f7c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -822,25 +822,31 @@ dataControllerMod.controller('DataController', $scope.jumpToNow(); $scope.loadTaskByOTDBIdSelectAndJumpIntoView = function(otdb_id) { + var defer = $q.defer(); $scope.dataService.getTaskByOTDBId(otdb_id).then(function(task) { if(task) { $scope.dataService.tasks.push(task); $scope.dataService.taskDict[task.id] = task; $scope.dataService.setSelectedTaskId(task.id); $scope.jumpToSelectedTask(); + defer.resolve(); } }); + return defer.promise; }; $scope.loadTaskByMoMIdSelectAndJumpIntoView = function(mom_id) { + var defer = $q.defer(); $scope.dataService.getTaskByMoMId(mom_id).then(function(task) { if(task) { $scope.dataService.tasks.push(task); $scope.dataService.taskDict[task.id] = task; $scope.dataService.setSelectedTaskId(task.id); $scope.jumpToSelectedTask(); + defer.resolve(); } }); + return defer.promise; }; $scope.selectCurrentTask = function() { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index e54e9c8b7ff..1b11c89939e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -153,15 +153,17 @@ $scope.columns = [ var otdb_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'otdb_id'; }); if(otdb_col && otdb_col.filters.length && otdb_col.filters[0].hasOwnProperty('term')) { var otdb_id = otdb_col.filters[0].term; - $scope.$parent.$parent.loadTaskByOTDBIdSelectAndJumpIntoView(otdb_id); - otdb_col.filters[0].term = null; + $scope.$parent.$parent.loadTaskByOTDBIdSelectAndJumpIntoView(otdb_id).then(function() { + otdb_col.filters[0].term = null; + }); } else { var mom_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_id'; }); if(mom_col && mom_col.filters.length && mom_col.filters[0].hasOwnProperty('term')) { var mom_id = mom_col.filters[0].term; - $scope.$parent.$parent.loadTaskByMoMIdSelectAndJumpIntoView(mom_id); - mom_col.filters[0].term = null; + $scope.$parent.$parent.loadTaskByMoMIdSelectAndJumpIntoView(mom_id).then(function() { + mom_col.filters[0].term = null; + }); } } } -- GitLab From 6263ee2cc4358a2568053bf6e4a8cafa83560d7f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 15 Aug 2016 13:17:14 +0000 Subject: [PATCH 616/933] Task #9607: only load task for mom/otdb id if not already present --- .../static/app/controllers/datacontroller.js | 86 +++++++++++++------ 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index c35c2c02f7c..6264bed1d32 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -340,31 +340,67 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.getTaskByOTDBId = function(otdb_id) { var defer = $q.defer(); - $http.get('/rest/tasks/otdb/' + otdb_id).success(function(result) { - var task = result.task; - if(task) { - task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); - task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); - } - defer.resolve(task); - }).error(function(result) { - defer.resolve(undefined); - }) + + if(typeof(otdb_id) === 'string') { + otdb_id = parseInt(otdb_id); + } + + var foundTask = self.tasks.find(function(t) { return t.otdb_id == otdb_id; }); + + if(foundTask) { + defer.resolve(foundTask); + } else { + $http.get('/rest/tasks/otdb/' + otdb_id).success(function(result) { + var task = result.task; + if(task) { + task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); + task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); + + if(!self.taskDict.hasOwnProperty(task.id)) { + self.tasks.push(task); + self.taskDict[task.id] = task; + self.taskChangeCntr++; + } + } + defer.resolve(task); + }).error(function(result) { + defer.resolve(undefined); + }) + } + return defer.promise; }; self.getTaskByMoMId = function(mom_id) { var defer = $q.defer(); - $http.get('/rest/tasks/mom/' + mom_id).success(function(result) { - var task = result.task; - if(task) { - task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); - task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); - } - defer.resolve(task); - }).error(function(result) { - defer.resolve(undefined); - }) + + if(typeof(mom_id) === 'string') { + mom_id = parseInt(mom_id); + } + + var foundTask = self.tasks.find(function(t) { return t.mom_id == mom_id; }); + + if(foundTask) { + defer.resolve(foundTask); + } else { + $http.get('/rest/tasks/mom/' + mom_id).success(function(result) { + var task = result.task; + if(task) { + task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); + task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); + + if(!self.taskDict.hasOwnProperty(task.id)) { + self.tasks.push(task); + self.taskDict[task.id] = task; + self.taskChangeCntr++; + } + } + defer.resolve(task); + }).error(function(result) { + defer.resolve(undefined); + }) + } + return defer.promise; }; @@ -748,7 +784,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } } } - + self.claimChangeCntr++; self.computeMinMaxResourceClaimTimes(); } else if(change.objectType == 'resourceCapacity') { @@ -793,8 +829,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, var dataControllerMod = angular.module('DataControllerMod', ['ngResource']); dataControllerMod.controller('DataController', - ['$scope', 'dataService', - function($scope, dataService) { + ['$scope', '$q', 'dataService', + function($scope, $q, dataService) { var self = this; $scope.dataService = dataService; dataService.dataCtrl = this; @@ -825,8 +861,6 @@ dataControllerMod.controller('DataController', var defer = $q.defer(); $scope.dataService.getTaskByOTDBId(otdb_id).then(function(task) { if(task) { - $scope.dataService.tasks.push(task); - $scope.dataService.taskDict[task.id] = task; $scope.dataService.setSelectedTaskId(task.id); $scope.jumpToSelectedTask(); defer.resolve(); @@ -839,8 +873,6 @@ dataControllerMod.controller('DataController', var defer = $q.defer(); $scope.dataService.getTaskByMoMId(mom_id).then(function(task) { if(task) { - $scope.dataService.tasks.push(task); - $scope.dataService.taskDict[task.id] = task; $scope.dataService.setSelectedTaskId(task.id); $scope.jumpToSelectedTask(); defer.resolve(); -- GitLab From ff109ea992f729575026303982e213e162415a3d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 15 Aug 2016 14:14:07 +0000 Subject: [PATCH 617/933] Task #9607: proper HH:mm::ss conversion for > 24 hours --- .../lib/static/app/app.js | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js index 7b4e06b1712..d77138a2e32 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js @@ -16,8 +16,29 @@ app.config(['$compileProvider', function ($compileProvider) { $compileProvider.debugInfoEnabled(false); }]); +var secondsToHHmmss = function(seconds) { + var hours = Math.floor(seconds / 3600); + var remaining_seconds = seconds - (3600 * hours); + + var minutes = Math.floor(remaining_seconds / 60); + remaining_seconds = remaining_seconds - (60 * minutes); + + var str = ''; + if(hours < 10) { + str += '0'; + } + str += hours + ':'; + if(minutes < 10) { + str += '0'; + } + str += minutes + ':'; + if(remaining_seconds < 10) { + str += '0'; + } + str += remaining_seconds; + return str; +}; + app.filter('secondsToHHmmss', function($filter) { - return function(seconds) { - return $filter('date')(new Date(0, 0, 0).setSeconds(seconds), 'HH:mm:ss'); - }; + return secondsToHHmmss; }) -- GitLab From 63cec7934363547892e79c50fcd89a2797545e35 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 15 Aug 2016 14:14:33 +0000 Subject: [PATCH 618/933] Task #9607: proper filtering on duration --- .../lib/static/app/controllers/gridcontroller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 1b11c89939e..f420d6121ce 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -46,6 +46,7 @@ $scope.columns = [ { field: 'duration', displayName: 'Duration', width: '6%', + type: 'number', enableFiltering: false, enableCellEdit: false, enableCellEditOnFocus: false, -- GitLab From d322a259b3bc6ad288ab4743e3b4e74ee3641fd3 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 15 Aug 2016 14:57:19 +0000 Subject: [PATCH 619/933] Task #9607: show cluster column in listview --- .../static/app/controllers/datacontroller.js | 39 ++++++++++++++++++- .../static/app/controllers/gridcontroller.js | 24 ++++++++++-- .../lib/static/css/main.css | 7 ++++ 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 6264bed1d32..b77735c1f72 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -15,6 +15,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.resourceGroupsDict = {}; self.resourceGroupMemberships = {}; self.resourceClaimDict = {}; + self.task2resourceClaimDict = {}; self.resourceUsagesDict = {}; self.tasktypesDict = {}; @@ -246,6 +247,21 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.resourceClaims = visibleClaims; self.resourceClaimDict = self.toIdBasedDict(self.resourceClaims); + self.task2resourceClaimDict = {}; + + for(var claim_id in self.resourceClaimDict) { + var claim = self.resourceClaimDict[claim_id]; + + if(!self.task2resourceClaimDict.hasOwnProperty(claim.task_id)) { + self.task2resourceClaimDict[claim.task_id] = {}; + } + + if(!self.task2resourceClaimDict[claim.task_id].hasOwnProperty(claim_id)) { + self.task2resourceClaimDict[claim.task_id][claim_id] = claim; + } + } + + self.computeMinMaxResourceClaimTimes(); var newLoadedHours = {}; @@ -516,11 +532,20 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, for(var i = newClaimIds.length-1; i >= 0; i--) { var claim_id = newClaimIds[i]; + var claim = newClaimDict[claim_id]; + if(!self.resourceClaimDict.hasOwnProperty(claim_id)) { - var claim = newClaimDict[claim_id]; self.resourceClaims.push(claim); self.resourceClaimDict[claim_id] = claim; } + + if(!self.task2resourceClaimDict.hasOwnProperty(claim.task_id)) { + self.task2resourceClaimDict[claim.task_id] = {}; + } + + if(!self.task2resourceClaimDict[claim.task_id].hasOwnProperty(claim_id)) { + self.task2resourceClaimDict[claim.task_id][claim_id] = claim; + } } self.computeMinMaxResourceClaimTimes(); @@ -774,6 +799,14 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, changedClaim.endtime = new Date(changedClaim.endtime); self.resourceClaims.push(changedClaim); self.resourceClaimDict[changedClaim.id] = changedClaim; + + if(!self.task2resourceClaimDict.hasOwnProperty(changedClaim.task_id)) { + self.task2resourceClaimDict[changedClaim.task_id] = {}; + } + + if(!self.task2resourceClaimDict[changedClaim.task_id].hasOwnProperty(changedClaim.id)) { + self.task2resourceClaimDict[changedClaim.task_id][changedClaim.id] = claim; + } } } else if(change.changeType == 'delete') { delete self.resourceClaimDict[changedClaim.id] @@ -783,6 +816,10 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, break; } } + + if(self.task2resourceClaimDict.hasOwnProperty(changedClaim.task_id)) { + delete self.task2resourceClaimDict[changedClaim.task_id][changedClaim.id]; + } } self.claimChangeCntr++; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index f420d6121ce..f59a1eb8935 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -14,7 +14,7 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid $scope.columns = [ { field: 'name', enableCellEdit: false, - width: '15%' + width: '12%' }, { field: 'project_name', displayName:'Project', @@ -80,7 +80,7 @@ $scope.columns = [ displayName: 'Group', enableCellEdit: false, cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity.mom_object_group_id}} {{row.entity.mom_object_group_name}}</a>', - width: '13%', + width: '12%', filter: { condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, @@ -103,6 +103,20 @@ $scope.columns = [ enableCellEdit: false, cellTemplate:'<a target="_blank" href="tasks/{{row.entity.id}}.html">{{row.entity[col.field]}}</a>', width: '6%' + }, + { field: 'cluster', + displayName: 'cluster', + enableCellEdit: false, + width: '6%', + filter: { + condition: uiGridConstants.filter.EXACT, + type: uiGridConstants.filter.SELECT, + selectOptions: [] + }, + cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) { + var value = grid.getCellValue(row,col); + return "grid-cluster-" + value; + } }]; $scope.gridOptions = { enableGridMenu: false, @@ -235,7 +249,8 @@ $scope.columns = [ mom2object_id: task.mom2object_id, mom_object_group_id: task.mom_object_group_id, mom_object_group_name: task.mom_object_group_name, - mom_object_group_mom2object_id: task.mom_object_group_mom2object_id + mom_object_group_mom2object_id: task.mom_object_group_mom2object_id, + cluster: $scope.dataService.task2resourceClaimDict.hasOwnProperty(task.id) ? 'CEP4' : 'CEP2' }; gridTasks.push(gridTask); } @@ -270,6 +285,7 @@ $scope.columns = [ }; $scope.$watch('dataService.taskChangeCntr', function() { $scope.$evalAsync(populateList); }); + $scope.$watch('dataService.claimChangeCntr', function() { $scope.$evalAsync(populateList); }); $scope.$watch('dataService.viewTimeSpan', function() { $scope.$evalAsync(populateList); $scope.$evalAsync(jumpToSelectedTaskRows); @@ -289,6 +305,8 @@ $scope.columns = [ tasktypenames = $scope.dataService.tasktypes.map(function(x) { return x.name; }); fillColumFilterSelectOptions(tasktypenames, $scope.columns[6]); + fillColumFilterSelectOptions(['CEP2', 'CEP4'], $scope.columns[11]); + fillProjectsColumFilterSelectOptions(); }); }); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index b66705bfd49..6f5286b791e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -227,6 +227,13 @@ div.gantt-task.claim-task-status-suspended span { } +.grid-cluster-CEP2 { + background-color: #ccffff !important; +} + +.grid-cluster-CEP4 { + background-color: #ccffcc !important; +} -- GitLab From ac8c4955304a530ae3614d0da5f7d60849b5a283 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 16 Aug 2016 07:12:59 +0000 Subject: [PATCH 620/933] Task #9682: Let SLURM jobs result in FAILED if pipeline fails, but do not run abort-trigger if pipeline could set the ABORTED status itself --- MAC/Services/src/PipelineControl.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index efef3e65f8d..a867662a91d 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -512,7 +512,9 @@ runcmd docker run --rm --net=host \ {image} \ runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -if [ $? -eq 0 ]; then +RESULT=$? + +if [ $RESULT -eq 0 ]; then # notify that we're tearing down runcmd {setStatus_completing} @@ -526,15 +528,18 @@ if [ $? -eq 0 ]; then wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} FINISHED&host_regex=" else # notify system that we've aborted - {setStatus_aborted} + runcmd {setStatus_aborted} # notify ganglia wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} ABORTED&host_regex=" fi -# if we've reached this point, the process (not the pipeline) ran ok. -# we do not want to trigger the $OBSID-aborted job. -exit 0 +# remove the abort-trigger job +scancel --jobname={obsid}-abort-trigger + +# report status back to SLURM +echo "Pipeline exited with status $RESULT" +exit $RESULT """.format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, -- GitLab From 109cebdfae7782dd0780ec38a1a757bffc0ebfd3 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 16 Aug 2016 08:06:25 +0000 Subject: [PATCH 621/933] Task #9607: get cluster info from spec in radb --- .../ResourceAssignmentDatabase/radb.py | 63 ++++++++++++++++++- .../static/app/controllers/datacontroller.js | 36 ----------- .../static/app/controllers/gridcontroller.js | 2 +- 3 files changed, 61 insertions(+), 40 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index e013b2f0377..f974a13f872 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -192,6 +192,8 @@ class RADatabase: tasks = list(self._executeQuery(query, qargs, fetch=_FETCH_ALL)) + self._addClusterToTasks(tasks) + return tasks @@ -221,8 +223,48 @@ class RADatabase: if task['successor_ids'] is None: task['successor_ids'] = [] + self._addClusterToTasks([task]) return task + def _addClusterToTasks(self, tasks): + #parse the parset content of the specification + #and retreive the storageClusterName for each task if possible + try: + spec_ids = [t['specification_id'] for t in tasks if t != None] + + specs = self.getSpecifications(specification_ids=spec_ids) + spec_dict = {s['id']:s for s in specs} + + keys = ['Output_Correlated', + 'Output_IncoherentStokes', + 'Output_CoherentStokes', + 'Output_InstrumentModel', + 'Output_SkyImage', + 'Output_Pulsar'] + + for task in tasks: + try: + spec = spec_dict.get(task['specification_id']) + if spec: + parset = [line.strip() for line in spec['content'].split('\n')] + parset = [line for line in parset if line.startswith('Observation.DataProducts')] + parset = [line.split('=') for line in parset] + parset = {items[0]:items[1] for items in parset} + + enabled_keys = [key for key in keys if parset.get('Observation.DataProducts.%s.enabled' % key, '').lower() in ['true', 't', 'yes', 'y', '1']] + clusters = [parset.get('Observation.DataProducts.%s.storageClusterName' % key, 'CEP2') for key in enabled_keys] + clusters = [c if c else 'CEP2' for c in clusters] + + #pick median cluster + clusters.sort() + task['cluster'] = clusters[len(clusters)/2] + else: + task['cluster'] = 'unknown' + except Exception as e: + task['cluster'] = 'unknown' + except Exception as e: + pass + def _convertTaskTypeAndStatusToIds(self, task_status, task_type): '''converts task_status and task_type to id's in case one and/or the other are strings''' if task_status and isinstance(task_status, basestring): @@ -382,10 +424,25 @@ class RADatabase: self.commit() return ids - def getSpecifications(self): - query = '''SELECT * from resource_allocation.specification;''' + def getSpecifications(self, specification_ids = None): + query = '''SELECT * from resource_allocation.specification''' - return list(self._executeQuery(query, fetch=_FETCH_ALL)) + conditions = [] + qargs = [] + + if specification_ids is not None: + if isinstance(specification_ids, int): # just a single id + conditions.append('id = %s') + qargs.append(specification_ids) + else: #assume a list/enumerable of id's + if len(specification_ids): + conditions.append('id in %s') + qargs.append(tuple(specification_ids)) + + if conditions: + query += ' WHERE ' + ' AND '.join(conditions) + + return list(self._executeQuery(query, qargs, fetch=_FETCH_ALL)) def getSpecification(self, specification_id): query = '''SELECT * from resource_allocation.specification spec diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index b77735c1f72..a7f4e8870ca 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -15,7 +15,6 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.resourceGroupsDict = {}; self.resourceGroupMemberships = {}; self.resourceClaimDict = {}; - self.task2resourceClaimDict = {}; self.resourceUsagesDict = {}; self.tasktypesDict = {}; @@ -247,21 +246,6 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.resourceClaims = visibleClaims; self.resourceClaimDict = self.toIdBasedDict(self.resourceClaims); - self.task2resourceClaimDict = {}; - - for(var claim_id in self.resourceClaimDict) { - var claim = self.resourceClaimDict[claim_id]; - - if(!self.task2resourceClaimDict.hasOwnProperty(claim.task_id)) { - self.task2resourceClaimDict[claim.task_id] = {}; - } - - if(!self.task2resourceClaimDict[claim.task_id].hasOwnProperty(claim_id)) { - self.task2resourceClaimDict[claim.task_id][claim_id] = claim; - } - } - - self.computeMinMaxResourceClaimTimes(); var newLoadedHours = {}; @@ -538,14 +522,6 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.resourceClaims.push(claim); self.resourceClaimDict[claim_id] = claim; } - - if(!self.task2resourceClaimDict.hasOwnProperty(claim.task_id)) { - self.task2resourceClaimDict[claim.task_id] = {}; - } - - if(!self.task2resourceClaimDict[claim.task_id].hasOwnProperty(claim_id)) { - self.task2resourceClaimDict[claim.task_id][claim_id] = claim; - } } self.computeMinMaxResourceClaimTimes(); @@ -799,14 +775,6 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, changedClaim.endtime = new Date(changedClaim.endtime); self.resourceClaims.push(changedClaim); self.resourceClaimDict[changedClaim.id] = changedClaim; - - if(!self.task2resourceClaimDict.hasOwnProperty(changedClaim.task_id)) { - self.task2resourceClaimDict[changedClaim.task_id] = {}; - } - - if(!self.task2resourceClaimDict[changedClaim.task_id].hasOwnProperty(changedClaim.id)) { - self.task2resourceClaimDict[changedClaim.task_id][changedClaim.id] = claim; - } } } else if(change.changeType == 'delete') { delete self.resourceClaimDict[changedClaim.id] @@ -816,10 +784,6 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, break; } } - - if(self.task2resourceClaimDict.hasOwnProperty(changedClaim.task_id)) { - delete self.task2resourceClaimDict[changedClaim.task_id][changedClaim.id]; - } } self.claimChangeCntr++; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index f59a1eb8935..97d6da7fd8c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -250,7 +250,7 @@ $scope.columns = [ mom_object_group_id: task.mom_object_group_id, mom_object_group_name: task.mom_object_group_name, mom_object_group_mom2object_id: task.mom_object_group_mom2object_id, - cluster: $scope.dataService.task2resourceClaimDict.hasOwnProperty(task.id) ? 'CEP4' : 'CEP2' + cluster: task.cluster }; gridTasks.push(gridTask); } -- GitLab From 0aa4cd1093e2979ecc64bd6381817416fae7c9f0 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Tue, 16 Aug 2016 09:26:06 +0000 Subject: [PATCH 622/933] Task #9640: SB should be SBG in output filenames of LB pipeline --- .../RAtoOTDBTaskSpecificationPropagator/lib/translator.py | 6 +++++- .../sql/add_resource_allocation_statics.sql | 2 +- SAS/XML_generator/src/xmlgen.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 60a68de5a17..15d4c860a2d 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -79,9 +79,13 @@ class RAtoOTDBTranslator(): else: ## It's a pipeline (no SAPs) if 'start_sb_nr' in storage_properties: sb_nr = storage_properties['start_sb_nr'] + filename_template = "L%d_SB%03d_uv.MS" + elif 'start_sbg_nr' in storage_properties: #Right now we're assuming this doesn't happen on raw data! + sb_nr = storage_properties['start_sbg_nr'] + filename_template = "L%d_SBG%03d_uv.MS" for _ in xrange(storage_properties['nr_of_uv_files']): locations.append(self.locationPath(project_name, otdb_id) + '/uv') - filenames.append("L%d_SB%03d_uv.MS" % (otdb_id, sb_nr)) + filenames.append(filename_template % (otdb_id, sb_nr)) skip.append("0") sb_nr += 1 result[PREFIX + 'DataProducts.%s_Correlated.locations' % (io_type)] = '[' + to_csv_string(locations) + ']' diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql index 6e8af1e2ecc..ef70cae296b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_resource_allocation_statics.sql @@ -7,7 +7,7 @@ INSERT INTO resource_allocation.task_status VALUES (200, 'prepared'), (300, 'app (1150, 'error'), (1200, 'obsolete'); -- This is the list from OTDB, we'll need to merge it with the list from MoM in the future, might use different indexes? INSERT INTO resource_allocation.task_type VALUES (0, 'observation'),(1, 'pipeline'); -- We'll need more types INSERT INTO resource_allocation.resource_claim_status VALUES (0, 'claimed'), (1, 'allocated'), (2, 'conflict'); -INSERT INTO resource_allocation.resource_claim_property_type VALUES (0, 'nr_of_is_files'),(1, 'nr_of_cs_files'),(2, 'nr_of_uv_files'),(3, 'nr_of_im_files'),(4, 'nr_of_img_files'),(5, 'nr_of_pulp_files'),(6, 'nr_of_cs_stokes'),(7, 'nr_of_is_stokes'),(8, 'is_file_size'),(9, 'cs_file_size'),(10, 'uv_file_size'),(11, 'im_file_size'),(12, 'img_file_size'),(13, 'nr_of_pulp_files'),(14, 'nr_of_tabs'),(15, 'start_sb_nr'),(16,'uv_otdb_id'),(17,'cs_otdb_id'),(18,'is_otdb_id'),(19,'im_otdb_id'),(20,'img_otdb_id'),(21,'pulp_otdb_id'),(22, 'is_tab_nr'); +INSERT INTO resource_allocation.resource_claim_property_type VALUES (0, 'nr_of_is_files'),(1, 'nr_of_cs_files'),(2, 'nr_of_uv_files'),(3, 'nr_of_im_files'),(4, 'nr_of_img_files'),(5, 'nr_of_pulp_files'),(6, 'nr_of_cs_stokes'),(7, 'nr_of_is_stokes'),(8, 'is_file_size'),(9, 'cs_file_size'),(10, 'uv_file_size'),(11, 'im_file_size'),(12, 'img_file_size'),(13, 'nr_of_pulp_files'),(14, 'nr_of_tabs'),(15, 'start_sb_nr'),(16,'uv_otdb_id'),(17,'cs_otdb_id'),(18,'is_otdb_id'),(19,'im_otdb_id'),(20,'img_otdb_id'),(21,'pulp_otdb_id'),(22, 'is_tab_nr'),(23, 'start_sbg_nr'); INSERT INTO resource_allocation.resource_claim_property_io_type VALUES (0, 'output'),(1, 'input'); INSERT INTO resource_allocation.config VALUES (0, 'max_fill_percentage_cep4', '85.00'), (1, 'claim_timeout', '172800'), (2, 'min_inter_task_delay', '60'); -- Just some values 172800 is two days in seconds INSERT INTO resource_allocation.conflict_reason diff --git a/SAS/XML_generator/src/xmlgen.py b/SAS/XML_generator/src/xmlgen.py index 208236639f4..f75d2a754c3 100755 --- a/SAS/XML_generator/src/xmlgen.py +++ b/SAS/XML_generator/src/xmlgen.py @@ -29,7 +29,7 @@ # URL : $URL: https://svn.astron.nl/ROD/trunk/LOFAR_Scheduler/DataHandler.cpp $ -VERSION = "2.16.3" +VERSION = "2.17.0" import sys, getopt, time from xml.sax.saxutils import escape as XMLescape -- GitLab From b2527ff56f80235961b3a3ae6aeae8ee1b3b89c3 Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Tue, 16 Aug 2016 09:27:51 +0000 Subject: [PATCH 623/933] Task #9640: SB should be SBG in output filenames of LB pipeline --- .../resource_estimators/longbaseline_pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py index 07779a61e5c..2a6678c0f5a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py @@ -91,9 +91,9 @@ class LongBaselinePipelineResourceEstimator(BaseResourceEstimator): result['storage']['output_files'] = {} nr_output_files = nr_input_files / (subbands_per_subbandgroup * subbandgroups_per_ms) result['storage']['output_files']['uv'] = {'nr_of_uv_files': nr_output_files, - 'uv_file_size': 1000, + 'uv_file_size': 1000, # 1 kB was hardcoded in the Scheduler 'identifications': parset.getStringVector(DATAPRODUCTS + 'Output_Correlated.identifications'), - 'start_sb_nr': input_files['uv']['start_sb_nr']} # 1 kB was hardcoded in the Scheduler + 'start_sbg_nr': input_files['uv']['start_sb_nr'] / (subbands_per_subbandgroup * subbandgroups_per_ms)} logger.debug("correlated_uv: {} files {} bytes each".format(result['storage']['output_files']['uv']['nr_of_uv_files'], result['storage']['output_files']['uv']['uv_file_size'])) # count total data size -- GitLab From 541fd7610fc292dcf2df3e7fb156e8dbbddfd663 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 16 Aug 2016 10:12:01 +0000 Subject: [PATCH 624/933] Task #9607: draw gantt project task rows in same order as grid, add row in case of overlapping tasks --- .../app/controllers/ganttprojectcontroller.js | 134 ++++++++---------- .../static/app/controllers/gridcontroller.js | 2 +- 2 files changed, 61 insertions(+), 75 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index 526e3c87849..f124ea381a9 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -113,24 +113,18 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS var projectsDict = $scope.dataService.momProjectsDict; var numProjecs = $scope.dataService.momProjects.length; - var taskDict = $scope.dataService.filteredTaskDict; - var tasks = $scope.dataService.filteredTasks; - var numTasks = tasks.length; - - var tasktypesDict = $scope.dataService.tasktypesDict; - var tasktypes = $scope.dataService.tasktypes; - var numTasktypes = tasktypes.length; - - if(numProjecs == 0 || numTasktypes == 0){ + if(numProjecs == 0) { $scope.ganttData = []; return; } - var editableTaskStatusIds = $scope.dataService.editableTaskStatusIds; + var taskDict = $scope.dataService.filteredTaskDict; + var tasks = $scope.dataService.filteredTasks; + var numTasks = tasks.length; - var ganntRowsDict = {}; + var ganntRows = []; - if(numProjecs > 0 && numTasks > 0 && numTasktypes > 0) { + if(numProjecs > 0 && numTasks > 0) { $scope.options.fromDate = $scope.dataService.viewTimeSpan.from; $scope.options.toDate = $scope.dataService.viewTimeSpan.to; var fullTimespanInMinutes = ($scope.options.toDate - $scope.options.fromDate) / (60 * 1000); @@ -154,82 +148,74 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS //only enable dependencies (arrows between tasks) in detailed view $scope.options.dependencies = (fullTimespanInMinutes <= 3*60); - for(var i = 0; i < numTasks; i++) { - var task = tasks[i]; - - var projectRowId = 'project_' + task.project_mom_id; - var ganntProjectRow = ganntRowsDict[projectRowId]; + var editableTaskStatusIds = $scope.dataService.editableTaskStatusIds; - if(!ganntProjectRow) { - var project = projectsDict[task.project_mom_id]; + var ganntRowsDict = {}; - if(project) { - ganntProjectRow = { - id: projectRowId, - name: project.name, - project: project, - tasks: [] - }; + for(var i = 0; i < numTasks; i++) { + var task = tasks[i]; + var project = projectsDict[task.project_mom_id]; - ganntRowsDict[projectRowId] = ganntProjectRow; - } + if(!project) { + continue; } - if(ganntProjectRow) { - var typeRowId = 'project_' + task.project_mom_id + '_type_' + task.type_id; - var ganntTypeRow = ganntRowsDict[typeRowId]; + var projectTypeRowsId = 'project_' + task.project_mom_id + '_type_' + task.type_id; + var ganntProjectTypeRows = ganntRowsDict[projectTypeRowsId]; - if(!ganntTypeRow) { - var tasktype = tasktypesDict[task.type_id].name; - - if(tasktype) { - ganntTypeRow = { - id: typeRowId, - parent: projectRowId, - name: tasktype, - project: project, - tasks: [] - }; - - ganntRowsDict[typeRowId] = ganntTypeRow; - } - } + if(!ganntProjectTypeRows) { + ganntProjectTypeRows = []; + ganntRowsDict[projectTypeRowsId] = ganntProjectTypeRows; + } - if(ganntTypeRow) { - var rowTask = { - id: task.id.toString(), - name: task.name, - from: task.starttime, - to: task.endtime, - raTask: task, - color: self.taskStatusColors[task.status], - classes: 'task-status-' + task.status, - movable: $.inArray(task.status_id, editableTaskStatusIds) > -1 - }; - - if(dataService.isTaskIdSelected(task.id)) { - rowTask.classes += ' task-selected-task'; - } + var availableRow = ganntProjectTypeRows.find(function(row) { + var overlappingTasks = row.tasks.filter(function(t) { + return (t.from >= task.starttime && t.from <= task.endtime) || + (t.to >= task.starttime && t.to <= task.endtime) || + (t.from <= task.starttime && t.to >= task.endtime); + }); + return overlappingTasks.length == 0; + }); + + if(!availableRow) + { + availableRow = { + id: projectTypeRowsId + '_' + (ganntProjectTypeRows.length+1), + name: project.name + ' ' + task.type, + project: project, + tasks: [] + }; + + ganntProjectTypeRows.push(availableRow); + ganntRows.push(availableRow); + } - if($scope.options.dependencies && task.predecessor_ids && task.predecessor_ids.length > 0) { - rowTask['dependencies'] = []; - for(var predId of task.predecessor_ids) { - rowTask['dependencies'].push({'from': predId}); - } - } + var rowTask = { + id: task.id.toString(), + name: task.name, + from: task.starttime, + to: task.endtime, + raTask: task, + color: self.taskStatusColors[task.status], + classes: 'task-status-' + task.status, + movable: $.inArray(task.status_id, editableTaskStatusIds) > -1 + }; + + if(dataService.isTaskIdSelected(task.id)) { + rowTask.classes += ' task-selected-task'; + } - ganntTypeRow.tasks.push(rowTask); + if($scope.options.dependencies && task.predecessor_ids && task.predecessor_ids.length > 0) { + rowTask['dependencies'] = []; + for(var predId of task.predecessor_ids) { + rowTask['dependencies'].push({'from': predId}); } } + + availableRow.tasks.push(rowTask); } } - var ganntRows = []; - - for (var rowId in ganntRowsDict) - ganntRows.push(ganntRowsDict[rowId]); - - ganntRows.sort(function(a, b) { return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0)); }); $scope.ganttData = ganntRows; }; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 97d6da7fd8c..ecd9a4d7585 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -145,7 +145,7 @@ $scope.columns = [ $scope.$evalAsync(function() { var taskDict = $scope.dataService.taskDict; $scope.dataService.filteredTasks = []; - var rows = $scope.gridApi.grid.rows; + var rows = $scope.gridApi.core.getVisibleRows(grid); var numRows = rows.length; for(var i = 0; i < numRows; i++) { var row = rows[i]; -- GitLab From 350e4524672c02d41972c73f16fbeb8ec07a0b85 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 16 Aug 2016 13:39:34 +0000 Subject: [PATCH 625/933] Task #9607: added anguler-materials module. Use md-tabs for tasks/resources. Only update gantts when enabled in tabs --- .gitattributes | 6 +- .../lib/CMakeLists.txt | 6 + .../lib/static/app/app.js | 3 +- .../app/controllers/ganttprojectcontroller.js | 12 +- .../controllers/ganttresourcecontroller.js | 21 +- .../js/angular-animate/angular-animate.min.js | 56 + .../js/angular-aria/angular-aria.min.js | 14 + .../angular-material/angular-material.min.css | 6 + .../angular-material/angular-material.min.js | 17 + .../lib/static/js/angular/angular.js | 28904 ---------------- .../lib/static/js/angular/angular.min.js | 600 +- .../lib/static/js/angular/angular.min.js.map | 8 - .../lib/templates/index.html | 103 +- 13 files changed, 486 insertions(+), 29270 deletions(-) create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-animate/angular-animate.min.js create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-aria/angular-aria.min.js create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-material/angular-material.min.css create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-material/angular-material.min.js delete mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.js delete mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.min.js.map diff --git a/.gitattributes b/.gitattributes index b6617db58c3..e2a508e2662 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5133,6 +5133,8 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-half SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-halflings-regular.ttf -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-halflings-regular.woff -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-halflings-regular.woff2 -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-animate/angular-animate.min.js -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-aria/angular-aria.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-gantt/.bower.json -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-gantt/angular-gantt-bounds-plugin.css -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-gantt/angular-gantt-bounds-plugin.js -text @@ -5222,6 +5224,8 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-gantt/angu SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-gantt/angular-gantt.min.css -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-gantt/angular-gantt.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-gantt/angular-gantt.min.js.map -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-material/angular-material.min.css -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-material/angular-material.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-moment/.bower.json -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-moment/.editorconfig -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-moment/.jshintrc -text @@ -5256,9 +5260,7 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-ui-tabs/ta SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-ui-tree/angular-ui-tree.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-ui-tree/angular-ui-tree.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular-csp.css -text -SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.min.js -text -SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.min.js.map -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/highcharts/exporting.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/highcharts/highcharts-ng.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/highcharts/highcharts.js -text diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt index 1bfa8364628..d72973220ff 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt @@ -19,6 +19,9 @@ file(GLOB_RECURSE angular_ui_tree_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} sta file(GLOB_RECURSE angular_ui_layout_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-ui-layout/*) file(GLOB_RECURSE angular_ui_tabs_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-ui-tabs/*) file(GLOB_RECURSE angular_gantt_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-gantt/*) +file(GLOB_RECURSE angular_animate_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-animate/*) +file(GLOB_RECURSE angular_aria_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-aria/*) +file(GLOB_RECURSE angular_material_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-material/*) file(GLOB_RECURSE angular_moment_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-moment/*) file(GLOB_RECURSE jsplumb_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/jsplumb/*) file(GLOB_RECURSE moment_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/moment/*) @@ -51,6 +54,9 @@ set(web_files ${angular_ui_layout_files} ${angular_ui_tabs_files} ${angular_gantt_files} + ${angular_animate_files} + ${angular_aria_files} + ${angular_material_files} ${angular_moment_files} ${moment_files} ${jsplumb_files} diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js index d77138a2e32..d06132cd041 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js @@ -10,7 +10,8 @@ var app = angular.module('raeApp', 'ui.layout', 'ui.bootstrap', 'ui.bootstrap.tabs', - 'highcharts-ng']); + 'highcharts-ng', + 'ngMaterial']); app.config(['$compileProvider', function ($compileProvider) { $compileProvider.debugInfoEnabled(false); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index f124ea381a9..3f2ea94bc6b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -25,6 +25,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS $scope.dataService = dataService; $scope.ganttData = []; + $scope.enabled = true; self.taskStatusColors = dataService.taskStatusColors; @@ -113,7 +114,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS var projectsDict = $scope.dataService.momProjectsDict; var numProjecs = $scope.dataService.momProjects.length; - if(numProjecs == 0) { + if(numProjecs == 0 || !$scope.enabled) { $scope.ganttData = []; return; } @@ -219,10 +220,11 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS $scope.ganttData = ganntRows; }; - $scope.$watch('dataService.initialLoadComplete', function() { updateGanttDataAsync(); }); - $scope.$watch('dataService.selected_task_ids', function() { updateGanttDataAsync(); }, true); - $scope.$watch('dataService.viewTimeSpan', function() { updateGanttDataAsync(); }, true); - $scope.$watch('dataService.filteredTaskChangeCntr', function() { updateGanttDataAsync(); }); + $scope.$watch('dataService.initialLoadComplete', updateGanttDataAsync); + $scope.$watch('dataService.selected_task_ids', updateGanttDataAsync, true); + $scope.$watch('dataService.viewTimeSpan', updateGanttDataAsync, true); + $scope.$watch('dataService.filteredTaskChangeCntr', updateGanttDataAsync); + $scope.$watch('enabled', updateGanttDataAsync); $scope.$watch('dataService.lofarTime', function() { $scope.$evalAsync(function() { if($scope.dataService.lofarTime.getSeconds() % 10 == 0) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js index 85eb5f313d9..1ceadb2f967 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js @@ -19,10 +19,10 @@ var ganttResourceControllerMod = angular.module('GanttResourceControllerMod', [ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dataService', function($scope, dataService) { var self = this; - self.doInitialCollapse = true; $scope.dataService = dataService; $scope.ganttData = [] + $scope.enabled = true; self.taskStatusColors = dataService.taskStatusColors; self.resourceClaimStatusColors = dataService.resourceClaimStatusColors; @@ -153,7 +153,7 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat var resourceClaims = $scope.dataService.resourceClaims; var numResourceClaims = resourceClaims.length; - if(numResourceGroups == 0 || numResources == 0){ + if(numResourceGroups == 0 || numResources == 0 || !$scope.enabled){ $scope.ganttData = []; return; } @@ -463,19 +463,14 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat ganttRows.push(ganttRowsDict[rowId]); $scope.ganttData = ganttRows; - - if(self.doInitialCollapse && numResources && numResourceGroups) - { - doInitialCollapse = false; -// setTimeout(function() { $scope.api.tree.collapseAll(); }, 50); - } }; - $scope.$watch('dataService.initialLoadComplete', function() { updateGanttDataAsync(); }); - $scope.$watch('dataService.selected_task_ids', function() { updateGanttDataAsync(); }, true); - $scope.$watch('dataService.viewTimeSpan', function() { updateGanttDataAsync(); }, true); - $scope.$watch('dataService.claimChangeCntr', function() { updateGanttDataAsync(); }); - $scope.$watch('dataService.filteredTaskChangeCntr', function() { updateGanttDataAsync(); }); + $scope.$watch('dataService.initialLoadComplete', updateGanttDataAsync); + $scope.$watch('dataService.selected_task_ids', updateGanttDataAsync, true); + $scope.$watch('dataService.viewTimeSpan', updateGanttDataAsync, true); + $scope.$watch('dataService.claimChangeCntr', updateGanttDataAsync); + $scope.$watch('dataService.filteredTaskChangeCntr', updateGanttDataAsync); + $scope.$watch('enabled', updateGanttDataAsync); $scope.$watch('dataService.lofarTime', function() { $scope.$evalAsync(function() { if($scope.dataService.lofarTime.getSeconds() % 10 == 0) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-animate/angular-animate.min.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-animate/angular-animate.min.js new file mode 100644 index 00000000000..a7d2f272bfa --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-animate/angular-animate.min.js @@ -0,0 +1,56 @@ +/* + AngularJS v1.5.5 + (c) 2010-2016 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(S,q){'use strict';function Aa(a,b,c){if(!a)throw Ma("areq",b||"?",c||"required");return a}function Ba(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;ba(a)&&(a=a.join(" "));ba(b)&&(b=b.join(" "));return a+" "+b}function Na(a){var b={};a&&(a.to||a.from)&&(b.to=a.to,b.from=a.from);return b}function X(a,b,c){var d="";a=ba(a)?a:a&&P(a)&&a.length?a.split(/\s+/):[];r(a,function(a,f){a&&0<a.length&&(d+=0<f?" ":"",d+=c?b+a:a+b)});return d}function Oa(a){if(a instanceof G)switch(a.length){case 0:return[]; +case 1:if(1===a[0].nodeType)return a;break;default:return G(ca(a))}if(1===a.nodeType)return G(a)}function ca(a){if(!a[0])return a;for(var b=0;b<a.length;b++){var c=a[b];if(1==c.nodeType)return c}}function Pa(a,b,c){r(b,function(b){a.addClass(b,c)})}function Qa(a,b,c){r(b,function(b){a.removeClass(b,c)})}function U(a){return function(b,c){c.addClass&&(Pa(a,b,c.addClass),c.addClass=null);c.removeClass&&(Qa(a,b,c.removeClass),c.removeClass=null)}}function pa(a){a=a||{};if(!a.$$prepared){var b=a.domOperation|| +Q;a.domOperation=function(){a.$$domOperationFired=!0;b();b=Q};a.$$prepared=!0}return a}function ga(a,b){Ca(a,b);Da(a,b)}function Ca(a,b){b.from&&(a.css(b.from),b.from=null)}function Da(a,b){b.to&&(a.css(b.to),b.to=null)}function V(a,b,c){var d=b.options||{};c=c.options||{};var e=(d.addClass||"")+" "+(c.addClass||""),f=(d.removeClass||"")+" "+(c.removeClass||"");a=Ra(a.attr("class"),e,f);c.preparationClasses&&(d.preparationClasses=Y(c.preparationClasses,d.preparationClasses),delete c.preparationClasses); +e=d.domOperation!==Q?d.domOperation:null;Ea(d,c);e&&(d.domOperation=e);d.addClass=a.addClass?a.addClass:null;d.removeClass=a.removeClass?a.removeClass:null;b.addClass=d.addClass;b.removeClass=d.removeClass;return d}function Ra(a,b,c){function d(a){P(a)&&(a=a.split(" "));var b={};r(a,function(a){a.length&&(b[a]=!0)});return b}var e={};a=d(a);b=d(b);r(b,function(a,b){e[b]=1});c=d(c);r(c,function(a,b){e[b]=1===e[b]?null:-1});var f={addClass:"",removeClass:""};r(e,function(b,c){var d,e;1===b?(d="addClass", +e=!a[c]):-1===b&&(d="removeClass",e=a[c]);e&&(f[d].length&&(f[d]+=" "),f[d]+=c)});return f}function D(a){return a instanceof q.element?a[0]:a}function Sa(a,b,c){var d="";b&&(d=X(b,"ng-",!0));c.addClass&&(d=Y(d,X(c.addClass,"-add")));c.removeClass&&(d=Y(d,X(c.removeClass,"-remove")));d.length&&(c.preparationClasses=d,a.addClass(d))}function qa(a,b){var c=b?"-"+b+"s":"";la(a,[ma,c]);return[ma,c]}function ta(a,b){var c=b?"paused":"",d=Z+"PlayState";la(a,[d,c]);return[d,c]}function la(a,b){a.style[b[0]]= +b[1]}function Y(a,b){return a?b?a+" "+b:a:b}function Fa(a,b,c){var d=Object.create(null),e=a.getComputedStyle(b)||{};r(c,function(a,b){var c=e[a];if(c){var s=c.charAt(0);if("-"===s||"+"===s||0<=s)c=Ta(c);0===c&&(c=null);d[b]=c}});return d}function Ta(a){var b=0;a=a.split(/\s*,\s*/);r(a,function(a){"s"==a.charAt(a.length-1)&&(a=a.substring(0,a.length-1));a=parseFloat(a)||0;b=b?Math.max(a,b):a});return b}function ua(a){return 0===a||null!=a}function Ga(a,b){var c=T,d=a+"s";b?c+="Duration":d+=" linear all"; +return[c,d]}function Ha(){var a=Object.create(null);return{flush:function(){a=Object.create(null)},count:function(b){return(b=a[b])?b.total:0},get:function(b){return(b=a[b])&&b.value},put:function(b,c){a[b]?a[b].total++:a[b]={total:1,value:c}}}}function Ia(a,b,c){r(c,function(c){a[c]=da(a[c])?a[c]:b.style.getPropertyValue(c)})}var Q=q.noop,Ja=q.copy,Ea=q.extend,G=q.element,r=q.forEach,ba=q.isArray,P=q.isString,va=q.isObject,C=q.isUndefined,da=q.isDefined,Ka=q.isFunction,wa=q.isElement,T,xa,Z,ya;C(S.ontransitionend)&& +da(S.onwebkittransitionend)?(T="WebkitTransition",xa="webkitTransitionEnd transitionend"):(T="transition",xa="transitionend");C(S.onanimationend)&&da(S.onwebkitanimationend)?(Z="WebkitAnimation",ya="webkitAnimationEnd animationend"):(Z="animation",ya="animationend");var ra=Z+"Delay",za=Z+"Duration",ma=T+"Delay",La=T+"Duration",Ma=q.$$minErr("ng"),Ua={transitionDuration:La,transitionDelay:ma,transitionProperty:T+"Property",animationDuration:za,animationDelay:ra,animationIterationCount:Z+"IterationCount"}, +Va={transitionDuration:La,transitionDelay:ma,animationDuration:za,animationDelay:ra};q.module("ngAnimate",[]).directive("ngAnimateSwap",["$animate","$rootScope",function(a,b){return{restrict:"A",transclude:"element",terminal:!0,priority:600,link:function(b,d,e,f,z){var B,s;b.$watchCollection(e.ngAnimateSwap||e["for"],function(e){B&&a.leave(B);s&&(s.$destroy(),s=null);if(e||0===e)s=b.$new(),z(s,function(b){B=b;a.enter(b,null,d)})})}}}]).directive("ngAnimateChildren",["$interpolate",function(a){return{link:function(b, +c,d){function e(a){c.data("$$ngAnimateChildren","on"===a||"true"===a)}var f=d.ngAnimateChildren;q.isString(f)&&0===f.length?c.data("$$ngAnimateChildren",!0):(e(a(f)(b)),d.$observe("ngAnimateChildren",e))}}}]).factory("$$rAFScheduler",["$$rAF",function(a){function b(a){d=d.concat(a);c()}function c(){if(d.length){for(var b=d.shift(),z=0;z<b.length;z++)b[z]();e||a(function(){e||c()})}}var d,e;d=b.queue=[];b.waitUntilQuiet=function(b){e&&e();e=a(function(){e=null;b();c()})};return b}]).provider("$$animateQueue", +["$animateProvider",function(a){function b(a){if(!a)return null;a=a.split(" ");var b=Object.create(null);r(a,function(a){b[a]=!0});return b}function c(a,c){if(a&&c){var d=b(c);return a.split(" ").some(function(a){return d[a]})}}function d(a,b,c,d){return f[a].some(function(a){return a(b,c,d)})}function e(a,b){var c=0<(a.addClass||"").length,d=0<(a.removeClass||"").length;return b?c&&d:c||d}var f=this.rules={skip:[],cancel:[],join:[]};f.join.push(function(a,b,c){return!b.structural&&e(b)});f.skip.push(function(a, +b,c){return!b.structural&&!e(b)});f.skip.push(function(a,b,c){return"leave"==c.event&&b.structural});f.skip.push(function(a,b,c){return c.structural&&2===c.state&&!b.structural});f.cancel.push(function(a,b,c){return c.structural&&b.structural});f.cancel.push(function(a,b,c){return 2===c.state&&b.structural});f.cancel.push(function(a,b,d){if(d.structural)return!1;a=b.addClass;b=b.removeClass;var e=d.addClass;d=d.removeClass;return C(a)&&C(b)||C(e)&&C(d)?!1:c(a,d)||c(b,e)});this.$get=["$$rAF","$rootScope", +"$rootElement","$document","$$HashMap","$$animation","$$AnimateRunner","$templateRequest","$$jqLite","$$forceReflow",function(b,c,f,v,I,Wa,u,sa,w,x){function R(){var a=!1;return function(b){a?b():c.$$postDigest(function(){a=!0;b()})}}function J(a,b,c){var g=D(b),d=D(a),k=[];(a=h[c])&&r(a,function(a){ia.call(a.node,g)?k.push(a.callback):"leave"===c&&ia.call(a.node,d)&&k.push(a.callback)});return k}function k(a,b,c){var g=ca(b);return a.filter(function(a){return!(a.node===g&&(!c||a.callback===c))})} +function p(a,k,h){function l(c,g,d,h){f(function(){var c=J(oa,a,g);c.length?b(function(){r(c,function(b){b(a,d,h)});"close"!==d||a[0].parentNode||N.off(a)}):"close"!==d||a[0].parentNode||N.off(a)});c.progress(g,d,h)}function A(b){var c=a,g=m;g.preparationClasses&&(c.removeClass(g.preparationClasses),g.preparationClasses=null);g.activeClasses&&(c.removeClass(g.activeClasses),g.activeClasses=null);F(a,m);ga(a,m);m.domOperation();p.complete(!b)}var m=Ja(h),x,oa;if(a=Oa(a))x=D(a),oa=a.parent();var m= +pa(m),p=new u,f=R();ba(m.addClass)&&(m.addClass=m.addClass.join(" "));m.addClass&&!P(m.addClass)&&(m.addClass=null);ba(m.removeClass)&&(m.removeClass=m.removeClass.join(" "));m.removeClass&&!P(m.removeClass)&&(m.removeClass=null);m.from&&!va(m.from)&&(m.from=null);m.to&&!va(m.to)&&(m.to=null);if(!x)return A(),p;h=[x.className,m.addClass,m.removeClass].join(" ");if(!Xa(h))return A(),p;var s=0<=["enter","move","leave"].indexOf(k),t=v[0].hidden,w=!g||t||H.get(x);h=!w&&y.get(x)||{};var I=!!h.state;w|| +I&&1==h.state||(w=!K(a,oa,k));if(w)return t&&l(p,k,"start"),A(),t&&l(p,k,"close"),p;s&&L(a);t={structural:s,element:a,event:k,addClass:m.addClass,removeClass:m.removeClass,close:A,options:m,runner:p};if(I){if(d("skip",a,t,h)){if(2===h.state)return A(),p;V(a,h,t);return h.runner}if(d("cancel",a,t,h))if(2===h.state)h.runner.end();else if(h.structural)h.close();else return V(a,h,t),h.runner;else if(d("join",a,t,h))if(2===h.state)V(a,t,{});else return Sa(a,s?k:null,m),k=t.event=h.event,m=V(a,h,t),h.runner}else V(a, +t,{});(I=t.structural)||(I="animate"===t.event&&0<Object.keys(t.options.to||{}).length||e(t));if(!I)return A(),O(a),p;var ia=(h.counter||0)+1;t.counter=ia;M(a,1,t);c.$$postDigest(function(){var b=y.get(x),c=!b,b=b||{},g=0<(a.parent()||[]).length&&("animate"===b.event||b.structural||e(b));if(c||b.counter!==ia||!g){c&&(F(a,m),ga(a,m));if(c||s&&b.event!==k)m.domOperation(),p.end();g||O(a)}else k=!b.structural&&e(b,!0)?"setClass":b.event,M(a,2),b=Wa(a,k,b.options),p.setHost(b),l(p,k,"start",{}),b.done(function(b){A(!b); +(b=y.get(x))&&b.counter===ia&&O(D(a));l(p,k,"close",{})})});return p}function L(a){a=D(a).querySelectorAll("[data-ng-animate]");r(a,function(a){var b=parseInt(a.getAttribute("data-ng-animate")),c=y.get(a);if(c)switch(b){case 2:c.runner.end();case 1:y.remove(a)}})}function O(a){a=D(a);a.removeAttribute("data-ng-animate");y.remove(a)}function l(a,b){return D(a)===D(b)}function K(a,b,c){c=G(v[0].body);var g=l(a,c)||"HTML"===a[0].nodeName,d=l(a,f),h=!1,k,e=H.get(D(a));(a=G.data(a[0],"$ngAnimatePin"))&& +(b=a);for(b=D(b);b;){d||(d=l(b,f));if(1!==b.nodeType)break;a=y.get(b)||{};if(!h){var p=H.get(b);if(!0===p&&!1!==e){e=!0;break}else!1===p&&(e=!1);h=a.structural}if(C(k)||!0===k)a=G.data(b,"$$ngAnimateChildren"),da(a)&&(k=a);if(h&&!1===k)break;g||(g=l(b,c));if(g&&d)break;if(!d&&(a=G.data(b,"$ngAnimatePin"))){b=D(a);continue}b=b.parentNode}return(!h||k)&&!0!==e&&d&&g}function M(a,b,c){c=c||{};c.state=b;a=D(a);a.setAttribute("data-ng-animate",b);c=(b=y.get(a))?Ea(b,c):c;y.put(a,c)}var y=new I,H=new I, +g=null,oa=c.$watch(function(){return 0===sa.totalPendingRequests},function(a){a&&(oa(),c.$$postDigest(function(){c.$$postDigest(function(){null===g&&(g=!0)})}))}),h={},A=a.classNameFilter(),Xa=A?function(a){return A.test(a)}:function(){return!0},F=U(w),ia=S.Node.prototype.contains||function(a){return this===a||!!(this.compareDocumentPosition(a)&16)},N={on:function(a,b,c){var g=ca(b);h[a]=h[a]||[];h[a].push({node:g,callback:c});G(b).on("$destroy",function(){y.get(g)||N.off(a,b,c)})},off:function(a, +b,c){if(1!==arguments.length||q.isString(arguments[0])){var g=h[a];g&&(h[a]=1===arguments.length?null:k(g,b,c))}else for(g in b=arguments[0],h)h[g]=k(h[g],b)},pin:function(a,b){Aa(wa(a),"element","not an element");Aa(wa(b),"parentElement","not an element");a.data("$ngAnimatePin",b)},push:function(a,b,c,g){c=c||{};c.domOperation=g;return p(a,b,c)},enabled:function(a,b){var c=arguments.length;if(0===c)b=!!g;else if(wa(a)){var d=D(a),h=H.get(d);1===c?b=!h:H.put(d,!b)}else b=g=!!a;return b}};return N}]}]).provider("$$animation", +["$animateProvider",function(a){function b(a){return a.data("$$animationRunner")}var c=this.drivers=[];this.$get=["$$jqLite","$rootScope","$injector","$$AnimateRunner","$$HashMap","$$rAFScheduler",function(a,e,f,z,B,s){function v(a){function b(a){if(a.processed)return a;a.processed=!0;var d=a.domNode,L=d.parentNode;e.put(d,a);for(var f;L;){if(f=e.get(L)){f.processed||(f=b(f));break}L=L.parentNode}(f||c).children.push(a);return a}var c={children:[]},d,e=new B;for(d=0;d<a.length;d++){var f=a[d];e.put(f.domNode, +a[d]={domNode:f.domNode,fn:f.fn,children:[]})}for(d=0;d<a.length;d++)b(a[d]);return function(a){var b=[],c=[],d;for(d=0;d<a.children.length;d++)c.push(a.children[d]);a=c.length;var e=0,f=[];for(d=0;d<c.length;d++){var x=c[d];0>=a&&(a=e,e=0,b.push(f),f=[]);f.push(x.fn);x.children.forEach(function(a){e++;c.push(a)});a--}f.length&&b.push(f);return b}(c)}var I=[],q=U(a);return function(u,B,w){function x(a){a=a.hasAttribute("ng-animate-ref")?[a]:a.querySelectorAll("[ng-animate-ref]");var b=[];r(a,function(a){var c= +a.getAttribute("ng-animate-ref");c&&c.length&&b.push(a)});return b}function R(a){var b=[],c={};r(a,function(a,g){var d=D(a.element),e=0<=["enter","move"].indexOf(a.event),d=a.structural?x(d):[];if(d.length){var k=e?"to":"from";r(d,function(a){var b=a.getAttribute("ng-animate-ref");c[b]=c[b]||{};c[b][k]={animationID:g,element:G(a)}})}else b.push(a)});var d={},e={};r(c,function(c,h){var k=c.from,f=c.to;if(k&&f){var p=a[k.animationID],y=a[f.animationID],l=k.animationID.toString();if(!e[l]){var x=e[l]= +{structural:!0,beforeStart:function(){p.beforeStart();y.beforeStart()},close:function(){p.close();y.close()},classes:J(p.classes,y.classes),from:p,to:y,anchors:[]};x.classes.length?b.push(x):(b.push(p),b.push(y))}e[l].anchors.push({out:k.element,"in":f.element})}else k=k?k.animationID:f.animationID,f=k.toString(),d[f]||(d[f]=!0,b.push(a[k]))});return b}function J(a,b){a=a.split(" ");b=b.split(" ");for(var c=[],d=0;d<a.length;d++){var k=a[d];if("ng-"!==k.substring(0,3))for(var e=0;e<b.length;e++)if(k=== +b[e]){c.push(k);break}}return c.join(" ")}function k(a){for(var b=c.length-1;0<=b;b--){var d=c[b];if(f.has(d)&&(d=f.get(d)(a)))return d}}function p(a,c){a.from&&a.to?(b(a.from.element).setHost(c),b(a.to.element).setHost(c)):b(a.element).setHost(c)}function L(){var a=b(u);!a||"leave"===B&&w.$$domOperationFired||a.end()}function O(b){u.off("$destroy",L);u.removeData("$$animationRunner");q(u,w);ga(u,w);w.domOperation();y&&a.removeClass(u,y);u.removeClass("ng-animate");K.complete(!b)}w=pa(w);var l=0<= +["enter","move","leave"].indexOf(B),K=new z({end:function(){O()},cancel:function(){O(!0)}});if(!c.length)return O(),K;u.data("$$animationRunner",K);var M=Ba(u.attr("class"),Ba(w.addClass,w.removeClass)),y=w.tempClasses;y&&(M+=" "+y,w.tempClasses=null);var H;l&&(H="ng-"+B+"-prepare",a.addClass(u,H));I.push({element:u,classes:M,event:B,structural:l,options:w,beforeStart:function(){u.addClass("ng-animate");y&&a.addClass(u,y);H&&(a.removeClass(u,H),H=null)},close:O});u.on("$destroy",L);if(1<I.length)return K; +e.$$postDigest(function(){var a=[];r(I,function(c){b(c.element)?a.push(c):c.close()});I.length=0;var c=R(a),d=[];r(c,function(a){d.push({domNode:D(a.from?a.from.element:a.element),fn:function(){a.beforeStart();var c,d=a.close;if(b(a.anchors?a.from.element||a.to.element:a.element)){var g=k(a);g&&(c=g.start)}c?(c=c(),c.done(function(a){d(!a)}),p(a,c)):d()}})});s(v(d))});return K}}]}]).provider("$animateCss",["$animateProvider",function(a){var b=Ha(),c=Ha();this.$get=["$window","$$jqLite","$$AnimateRunner", +"$timeout","$$forceReflow","$sniffer","$$rAFScheduler","$$animateQueue",function(a,e,f,z,B,s,v,I){function q(a,b){var c=a.parentNode;return(c.$$ngAnimateParentKey||(c.$$ngAnimateParentKey=++R))+"-"+a.getAttribute("class")+"-"+b}function u(k,f,x,s){var l;0<b.count(x)&&(l=c.get(x),l||(f=X(f,"-stagger"),e.addClass(k,f),l=Fa(a,k,s),l.animationDuration=Math.max(l.animationDuration,0),l.transitionDuration=Math.max(l.transitionDuration,0),e.removeClass(k,f),c.put(x,l)));return l||{}}function sa(a){J.push(a); +v.waitUntilQuiet(function(){b.flush();c.flush();for(var a=B(),d=0;d<J.length;d++)J[d](a);J.length=0})}function w(c,e,f){e=b.get(f);e||(e=Fa(a,c,Ua),"infinite"===e.animationIterationCount&&(e.animationIterationCount=1));b.put(f,e);c=e;f=c.animationDelay;e=c.transitionDelay;c.maxDelay=f&&e?Math.max(f,e):f||e;c.maxDuration=Math.max(c.animationDuration*c.animationIterationCount,c.transitionDuration);return c}var x=U(e),R=0,J=[];return function(a,c){function d(){l()}function v(){l(!0)}function l(b){if(!(R|| +G&&N)){R=!0;N=!1;g.$$skipPreparationClasses||e.removeClass(a,fa);e.removeClass(a,da);ta(h,!1);qa(h,!1);r(A,function(a){h.style[a[0]]=""});x(a,g);ga(a,g);Object.keys(J).length&&r(J,function(a,b){a?h.style.setProperty(b,a):h.style.removeProperty(b)});if(g.onDone)g.onDone();ea&&ea.length&&a.off(ea.join(" "),y);var c=a.data("$$animateCss");c&&(z.cancel(c[0].timer),a.removeData("$$animateCss"));C&&C.complete(!b)}}function K(a){n.blockTransition&&qa(h,a);n.blockKeyframeAnimation&&ta(h,!!a)}function M(){C= +new f({end:d,cancel:v});sa(Q);l();return{$$willAnimate:!1,start:function(){return C},end:d}}function y(a){a.stopPropagation();var b=a.originalEvent||a;a=b.$manualTimeStamp||Date.now();b=parseFloat(b.elapsedTime.toFixed(3));Math.max(a-V,0)>=S&&b>=m&&(G=!0,l())}function H(){function b(){if(!R){K(!1);r(A,function(a){h.style[a[0]]=a[1]});x(a,g);e.addClass(a,da);if(n.recalculateTimingStyles){na=h.className+" "+fa;ja=q(h,na);E=w(h,na,ja);$=E.maxDelay;ha=Math.max($,0);m=E.maxDuration;if(0===m){l();return}n.hasTransitions= +0<E.transitionDuration;n.hasAnimations=0<E.animationDuration}n.applyAnimationDelay&&($="boolean"!==typeof g.delay&&ua(g.delay)?parseFloat(g.delay):$,ha=Math.max($,0),E.animationDelay=$,aa=[ra,$+"s"],A.push(aa),h.style[aa[0]]=aa[1]);S=1E3*ha;U=1E3*m;if(g.easing){var d,f=g.easing;n.hasTransitions&&(d=T+"TimingFunction",A.push([d,f]),h.style[d]=f);n.hasAnimations&&(d=Z+"TimingFunction",A.push([d,f]),h.style[d]=f)}E.transitionDuration&&ea.push(xa);E.animationDuration&&ea.push(ya);V=Date.now();var H=S+ +1.5*U;d=V+H;var f=a.data("$$animateCss")||[],s=!0;if(f.length){var p=f[0];(s=d>p.expectedEndTime)?z.cancel(p.timer):f.push(l)}s&&(H=z(c,H,!1),f[0]={timer:H,expectedEndTime:d},f.push(l),a.data("$$animateCss",f));if(ea.length)a.on(ea.join(" "),y);g.to&&(g.cleanupStyles&&Ia(J,h,Object.keys(g.to)),Da(a,g))}}function c(){var b=a.data("$$animateCss");if(b){for(var d=1;d<b.length;d++)b[d]();a.removeData("$$animateCss")}}if(!R)if(h.parentNode){var d=function(a){if(G)N&&a&&(N=!1,l());else if(N=!a,E.animationDuration)if(a= +ta(h,N),N)A.push(a);else{var b=A,c=b.indexOf(a);0<=a&&b.splice(c,1)}},f=0<ca&&(E.transitionDuration&&0===W.transitionDuration||E.animationDuration&&0===W.animationDuration)&&Math.max(W.animationDelay,W.transitionDelay);f?z(b,Math.floor(f*ca*1E3),!1):b();P.resume=function(){d(!0)};P.pause=function(){d(!1)}}else l()}var g=c||{};g.$$prepared||(g=pa(Ja(g)));var J={},h=D(a);if(!h||!h.parentNode||!I.enabled())return M();var A=[],B=a.attr("class"),F=Na(g),R,N,G,C,P,ha,S,m,U,V,ea=[];if(0===g.duration||!s.animations&& +!s.transitions)return M();var ka=g.event&&ba(g.event)?g.event.join(" "):g.event,Y="",t="";ka&&g.structural?Y=X(ka,"ng-",!0):ka&&(Y=ka);g.addClass&&(t+=X(g.addClass,"-add"));g.removeClass&&(t.length&&(t+=" "),t+=X(g.removeClass,"-remove"));g.applyClassesEarly&&t.length&&x(a,g);var fa=[Y,t].join(" ").trim(),na=B+" "+fa,da=X(fa,"-active"),B=F.to&&0<Object.keys(F.to).length;if(!(0<(g.keyframeStyle||"").length||B||fa))return M();var ja,W;0<g.stagger?(F=parseFloat(g.stagger),W={transitionDelay:F,animationDelay:F, +transitionDuration:0,animationDuration:0}):(ja=q(h,na),W=u(h,fa,ja,Va));g.$$skipPreparationClasses||e.addClass(a,fa);g.transitionStyle&&(F=[T,g.transitionStyle],la(h,F),A.push(F));0<=g.duration&&(F=0<h.style[T].length,F=Ga(g.duration,F),la(h,F),A.push(F));g.keyframeStyle&&(F=[Z,g.keyframeStyle],la(h,F),A.push(F));var ca=W?0<=g.staggerIndex?g.staggerIndex:b.count(ja):0;(ka=0===ca)&&!g.skipBlocking&&qa(h,9999);var E=w(h,na,ja),$=E.maxDelay;ha=Math.max($,0);m=E.maxDuration;var n={};n.hasTransitions= +0<E.transitionDuration;n.hasAnimations=0<E.animationDuration;n.hasTransitionAll=n.hasTransitions&&"all"==E.transitionProperty;n.applyTransitionDuration=B&&(n.hasTransitions&&!n.hasTransitionAll||n.hasAnimations&&!n.hasTransitions);n.applyAnimationDuration=g.duration&&n.hasAnimations;n.applyTransitionDelay=ua(g.delay)&&(n.applyTransitionDuration||n.hasTransitions);n.applyAnimationDelay=ua(g.delay)&&n.hasAnimations;n.recalculateTimingStyles=0<t.length;if(n.applyTransitionDuration||n.applyAnimationDuration)m= +g.duration?parseFloat(g.duration):m,n.applyTransitionDuration&&(n.hasTransitions=!0,E.transitionDuration=m,F=0<h.style[T+"Property"].length,A.push(Ga(m,F))),n.applyAnimationDuration&&(n.hasAnimations=!0,E.animationDuration=m,A.push([za,m+"s"]));if(0===m&&!n.recalculateTimingStyles)return M();if(null!=g.delay){var aa;"boolean"!==typeof g.delay&&(aa=parseFloat(g.delay),ha=Math.max(aa,0));n.applyTransitionDelay&&A.push([ma,aa+"s"]);n.applyAnimationDelay&&A.push([ra,aa+"s"])}null==g.duration&&0<E.transitionDuration&& +(n.recalculateTimingStyles=n.recalculateTimingStyles||ka);S=1E3*ha;U=1E3*m;g.skipBlocking||(n.blockTransition=0<E.transitionDuration,n.blockKeyframeAnimation=0<E.animationDuration&&0<W.animationDelay&&0===W.animationDuration);g.from&&(g.cleanupStyles&&Ia(J,h,Object.keys(g.from)),Ca(a,g));n.blockTransition||n.blockKeyframeAnimation?K(m):g.skipBlocking||qa(h,!1);return{$$willAnimate:!0,end:d,start:function(){if(!R)return P={end:d,cancel:v,resume:null,pause:null},C=new f(P),sa(H),C}}}}]}]).provider("$$animateCssDriver", +["$$animationProvider",function(a){a.drivers.push("$$animateCssDriver");this.$get=["$animateCss","$rootScope","$$AnimateRunner","$rootElement","$sniffer","$$jqLite","$document",function(a,c,d,e,f,z,B){function s(a){return a.replace(/\bng-\S+\b/g,"")}function v(a,b){P(a)&&(a=a.split(" "));P(b)&&(b=b.split(" "));return a.filter(function(a){return-1===b.indexOf(a)}).join(" ")}function I(c,e,f){function k(a){var b={},c=D(a).getBoundingClientRect();r(["width","height","top","left"],function(a){var d=c[a]; +switch(a){case "top":d+=C.scrollTop;break;case "left":d+=C.scrollLeft}b[a]=Math.floor(d)+"px"});return b}function p(){var c=s(f.attr("class")||""),d=v(c,l),c=v(l,c),d=a(z,{to:k(f),addClass:"ng-anchor-in "+d,removeClass:"ng-anchor-out "+c,delay:!0});return d.$$willAnimate?d:null}function B(){z.remove();e.removeClass("ng-animate-shim");f.removeClass("ng-animate-shim")}var z=G(D(e).cloneNode(!0)),l=s(z.attr("class")||"");e.addClass("ng-animate-shim");f.addClass("ng-animate-shim");z.addClass("ng-anchor"); +w.append(z);var K;c=function(){var c=a(z,{addClass:"ng-anchor-out",delay:!0,from:k(e)});return c.$$willAnimate?c:null}();if(!c&&(K=p(),!K))return B();var M=c||K;return{start:function(){function a(){c&&c.end()}var b,c=M.start();c.done(function(){c=null;if(!K&&(K=p()))return c=K.start(),c.done(function(){c=null;B();b.complete()}),c;B();b.complete()});return b=new d({end:a,cancel:a})}}}function q(a,b,c,e){var f=u(a,Q),s=u(b,Q),z=[];r(e,function(a){(a=I(c,a.out,a["in"]))&&z.push(a)});if(f||s||0!==z.length)return{start:function(){function a(){r(b, +function(a){a.end()})}var b=[];f&&b.push(f.start());s&&b.push(s.start());r(z,function(a){b.push(a.start())});var c=new d({end:a,cancel:a});d.all(b,function(a){c.complete(a)});return c}}}function u(c){var d=c.element,e=c.options||{};c.structural&&(e.event=c.event,e.structural=!0,e.applyClassesEarly=!0,"leave"===c.event&&(e.onDone=e.domOperation));e.preparationClasses&&(e.event=Y(e.event,e.preparationClasses));c=a(d,e);return c.$$willAnimate?c:null}if(!f.animations&&!f.transitions)return Q;var C=B[0].body; +c=D(e);var w=G(c.parentNode&&11===c.parentNode.nodeType||C.contains(c)?c:C);U(z);return function(a){return a.from&&a.to?q(a.from,a.to,a.classes,a.anchors):u(a)}}]}]).provider("$$animateJs",["$animateProvider",function(a){this.$get=["$injector","$$AnimateRunner","$$jqLite",function(b,c,d){function e(c){c=ba(c)?c:c.split(" ");for(var d=[],e={},f=0;f<c.length;f++){var r=c[f],q=a.$$registeredAnimations[r];q&&!e[r]&&(d.push(b.get(q)),e[r]=!0)}return d}var f=U(d);return function(a,b,d,v){function q(){v.domOperation(); +f(a,v)}function D(a,b,d,e,g){switch(d){case "animate":b=[b,e.from,e.to,g];break;case "setClass":b=[b,x,G,g];break;case "addClass":b=[b,x,g];break;case "removeClass":b=[b,G,g];break;default:b=[b,g]}b.push(e);if(a=a.apply(a,b))if(Ka(a.start)&&(a=a.start()),a instanceof c)a.done(g);else if(Ka(a))return a;return Q}function u(a,b,d,e,g){var f=[];r(e,function(e){var k=e[g];k&&f.push(function(){var e,g,f=!1,h=function(a){f||(f=!0,(g||Q)(a),e.complete(!a))};e=new c({end:function(){h()},cancel:function(){h(!0)}}); +g=D(k,a,b,d,function(a){h(!1===a)});return e})});return f}function C(a,b,d,e,g){var f=u(a,b,d,e,g);if(0===f.length){var h,k;"beforeSetClass"===g?(h=u(a,"removeClass",d,e,"beforeRemoveClass"),k=u(a,"addClass",d,e,"beforeAddClass")):"setClass"===g&&(h=u(a,"removeClass",d,e,"removeClass"),k=u(a,"addClass",d,e,"addClass"));h&&(f=f.concat(h));k&&(f=f.concat(k))}if(0!==f.length)return function(a){var b=[];f.length&&r(f,function(a){b.push(a())});b.length?c.all(b,a):a();return function(a){r(b,function(b){a? +b.cancel():b.end()})}}}var w=!1;3===arguments.length&&va(d)&&(v=d,d=null);v=pa(v);d||(d=a.attr("class")||"",v.addClass&&(d+=" "+v.addClass),v.removeClass&&(d+=" "+v.removeClass));var x=v.addClass,G=v.removeClass,J=e(d),k,p;if(J.length){var L,O;"leave"==b?(O="leave",L="afterLeave"):(O="before"+b.charAt(0).toUpperCase()+b.substr(1),L=b);"enter"!==b&&"move"!==b&&(k=C(a,b,v,J,O));p=C(a,b,v,J,L)}if(k||p){var l;return{$$willAnimate:!0,end:function(){l?l.end():(w=!0,q(),ga(a,v),l=new c,l.complete(!0));return l}, +start:function(){function b(c){w=!0;q();ga(a,v);l.complete(c)}if(l)return l;l=new c;var d,e=[];k&&e.push(function(a){d=k(a)});e.length?e.push(function(a){q();a(!0)}):q();p&&e.push(function(a){d=p(a)});l.setHost({end:function(){w||((d||Q)(void 0),b(void 0))},cancel:function(){w||((d||Q)(!0),b(!0))}});c.chain(e,b);return l}}}}}]}]).provider("$$animateJsDriver",["$$animationProvider",function(a){a.drivers.push("$$animateJsDriver");this.$get=["$$animateJs","$$AnimateRunner",function(a,c){function d(c){return a(c.element, +c.event,c.classes,c.options)}return function(a){if(a.from&&a.to){var b=d(a.from),q=d(a.to);if(b||q)return{start:function(){function a(){return function(){r(d,function(a){a.end()})}}var d=[];b&&d.push(b.start());q&&d.push(q.start());c.all(d,function(a){e.complete(a)});var e=new c({end:a(),cancel:a()});return e}}}else return d(a)}}]}])})(window,window.angular); +//# sourceMappingURL=angular-animate.min.js.map diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-aria/angular-aria.min.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-aria/angular-aria.min.js new file mode 100644 index 00000000000..274cce2f9da --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-aria/angular-aria.min.js @@ -0,0 +1,14 @@ +/* + AngularJS v1.5.5 + (c) 2010-2016 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(t,p){'use strict';var b="BUTTON A INPUT TEXTAREA SELECT DETAILS SUMMARY".split(" "),l=function(a,c){if(-1!==c.indexOf(a[0].nodeName))return!0};p.module("ngAria",["ng"]).provider("$aria",function(){function a(a,b,m,h){return function(d,f,e){var q=e.$normalize(b);!c[q]||l(f,m)||e[q]||d.$watch(e[a],function(a){a=h?!a:!!a;f.attr(b,a)})}}var c={ariaHidden:!0,ariaChecked:!0,ariaReadonly:!0,ariaDisabled:!0,ariaRequired:!0,ariaInvalid:!0,ariaValue:!0,tabindex:!0,bindKeypress:!0,bindRoleForClick:!0}; +this.config=function(a){c=p.extend(c,a)};this.$get=function(){return{config:function(a){return c[a]},$$watchExpr:a}}}).directive("ngShow",["$aria",function(a){return a.$$watchExpr("ngShow","aria-hidden",[],!0)}]).directive("ngHide",["$aria",function(a){return a.$$watchExpr("ngHide","aria-hidden",[],!1)}]).directive("ngValue",["$aria",function(a){return a.$$watchExpr("ngValue","aria-checked",b,!1)}]).directive("ngChecked",["$aria",function(a){return a.$$watchExpr("ngChecked","aria-checked",b,!1)}]).directive("ngReadonly", +["$aria",function(a){return a.$$watchExpr("ngReadonly","aria-readonly",b,!1)}]).directive("ngRequired",["$aria",function(a){return a.$$watchExpr("ngRequired","aria-required",b,!1)}]).directive("ngModel",["$aria",function(a){function c(c,h,d,f){return a.config(h)&&!d.attr(c)&&(f||!l(d,b))}function g(a,c){return!c.attr("role")&&c.attr("type")===a&&"INPUT"!==c[0].nodeName}function k(a,c){var d=a.type,f=a.role;return"checkbox"===(d||f)||"menuitemcheckbox"===f?"checkbox":"radio"===(d||f)||"menuitemradio"=== +f?"radio":"range"===d||"progressbar"===f||"slider"===f?"range":""}return{restrict:"A",require:"ngModel",priority:200,compile:function(b,h){var d=k(h,b);return{pre:function(a,e,c,b){"checkbox"===d&&(b.$isEmpty=function(a){return!1===a})},post:function(f,e,b,n){function h(){return n.$modelValue}function k(a){e.attr("aria-checked",b.value==n.$viewValue)}function l(){e.attr("aria-checked",!n.$isEmpty(n.$viewValue))}var m=c("tabindex","tabindex",e,!1);switch(d){case "radio":case "checkbox":g(d,e)&&e.attr("role", +d);c("aria-checked","ariaChecked",e,!1)&&f.$watch(h,"radio"===d?k:l);m&&e.attr("tabindex",0);break;case "range":g(d,e)&&e.attr("role","slider");if(a.config("ariaValue")){var p=!e.attr("aria-valuemin")&&(b.hasOwnProperty("min")||b.hasOwnProperty("ngMin")),r=!e.attr("aria-valuemax")&&(b.hasOwnProperty("max")||b.hasOwnProperty("ngMax")),s=!e.attr("aria-valuenow");p&&b.$observe("min",function(a){e.attr("aria-valuemin",a)});r&&b.$observe("max",function(a){e.attr("aria-valuemax",a)});s&&f.$watch(h,function(a){e.attr("aria-valuenow", +a)})}m&&e.attr("tabindex",0)}!b.hasOwnProperty("ngRequired")&&n.$validators.required&&c("aria-required","ariaRequired",e,!1)&&b.$observe("required",function(){e.attr("aria-required",!!b.required)});c("aria-invalid","ariaInvalid",e,!0)&&f.$watch(function(){return n.$invalid},function(a){e.attr("aria-invalid",!!a)})}}}}}]).directive("ngDisabled",["$aria",function(a){return a.$$watchExpr("ngDisabled","aria-disabled",b,!1)}]).directive("ngMessages",function(){return{restrict:"A",require:"?ngMessages", +link:function(a,b,g,k){b.attr("aria-live")||b.attr("aria-live","assertive")}}}).directive("ngClick",["$aria","$parse",function(a,c){return{restrict:"A",compile:function(g,k){var m=c(k.ngClick,null,!0);return function(c,d,f){if(!l(d,b)&&(a.config("bindRoleForClick")&&!d.attr("role")&&d.attr("role","button"),a.config("tabindex")&&!d.attr("tabindex")&&d.attr("tabindex",0),a.config("bindKeypress")&&!f.ngKeypress))d.on("keypress",function(a){function b(){m(c,{$event:a})}var d=a.which||a.keyCode;32!==d&& +13!==d||c.$apply(b)})}}}}]).directive("ngDblclick",["$aria",function(a){return function(c,g,k){!a.config("tabindex")||g.attr("tabindex")||l(g,b)||g.attr("tabindex",0)}}])})(window,window.angular); +//# sourceMappingURL=angular-aria.min.js.map diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-material/angular-material.min.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-material/angular-material.min.css new file mode 100644 index 00000000000..50b66e8d51d --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-material/angular-material.min.css @@ -0,0 +1,6 @@ +/*! + * Angular Material Design + * https://github.com/angular/material + * @license MIT + * v1.1.0-rc.5 + */body,html{height:100%;position:relative}body{margin:0;padding:0}[tabindex='-1']:focus{outline:0}.inset{padding:10px}a._md-no-style,button._md-no-style{font-weight:400;background-color:inherit;text-align:left;border:none;padding:0;margin:0}button,input,select,textarea{vertical-align:baseline}button,html input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default}textarea{vertical-align:top;overflow:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box;-webkit-box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input:-webkit-autofill{text-shadow:none}._md-visually-hidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;text-transform:none;width:1px}.md-shadow{position:absolute;top:0;left:0;bottom:0;right:0;border-radius:inherit;pointer-events:none}.md-shadow-bottom-z-1{box-shadow:0 2px 5px 0 rgba(0,0,0,.26)}.md-shadow-bottom-z-2{box-shadow:0 4px 8px 0 rgba(0,0,0,.4)}.md-shadow-animated.md-shadow{transition:box-shadow .28s cubic-bezier(.4,0,.2,1)}.md-ripple-container{pointer-events:none;position:absolute;overflow:hidden;left:0;top:0;width:100%;height:100%;transition:all .55s cubic-bezier(.25,.8,.25,1)}.md-ripple{position:absolute;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0);-webkit-transform-origin:50% 50%;transform-origin:50% 50%;opacity:0;border-radius:50%}.md-ripple.md-ripple-placed{transition:margin .9s cubic-bezier(.25,.8,.25,1),border .9s cubic-bezier(.25,.8,.25,1),width .9s cubic-bezier(.25,.8,.25,1),height .9s cubic-bezier(.25,.8,.25,1),opacity .9s cubic-bezier(.25,.8,.25,1),-webkit-transform .9s cubic-bezier(.25,.8,.25,1);transition:margin .9s cubic-bezier(.25,.8,.25,1),border .9s cubic-bezier(.25,.8,.25,1),width .9s cubic-bezier(.25,.8,.25,1),height .9s cubic-bezier(.25,.8,.25,1),opacity .9s cubic-bezier(.25,.8,.25,1),transform .9s cubic-bezier(.25,.8,.25,1)}.md-ripple.md-ripple-scaled{-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}.md-ripple.md-ripple-active,.md-ripple.md-ripple-full,.md-ripple.md-ripple-visible{opacity:.2}.md-padding{padding:8px}.md-margin{margin:8px}.md-scroll-mask{position:absolute;background-color:transparent;top:0;right:0;bottom:0;left:0;z-index:50}.md-scroll-mask>.md-scroll-mask-bar{display:block;position:absolute;background-color:#fafafa;right:0;top:0;bottom:0;z-index:65;box-shadow:inset 0 0 1px rgba(0,0,0,.3)}@media (min-width:960px){.md-padding{padding:16px}}body[dir=ltr],body[dir=rtl],html[dir=ltr],html[dir=rtl]{unicode-bidi:embed}bdo[dir=rtl]{direction:rtl;unicode-bidi:bidi-override}bdo[dir=ltr]{direction:ltr;unicode-bidi:bidi-override}body,html{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;min-height:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.md-display-4{font-size:112px;font-weight:300;letter-spacing:-.010em;line-height:112px}.md-display-3{font-size:56px;font-weight:400;letter-spacing:-.005em;line-height:56px}.md-display-2{font-size:45px;font-weight:400;line-height:64px}.md-display-1{font-size:34px;font-weight:400;line-height:40px}.md-headline{font-size:24px;font-weight:400;line-height:32px}.md-title{font-size:20px;font-weight:500;letter-spacing:.005em}.md-subhead{font-size:16px;font-weight:400;letter-spacing:.010em;line-height:24px}.md-body-1{font-size:14px;font-weight:400;letter-spacing:.010em;line-height:20px}.md-body-2{font-size:14px;font-weight:500;letter-spacing:.010em;line-height:24px}.md-caption{font-size:12px;letter-spacing:.020em}.md-button{letter-spacing:.010em}button,html,input,select,textarea{font-family:Roboto,"Helvetica Neue",sans-serif}button,input,select,textarea{font-size:100%}.layout-column>.flex{-ms-flex-basis:auto;-webkit-flex-basis:auto;-ms-flex-preferred-size:auto;flex-basis:auto}@-webkit-keyframes md-autocomplete-list-out{0%{-webkit-animation-timing-function:linear;animation-timing-function:linear}50%{opacity:0;height:40px;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}100%{height:0;opacity:0}}@keyframes md-autocomplete-list-out{0%{-webkit-animation-timing-function:linear;animation-timing-function:linear}50%{opacity:0;height:40px;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}100%{height:0;opacity:0}}@-webkit-keyframes md-autocomplete-list-in{0%{opacity:0;height:0;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{opacity:0;height:40px}100%{opacity:1;height:40px}}@keyframes md-autocomplete-list-in{0%{opacity:0;height:0;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{opacity:0;height:40px}100%{opacity:1;height:40px}}md-autocomplete{border-radius:2px;display:block;height:40px;position:relative;overflow:visible;min-width:190px}md-autocomplete[disabled] input{cursor:default}md-autocomplete[md-floating-label]{border-radius:0;background:0 0;height:auto}md-autocomplete[md-floating-label] md-input-container{padding-bottom:0}md-autocomplete[md-floating-label] md-autocomplete-wrap{height:auto}md-autocomplete[md-floating-label] button{position:absolute;top:auto;bottom:0;right:0;width:30px;height:30px}md-autocomplete md-autocomplete-wrap{display:block;position:relative;overflow:visible;height:40px}md-autocomplete md-autocomplete-wrap.md-menu-showing{z-index:51}md-autocomplete md-autocomplete-wrap md-progress-linear{position:absolute;bottom:-2px;left:0}md-autocomplete md-autocomplete-wrap md-progress-linear.md-inline{bottom:40px;right:2px;left:2px;width:auto}md-autocomplete md-autocomplete-wrap md-progress-linear ._md-mode-indeterminate{position:absolute;top:0;left:0;width:100%;height:3px;transition:none}md-autocomplete md-autocomplete-wrap md-progress-linear ._md-mode-indeterminate .md-container{transition:none;height:3px}md-autocomplete md-autocomplete-wrap md-progress-linear ._md-mode-indeterminate.ng-enter{transition:opacity .15s linear}md-autocomplete md-autocomplete-wrap md-progress-linear ._md-mode-indeterminate.ng-enter.ng-enter-active{opacity:1}md-autocomplete md-autocomplete-wrap md-progress-linear ._md-mode-indeterminate.ng-leave{transition:opacity .15s linear}md-autocomplete md-autocomplete-wrap md-progress-linear ._md-mode-indeterminate.ng-leave.ng-leave-active{opacity:0}md-autocomplete input:not(.md-input){font-size:14px;box-sizing:border-box;border:none;box-shadow:none;outline:0;background:0 0;width:100%;padding:0 15px;line-height:40px;height:40px}md-autocomplete input:not(.md-input)::-ms-clear{display:none}md-autocomplete button{position:relative;line-height:20px;text-align:center;width:30px;height:30px;cursor:pointer;border:none;border-radius:50%;padding:0;font-size:12px;background:0 0;margin:auto 5px}md-autocomplete button:after{content:'';position:absolute;top:-6px;right:-6px;bottom:-6px;left:-6px;border-radius:50%;-webkit-transform:scale(0);transform:scale(0);opacity:0;transition:all .4s cubic-bezier(.25,.8,.25,1)}md-autocomplete button:focus{outline:0}md-autocomplete button:focus:after{-webkit-transform:scale(1);transform:scale(1);opacity:1}md-autocomplete button md-icon{position:absolute;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0) scale(.9);transform:translate3d(-50%,-50%,0) scale(.9)}md-autocomplete button md-icon path{stroke-width:0}md-autocomplete button.ng-enter{-webkit-transform:scale(0);transform:scale(0);transition:-webkit-transform .15s ease-out;transition:transform .15s ease-out}md-autocomplete button.ng-enter.ng-enter-active{-webkit-transform:scale(1);transform:scale(1)}md-autocomplete button.ng-leave{transition:-webkit-transform .15s ease-out;transition:transform .15s ease-out}md-autocomplete button.ng-leave.ng-leave-active{-webkit-transform:scale(0);transform:scale(0)}@media screen and (-ms-high-contrast:active){md-autocomplete input{border:1px solid #fff}md-autocomplete li:focus{color:#fff}}.md-virtual-repeat-container.md-autocomplete-suggestions-container{position:absolute;box-shadow:0 2px 5px rgba(0,0,0,.25);height:225.5px;max-height:225.5px;z-index:100}.md-virtual-repeat-container.md-not-found{height:48px}.md-autocomplete-suggestions{margin:0;list-style:none;padding:0}.md-autocomplete-suggestions li{font-size:14px;overflow:hidden;padding:0 15px;line-height:48px;height:48px;transition:background .15s linear;margin:0;white-space:nowrap;text-overflow:ellipsis}.md-autocomplete-suggestions li:focus{outline:0}.md-autocomplete-suggestions li:not(.md-not-found-wrapper){cursor:pointer}@media screen and (-ms-high-contrast:active){.md-autocomplete-suggestions,md-autocomplete{border:1px solid #fff}}md-backdrop{transition:opacity 450ms;position:absolute;top:0;bottom:0;left:0;right:0;z-index:50}md-backdrop._md-menu-backdrop{position:fixed!important;z-index:99}md-backdrop._md-select-backdrop{z-index:81;transition-duration:0}md-backdrop._md-dialog-backdrop{z-index:79}md-backdrop._md-bottom-sheet-backdrop{z-index:69}md-backdrop._md-sidenav-backdrop{z-index:59}md-backdrop._md-click-catcher{position:absolute}md-backdrop.md-opaque{opacity:.48}md-backdrop.md-opaque.ng-enter{opacity:0}md-backdrop.md-opaque.ng-enter.md-opaque.ng-enter-active{opacity:.48}md-backdrop.md-opaque.ng-leave{opacity:.48;transition:opacity 400ms}md-backdrop.md-opaque.ng-leave.md-opaque.ng-leave-active{opacity:0}button.md-button::-moz-focus-inner{border:0}.md-button{border-radius:3px;box-sizing:border-box;color:currentColor;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;outline:0;border:0;display:inline-block;-webkit-align-items:center;-ms-flex-align:center;align-items:center;padding:0 6px;margin:6px 8px;line-height:36px;min-height:36px;background:0 0;white-space:nowrap;min-width:88px;text-align:center;text-transform:uppercase;font-weight:500;font-size:14px;font-style:inherit;font-variant:inherit;font-family:inherit;text-decoration:none;cursor:pointer;overflow:hidden;transition:box-shadow .4s cubic-bezier(.25,.8,.25,1),background-color .4s cubic-bezier(.25,.8,.25,1)}.md-button:focus{outline:0}.md-button:focus,.md-button:hover{text-decoration:none}.md-button.ng-hide,.md-button.ng-leave{transition:none}.md-button.md-cornered{border-radius:0}.md-button.md-icon{padding:0;background:0 0}.md-button.md-raised:not([disabled]){box-shadow:0 2px 5px 0 rgba(0,0,0,.26)}.md-button.md-icon-button{margin:0 6px;height:40px;min-width:0;line-height:24px;padding:8px;width:40px;border-radius:50%}.md-button.md-icon-button .md-ripple-container{border-radius:50%;background-clip:padding-box;overflow:hidden;-webkit-mask-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC)}.md-button.md-fab{z-index:20;line-height:56px;min-width:0;width:56px;height:56px;vertical-align:middle;box-shadow:0 2px 5px 0 rgba(0,0,0,.26);border-radius:50%;background-clip:padding-box;overflow:hidden;transition:all .3s cubic-bezier(.55,0,.55,.2);transition-property:background-color,box-shadow,-webkit-transform;transition-property:background-color,box-shadow,transform}.md-button.md-fab.md-fab-bottom-right{top:auto;right:20px;bottom:20px;left:auto;position:absolute}.md-button.md-fab.md-fab-bottom-left{top:auto;right:auto;bottom:20px;left:20px;position:absolute}.md-button.md-fab.md-fab-top-right{top:20px;right:20px;bottom:auto;left:auto;position:absolute}.md-button.md-fab.md-fab-top-left{top:20px;right:auto;bottom:auto;left:20px;position:absolute}.md-button.md-fab .md-ripple-container{border-radius:50%;background-clip:padding-box;overflow:hidden;-webkit-mask-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC)}.md-button.md-fab.md-mini{line-height:40px;width:40px;height:40px}.md-button.md-fab.ng-hide,.md-button.md-fab.ng-leave{transition:none}.md-button:not([disabled]).md-fab.md-focused,.md-button:not([disabled]).md-raised.md-focused{box-shadow:0 2px 5px 0 rgba(0,0,0,.26)}.md-button:not([disabled]).md-fab:active,.md-button:not([disabled]).md-raised:active{box-shadow:0 4px 8px 0 rgba(0,0,0,.4)}.md-button .md-ripple-container{border-radius:3px;background-clip:padding-box;overflow:hidden;-webkit-mask-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC)}.md-button.md-icon-button md-icon,button.md-button.md-fab md-icon{display:block}._md-toast-open-top .md-button.md-fab-top-left,._md-toast-open-top .md-button.md-fab-top-right{transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transform:translate3d(0,42px,0);transform:translate3d(0,42px,0)}._md-toast-open-top .md-button.md-fab-top-left:not([disabled]).md-focused,._md-toast-open-top .md-button.md-fab-top-left:not([disabled]):hover,._md-toast-open-top .md-button.md-fab-top-right:not([disabled]).md-focused,._md-toast-open-top .md-button.md-fab-top-right:not([disabled]):hover{-webkit-transform:translate3d(0,41px,0);transform:translate3d(0,41px,0)}._md-toast-open-bottom .md-button.md-fab-bottom-left,._md-toast-open-bottom .md-button.md-fab-bottom-right{transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transform:translate3d(0,-42px,0);transform:translate3d(0,-42px,0)}._md-toast-open-bottom .md-button.md-fab-bottom-left:not([disabled]).md-focused,._md-toast-open-bottom .md-button.md-fab-bottom-left:not([disabled]):hover,._md-toast-open-bottom .md-button.md-fab-bottom-right:not([disabled]).md-focused,._md-toast-open-bottom .md-button.md-fab-bottom-right:not([disabled]):hover{-webkit-transform:translate3d(0,-43px,0);transform:translate3d(0,-43px,0)}.md-button-group{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex:1;-ms-flex:1;flex:1;width:100%}.md-button-group>.md-button{-webkit-flex:1;-ms-flex:1;flex:1;display:block;overflow:hidden;width:0;border-width:1px 0 1px 1px;border-radius:0;text-align:center;text-overflow:ellipsis;white-space:nowrap}.md-button-group>.md-button:first-child{border-radius:2px 0 0 2px}.md-button-group>.md-button:last-child{border-right-width:1px;border-radius:0 2px 2px 0}@media screen and (-ms-high-contrast:active){.md-button.md-fab,.md-button.md-raised{border:1px solid #fff}}md-bottom-sheet{position:absolute;left:0;right:0;bottom:0;padding:8px 16px 88px;z-index:70;border-top-width:1px;border-top-style:solid;-webkit-transform:translate3d(0,80px,0);transform:translate3d(0,80px,0);transition:all .4s cubic-bezier(.25,.8,.25,1);transition-property:-webkit-transform;transition-property:transform}md-bottom-sheet.md-has-header{padding-top:0}md-bottom-sheet.ng-enter{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}md-bottom-sheet.ng-enter-active{opacity:1;display:block;-webkit-transform:translate3d(0,80px,0)!important;transform:translate3d(0,80px,0)!important}md-bottom-sheet.ng-leave-active{-webkit-transform:translate3d(0,100%,0)!important;transform:translate3d(0,100%,0)!important;transition:all .3s cubic-bezier(.55,0,.55,.2)}md-bottom-sheet .md-subheader{background-color:transparent;font-family:Roboto,"Helvetica Neue",sans-serif;line-height:56px;padding:0;white-space:nowrap}md-bottom-sheet md-inline-icon{display:inline-block;height:24px;width:24px;fill:#444}md-bottom-sheet md-list-item{display:-webkit-flex;display:-ms-flexbox;display:flex;outline:0}md-bottom-sheet md-list-item:hover{cursor:pointer}md-bottom-sheet.md-list md-list-item{padding:0;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:48px}md-bottom-sheet.md-grid{padding-left:24px;padding-right:24px;padding-top:0}md-bottom-sheet.md-grid md-list{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;transition:all .5s;-webkit-align-items:center;-ms-flex-align:center;align-items:center}md-bottom-sheet.md-grid md-list-item{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:center;-ms-flex-align:center;align-items:center;transition:all .5s;height:96px;margin-top:8px;margin-bottom:8px}@media (max-width:960px){md-bottom-sheet.md-grid md-list-item{-webkit-flex:1 1 33.33333%;-ms-flex:1 1 33.33333%;flex:1 1 33.33333%;max-width:33.33333%}md-bottom-sheet.md-grid md-list-item:nth-of-type(3n+1){-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start}md-bottom-sheet.md-grid md-list-item:nth-of-type(3n){-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end}}@media (min-width:960px) and (max-width:1279px){md-bottom-sheet.md-grid md-list-item{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:25%}}@media (min-width:1280px) and (max-width:1919px){md-bottom-sheet.md-grid md-list-item{-webkit-flex:1 1 16.66667%;-ms-flex:1 1 16.66667%;flex:1 1 16.66667%;max-width:16.66667%}}@media (min-width:1920px){md-bottom-sheet.md-grid md-list-item{-webkit-flex:1 1 14.28571%;-ms-flex:1 1 14.28571%;flex:1 1 14.28571%;max-width:14.28571%}}md-bottom-sheet.md-grid md-list-item .md-list-item-content{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:center;-ms-flex-align:center;align-items:center;width:48px;padding-bottom:16px}md-bottom-sheet.md-grid md-list-item .md-grid-item-content{border:1px solid transparent;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:center;-ms-flex-align:center;align-items:center;width:80px}md-bottom-sheet.md-grid md-list-item .md-grid-text{font-weight:400;line-height:16px;font-size:13px;margin:0;white-space:nowrap;width:64px;text-align:center;text-transform:none;padding-top:8px}@media screen and (-ms-high-contrast:active){md-bottom-sheet{border:1px solid #fff}}.md-inline-form md-checkbox{margin:19px 0 18px}md-checkbox{box-sizing:border-box;display:inline-block;margin-bottom:16px;white-space:nowrap;cursor:pointer;outline:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;min-width:20px;min-height:20px;margin-left:0;margin-right:16px}[dir=rtl] md-checkbox{margin-left:16px;margin-right:0}md-checkbox:last-of-type{margin-left:0;margin-right:0}md-checkbox.md-focused:not([disabled]) ._md-container:before{left:-8px;top:-8px;right:-8px;bottom:-8px}md-checkbox.md-focused:not([disabled]):not(.md-checked) ._md-container:before{background-color:rgba(0,0,0,.12)}md-checkbox.md-align-top-left>div._md-container{top:12px}md-checkbox ._md-container{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);box-sizing:border-box;display:inline-block;width:20px;height:20px;left:0;right:auto}[dir=rtl] md-checkbox ._md-container{left:auto;right:0}md-checkbox ._md-container:before{box-sizing:border-box;background-color:transparent;border-radius:50%;content:'';position:absolute;display:block;height:auto;left:0;top:0;right:0;bottom:0;transition:all .5s;width:auto}md-checkbox ._md-container:after{box-sizing:border-box;content:'';position:absolute;top:-10px;right:-10px;bottom:-10px;left:-10px}md-checkbox ._md-container .md-ripple-container{position:absolute;display:block;width:auto;height:auto;left:-15px;top:-15px;right:-15px;bottom:-15px}md-checkbox ._md-icon{box-sizing:border-box;transition:240ms;position:absolute;top:0;left:0;width:20px;height:20px;border-width:2px;border-style:solid;border-radius:2px}md-checkbox.md-checked ._md-icon{border:none}md-checkbox.md-checked ._md-icon:after{box-sizing:border-box;-webkit-transform:rotate(45deg);transform:rotate(45deg);position:absolute;left:6.67px;top:2.22px;display:table;width:6.67px;height:13.33px;border-width:2px;border-style:solid;border-top:0;border-left:0;content:''}md-checkbox[disabled]{cursor:default}md-checkbox.md-indeterminate ._md-icon:after{box-sizing:border-box;position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);display:table;width:12px;height:2px;border-width:2px;border-style:solid;border-top:0;border-left:0;content:''}md-checkbox ._md-label{box-sizing:border-box;position:relative;display:inline-block;vertical-align:middle;white-space:normal;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;margin-left:30px;margin-right:0}[dir=rtl] md-checkbox ._md-label{margin-left:0;margin-right:30px}.md-contact-chips .md-chips md-chip{padding:0 25px 0 0}[dir=rtl] .md-contact-chips .md-chips md-chip{padding:0 0 0 25px}.md-contact-chips .md-chips md-chip .md-contact-avatar{float:left}[dir=rtl] .md-contact-chips .md-chips md-chip .md-contact-avatar{float:right}.md-contact-chips .md-chips md-chip .md-contact-avatar img{height:32px;border-radius:16px}.md-contact-chips .md-chips md-chip .md-contact-name{display:inline-block;height:32px;margin-left:8px}[dir=rtl] .md-contact-chips .md-chips md-chip .md-contact-name{margin-left:auto;margin-left:initial;margin-right:8px}.md-contact-suggestion{height:56px}.md-contact-suggestion img{height:40px;border-radius:20px;margin-top:8px}.md-contact-suggestion .md-contact-name{margin-left:8px;width:120px}[dir=rtl] .md-contact-suggestion .md-contact-name{margin-left:auto;margin-left:initial;margin-right:8px}.md-contact-suggestion .md-contact-email,.md-contact-suggestion .md-contact-name{display:inline-block;overflow:hidden;text-overflow:ellipsis}.md-contact-chips-suggestions li{height:100%}.md-chips{display:block;font-family:Roboto,"Helvetica Neue",sans-serif;font-size:16px;padding:0 0 8px 3px;vertical-align:middle}.md-chips:after{content:'';display:table;clear:both}[dir=rtl] .md-chips{padding:0 3px 8px 0}.md-chips.md-readonly ._md-chip-input-container{min-height:32px}.md-chips:not(.md-readonly){cursor:text}.md-chips:not(.md-readonly) md-chip:not(.md-readonly){padding-right:22px}[dir=rtl] .md-chips:not(.md-readonly) md-chip:not(.md-readonly){padding-right:auto;padding-right:initial;padding-left:22px}.md-chips:not(.md-readonly) md-chip:not(.md-readonly) ._md-chip-content{padding-right:4px}[dir=rtl] .md-chips:not(.md-readonly) md-chip:not(.md-readonly) ._md-chip-content{padding-right:auto;padding-right:initial;padding-left:4px}.md-chips md-chip{cursor:default;border-radius:16px;display:block;height:32px;line-height:32px;margin:8px 8px 0 0;padding:0 12px;float:left;box-sizing:border-box;max-width:100%;position:relative}[dir=rtl] .md-chips md-chip{margin:8px 0 0 8px;float:right}.md-chips md-chip ._md-chip-content{display:block;float:left;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}[dir=rtl] .md-chips md-chip ._md-chip-content{float:right}.md-chips md-chip ._md-chip-content:focus{outline:0}.md-chips md-chip._md-chip-content-edit-is-enabled{-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none}.md-chips md-chip ._md-chip-remove-container{position:absolute;right:0;line-height:22px}[dir=rtl] .md-chips md-chip ._md-chip-remove-container{right:0;right:auto;right:initial;left:0}.md-chips md-chip ._md-chip-remove{text-align:center;width:32px;height:32px;min-width:0;padding:0;background:0 0;border:none;box-shadow:none;margin:0;position:relative}.md-chips md-chip ._md-chip-remove md-icon{height:18px;width:18px;position:absolute;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0)}.md-chips ._md-chip-input-container{display:block;line-height:32px;margin:8px 8px 0 0;padding:0;float:left}[dir=rtl] .md-chips ._md-chip-input-container{margin:8px 0 0 8px;float:right}.md-chips ._md-chip-input-container input:not([type]),.md-chips ._md-chip-input-container input[type=url],.md-chips ._md-chip-input-container input[type=text],.md-chips ._md-chip-input-container input[type=email],.md-chips ._md-chip-input-container input[type=number],.md-chips ._md-chip-input-container input[type=tel]{border:0;height:32px;line-height:32px;padding:0}.md-chips ._md-chip-input-container input:not([type]):focus,.md-chips ._md-chip-input-container input[type=url]:focus,.md-chips ._md-chip-input-container input[type=text]:focus,.md-chips ._md-chip-input-container input[type=email]:focus,.md-chips ._md-chip-input-container input[type=number]:focus,.md-chips ._md-chip-input-container input[type=tel]:focus{outline:0}.md-chips ._md-chip-input-container md-autocomplete,.md-chips ._md-chip-input-container md-autocomplete-wrap{background:0 0}.md-chips ._md-chip-input-container md-autocomplete md-autocomplete-wrap{box-shadow:none}.md-chips ._md-chip-input-container input{border:0;height:32px;line-height:32px;padding:0}.md-chips ._md-chip-input-container input:focus{outline:0}.md-chips ._md-chip-input-container md-autocomplete,.md-chips ._md-chip-input-container md-autocomplete-wrap{height:32px}.md-chips ._md-chip-input-container md-autocomplete{box-shadow:none}.md-chips ._md-chip-input-container md-autocomplete input{position:relative}.md-chips ._md-chip-input-container:not(:first-child){margin:8px 8px 0 0}[dir=rtl] .md-chips ._md-chip-input-container:not(:first-child){margin:8px 0 0 8px}.md-chips ._md-chip-input-container input{background:0 0;border-width:0}.md-chips md-autocomplete button{display:none}@media screen and (-ms-high-contrast:active){._md-chip-input-container,md-chip{border:1px solid #fff}._md-chip-input-container md-autocomplete{border:none}}md-card{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;margin:8px;box-shadow:0 1px 3px 0 rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 2px 1px -1px rgba(0,0,0,.12)}md-card md-card-header{padding:16px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}md-card md-card-header:first-child md-card-avatar{margin-right:12px}[dir=rtl] md-card md-card-header:first-child md-card-avatar{margin-right:auto;margin-right:initial;margin-left:12px}md-card md-card-header:last-child md-card-avatar{margin-left:12px}[dir=rtl] md-card md-card-header:last-child md-card-avatar{margin-left:auto;margin-left:initial;margin-right:12px}md-card md-card-header md-card-avatar{width:40px;height:40px}md-card md-card-header md-card-avatar .md-user-avatar,md-card md-card-header md-card-avatar md-icon{border-radius:50%}md-card md-card-header md-card-avatar md-icon{padding:8px}md-card md-card-header md-card-avatar+md-card-header-text{max-height:40px}md-card md-card-header md-card-avatar+md-card-header-text .md-title{font-size:14px}md-card md-card-header md-card-header-text{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex:1;-ms-flex:1;flex:1;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}md-card md-card-header md-card-header-text .md-subhead{font-size:14px}md-card>:not(md-card-content) img,md-card>img{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:100%;height:100%!important}md-card md-card-title{padding:24px 16px 16px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}md-card md-card-title+md-card-content{padding-top:0}md-card md-card-title md-card-title-text{-webkit-flex:1;-ms-flex:1;flex:1;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;display:-webkit-flex;display:-ms-flexbox;display:flex}md-card md-card-title md-card-title-text .md-subhead{padding-top:0;font-size:14px}md-card md-card-title md-card-title-text:only-child .md-subhead{padding-top:12px}md-card md-card-title md-card-title-media{margin-top:-8px}md-card md-card-title md-card-title-media .md-media-sm{height:80px;width:80px}md-card md-card-title md-card-title-media .md-media-md{height:112px;width:112px}md-card md-card-title md-card-title-media .md-media-lg{height:152px;width:152px}md-card md-card-content{display:block;padding:16px}md-card md-card-content>p:first-child{margin-top:0}md-card md-card-content>p:last-child{margin-bottom:0}md-card md-card-content .md-media-xl{height:240px;width:240px}md-card .md-actions,md-card md-card-actions{margin:8px}md-card .md-actions.layout-column .md-button:not(.md-icon-button),md-card md-card-actions.layout-column .md-button:not(.md-icon-button){margin:2px 0}md-card .md-actions.layout-column .md-button:not(.md-icon-button):first-of-type,md-card md-card-actions.layout-column .md-button:not(.md-icon-button):first-of-type{margin-top:0}md-card .md-actions.layout-column .md-button:not(.md-icon-button):last-of-type,md-card md-card-actions.layout-column .md-button:not(.md-icon-button):last-of-type{margin-bottom:0}md-card .md-actions.layout-column .md-button.md-icon-button,md-card md-card-actions.layout-column .md-button.md-icon-button{margin-top:6px;margin-bottom:6px}md-card .md-actions md-card-icon-actions,md-card md-card-actions md-card-icon-actions{-webkit-flex:1;-ms-flex:1;flex:1;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button),md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button){margin:0 4px}md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type,md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type{margin-left:0}[dir=rtl] md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type,[dir=rtl] md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):first-of-type{margin-left:auto;margin-left:initial;margin-right:0}md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type,md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type{margin-right:0}[dir=rtl] md-card .md-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type,[dir=rtl] md-card md-card-actions:not(.layout-column) .md-button:not(.md-icon-button):last-of-type{margin-right:auto;margin-right:initial;margin-left:0}md-card .md-actions:not(.layout-column) .md-button.md-icon-button,md-card md-card-actions:not(.layout-column) .md-button.md-icon-button{margin-left:6px;margin-right:6px}md-card .md-actions:not(.layout-column) .md-button.md-icon-button:first-of-type,md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:first-of-type{margin-left:12px}[dir=rtl] md-card .md-actions:not(.layout-column) .md-button.md-icon-button:first-of-type,[dir=rtl] md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:first-of-type{margin-left:auto;margin-left:initial;margin-right:12px}md-card .md-actions:not(.layout-column) .md-button.md-icon-button:last-of-type,md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:last-of-type{margin-right:12px}[dir=rtl] md-card .md-actions:not(.layout-column) .md-button.md-icon-button:last-of-type,[dir=rtl] md-card md-card-actions:not(.layout-column) .md-button.md-icon-button:last-of-type{margin-right:auto;margin-right:initial;margin-left:12px}md-card .md-actions:not(.layout-column) .md-button+md-card-icon-actions,md-card md-card-actions:not(.layout-column) .md-button+md-card-icon-actions{-webkit-flex:1;-ms-flex:1;flex:1;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}md-card md-card-footer{margin-top:auto;padding:16px}@media screen and (-ms-high-contrast:active){md-card{border:1px solid #fff}}md-content{display:block;position:relative;overflow:auto;-webkit-overflow-scrolling:touch}md-content[md-scroll-y]{overflow-y:auto;overflow-x:hidden}md-content[md-scroll-x]{overflow-x:auto;overflow-y:hidden}md-content.md-no-momentum{-webkit-overflow-scrolling:auto}@media print{md-content{overflow:visible!important}}md-calendar{font-size:13px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-calendar-scroll-mask{display:inline-block;overflow:hidden;height:308px}.md-calendar-scroll-mask .md-virtual-repeat-scroller{overflow-y:scroll;-webkit-overflow-scrolling:touch}.md-calendar-scroll-mask .md-virtual-repeat-scroller::-webkit-scrollbar{display:none}.md-calendar-scroll-mask .md-virtual-repeat-offsetter{width:100%}.md-calendar-scroll-container{box-shadow:inset -3px 3px 6px rgba(0,0,0,.2);display:inline-block;height:308px;width:346px}.md-calendar-date{height:44px;width:44px;text-align:center;padding:0;border:none;box-sizing:content-box}.md-calendar-date:first-child{padding-left:16px}[dir=rtl] .md-calendar-date:first-child{padding-left:auto;padding-left:initial;padding-right:16px}.md-calendar-date:last-child{padding-right:16px}[dir=rtl] .md-calendar-date:last-child{padding-right:auto;padding-right:initial;padding-left:16px}.md-calendar-date.md-calendar-date-disabled{cursor:default}.md-calendar-date-selection-indicator{transition:background-color,color .4s cubic-bezier(.25,.8,.25,1);border-radius:50%;display:inline-block;width:40px;height:40px;line-height:40px}.md-calendar-date:not(.md-disabled) .md-calendar-date-selection-indicator{cursor:pointer}.md-calendar-month-label{height:44px;font-size:14px;font-weight:500;padding:0 0 0 24px}[dir=rtl] .md-calendar-month-label{padding:0 24px 0 0}md-calendar-month .md-calendar-month-label:not(.md-calendar-month-label-disabled){cursor:pointer}.md-calendar-day-header{table-layout:fixed;border-spacing:0;border-collapse:collapse}.md-calendar-day-header th{width:44px;text-align:center;padding:0;border:none;box-sizing:content-box;font-weight:400;height:40px}.md-calendar-day-header th:first-child{padding-left:16px}[dir=rtl] .md-calendar-day-header th:first-child{padding-left:auto;padding-left:initial;padding-right:16px}.md-calendar-day-header th:last-child{padding-right:16px}[dir=rtl] .md-calendar-day-header th:last-child{padding-right:auto;padding-right:initial;padding-left:16px}.md-calendar{table-layout:fixed;border-spacing:0;border-collapse:collapse}.md-calendar tr:last-child td{border-bottom-width:1px;border-bottom-style:solid}.md-calendar:first-child{border-top:1px solid transparent}.md-calendar tbody,.md-calendar td,.md-calendar tr{vertical-align:middle;box-sizing:content-box}md-datepicker{white-space:nowrap;overflow:hidden;padding-right:18px;margin-right:-18px;vertical-align:middle}[dir=rtl] md-datepicker{padding-right:auto;padding-right:initial;padding-left:18px;margin-right:auto;margin-right:initial;margin-left:-18px}.md-inline-form md-datepicker{margin-top:12px}.md-datepicker-button{display:inline-block;box-sizing:border-box;background:0 0}.md-datepicker-input{font-size:14px;box-sizing:border-box;border:none;box-shadow:none;outline:0;background:0 0;min-width:120px;max-width:328px}.md-datepicker-input::-ms-clear{display:none}.md-datepicker-input-container{position:relative;padding-bottom:5px;border-bottom-width:1px;border-bottom-style:solid;display:inline-block;width:auto;margin-left:12px}[dir=rtl] .md-datepicker-input-container{margin-left:auto;margin-left:initial;margin-right:12px}.md-datepicker-input-container.md-datepicker-focused{border-bottom-width:2px}.md-datepicker-is-showing .md-scroll-mask{z-index:99}.md-datepicker-calendar-pane{position:absolute;top:0;left:0;z-index:100;border-width:1px;border-style:solid;background:0 0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:0 0;transform-origin:0 0;transition:-webkit-transform .2s cubic-bezier(.25,.8,.25,1);transition:transform .2s cubic-bezier(.25,.8,.25,1)}.md-datepicker-calendar-pane.md-pane-open{-webkit-transform:scale(1);transform:scale(1)}.md-datepicker-input-mask{height:40px;width:340px;position:relative;background:0 0;pointer-events:none;cursor:text}.md-datepicker-input-mask-opaque{position:absolute;right:0;left:120px;height:100%;margin-left:-1px}.md-datepicker-calendar{opacity:0;transition:opacity .2s cubic-bezier(.5,0,.25,1)}.md-pane-open .md-datepicker-calendar{opacity:1}.md-datepicker-calendar md-calendar:focus{outline:0}.md-datepicker-expand-triangle{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid}.md-datepicker-triangle-button{position:absolute;right:0;top:0;-webkit-transform:translateY(-25%) translateX(45%);transform:translateY(-25%) translateX(45%)}[dir=rtl] .md-datepicker-triangle-button{right:0;right:auto;right:initial;left:0;-webkit-transform:translateY(-25%) translateX(-45%);transform:translateY(-25%) translateX(-45%)}.md-datepicker-triangle-button.md-button.md-icon-button{height:100%;width:36px;position:absolute}md-datepicker[disabled] .md-datepicker-input-container{border-bottom-color:transparent}md-datepicker[disabled] .md-datepicker-triangle-button{display:none}.md-datepicker-open .md-datepicker-input-container{margin-left:-12px;margin-bottom:-5px;border:none}[dir=rtl] .md-datepicker-open .md-datepicker-input-container{margin-left:auto;margin-left:initial;margin-right:-12px}.md-datepicker-open .md-datepicker-input{margin-left:24px;height:40px}[dir=rtl] .md-datepicker-open .md-datepicker-input{margin-left:auto;margin-left:initial;margin-right:24px}.md-datepicker-open .md-datepicker-triangle-button,.md-datepicker-pos-adjusted .md-datepicker-input-mask{display:none}.md-datepicker-calendar-pane .md-calendar{-webkit-transform:translateY(-85px);transform:translateY(-85px);transition:-webkit-transform .65s cubic-bezier(.25,.8,.25,1);transition:transform .65s cubic-bezier(.25,.8,.25,1);transition-delay:.125s}.md-datepicker-calendar-pane.md-pane-open .md-calendar{-webkit-transform:translateY(0);transform:translateY(0)}.md-dialog-is-showing{max-height:100%}.md-dialog-container{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:absolute;top:0;left:0;width:100%;height:100%;z-index:80;overflow:hidden}md-dialog{opacity:0;min-width:240px;max-width:80%;max-height:80%;position:relative;overflow:auto;box-shadow:0 7px 8px -4px rgba(0,0,0,.2),0 13px 19px 2px rgba(0,0,0,.14),0 5px 24px 4px rgba(0,0,0,.12);display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}md-dialog._md-transition-in{opacity:1;transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transform:translate(0,0) scale(1);transform:translate(0,0) scale(1)}md-dialog._md-transition-out{opacity:0;transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-transform:translate(0,100%) scale(.2);transform:translate(0,100%) scale(.2)}md-dialog>form{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;overflow:auto}md-dialog .md-dialog-content{padding:24px}md-dialog md-dialog-content{-webkit-order:1;-ms-flex-order:1;order:1;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;overflow:auto;-webkit-overflow-scrolling:touch}md-dialog md-dialog-content:not([layout=row])>:first-child:not(.md-subheader){margin-top:0}md-dialog md-dialog-content:focus{outline:0}md-dialog md-dialog-content .md-subheader{margin:0}md-dialog md-dialog-content ._md-dialog-content-body{width:100%}md-dialog md-dialog-content .md-prompt-input-container{width:100%;box-sizing:border-box}md-dialog .md-actions,md-dialog md-dialog-actions{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-order:2;-ms-flex-order:2;order:2;box-sizing:border-box;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end;margin-bottom:0;padding-right:8px;padding-left:16px;min-height:52px;overflow:hidden}[dir=rtl] md-dialog .md-actions,[dir=rtl] md-dialog md-dialog-actions{padding-right:16px;padding-left:8px}md-dialog .md-actions .md-button,md-dialog md-dialog-actions .md-button{margin:8px 0 8px 8px}[dir=rtl] md-dialog .md-actions .md-button,[dir=rtl] md-dialog md-dialog-actions .md-button{margin-left:0;margin-right:8px}md-dialog.md-content-overflow .md-actions,md-dialog.md-content-overflow md-dialog-actions{border-top-width:1px;border-top-style:solid}@media screen and (-ms-high-contrast:active){md-dialog{border:1px solid #fff}}@media (max-width:959px){md-dialog.md-dialog-fullscreen{min-height:100%;min-width:100%;border-radius:0}}md-divider{display:block;border-top-width:1px;border-top-style:solid;margin:0}md-divider[md-inset]{margin-left:80px}[dir=rtl] md-divider[md-inset]{margin-left:auto;margin-left:initial;margin-right:80px}.layout-gt-lg-row>md-divider,.layout-gt-md-row>md-divider,.layout-gt-sm-row>md-divider,.layout-gt-xs-row>md-divider,.layout-lg-row>md-divider,.layout-md-row>md-divider,.layout-row>md-divider,.layout-sm-row>md-divider,.layout-xl-row>md-divider,.layout-xs-row>md-divider{border-top-width:0;border-right-width:1px;border-right-style:solid}md-fab-speed-dial{position:relative;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;z-index:20}md-fab-speed-dial.md-fab-bottom-right{top:auto;right:20px;bottom:20px;left:auto;position:absolute}md-fab-speed-dial.md-fab-bottom-left{top:auto;right:auto;bottom:20px;left:20px;position:absolute}md-fab-speed-dial.md-fab-top-right{top:20px;right:20px;bottom:auto;left:auto;position:absolute}md-fab-speed-dial.md-fab-top-left{top:20px;right:auto;bottom:auto;left:20px;position:absolute}md-fab-speed-dial:not(.md-hover-full){pointer-events:none}md-fab-speed-dial:not(.md-hover-full) .md-fab-action-item,md-fab-speed-dial:not(.md-hover-full) md-fab-trigger,md-fab-speed-dial:not(.md-hover-full).md-is-open{pointer-events:auto}md-fab-speed-dial ._md-css-variables{z-index:20}md-fab-speed-dial.md-is-open .md-fab-action-item{-webkit-align-items:center;-ms-flex-align:center;align-items:center}md-fab-speed-dial md-fab-actions{display:-webkit-flex;display:-ms-flexbox;display:flex;height:auto}md-fab-speed-dial md-fab-actions .md-fab-action-item{transition:all .3s cubic-bezier(.55,0,.55,.2)}md-fab-speed-dial.md-down{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}md-fab-speed-dial.md-down md-fab-trigger{-webkit-order:1;-ms-flex-order:1;order:1}md-fab-speed-dial.md-down md-fab-actions{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-order:2;-ms-flex-order:2;order:2}md-fab-speed-dial.md-up{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}md-fab-speed-dial.md-up md-fab-trigger{-webkit-order:2;-ms-flex-order:2;order:2}md-fab-speed-dial.md-up md-fab-actions{-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse;-webkit-order:1;-ms-flex-order:1;order:1}md-fab-speed-dial.md-left{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}md-fab-speed-dial.md-left md-fab-trigger{-webkit-order:2;-ms-flex-order:2;order:2}md-fab-speed-dial.md-left md-fab-actions{-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;-webkit-order:1;-ms-flex-order:1;order:1}md-fab-speed-dial.md-left md-fab-actions .md-fab-action-item{transition:all .3s cubic-bezier(.55,0,.55,.2)}md-fab-speed-dial.md-right{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}md-fab-speed-dial.md-right md-fab-trigger{-webkit-order:1;-ms-flex-order:1;order:1}md-fab-speed-dial.md-right md-fab-actions{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-order:2;-ms-flex-order:2;order:2}md-fab-speed-dial.md-right md-fab-actions .md-fab-action-item{transition:all .3s cubic-bezier(.55,0,.55,.2)}md-fab-speed-dial.md-fling-remove .md-fab-action-item>*,md-fab-speed-dial.md-scale-remove .md-fab-action-item>*{visibility:hidden}md-fab-speed-dial.md-fling .md-fab-action-item{opacity:1}md-fab-speed-dial.md-fling._md-animations-waiting .md-fab-action-item{opacity:0;transition-duration:0s}md-fab-speed-dial.md-scale .md-fab-action-item{-webkit-transform:scale(0);transform:scale(0);transition:all .3s cubic-bezier(.55,0,.55,.2);transition-duration:.14286s}md-fab-toolbar{display:block}md-fab-toolbar.md-fab-bottom-right{top:auto;right:20px;bottom:20px;left:auto;position:absolute}md-fab-toolbar.md-fab-bottom-left{top:auto;right:auto;bottom:20px;left:20px;position:absolute}md-fab-toolbar.md-fab-top-right{top:20px;right:20px;bottom:auto;left:auto;position:absolute}md-fab-toolbar.md-fab-top-left{top:20px;right:auto;bottom:auto;left:20px;position:absolute}md-fab-toolbar ._md-fab-toolbar-wrapper{display:block;position:relative;overflow:hidden;height:68px}md-fab-toolbar md-fab-trigger{position:absolute;z-index:20}md-fab-toolbar md-fab-trigger button{overflow:visible!important}md-fab-toolbar md-fab-trigger ._md-fab-toolbar-background{display:block;position:absolute;z-index:21;opacity:1;transition:all .3s cubic-bezier(.55,0,.55,.2)}md-fab-toolbar md-fab-trigger md-icon{position:relative;z-index:22;opacity:1;transition:all 200ms ease-in}md-fab-toolbar.md-left md-fab-trigger{right:0}[dir=rtl] md-fab-toolbar.md-left md-fab-trigger{right:0;right:auto;right:initial;left:0}md-fab-toolbar.md-left .md-toolbar-tools{-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}md-fab-toolbar.md-left .md-toolbar-tools>.md-button:first-child{margin-right:.6rem}md-fab-toolbar.md-left .md-toolbar-tools>.md-button:first-child{margin-left:-.8rem}[dir=rtl] md-fab-toolbar.md-left .md-toolbar-tools>.md-button:first-child{margin-left:auto;margin-left:initial;margin-right:-.8rem}md-fab-toolbar.md-left .md-toolbar-tools>.md-button:last-child{margin-right:8px}[dir=rtl] md-fab-toolbar.md-left .md-toolbar-tools>.md-button:last-child{margin-right:auto;margin-right:initial;margin-left:8px}md-fab-toolbar.md-right md-fab-trigger{left:0}[dir=rtl] md-fab-toolbar.md-right md-fab-trigger{left:0;left:auto;left:initial;right:0}md-fab-toolbar.md-right .md-toolbar-tools{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}md-fab-toolbar md-toolbar{background-color:transparent!important;pointer-events:none;z-index:23}md-fab-toolbar md-toolbar .md-toolbar-tools{padding:0 20px;margin-top:3px}md-fab-toolbar md-toolbar .md-fab-action-item{opacity:0;-webkit-transform:scale(0);transform:scale(0);transition:all .3s cubic-bezier(.55,0,.55,.2);transition-duration:.15s}md-fab-toolbar.md-is-open md-fab-trigger>button{box-shadow:none}md-fab-toolbar.md-is-open md-fab-trigger>button md-icon{opacity:0}md-fab-toolbar.md-is-open .md-fab-action-item{opacity:1;-webkit-transform:scale(1);transform:scale(1)}md-icon{margin:auto;background-repeat:no-repeat no-repeat;display:inline-block;vertical-align:middle;fill:currentColor;height:24px;width:24px;min-height:24px;min-width:24px}md-icon svg{pointer-events:none;display:block}md-icon[md-font-icon]{line-height:24px;width:auto}md-grid-list{box-sizing:border-box;display:block;position:relative}md-grid-list md-grid-tile,md-grid-list md-grid-tile-footer,md-grid-list md-grid-tile-header,md-grid-list md-grid-tile>figure{box-sizing:border-box}md-grid-list md-grid-tile{display:block;position:absolute}md-grid-list md-grid-tile figure{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;height:100%;position:absolute;top:0;right:0;bottom:0;left:0;padding:0;margin:0}md-grid-list md-grid-tile md-grid-tile-footer,md-grid-list md-grid-tile md-grid-tile-header{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:48px;color:#fff;background:rgba(0,0,0,.18);overflow:hidden;position:absolute;left:0;right:0}md-grid-list md-grid-tile md-grid-tile-footer h3,md-grid-list md-grid-tile md-grid-tile-footer h4,md-grid-list md-grid-tile md-grid-tile-header h3,md-grid-list md-grid-tile md-grid-tile-header h4{font-weight:400;margin:0 0 0 16px}md-grid-list md-grid-tile md-grid-tile-footer h3,md-grid-list md-grid-tile md-grid-tile-header h3{font-size:14px}md-grid-list md-grid-tile md-grid-tile-footer h4,md-grid-list md-grid-tile md-grid-tile-header h4{font-size:12px}md-grid-list md-grid-tile md-grid-tile-header{top:0}md-grid-list md-grid-tile md-grid-tile-footer{bottom:0}@media screen and (-ms-high-contrast:active){md-grid-tile{border:1px solid #fff}md-grid-tile-footer{border-top:1px solid #fff}}md-input-container{display:inline-block;position:relative;padding:2px;margin:18px 0;vertical-align:middle}md-input-container:after{content:'';display:table;clear:both}md-input-container.md-block{display:block}md-input-container .md-errors-spacer{float:right;min-height:24px;min-width:1px}[dir=rtl] md-input-container .md-errors-spacer{float:left}md-input-container .md-resize-handle{position:absolute;bottom:22px;left:0;height:10px;background:0 0;width:100%;cursor:ns-resize}md-input-container>md-icon{position:absolute;top:8px;left:2px;right:auto}[dir=rtl] md-input-container>md-icon{left:auto;right:2px}md-input-container input[type=url],md-input-container input[type=text],md-input-container input[type=password],md-input-container input[type=datetime],md-input-container input[type=datetime-local],md-input-container input[type=date],md-input-container input[type=month],md-input-container input[type=time],md-input-container input[type=week],md-input-container input[type=color],md-input-container input[type=search],md-input-container input[type=email],md-input-container input[type=number],md-input-container input[type=tel],md-input-container textarea{-moz-appearance:none;-webkit-appearance:none}md-input-container input[type=datetime-local],md-input-container input[type=date],md-input-container input[type=month],md-input-container input[type=time],md-input-container input[type=week]{min-height:26px}md-input-container textarea{resize:none;overflow:hidden}md-input-container textarea.md-input{min-height:26px;-ms-flex-preferred-size:auto}md-input-container textarea[md-no-autogrow]{height:auto;overflow:auto}md-input-container label:not(._md-container-ignore){position:absolute;bottom:100%;left:0;right:auto}[dir=rtl] md-input-container label:not(._md-container-ignore){left:auto;right:0}md-input-container label:not(._md-container-ignore).md-required:after{content:' *';font-size:13px;vertical-align:top}md-input-container ._md-placeholder,md-input-container label:not(.md-no-float):not(._md-container-ignore){overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:100%;-webkit-order:1;-ms-flex-order:1;order:1;pointer-events:none;-webkit-font-smoothing:antialiased;padding-left:3px;padding-right:0;z-index:1;-webkit-transform:translate3d(0,28px,0) scale(1);transform:translate3d(0,28px,0) scale(1);transition:-webkit-transform .4s cubic-bezier(.25,.8,.25,1);transition:transform .4s cubic-bezier(.25,.8,.25,1);max-width:100%;-webkit-transform-origin:left top;transform-origin:left top}[dir=rtl] md-input-container ._md-placeholder,[dir=rtl] md-input-container label:not(.md-no-float):not(._md-container-ignore){padding-left:0;padding-right:3px;-webkit-transform-origin:right top;transform-origin:right top}md-input-container ._md-placeholder{position:absolute;top:0;opacity:0;transition-property:opacity,-webkit-transform;transition-property:opacity,transform;-webkit-transform:translate3d(0,30px,0);transform:translate3d(0,30px,0)}md-input-container.md-input-focused ._md-placeholder{opacity:1;-webkit-transform:translate3d(0,24px,0);transform:translate3d(0,24px,0)}md-input-container.md-input-has-value ._md-placeholder{transition:none;opacity:0}md-input-container:not(.md-input-has-value) input:not(:focus),md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-ampm-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-day-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-hour-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-millisecond-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-minute-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-month-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-second-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-text,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-week-field,md-input-container:not(.md-input-has-value) input:not(:focus)::-webkit-datetime-edit-year-field{color:transparent}md-input-container .md-input{-webkit-order:2;-ms-flex-order:2;order:2;display:block;margin-top:0;background:0 0;padding:2px 2px 1px;border-width:0 0 1px;line-height:26px;height:30px;-ms-flex-preferred-size:26px;border-radius:0;border-style:solid;width:100%;box-sizing:border-box;float:left}[dir=rtl] md-input-container .md-input{float:right}md-input-container .md-input:focus{outline:0}md-input-container .md-input:invalid{outline:0;box-shadow:none}md-input-container .md-input.md-no-flex{-webkit-flex:none!important;-ms-flex:none!important;flex:none!important}md-input-container .md-char-counter{text-align:right;padding-right:2px;padding-left:0}[dir=rtl] md-input-container .md-char-counter{text-align:left;padding-right:0;padding-left:2px}md-input-container .md-input-messages-animation{position:relative;-webkit-order:4;-ms-flex-order:4;order:4;overflow:hidden;clear:left}[dir=rtl] md-input-container .md-input-messages-animation{clear:right}md-input-container .md-input-messages-animation.ng-enter .md-input-message-animation{opacity:0;margin-top:-100px}md-input-container .md-char-counter,md-input-container .md-input-message-animation{font-size:12px;line-height:14px;overflow:hidden;transition:all .3s cubic-bezier(.55,0,.55,.2);opacity:1;margin-top:0;padding-top:5px}md-input-container .md-char-counter:not(.md-char-counter),md-input-container .md-input-message-animation:not(.md-char-counter){padding-right:5px;padding-left:0}[dir=rtl] md-input-container .md-char-counter:not(.md-char-counter),[dir=rtl] md-input-container .md-input-message-animation:not(.md-char-counter){padding-right:0;padding-left:5px}md-input-container .md-auto-hide .md-input-message-animation:not(.ng-animate),md-input-container .md-input-message-animation.ng-enter,md-input-container:not(.md-input-invalid) .md-auto-hide .md-input-message-animation{opacity:0;margin-top:-100px}md-input-container.md-input-focused label:not(.md-no-float),md-input-container.md-input-has-placeholder label:not(.md-no-float),md-input-container.md-input-has-value label:not(.md-no-float){-webkit-transform:translate3d(0,6px,0) scale(.75);transform:translate3d(0,6px,0) scale(.75);transition:-webkit-transform cubic-bezier(.25,.8,.25,1) .4s,width cubic-bezier(.25,.8,.25,1) .4s;transition:transform cubic-bezier(.25,.8,.25,1) .4s,width cubic-bezier(.25,.8,.25,1) .4s}md-input-container.md-input-has-value label{transition:none}md-input-container .md-input.ng-invalid.ng-dirty,md-input-container.md-input-focused .md-input,md-input-container.md-input-resized .md-input{padding-bottom:0;border-width:0 0 2px}[disabled] md-input-container .md-input,md-input-container .md-input[disabled]{background-position:bottom -1px left 0;background-size:4px 1px;background-repeat:repeat-x}md-input-container.md-icon-float{transition:margin-top .4s cubic-bezier(.25,.8,.25,1)}md-input-container.md-icon-float>label{pointer-events:none;position:absolute}md-input-container.md-icon-float>md-icon{top:8px;left:2px;right:auto}[dir=rtl] md-input-container.md-icon-float>md-icon{left:auto;right:2px}md-input-container.md-icon-left>label .md-placeholder,md-input-container.md-icon-left>label:not(.md-no-float):not(._md-container-ignore),md-input-container.md-icon-right>label .md-placeholder,md-input-container.md-icon-right>label:not(.md-no-float):not(._md-container-ignore){width:calc(100% - 36px - 18px)}md-input-container.md-icon-left{padding-left:36px;padding-right:0}[dir=rtl] md-input-container.md-icon-left{padding-left:0;padding-right:36px}md-input-container.md-icon-left>label{left:36px;right:auto}[dir=rtl] md-input-container.md-icon-left>label{left:auto;right:36px}md-input-container.md-icon-right{padding-left:0;padding-right:36px}[dir=rtl] md-input-container.md-icon-right{padding-left:36px;padding-right:0}md-input-container.md-icon-right>md-icon:last-of-type{margin:0;right:2px;left:auto}[dir=rtl] md-input-container.md-icon-right>md-icon:last-of-type{right:auto;left:2px}md-input-container.md-icon-left.md-icon-right{padding-left:36px;padding-right:36px}md-input-container.md-icon-left.md-icon-right>label .md-placeholder,md-input-container.md-icon-left.md-icon-right>label:not(.md-no-float):not(._md-container-ignore){width:calc(100% - (36px * 2))}@media screen and (-ms-high-contrast:active){md-input-container.md-default-theme>md-icon{fill:#fff}}md-list{display:block;padding:8px 0}md-list .md-subheader{font-size:14px;font-weight:500;letter-spacing:.010em;line-height:1.2em}md-list.md-dense md-list-item,md-list.md-dense md-list-item ._md-list-item-inner{min-height:48px}md-list.md-dense md-list-item ._md-list-item-inner md-icon:first-child,md-list.md-dense md-list-item md-icon:first-child{width:20px;height:20px}md-list.md-dense md-list-item ._md-list-item-inner>md-icon:first-child:not(.md-avatar-icon),md-list.md-dense md-list-item>md-icon:first-child:not(.md-avatar-icon){margin-right:36px}[dir=rtl] md-list.md-dense md-list-item ._md-list-item-inner>md-icon:first-child:not(.md-avatar-icon),[dir=rtl] md-list.md-dense md-list-item>md-icon:first-child:not(.md-avatar-icon){margin-right:auto;margin-right:initial;margin-left:36px}md-list.md-dense md-list-item ._md-list-item-inner .md-avatar,md-list.md-dense md-list-item ._md-list-item-inner .md-avatar-icon,md-list.md-dense md-list-item .md-avatar,md-list.md-dense md-list-item .md-avatar-icon{margin-right:20px}[dir=rtl] md-list.md-dense md-list-item ._md-list-item-inner .md-avatar,[dir=rtl] md-list.md-dense md-list-item ._md-list-item-inner .md-avatar-icon,[dir=rtl] md-list.md-dense md-list-item .md-avatar,[dir=rtl] md-list.md-dense md-list-item .md-avatar-icon{margin-right:auto;margin-right:initial;margin-left:20px}md-list.md-dense md-list-item ._md-list-item-inner .md-avatar,md-list.md-dense md-list-item .md-avatar{-webkit-flex:none;-ms-flex:none;flex:none;width:36px;height:36px}md-list.md-dense md-list-item.md-2-line .md-list-item-text.md-offset,md-list.md-dense md-list-item.md-2-line>._md-no-style .md-list-item-text.md-offset,md-list.md-dense md-list-item.md-3-line .md-list-item-text.md-offset,md-list.md-dense md-list-item.md-3-line>._md-no-style .md-list-item-text.md-offset{margin-left:56px}[dir=rtl] md-list.md-dense md-list-item.md-2-line .md-list-item-text.md-offset,[dir=rtl] md-list.md-dense md-list-item.md-2-line>._md-no-style .md-list-item-text.md-offset,[dir=rtl] md-list.md-dense md-list-item.md-3-line .md-list-item-text.md-offset,[dir=rtl] md-list.md-dense md-list-item.md-3-line>._md-no-style .md-list-item-text.md-offset{margin-left:auto;margin-left:initial;margin-right:56px}md-list.md-dense md-list-item.md-2-line .md-list-item-text h3,md-list.md-dense md-list-item.md-2-line .md-list-item-text h4,md-list.md-dense md-list-item.md-2-line .md-list-item-text p,md-list.md-dense md-list-item.md-2-line>._md-no-style .md-list-item-text h3,md-list.md-dense md-list-item.md-2-line>._md-no-style .md-list-item-text h4,md-list.md-dense md-list-item.md-2-line>._md-no-style .md-list-item-text p,md-list.md-dense md-list-item.md-3-line .md-list-item-text h3,md-list.md-dense md-list-item.md-3-line .md-list-item-text h4,md-list.md-dense md-list-item.md-3-line .md-list-item-text p,md-list.md-dense md-list-item.md-3-line>._md-no-style .md-list-item-text h3,md-list.md-dense md-list-item.md-3-line>._md-no-style .md-list-item-text h4,md-list.md-dense md-list-item.md-3-line>._md-no-style .md-list-item-text p{line-height:1.05;font-size:12px}md-list.md-dense md-list-item.md-2-line .md-list-item-text h3,md-list.md-dense md-list-item.md-2-line>._md-no-style .md-list-item-text h3,md-list.md-dense md-list-item.md-3-line .md-list-item-text h3,md-list.md-dense md-list-item.md-3-line>._md-no-style .md-list-item-text h3{font-size:13px}md-list.md-dense md-list-item.md-2-line,md-list.md-dense md-list-item.md-2-line>._md-no-style{min-height:60px}md-list.md-dense md-list-item.md-2-line div.md-button:first-child::before,md-list.md-dense md-list-item.md-2-line>._md-no-style div.md-button:first-child::before{content:'';min-height:60px;visibility:hidden;display:inline-block}md-list.md-dense md-list-item.md-2-line .md-avatar-icon,md-list.md-dense md-list-item.md-2-line>._md-no-style .md-avatar-icon,md-list.md-dense md-list-item.md-2-line>._md-no-style>.md-avatar,md-list.md-dense md-list-item.md-2-line>.md-avatar{margin-top:12px}md-list.md-dense md-list-item.md-3-line,md-list.md-dense md-list-item.md-3-line>._md-no-style{min-height:76px}md-list.md-dense md-list-item.md-3-line div.md-button:first-child::before,md-list.md-dense md-list-item.md-3-line>._md-no-style div.md-button:first-child::before{content:'';min-height:76px;visibility:hidden;display:inline-block}md-list.md-dense md-list-item.md-3-line>._md-no-style>.md-avatar,md-list.md-dense md-list-item.md-3-line>._md-no-style>md-icon:first-child,md-list.md-dense md-list-item.md-3-line>.md-avatar,md-list.md-dense md-list-item.md-3-line>md-icon:first-child{margin-top:16px}md-list-item{position:relative}md-list-item._md-proxy-focus.md-focused ._md-no-style{transition:background-color .15s linear}md-list-item._md-button-wrap{position:relative}md-list-item._md-button-wrap>div.md-button:first-child{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;padding:0 16px;margin:0;background-color:initial;font-weight:400;text-align:left;border:none}[dir=rtl] md-list-item._md-button-wrap>div.md-button:first-child{text-align:right}md-list-item._md-button-wrap>div.md-button:first-child>.md-button:first-child{position:absolute;top:0;left:0;height:100%;margin:0;padding:0}md-list-item._md-button-wrap>div.md-button:first-child ._md-list-item-inner{width:100%;height:100%}md-list-item ._md-no-style,md-list-item._md-no-proxy{position:relative;padding:0 16px;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto}md-list-item ._md-no-style.md-button,md-list-item._md-no-proxy.md-button{font-size:inherit;height:inherit;text-align:left;text-transform:none;width:100%;white-space:normal;-webkit-flex-direction:inherit;-ms-flex-direction:inherit;flex-direction:inherit;-webkit-align-items:inherit;-ms-flex-align:inherit;align-items:inherit;border-radius:0;margin:0}[dir=rtl] md-list-item ._md-no-style.md-button,[dir=rtl] md-list-item._md-no-proxy.md-button{text-align:right}md-list-item ._md-no-style.md-button>.md-ripple-container,md-list-item._md-no-proxy.md-button>.md-ripple-container{border-radius:0}md-list-item ._md-no-style:focus,md-list-item._md-no-proxy:focus{outline:0}md-list-item.md-clickable:hover{cursor:pointer}md-list-item md-divider{position:absolute;bottom:0;left:0;width:100%}[dir=rtl] md-list-item md-divider{left:0;left:auto;left:initial;right:0}md-list-item md-divider[md-inset]{left:72px;width:calc(100% - 72px);margin:0!important}[dir=rtl] md-list-item md-divider[md-inset]{left:0;left:auto;left:initial;right:72px}md-list-item,md-list-item ._md-list-item-inner{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-items:center;-ms-flex-align:center;align-items:center;min-height:48px;height:auto}md-list-item ._md-list-item-inner>div.md-primary>md-icon:not(.md-avatar-icon),md-list-item ._md-list-item-inner>div.md-secondary>md-icon:not(.md-avatar-icon),md-list-item ._md-list-item-inner>md-icon.md-secondary:not(.md-avatar-icon),md-list-item ._md-list-item-inner>md-icon:first-child:not(.md-avatar-icon),md-list-item>div.md-primary>md-icon:not(.md-avatar-icon),md-list-item>div.md-secondary>md-icon:not(.md-avatar-icon),md-list-item>md-icon.md-secondary:not(.md-avatar-icon),md-list-item>md-icon:first-child:not(.md-avatar-icon){width:24px;margin-top:16px;margin-bottom:12px;box-sizing:content-box}md-list-item ._md-list-item-inner md-checkbox.md-secondary,md-list-item ._md-list-item-inner>div.md-primary>md-checkbox,md-list-item ._md-list-item-inner>div.md-secondary>md-checkbox,md-list-item ._md-list-item-inner>md-checkbox,md-list-item md-checkbox.md-secondary,md-list-item>div.md-primary>md-checkbox,md-list-item>div.md-secondary>md-checkbox,md-list-item>md-checkbox{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}md-list-item ._md-list-item-inner md-checkbox.md-secondary .md-label,md-list-item ._md-list-item-inner>div.md-primary>md-checkbox .md-label,md-list-item ._md-list-item-inner>div.md-secondary>md-checkbox .md-label,md-list-item ._md-list-item-inner>md-checkbox .md-label,md-list-item md-checkbox.md-secondary .md-label,md-list-item>div.md-primary>md-checkbox .md-label,md-list-item>div.md-secondary>md-checkbox .md-label,md-list-item>md-checkbox .md-label{display:none}md-list-item ._md-list-item-inner>md-icon:first-child:not(.md-avatar-icon),md-list-item>md-icon:first-child:not(.md-avatar-icon){margin-right:32px}[dir=rtl] md-list-item ._md-list-item-inner>md-icon:first-child:not(.md-avatar-icon),[dir=rtl] md-list-item>md-icon:first-child:not(.md-avatar-icon){margin-right:auto;margin-right:initial;margin-left:32px}md-list-item ._md-list-item-inner .md-avatar,md-list-item ._md-list-item-inner .md-avatar-icon,md-list-item .md-avatar,md-list-item .md-avatar-icon{margin-top:8px;margin-bottom:8px;margin-right:16px;border-radius:50%;box-sizing:content-box}[dir=rtl] md-list-item ._md-list-item-inner .md-avatar,[dir=rtl] md-list-item ._md-list-item-inner .md-avatar-icon,[dir=rtl] md-list-item .md-avatar,[dir=rtl] md-list-item .md-avatar-icon{margin-right:auto;margin-right:initial;margin-left:16px}md-list-item ._md-list-item-inner .md-avatar,md-list-item .md-avatar{-webkit-flex:none;-ms-flex:none;flex:none;width:40px;height:40px}md-list-item ._md-list-item-inner .md-avatar-icon,md-list-item .md-avatar-icon{padding:8px}md-list-item ._md-list-item-inner .md-avatar-icon svg,md-list-item .md-avatar-icon svg{width:24px;height:24px}md-list-item ._md-list-item-inner>md-checkbox,md-list-item>md-checkbox{width:24px;margin-left:3px;margin-right:29px;margin-top:16px}[dir=rtl] md-list-item ._md-list-item-inner>md-checkbox,[dir=rtl] md-list-item>md-checkbox{margin-left:29px;margin-right:3px}md-list-item ._md-list-item-inner ._md-secondary-container,md-list-item ._md-secondary-container{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;margin:auto 0 auto auto}[dir=rtl] md-list-item ._md-list-item-inner ._md-secondary-container,[dir=rtl] md-list-item ._md-secondary-container{margin-right:auto;margin-left:0}md-list-item ._md-list-item-inner ._md-secondary-container .md-button:last-of-type,md-list-item ._md-list-item-inner ._md-secondary-container .md-icon-button:last-of-type,md-list-item ._md-secondary-container .md-button:last-of-type,md-list-item ._md-secondary-container .md-icon-button:last-of-type{margin-right:0}[dir=rtl] md-list-item ._md-list-item-inner ._md-secondary-container .md-button:last-of-type,[dir=rtl] md-list-item ._md-list-item-inner ._md-secondary-container .md-icon-button:last-of-type,[dir=rtl] md-list-item ._md-secondary-container .md-button:last-of-type,[dir=rtl] md-list-item ._md-secondary-container .md-icon-button:last-of-type{margin-right:auto;margin-right:initial;margin-left:0}md-list-item ._md-list-item-inner ._md-secondary-container md-checkbox,md-list-item ._md-secondary-container md-checkbox{margin-top:0;margin-bottom:0}md-list-item ._md-list-item-inner ._md-secondary-container md-checkbox:last-child,md-list-item ._md-secondary-container md-checkbox:last-child{width:24px;margin-right:0}[dir=rtl] md-list-item ._md-list-item-inner ._md-secondary-container md-checkbox:last-child,[dir=rtl] md-list-item ._md-secondary-container md-checkbox:last-child{margin-right:auto;margin-right:initial;margin-left:0}md-list-item ._md-list-item-inner ._md-secondary-container md-switch,md-list-item ._md-secondary-container md-switch{margin-top:0;margin-bottom:0;margin-right:-6px}[dir=rtl] md-list-item ._md-list-item-inner ._md-secondary-container md-switch,[dir=rtl] md-list-item ._md-secondary-container md-switch{margin-right:auto;margin-right:initial;margin-left:-6px}md-list-item ._md-list-item-inner>._md-list-item-inner>p,md-list-item ._md-list-item-inner>p,md-list-item>._md-list-item-inner>p,md-list-item>p{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;margin:0}md-list-item.md-2-line,md-list-item.md-2-line>._md-no-style,md-list-item.md-3-line,md-list-item.md-3-line>._md-no-style{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}md-list-item.md-2-line.md-long-text,md-list-item.md-2-line>._md-no-style.md-long-text,md-list-item.md-3-line.md-long-text,md-list-item.md-3-line>._md-no-style.md-long-text{margin-top:8px;margin-bottom:8px}md-list-item.md-2-line .md-list-item-text,md-list-item.md-2-line>._md-no-style .md-list-item-text,md-list-item.md-3-line .md-list-item-text,md-list-item.md-3-line>._md-no-style .md-list-item-text{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;margin:auto;text-overflow:ellipsis;overflow:hidden}md-list-item.md-2-line .md-list-item-text.md-offset,md-list-item.md-2-line>._md-no-style .md-list-item-text.md-offset,md-list-item.md-3-line .md-list-item-text.md-offset,md-list-item.md-3-line>._md-no-style .md-list-item-text.md-offset{margin-left:56px}[dir=rtl] md-list-item.md-2-line .md-list-item-text.md-offset,[dir=rtl] md-list-item.md-2-line>._md-no-style .md-list-item-text.md-offset,[dir=rtl] md-list-item.md-3-line .md-list-item-text.md-offset,[dir=rtl] md-list-item.md-3-line>._md-no-style .md-list-item-text.md-offset{margin-left:auto;margin-left:initial;margin-right:56px}md-list-item.md-2-line .md-list-item-text h3,md-list-item.md-2-line>._md-no-style .md-list-item-text h3,md-list-item.md-3-line .md-list-item-text h3,md-list-item.md-3-line>._md-no-style .md-list-item-text h3{font-size:16px;font-weight:400;letter-spacing:.010em;margin:0;line-height:1.2em;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}md-list-item.md-2-line .md-list-item-text h4,md-list-item.md-2-line>._md-no-style .md-list-item-text h4,md-list-item.md-3-line .md-list-item-text h4,md-list-item.md-3-line>._md-no-style .md-list-item-text h4{font-size:14px;letter-spacing:.010em;margin:3px 0 1px;font-weight:400;line-height:1.2em;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}md-list-item.md-2-line .md-list-item-text p,md-list-item.md-2-line>._md-no-style .md-list-item-text p,md-list-item.md-3-line .md-list-item-text p,md-list-item.md-3-line>._md-no-style .md-list-item-text p{font-size:14px;font-weight:500;letter-spacing:.010em;margin:0;line-height:1.6em}md-list-item.md-2-line,md-list-item.md-2-line>._md-no-style{height:auto;min-height:72px}md-list-item.md-2-line div.md-button:first-child::before,md-list-item.md-2-line>._md-no-style div.md-button:first-child::before{content:'';min-height:72px;visibility:hidden;display:inline-block}md-list-item.md-2-line .md-avatar-icon,md-list-item.md-2-line>._md-no-style .md-avatar-icon,md-list-item.md-2-line>._md-no-style>.md-avatar,md-list-item.md-2-line>.md-avatar{margin-top:12px}md-list-item.md-2-line>._md-no-style>md-icon:first-child,md-list-item.md-2-line>md-icon:first-child{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}md-list-item.md-2-line .md-list-item-text,md-list-item.md-2-line>._md-no-style .md-list-item-text{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto}md-list-item.md-3-line,md-list-item.md-3-line>._md-no-style{height:auto;min-height:88px}md-list-item.md-3-line div.md-button:first-child::before,md-list-item.md-3-line>._md-no-style div.md-button:first-child::before{content:'';min-height:88px;visibility:hidden;display:inline-block}md-list-item.md-3-line>._md-no-style>.md-avatar,md-list-item.md-3-line>._md-no-style>md-icon:first-child,md-list-item.md-3-line>.md-avatar,md-list-item.md-3-line>md-icon:first-child{margin-top:16px}._md-open-menu-container{position:fixed;left:0;top:0;z-index:100;opacity:0;border-radius:2px}._md-open-menu-container md-menu-divider{margin-top:4px;margin-bottom:4px;height:1px;min-height:1px;max-height:1px;width:100%}._md-open-menu-container md-menu-content>*{opacity:0}._md-open-menu-container:not(._md-clickable){pointer-events:none}._md-open-menu-container._md-active{opacity:1;transition:all .4s cubic-bezier(.25,.8,.25,1);transition-duration:200ms}._md-open-menu-container._md-active>md-menu-content>*{opacity:1;transition:all .3s cubic-bezier(.55,0,.55,.2);transition-duration:200ms;transition-delay:100ms}._md-open-menu-container._md-leave{opacity:0;transition:all .3s cubic-bezier(.55,0,.55,.2);transition-duration:250ms}md-menu-content{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:8px 0;max-height:304px;overflow-y:auto}md-menu-content.md-dense{max-height:208px}md-menu-content.md-dense md-menu-item{height:32px;min-height:0}md-menu-item{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;min-height:48px;height:48px;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}md-menu-item>*{width:100%;margin:auto 0;padding-left:16px;padding-right:16px}md-menu-item>.md-button{border-radius:0;margin:auto 0;font-size:15px;text-transform:none;font-weight:400;height:100%;padding-left:16px;padding-right:16px;text-align:left;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:baseline;-ms-flex-align:baseline;align-items:baseline;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;width:100%}[dir=rtl] md-menu-item>.md-button{text-align:right}md-menu-item>.md-button md-icon{margin:auto 16px auto 0}[dir=rtl] md-menu-item>.md-button md-icon{margin:auto 0 auto 16px}md-menu-item>.md-button p{display:inline-block;margin:auto}md-menu-item>.md-button span{margin-top:auto;margin-bottom:auto}md-menu-item>.md-button .md-ripple-container{border-radius:inherit}.md-menu{padding:8px 0}md-toolbar .md-menu{height:auto;margin:auto;padding:0}@media (max-width:959px){md-menu-content{min-width:112px}md-menu-content[width="3"]{min-width:168px}md-menu-content[width="4"]{min-width:224px}md-menu-content[width="5"]{min-width:280px}md-menu-content[width="6"]{min-width:336px}md-menu-content[width="7"]{min-width:392px}}@media (min-width:960px){md-menu-content{min-width:96px}md-menu-content[width="3"]{min-width:192px}md-menu-content[width="4"]{min-width:256px}md-menu-content[width="5"]{min-width:320px}md-menu-content[width="6"]{min-width:384px}md-menu-content[width="7"]{min-width:448px}}md-toolbar.md-menu-toolbar h2.md-toolbar-tools{line-height:1rem;height:auto;padding:28px 28px 12px}md-menu-bar{padding:0 20px;display:block;position:relative;z-index:2}md-menu-bar .md-menu{display:inline-block;padding:0;position:relative}md-menu-bar button{font-size:14px;padding:0 10px;margin:0;border:0;background-color:transparent;height:40px}md-menu-bar md-backdrop._md-menu-backdrop{z-index:-2}md-menu-content._md-menu-bar-menu.md-dense{max-height:none;padding:16px 0}md-menu-content._md-menu-bar-menu.md-dense md-menu-item.md-indent{position:relative}md-menu-content._md-menu-bar-menu.md-dense md-menu-item.md-indent>md-icon{position:absolute;padding:0;width:24px;top:6px;left:24px}[dir=rtl] md-menu-content._md-menu-bar-menu.md-dense md-menu-item.md-indent>md-icon{left:0;left:auto;left:initial;right:24px}md-menu-content._md-menu-bar-menu.md-dense md-menu-item.md-indent .md-menu>.md-button,md-menu-content._md-menu-bar-menu.md-dense md-menu-item.md-indent>.md-button{padding:0 32px 0 64px}[dir=rtl] md-menu-content._md-menu-bar-menu.md-dense md-menu-item.md-indent .md-menu>.md-button,[dir=rtl] md-menu-content._md-menu-bar-menu.md-dense md-menu-item.md-indent>.md-button{padding:0 64px 0 32px}md-menu-content._md-menu-bar-menu.md-dense .md-button{min-height:0;height:32px;display:-webkit-flex;display:-ms-flexbox;display:flex}md-menu-content._md-menu-bar-menu.md-dense .md-button span{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}md-menu-content._md-menu-bar-menu.md-dense .md-button span.md-alt-text{-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end;margin:0 8px}md-menu-content._md-menu-bar-menu.md-dense md-menu-divider{margin:8px 0}md-menu-content._md-menu-bar-menu.md-dense .md-menu>.md-button,md-menu-content._md-menu-bar-menu.md-dense md-menu-item>.md-button{text-align:left;text-align:start}[dir=rtl] md-menu-content._md-menu-bar-menu.md-dense .md-menu>.md-button,[dir=rtl] md-menu-content._md-menu-bar-menu.md-dense md-menu-item>.md-button{text-align:right}md-menu-content._md-menu-bar-menu.md-dense .md-menu{padding:0}md-menu-content._md-menu-bar-menu.md-dense .md-menu>.md-button{position:relative;margin:0;width:100%;text-transform:none;font-weight:400;border-radius:0;padding-left:16px}[dir=rtl] md-menu-content._md-menu-bar-menu.md-dense .md-menu>.md-button{padding-left:auto;padding-left:initial;padding-right:16px}md-menu-content._md-menu-bar-menu.md-dense .md-menu>.md-button:after{display:block;content:'\25BC';position:absolute;top:0;speak:none;-webkit-transform:rotate(270deg) scaleY(.45) scaleX(.9);transform:rotate(270deg) scaleY(.45) scaleX(.9);right:28px}[dir=rtl] md-menu-content._md-menu-bar-menu.md-dense .md-menu>.md-button:after{-webkit-transform:rotate(90deg) scaleY(.45) scaleX(.9);transform:rotate(90deg) scaleY(.45) scaleX(.9);right:0;right:auto;right:initial;left:28px}.md-nav-bar{border-style:solid;border-width:0 0 1px;height:48px;position:relative}._md-nav-bar-list{outline:0;list-style:none;margin:0;padding:0}.md-nav-item:first-of-type{margin-left:8px}.md-button._md-nav-button{line-height:24px;margin:0 4px;padding:12px 16px;transition:background-color .35s cubic-bezier(.35,0,.25,1)}.md-button._md-nav-button:focus{outline:0}.md-button._md-nav-button:hover{background-color:inherit}md-nav-ink-bar{bottom:0;height:2px;left:auto;position:absolute;right:auto;background-color:#000}md-nav-ink-bar._md-left{transition:left .125s cubic-bezier(.35,0,.25,1),right .25s cubic-bezier(.35,0,.25,1)}md-nav-ink-bar._md-right{transition:left .25s cubic-bezier(.35,0,.25,1),right .125s cubic-bezier(.35,0,.25,1)}md-nav-extra-content{min-height:48px;padding-right:12px}.md-panel-outer-wrapper{height:100%;left:0;position:absolute;top:0;width:100%}._md-panel-hidden{display:none}._md-panel-fullscreen{border-radius:0;left:0;min-height:100%;min-width:100%;position:fixed;top:0}._md-panel-shown .md-panel{opacity:1;transition:none}.md-panel{opacity:0;position:fixed}.md-panel._md-panel-shown{opacity:1;transition:none}.md-panel._md-panel-animate-enter{opacity:1;transition:all .3s cubic-bezier(0,0,.2,1)}.md-panel._md-panel-animate-leave{opacity:1;transition:all .3s cubic-bezier(.4,0,1,1)}.md-panel._md-panel-animate-fade-out,.md-panel._md-panel-animate-scale-out{opacity:0}.md-panel._md-panel-backdrop{height:100%;position:absolute;width:100%}.md-panel._md-opaque-enter{opacity:.48;transition:opacity .3s cubic-bezier(0,0,.2,1)}.md-panel._md-opaque-leave{transition:opacity .3s cubic-bezier(.4,0,1,1)}md-progress-linear{display:block;position:relative;width:100%;height:5px;padding-top:0!important;margin-bottom:0!important}md-progress-linear._md-progress-linear-disabled{visibility:hidden}md-progress-linear ._md-container{display:block;position:relative;overflow:hidden;width:100%;height:5px;-webkit-transform:translate(0,0) scale(1,1);transform:translate(0,0) scale(1,1)}md-progress-linear ._md-container ._md-bar{position:absolute;left:0;top:0;bottom:0;width:100%;height:5px}md-progress-linear ._md-container ._md-dashed:before{content:"";display:none;position:absolute;margin-top:0;height:5px;width:100%;background-color:transparent;background-size:10px 10px!important;background-position:0 -23px}md-progress-linear ._md-container ._md-bar1,md-progress-linear ._md-container ._md-bar2{transition:-webkit-transform .2s linear;transition:transform .2s linear}md-progress-linear ._md-container._md-mode-query ._md-bar1{display:none}md-progress-linear ._md-container._md-mode-query ._md-bar2{transition:all .2s linear;-webkit-animation:query .8s infinite cubic-bezier(.39,.575,.565,1);animation:query .8s infinite cubic-bezier(.39,.575,.565,1)}md-progress-linear ._md-container._md-mode-determinate ._md-bar1{display:none}md-progress-linear ._md-container._md-mode-indeterminate ._md-bar1{-webkit-animation:md-progress-linear-indeterminate-scale-1 4s infinite,md-progress-linear-indeterminate-1 4s infinite;animation:md-progress-linear-indeterminate-scale-1 4s infinite,md-progress-linear-indeterminate-1 4s infinite}md-progress-linear ._md-container._md-mode-indeterminate ._md-bar2{-webkit-animation:md-progress-linear-indeterminate-scale-2 4s infinite,md-progress-linear-indeterminate-2 4s infinite;animation:md-progress-linear-indeterminate-scale-2 4s infinite,md-progress-linear-indeterminate-2 4s infinite}md-progress-linear ._md-container.ng-hide ._md-progress-linear-disabled md-progress-linear ._md-container{-webkit-animation:none;animation:none}md-progress-linear ._md-container.ng-hide ._md-progress-linear-disabled md-progress-linear ._md-container ._md-bar1,md-progress-linear ._md-container.ng-hide ._md-progress-linear-disabled md-progress-linear ._md-container ._md-bar2{-webkit-animation-name:none;animation-name:none}md-progress-linear ._md-container._md-mode-buffer{background-color:transparent!important;transition:all .2s linear}md-progress-linear ._md-container._md-mode-buffer ._md-dashed:before{display:block;-webkit-animation:buffer 3s infinite linear;animation:buffer 3s infinite linear}@-webkit-keyframes query{0%{opacity:1;-webkit-transform:translateX(35%) scale(.3,1);transform:translateX(35%) scale(.3,1)}100%{opacity:0;-webkit-transform:translateX(-50%) scale(0,1);transform:translateX(-50%) scale(0,1)}}@keyframes query{0%{opacity:1;-webkit-transform:translateX(35%) scale(.3,1);transform:translateX(35%) scale(.3,1)}100%{opacity:0;-webkit-transform:translateX(-50%) scale(0,1);transform:translateX(-50%) scale(0,1)}}@-webkit-keyframes buffer{0%{opacity:1;background-position:0 -23px}50%{opacity:0}100%{opacity:1;background-position:-200px -23px}}@keyframes buffer{0%{opacity:1;background-position:0 -23px}50%{opacity:0}100%{opacity:1;background-position:-200px -23px}}@-webkit-keyframes md-progress-linear-indeterminate-scale-1{0%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:linear;animation-timing-function:linear}36.6%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:cubic-bezier(.33473,.12482,.78584,1);animation-timing-function:cubic-bezier(.33473,.12482,.78584,1)}69.15%{-webkit-transform:scaleX(.83);transform:scaleX(.83);-webkit-animation-timing-function:cubic-bezier(.22573,0,.23365,1.37098);animation-timing-function:cubic-bezier(.22573,0,.23365,1.37098)}100%{-webkit-transform:scaleX(.1);transform:scaleX(.1)}}@keyframes md-progress-linear-indeterminate-scale-1{0%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:linear;animation-timing-function:linear}36.6%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:cubic-bezier(.33473,.12482,.78584,1);animation-timing-function:cubic-bezier(.33473,.12482,.78584,1)}69.15%{-webkit-transform:scaleX(.83);transform:scaleX(.83);-webkit-animation-timing-function:cubic-bezier(.22573,0,.23365,1.37098);animation-timing-function:cubic-bezier(.22573,0,.23365,1.37098)}100%{-webkit-transform:scaleX(.1);transform:scaleX(.1)}}@-webkit-keyframes md-progress-linear-indeterminate-1{0%{left:-105.16667%;-webkit-animation-timing-function:linear;animation-timing-function:linear}20%{left:-105.16667%;-webkit-animation-timing-function:cubic-bezier(.5,0,.70173,.49582);animation-timing-function:cubic-bezier(.5,0,.70173,.49582)}69.15%{left:21.5%;-webkit-animation-timing-function:cubic-bezier(.30244,.38135,.55,.95635);animation-timing-function:cubic-bezier(.30244,.38135,.55,.95635)}100%{left:95.44444%}}@keyframes md-progress-linear-indeterminate-1{0%{left:-105.16667%;-webkit-animation-timing-function:linear;animation-timing-function:linear}20%{left:-105.16667%;-webkit-animation-timing-function:cubic-bezier(.5,0,.70173,.49582);animation-timing-function:cubic-bezier(.5,0,.70173,.49582)}69.15%{left:21.5%;-webkit-animation-timing-function:cubic-bezier(.30244,.38135,.55,.95635);animation-timing-function:cubic-bezier(.30244,.38135,.55,.95635)}100%{left:95.44444%}}@-webkit-keyframes md-progress-linear-indeterminate-scale-2{0%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:cubic-bezier(.20503,.05705,.57661,.45397);animation-timing-function:cubic-bezier(.20503,.05705,.57661,.45397)}19.15%{-webkit-transform:scaleX(.57);transform:scaleX(.57);-webkit-animation-timing-function:cubic-bezier(.15231,.19643,.64837,1.00432);animation-timing-function:cubic-bezier(.15231,.19643,.64837,1.00432)}44.15%{-webkit-transform:scaleX(.91);transform:scaleX(.91);-webkit-animation-timing-function:cubic-bezier(.25776,-.00316,.21176,1.38179);animation-timing-function:cubic-bezier(.25776,-.00316,.21176,1.38179)}100%{-webkit-transform:scaleX(.1);transform:scaleX(.1)}}@keyframes md-progress-linear-indeterminate-scale-2{0%{-webkit-transform:scaleX(.1);transform:scaleX(.1);-webkit-animation-timing-function:cubic-bezier(.20503,.05705,.57661,.45397);animation-timing-function:cubic-bezier(.20503,.05705,.57661,.45397)}19.15%{-webkit-transform:scaleX(.57);transform:scaleX(.57);-webkit-animation-timing-function:cubic-bezier(.15231,.19643,.64837,1.00432);animation-timing-function:cubic-bezier(.15231,.19643,.64837,1.00432)}44.15%{-webkit-transform:scaleX(.91);transform:scaleX(.91);-webkit-animation-timing-function:cubic-bezier(.25776,-.00316,.21176,1.38179);animation-timing-function:cubic-bezier(.25776,-.00316,.21176,1.38179)}100%{-webkit-transform:scaleX(.1);transform:scaleX(.1)}}@-webkit-keyframes md-progress-linear-indeterminate-2{0%{left:-54.88889%;-webkit-animation-timing-function:cubic-bezier(.15,0,.51506,.40968);animation-timing-function:cubic-bezier(.15,0,.51506,.40968)}25%{left:-17.25%;-webkit-animation-timing-function:cubic-bezier(.31033,.28406,.8,.73372);animation-timing-function:cubic-bezier(.31033,.28406,.8,.73372)}48.35%{left:29.5%;-webkit-animation-timing-function:cubic-bezier(.4,.62703,.6,.90203);animation-timing-function:cubic-bezier(.4,.62703,.6,.90203)}100%{left:117.38889%}}@keyframes md-progress-linear-indeterminate-2{0%{left:-54.88889%;-webkit-animation-timing-function:cubic-bezier(.15,0,.51506,.40968);animation-timing-function:cubic-bezier(.15,0,.51506,.40968)}25%{left:-17.25%;-webkit-animation-timing-function:cubic-bezier(.31033,.28406,.8,.73372);animation-timing-function:cubic-bezier(.31033,.28406,.8,.73372)}48.35%{left:29.5%;-webkit-animation-timing-function:cubic-bezier(.4,.62703,.6,.90203);animation-timing-function:cubic-bezier(.4,.62703,.6,.90203)}100%{left:117.38889%}}@-webkit-keyframes indeterminate-rotate{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes indeterminate-rotate{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}md-progress-circular{position:relative}md-progress-circular._md-progress-circular-disabled{visibility:hidden}md-progress-circular._md-mode-indeterminate svg{-webkit-animation:indeterminate-rotate 2.9s linear infinite;animation:indeterminate-rotate 2.9s linear infinite}md-progress-circular svg{position:absolute;overflow:visible;top:0;left:0}md-radio-button{box-sizing:border-box;display:block;margin-bottom:16px;white-space:nowrap;cursor:pointer;position:relative}md-radio-button[disabled],md-radio-button[disabled] ._md-container{cursor:default}md-radio-button ._md-container{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);box-sizing:border-box;display:inline-block;width:20px;height:20px;cursor:pointer;left:0;right:auto}[dir=rtl] md-radio-button ._md-container{left:auto;right:0}md-radio-button ._md-container .md-ripple-container{position:absolute;display:block;width:auto;height:auto;left:-15px;top:-15px;right:-15px;bottom:-15px}md-radio-button ._md-container:before{box-sizing:border-box;background-color:transparent;border-radius:50%;content:'';position:absolute;display:block;height:auto;left:0;top:0;right:0;bottom:0;transition:all .5s;width:auto}md-radio-button.md-align-top-left>div._md-container{top:12px}md-radio-button ._md-off{box-sizing:border-box;position:absolute;top:0;left:0;width:20px;height:20px;border-style:solid;border-width:2px;border-radius:50%;transition:border-color ease .28s}md-radio-button ._md-on{box-sizing:border-box;position:absolute;top:0;left:0;width:20px;height:20px;border-radius:50%;transition:-webkit-transform ease .28s;transition:transform ease .28s;-webkit-transform:scale(0);transform:scale(0)}md-radio-button.md-checked ._md-on{-webkit-transform:scale(.5);transform:scale(.5)}md-radio-button ._md-label{box-sizing:border-box;position:relative;display:inline-block;margin-left:30px;margin-right:0;vertical-align:middle;white-space:normal;pointer-events:none;width:auto}[dir=rtl] md-radio-button ._md-label{margin-left:0;margin-right:30px}md-radio-group.layout-column md-radio-button,md-radio-group.layout-gt-lg-column md-radio-button,md-radio-group.layout-gt-md-column md-radio-button,md-radio-group.layout-gt-sm-column md-radio-button,md-radio-group.layout-gt-xs-column md-radio-button,md-radio-group.layout-lg-column md-radio-button,md-radio-group.layout-md-column md-radio-button,md-radio-group.layout-sm-column md-radio-button,md-radio-group.layout-xl-column md-radio-button,md-radio-group.layout-xs-column md-radio-button{margin-bottom:16px}md-radio-group.layout-gt-lg-row md-radio-button,md-radio-group.layout-gt-md-row md-radio-button,md-radio-group.layout-gt-sm-row md-radio-button,md-radio-group.layout-gt-xs-row md-radio-button,md-radio-group.layout-lg-row md-radio-button,md-radio-group.layout-md-row md-radio-button,md-radio-group.layout-row md-radio-button,md-radio-group.layout-sm-row md-radio-button,md-radio-group.layout-xl-row md-radio-button,md-radio-group.layout-xs-row md-radio-button{margin:0 16px 0 0}[dir=rtl] md-radio-group.layout-gt-lg-row md-radio-button,[dir=rtl] md-radio-group.layout-gt-md-row md-radio-button,[dir=rtl] md-radio-group.layout-gt-sm-row md-radio-button,[dir=rtl] md-radio-group.layout-gt-xs-row md-radio-button,[dir=rtl] md-radio-group.layout-lg-row md-radio-button,[dir=rtl] md-radio-group.layout-md-row md-radio-button,[dir=rtl] md-radio-group.layout-row md-radio-button,[dir=rtl] md-radio-group.layout-sm-row md-radio-button,[dir=rtl] md-radio-group.layout-xl-row md-radio-button,[dir=rtl] md-radio-group.layout-xs-row md-radio-button{margin-left:16px;margin-right:0}md-radio-group.layout-gt-lg-row md-radio-button:last-of-type,md-radio-group.layout-gt-md-row md-radio-button:last-of-type,md-radio-group.layout-gt-sm-row md-radio-button:last-of-type,md-radio-group.layout-gt-xs-row md-radio-button:last-of-type,md-radio-group.layout-lg-row md-radio-button:last-of-type,md-radio-group.layout-md-row md-radio-button:last-of-type,md-radio-group.layout-row md-radio-button:last-of-type,md-radio-group.layout-sm-row md-radio-button:last-of-type,md-radio-group.layout-xl-row md-radio-button:last-of-type,md-radio-group.layout-xs-row md-radio-button:last-of-type{margin-left:0;margin-right:0}md-radio-group:focus{outline:0}md-radio-group.md-focused .md-checked ._md-container:before{left:-8px;top:-8px;right:-8px;bottom:-8px}.md-inline-form md-radio-group{margin:18px 0 19px}.md-inline-form md-radio-group md-radio-button{display:inline-block;height:30px;padding:2px;box-sizing:border-box;margin-top:0;margin-bottom:0}@media screen and (-ms-high-contrast:active){md-radio-button.md-default-theme ._md-on{background-color:#fff}}._md-select-menu-container{position:fixed;left:0;top:0;z-index:90;opacity:0;display:none}._md-select-menu-container:not(._md-clickable){pointer-events:none}._md-select-menu-container md-progress-circular{display:table;margin:24px auto!important}._md-select-menu-container._md-active{display:block;opacity:1}._md-select-menu-container._md-active md-select-menu{transition:all .4s cubic-bezier(.25,.8,.25,1);transition-duration:150ms}._md-select-menu-container._md-active md-select-menu>*{opacity:1;transition:all .3s cubic-bezier(.55,0,.55,.2);transition-duration:150ms;transition-delay:100ms}._md-select-menu-container._md-leave{opacity:0;transition:all .3s cubic-bezier(.55,0,.55,.2);transition-duration:250ms}md-input-container>md-select{margin:0;-webkit-order:2;-ms-flex-order:2;order:2}md-select{display:-webkit-flex;display:-ms-flexbox;display:flex;margin:20px 0 26px}md-select[disabled] ._md-select-value{background-position:0 bottom;background-size:4px 1px;background-repeat:repeat-x;margin-bottom:-1px}md-select:focus{outline:0}md-select[disabled]:hover{cursor:default}md-select:not([disabled]):hover{cursor:pointer}md-select:not([disabled]).ng-invalid.ng-dirty ._md-select-value{border-bottom:2px solid;padding-bottom:0}md-select:not([disabled]):focus ._md-select-value{border-bottom-width:2px;border-bottom-style:solid;padding-bottom:0}._md-select-value{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;padding:2px 2px 1px;border-bottom-width:1px;border-bottom-style:solid;background-color:transparent;position:relative;box-sizing:content-box;min-width:64px;min-height:26px;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}._md-select-value>span:not(._md-select-icon){max-width:100%;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;-webkit-transform:translate3d(0,2px,0);transform:translate3d(0,2px,0);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._md-select-value>span:not(._md-select-icon) ._md-text{display:inline}._md-select-value ._md-select-icon{display:block;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;text-align:end;width:24px;margin:0 4px;-webkit-transform:translate3d(0,1px,0);transform:translate3d(0,1px,0)}._md-select-value ._md-select-icon:after{display:block;content:'\25BC';position:relative;top:2px;speak:none;-webkit-transform:scaleY(.6) scaleX(1);transform:scaleY(.6) scaleX(1)}._md-select-value._md-select-placeholder{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-order:1;-ms-flex-order:1;order:1;pointer-events:none;-webkit-font-smoothing:antialiased;padding-left:2px;z-index:1}md-select-menu{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;box-shadow:0 1px 3px 0 rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 2px 1px -1px rgba(0,0,0,.12);max-height:256px;min-height:48px;overflow-y:hidden;-webkit-transform-origin:left top;transform-origin:left top;-webkit-transform:scale(1);transform:scale(1)}md-select-menu.md-reverse{-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}md-select-menu:not(._md-overflow) md-content{padding-top:8px;padding-bottom:8px}[dir=rtl] md-select-menu{-webkit-transform-origin:right top;transform-origin:right top}md-select-menu md-content{min-width:136px;min-height:48px;max-height:256px;overflow-y:auto}md-select-menu>*{opacity:0}md-option{cursor:pointer;position:relative;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;width:auto;transition:background .15s linear;padding:0 16px;height:48px}md-option[disabled]{cursor:default}md-option:focus{outline:0}md-option ._md-text{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:16px}md-optgroup{display:block}md-optgroup label{display:block;font-size:14px;text-transform:uppercase;padding:16px;font-weight:500}md-optgroup md-option{padding-left:32px;padding-right:32px}@media screen and (-ms-high-contrast:active){._md-select-backdrop{background-color:transparent}md-select-menu{border:1px solid #fff}}md-select-menu[multiple] md-option._md-checkbox-enabled{padding-left:40px;padding-right:16px}[dir=rtl] md-select-menu[multiple] md-option._md-checkbox-enabled{padding-left:16px;padding-right:40px}md-select-menu[multiple] md-option._md-checkbox-enabled ._md-container{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);box-sizing:border-box;display:inline-block;width:20px;height:20px;left:0;right:auto}[dir=rtl] md-select-menu[multiple] md-option._md-checkbox-enabled ._md-container{left:auto;right:0}md-select-menu[multiple] md-option._md-checkbox-enabled ._md-container:before{box-sizing:border-box;background-color:transparent;border-radius:50%;content:'';position:absolute;display:block;height:auto;left:0;top:0;right:0;bottom:0;transition:all .5s;width:auto}md-select-menu[multiple] md-option._md-checkbox-enabled ._md-container:after{box-sizing:border-box;content:'';position:absolute;top:-10px;right:-10px;bottom:-10px;left:-10px}md-select-menu[multiple] md-option._md-checkbox-enabled ._md-container .md-ripple-container{position:absolute;display:block;width:auto;height:auto;left:-15px;top:-15px;right:-15px;bottom:-15px}md-select-menu[multiple] md-option._md-checkbox-enabled ._md-icon{box-sizing:border-box;transition:240ms;position:absolute;top:0;left:0;width:20px;height:20px;border-width:2px;border-style:solid;border-radius:2px}md-select-menu[multiple] md-option._md-checkbox-enabled[selected] ._md-icon{border:none}md-select-menu[multiple] md-option._md-checkbox-enabled[selected] ._md-icon:after{box-sizing:border-box;-webkit-transform:rotate(45deg);transform:rotate(45deg);position:absolute;left:6.67px;top:2.22px;display:table;width:6.67px;height:13.33px;border-width:2px;border-style:solid;border-top:0;border-left:0;content:''}md-select-menu[multiple] md-option._md-checkbox-enabled[disabled]{cursor:default}md-select-menu[multiple] md-option._md-checkbox-enabled.md-indeterminate ._md-icon:after{box-sizing:border-box;position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);display:table;width:12px;height:2px;border-width:2px;border-style:solid;border-top:0;border-left:0;content:''}md-select-menu[multiple] md-option._md-checkbox-enabled ._md-container{margin-left:10.67px;margin-right:auto}[dir=rtl] md-select-menu[multiple] md-option._md-checkbox-enabled ._md-container{margin-left:auto;margin-right:10.67px}md-sidenav{box-sizing:border-box;position:absolute;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;z-index:60;width:320px;max-width:320px;bottom:0;overflow:auto;-webkit-overflow-scrolling:touch}md-sidenav ul{list-style:none}md-sidenav._md-closed{display:none}md-sidenav._md-closed-add,md-sidenav._md-closed-remove{display:-webkit-flex;display:-ms-flexbox;display:flex;transition:.2s ease-in all}md-sidenav._md-closed-add._md-closed-add-active,md-sidenav._md-closed-remove._md-closed-remove-active{transition:all .4s cubic-bezier(.25,.8,.25,1)}md-sidenav._md-locked-open,md-sidenav._md-locked-open-add,md-sidenav._md-locked-open-remove,md-sidenav._md-locked-open-remove._md-closed,md-sidenav._md-locked-open._md-closed,md-sidenav._md-locked-open._md-closed.md-sidenav-left,md-sidenav._md-locked-open._md-closed.md-sidenav-right{position:static;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-sidenav._md-locked-open-remove-active{transition:width .3s cubic-bezier(.55,0,.55,.2),min-width .3s cubic-bezier(.55,0,.55,.2);width:0!important;min-width:0!important}md-sidenav._md-closed._md-locked-open-add{width:0!important;min-width:0!important;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-sidenav._md-closed._md-locked-open-add-active{transition:width .3s cubic-bezier(.55,0,.55,.2),min-width .3s cubic-bezier(.55,0,.55,.2);width:320px;min-width:320px;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}._md-sidenav-backdrop._md-locked-open{display:none}.md-sidenav-left,md-sidenav{left:0;top:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.md-sidenav-left._md-closed,md-sidenav._md-closed{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.md-sidenav-right{left:100%;top:0;-webkit-transform:translate(-100%,0);transform:translate(-100%,0)}.md-sidenav-right._md-closed{-webkit-transform:translate(0,0);transform:translate(0,0)}@media (min-width:600px){md-sidenav{max-width:400px}}@media (max-width:456px){md-sidenav{width:calc(100% - 56px);min-width:calc(100% - 56px);max-width:calc(100% - 56px)}}@media screen and (-ms-high-contrast:active){.md-sidenav-left,md-sidenav{border-right:1px solid #fff}.md-sidenav-right{border-left:1px solid #fff}}@-webkit-keyframes sliderFocusThumb{0%{-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1);transform:scale(1)}100%{-webkit-transform:scale(.7);transform:scale(.7)}}@keyframes sliderFocusThumb{0%{-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1);transform:scale(1)}100%{-webkit-transform:scale(.7);transform:scale(.7)}}@-webkit-keyframes sliderDiscreteFocusThumb{0%{-webkit-transform:scale(.7);transform:scale(.7)}50%{-webkit-transform:scale(.8);transform:scale(.8)}100%{-webkit-transform:scale(0);transform:scale(0)}}@keyframes sliderDiscreteFocusThumb{0%{-webkit-transform:scale(.7);transform:scale(.7)}50%{-webkit-transform:scale(.8);transform:scale(.8)}100%{-webkit-transform:scale(0);transform:scale(0)}}@-webkit-keyframes sliderDiscreteFocusRing{0%{-webkit-transform:scale(.7);transform:scale(.7);opacity:0}50%{-webkit-transform:scale(1);transform:scale(1);opacity:1}100%{-webkit-transform:scale(0);transform:scale(0)}}@keyframes sliderDiscreteFocusRing{0%{-webkit-transform:scale(.7);transform:scale(.7);opacity:0}50%{-webkit-transform:scale(1);transform:scale(1);opacity:1}100%{-webkit-transform:scale(0);transform:scale(0)}}md-slider{height:48px;min-width:128px;position:relative;margin-left:4px;margin-right:4px;padding:0;display:block;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}md-slider *,md-slider :after{box-sizing:border-box}md-slider ._md-slider-wrapper{outline:0;width:100%;height:100%}md-slider ._md-slider-content{position:relative}md-slider ._md-track-container{width:100%;position:absolute;top:23px;height:2px}md-slider ._md-track{position:absolute;left:0;right:0;height:100%}md-slider ._md-track-fill{transition:all .4s cubic-bezier(.25,.8,.25,1);transition-property:width,height}md-slider ._md-track-ticks{position:absolute;left:0;right:0;height:100%}md-slider ._md-track-ticks canvas{width:100%;height:100%}md-slider ._md-thumb-container{position:absolute;left:0;top:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0);transition:all .4s cubic-bezier(.25,.8,.25,1);transition-property:left,bottom}md-slider ._md-thumb{z-index:1;position:absolute;left:-10px;top:14px;width:20px;height:20px;border-radius:20px;-webkit-transform:scale(.7);transform:scale(.7);transition:all .4s cubic-bezier(.25,.8,.25,1)}md-slider ._md-thumb:after{content:'';position:absolute;width:20px;height:20px;border-radius:20px;border-width:3px;border-style:solid;transition:inherit}md-slider ._md-sign{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;position:absolute;left:-14px;top:-17px;width:28px;height:28px;border-radius:28px;-webkit-transform:scale(.4) translate3d(0,67.5px,0);transform:scale(.4) translate3d(0,67.5px,0);transition:all .3s cubic-bezier(.35,0,.25,1)}md-slider ._md-sign:after{position:absolute;content:'';left:0;border-radius:16px;top:19px;border-left:14px solid transparent;border-right:14px solid transparent;border-top-width:16px;border-top-style:solid;opacity:0;-webkit-transform:translate3d(0,-8px,0);transform:translate3d(0,-8px,0);transition:all .2s cubic-bezier(.35,0,.25,1)}md-slider ._md-sign ._md-thumb-text{z-index:1;font-size:12px;font-weight:700}md-slider ._md-focus-ring{position:absolute;left:-17px;top:7px;width:34px;height:34px;border-radius:34px;-webkit-transform:scale(.7);transform:scale(.7);opacity:0;transition:all .35s cubic-bezier(.35,0,.25,1)}md-slider ._md-disabled-thumb{position:absolute;left:-14px;top:10px;width:28px;height:28px;border-radius:28px;-webkit-transform:scale(.5);transform:scale(.5);border-width:4px;border-style:solid;display:none}md-slider._md-min ._md-sign{opacity:0}md-slider:focus{outline:0}md-slider._md-dragging ._md-thumb-container,md-slider._md-dragging ._md-track-fill{transition:none}md-slider:not([md-discrete]) ._md-sign,md-slider:not([md-discrete]) ._md-track-ticks{display:none}md-slider:not([md-discrete]):not([disabled]) ._md-slider-wrapper ._md-thumb:hover{-webkit-transform:scale(.8);transform:scale(.8)}md-slider:not([md-discrete]):not([disabled]) ._md-slider-wrapper.md-focused ._md-focus-ring{-webkit-transform:scale(1);transform:scale(1);opacity:1}md-slider:not([md-discrete]):not([disabled]) ._md-slider-wrapper.md-focused ._md-thumb{-webkit-animation:sliderFocusThumb .7s cubic-bezier(.35,0,.25,1);animation:sliderFocusThumb .7s cubic-bezier(.35,0,.25,1)}md-slider:not([md-discrete]):not([disabled])._md-active ._md-slider-wrapper ._md-thumb{-webkit-transform:scale(1);transform:scale(1)}md-slider[md-discrete]:not([disabled]) ._md-slider-wrapper.md-focused ._md-focus-ring{-webkit-transform:scale(0);transform:scale(0);-webkit-animation:sliderDiscreteFocusRing .5s cubic-bezier(.35,0,.25,1);animation:sliderDiscreteFocusRing .5s cubic-bezier(.35,0,.25,1)}md-slider[md-discrete]:not([disabled]) ._md-slider-wrapper.md-focused ._md-thumb{-webkit-animation:sliderDiscreteFocusThumb .5s cubic-bezier(.35,0,.25,1);animation:sliderDiscreteFocusThumb .5s cubic-bezier(.35,0,.25,1)}md-slider[md-discrete]:not([disabled]) ._md-slider-wrapper.md-focused ._md-thumb,md-slider[md-discrete]:not([disabled])._md-active ._md-thumb{-webkit-transform:scale(0);transform:scale(0)}md-slider[md-discrete]:not([disabled]) ._md-slider-wrapper.md-focused ._md-sign,md-slider[md-discrete]:not([disabled]) ._md-slider-wrapper.md-focused ._md-sign:after,md-slider[md-discrete]:not([disabled])._md-active ._md-sign,md-slider[md-discrete]:not([disabled])._md-active ._md-sign:after{opacity:1;-webkit-transform:translate3d(0,0,0) scale(1);transform:translate3d(0,0,0) scale(1)}md-slider[md-discrete][disabled][readonly] ._md-thumb{-webkit-transform:scale(0);transform:scale(0)}md-slider[md-discrete][disabled][readonly] ._md-sign,md-slider[md-discrete][disabled][readonly] ._md-sign:after{opacity:1;-webkit-transform:translate3d(0,0,0) scale(1);transform:translate3d(0,0,0) scale(1)}md-slider[disabled] ._md-track-fill{display:none}md-slider[disabled] ._md-track-ticks,md-slider[disabled]:not([readonly]) ._md-sign{opacity:0}md-slider[disabled] ._md-thumb{-webkit-transform:scale(.5);transform:scale(.5)}md-slider[disabled] ._md-disabled-thumb{display:block}md-slider[md-vertical]{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-height:128px;min-width:0}md-slider[md-vertical] ._md-slider-wrapper{-webkit-flex:1;-ms-flex:1;flex:1;padding-top:12px;padding-bottom:12px;width:48px;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}md-slider[md-vertical] ._md-track-container{height:100%;width:2px;top:0;left:calc(50% - (2px / 2))}md-slider[md-vertical] ._md-thumb-container{top:auto;margin-bottom:23px;left:calc(50% - 1px);bottom:0}md-slider[md-vertical] ._md-thumb-container ._md-thumb:after{left:1px}md-slider[md-vertical] ._md-thumb-container ._md-focus-ring{left:-16px}md-slider[md-vertical] ._md-track-fill{bottom:0}md-slider[md-vertical][md-discrete] ._md-sign{left:-40px;top:9.5px;-webkit-transform:scale(.4) translate3d(67.5px,0,0);transform:scale(.4) translate3d(67.5px,0,0)}md-slider[md-vertical][md-discrete] ._md-sign:after{top:9.5px;left:19px;border-top:14px solid transparent;border-right:0;border-bottom:14px solid transparent;border-left-width:16px;border-left-style:solid;opacity:0;-webkit-transform:translate3d(0,-8px,0);transform:translate3d(0,-8px,0);transition:all .2s ease-in-out}md-slider[md-vertical][md-discrete] ._md-sign ._md-thumb-text{z-index:1;font-size:12px;font-weight:700}md-slider[md-vertical][md-discrete] .md-focused ._md-sign:after,md-slider[md-vertical][md-discrete]._md-active ._md-sign:after,md-slider[md-vertical][md-discrete][disabled][readonly] ._md-sign:after{top:0}md-slider[md-vertical][disabled][readonly] ._md-thumb{-webkit-transform:scale(0);transform:scale(0)}md-slider[md-vertical][disabled][readonly] ._md-sign,md-slider[md-vertical][disabled][readonly] ._md-sign:after{opacity:1;-webkit-transform:translate3d(0,0,0) scale(1);transform:translate3d(0,0,0) scale(1)}md-slider[md-invert]:not([md-vertical]) ._md-track-fill{left:auto;right:0}md-slider[md-invert][md-vertical] ._md-track-fill{bottom:auto;top:0}md-slider-container{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}md-slider-container>:first-child:not(md-slider),md-slider-container>:last-child:not(md-slider){min-width:25px;max-width:42px;height:25px;transition:all .4s cubic-bezier(.25,.8,.25,1);transition-property:color,max-width}md-slider-container>:first-child:not(md-slider){margin-right:16px}md-slider-container>:last-child:not(md-slider){margin-left:16px}md-slider-container[md-vertical]{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}md-slider-container[md-vertical]>:first-child:not(md-slider),md-slider-container[md-vertical]>:last-child:not(md-slider){margin-right:0;margin-left:0;text-align:center}md-slider-container md-input-container input[type=number]{text-align:center;padding-left:15px;height:50px;margin-top:-25px}@media screen and (-ms-high-contrast:active){md-slider.md-default-theme ._md-track{border-bottom:1px solid #fff}}._md-sticky-clone{z-index:2;top:0;left:0;right:0;position:absolute!important;-webkit-transform:translate3d(-9999px,-9999px,0);transform:translate3d(-9999px,-9999px,0)}._md-sticky-clone[sticky-state=active]{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}._md-sticky-clone[sticky-state=active]:not(.md-sticky-no-effect) ._md-subheader-inner{-webkit-animation:subheaderStickyHoverIn .3s ease-out both;animation:subheaderStickyHoverIn .3s ease-out both}@-webkit-keyframes subheaderStickyHoverIn{0%{box-shadow:0 0 0 0 transparent}100%{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}}@keyframes subheaderStickyHoverIn{0%{box-shadow:0 0 0 0 transparent}100%{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}}@-webkit-keyframes subheaderStickyHoverOut{0%{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}100%{box-shadow:0 0 0 0 transparent}}@keyframes subheaderStickyHoverOut{0%{box-shadow:0 2px 4px 0 rgba(0,0,0,.16)}100%{box-shadow:0 0 0 0 transparent}}._md-subheader-wrapper:not(.md-sticky-no-effect){transition:.2s ease-out margin}._md-subheader-wrapper:not(.md-sticky-no-effect) .md-subheader{margin:0}._md-subheader-wrapper:not(.md-sticky-no-effect).md-sticky-clone{z-index:2}._md-subheader-wrapper:not(.md-sticky-no-effect)[sticky-state=active]{margin-top:-2px}._md-subheader-wrapper:not(.md-sticky-no-effect):not(.md-sticky-clone)[sticky-prev-state=active] ._md-subheader-inner:after{-webkit-animation:subheaderStickyHoverOut .3s ease-out both;animation:subheaderStickyHoverOut .3s ease-out both}.md-subheader{display:block;font-size:14px;font-weight:500;line-height:1em;margin:0;position:relative}.md-subheader ._md-subheader-inner{display:block;padding:16px}.md-subheader ._md-subheader-content{display:block;z-index:1;position:relative}.md-inline-form md-switch{margin-top:18px;margin-bottom:19px}md-switch{margin:16px;margin-left:inherit;white-space:nowrap;cursor:pointer;outline:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;height:30px;line-height:28px;-webkit-align-items:center;-ms-flex-align:center;align-items:center;display:-webkit-flex;display:-ms-flexbox;display:flex}[dir=rtl] md-switch{margin-left:16px;margin-right:inherit}md-switch:last-of-type{margin-left:inherit;margin-right:0}[dir=rtl] md-switch:last-of-type{margin-left:0;margin-right:inherit}md-switch[disabled],md-switch[disabled] ._md-container{cursor:default}md-switch ._md-container{cursor:-webkit-grab;cursor:grab;width:36px;height:24px;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;margin-right:8px;float:left}[dir=rtl] md-switch ._md-container{margin-right:auto;margin-right:initial;margin-left:8px}md-switch:not([disabled]) ._md-dragging,md-switch:not([disabled])._md-dragging ._md-container{cursor:-webkit-grabbing;cursor:grabbing}md-switch.md-focused:not([disabled]) ._md-thumb:before{left:-8px;top:-8px;right:-8px;bottom:-8px}md-switch.md-focused:not([disabled]):not(.md-checked) ._md-thumb:before{background-color:rgba(0,0,0,.12)}md-switch ._md-label{border-color:transparent;border-width:0;float:left}md-switch ._md-bar{left:1px;width:34px;top:5px;height:14px;border-radius:8px;position:absolute}md-switch ._md-thumb-container{top:2px;left:0;width:16px;position:absolute;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);z-index:1}md-switch.md-checked ._md-thumb-container{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}md-switch ._md-thumb{position:absolute;margin:0;left:0;top:0;outline:0;height:20px;width:20px;border-radius:50%;box-shadow:0 1px 3px 0 rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 2px 1px -1px rgba(0,0,0,.12)}md-switch ._md-thumb:before{background-color:transparent;border-radius:50%;content:'';position:absolute;display:block;height:auto;left:0;top:0;right:0;bottom:0;transition:all .5s;width:auto}md-switch ._md-thumb .md-ripple-container{position:absolute;display:block;width:auto;height:auto;left:-20px;top:-20px;right:-20px;bottom:-20px}md-switch:not(._md-dragging) ._md-bar,md-switch:not(._md-dragging) ._md-thumb,md-switch:not(._md-dragging) ._md-thumb-container{transition:all .08s linear;transition-property:-webkit-transform,background-color;transition-property:transform,background-color}md-switch:not(._md-dragging) ._md-bar,md-switch:not(._md-dragging) ._md-thumb{transition-delay:.05s}@media screen and (-ms-high-contrast:active){md-switch.md-default-theme ._md-bar{background-color:#666}md-switch.md-default-theme.md-checked ._md-bar{background-color:#9E9E9E}md-switch.md-default-theme ._md-thumb{background-color:#fff}}@-webkit-keyframes md-tab-content-hide{0%,50%{opacity:1}100%{opacity:0}}@keyframes md-tab-content-hide{0%,50%{opacity:1}100%{opacity:0}}md-tab-data{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1;opacity:0}md-tabs{display:block;margin:0;border-radius:2px;overflow:hidden;position:relative;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}md-tabs:not(.md-no-tab-content):not(.md-dynamic-height){min-height:248px}md-tabs[md-align-tabs=bottom]{padding-bottom:48px}md-tabs[md-align-tabs=bottom] md-tabs-wrapper{position:absolute;bottom:0;left:0;right:0;height:48px;z-index:2}md-tabs[md-align-tabs=bottom] md-tabs-content-wrapper{top:0;bottom:48px}md-tabs.md-dynamic-height md-tabs-content-wrapper{min-height:0;position:relative;top:auto;left:auto;right:auto;bottom:auto;overflow:visible}md-tabs.md-dynamic-height md-tab-content.md-active{position:relative}md-tabs[md-border-bottom] md-tabs-wrapper{border-width:0 0 1px;border-style:solid}md-tabs[md-border-bottom]:not(.md-dynamic-height) md-tabs-content-wrapper{top:49px}md-tabs-wrapper{display:block;position:relative;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-tabs-wrapper md-next-button,md-tabs-wrapper md-prev-button{height:100%;width:32px;position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);line-height:1em;z-index:2;cursor:pointer;font-size:16px;background:center center no-repeat;transition:all .5s cubic-bezier(.35,0,.25,1)}md-tabs-wrapper md-next-button:focus,md-tabs-wrapper md-prev-button:focus{outline:0}md-tabs-wrapper md-next-button.md-disabled,md-tabs-wrapper md-prev-button.md-disabled{opacity:.25;cursor:default}md-tabs-wrapper md-next-button.ng-leave,md-tabs-wrapper md-prev-button.ng-leave{transition:none}md-tabs-wrapper md-next-button md-icon,md-tabs-wrapper md-prev-button md-icon{position:absolute;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0)}md-tabs-wrapper md-prev-button{left:0;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE3LjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPiA8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPiA8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPiA8ZyBpZD0iSGVhZGVyIj4gPGc+IDxyZWN0IHg9Ii02MTgiIHk9Ii0xMjA4IiBmaWxsPSJub25lIiB3aWR0aD0iMTQwMCIgaGVpZ2h0PSIzNjAwIi8+IDwvZz4gPC9nPiA8ZyBpZD0iTGFiZWwiPiA8L2c+IDxnIGlkPSJJY29uIj4gPGc+IDxwb2x5Z29uIHBvaW50cz0iMTUuNCw3LjQgMTQsNiA4LDEyIDE0LDE4IDE1LjQsMTYuNiAxMC44LDEyIAkJIiBzdHlsZT0iZmlsbDp3aGl0ZTsiLz4gPHJlY3QgZmlsbD0ibm9uZSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ii8+IDwvZz4gPC9nPiA8ZyBpZD0iR3JpZCIgZGlzcGxheT0ibm9uZSI+IDxnIGRpc3BsYXk9ImlubGluZSI+IDwvZz4gPC9nPiA8L3N2Zz4NCg==)}[dir=rtl] md-tabs-wrapper md-prev-button{left:0;left:auto;left:initial;right:0}md-tabs-wrapper md-next-button{right:0;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE3LjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPiA8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPiA8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPiA8ZyBpZD0iSGVhZGVyIj4gPGc+IDxyZWN0IHg9Ii02MTgiIHk9Ii0xMzM2IiBmaWxsPSJub25lIiB3aWR0aD0iMTQwMCIgaGVpZ2h0PSIzNjAwIi8+IDwvZz4gPC9nPiA8ZyBpZD0iTGFiZWwiPiA8L2c+IDxnIGlkPSJJY29uIj4gPGc+IDxwb2x5Z29uIHBvaW50cz0iMTAsNiA4LjYsNy40IDEzLjIsMTIgOC42LDE2LjYgMTAsMTggMTYsMTIgCQkiIHN0eWxlPSJmaWxsOndoaXRlOyIvPiA8cmVjdCBmaWxsPSJub25lIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz4gPC9nPiA8L2c+IDxnIGlkPSJHcmlkIiBkaXNwbGF5PSJub25lIj4gPGcgZGlzcGxheT0iaW5saW5lIj4gPC9nPiA8L2c+IDwvc3ZnPg0K)}[dir=rtl] md-tabs-wrapper md-next-button{right:0;right:auto;right:initial;left:0}md-tabs-wrapper md-next-button md-icon{-webkit-transform:translate3d(-50%,-50%,0) rotate(180deg);transform:translate3d(-50%,-50%,0) rotate(180deg)}md-tabs-wrapper.md-stretch-tabs md-pagination-wrapper{width:100%;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}md-tabs-wrapper.md-stretch-tabs md-pagination-wrapper md-tab-item{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}md-tabs-canvas{position:relative;overflow:hidden;display:block;height:48px}md-tabs-canvas:after{content:'';display:table;clear:both}md-tabs-canvas .md-dummy-wrapper{position:absolute;top:0;left:0}[dir=rtl] md-tabs-canvas .md-dummy-wrapper{left:0;left:auto;left:initial;right:0}md-tabs-canvas.md-paginated{margin:0 32px}md-tabs-canvas.md-center-tabs{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;text-align:center}md-tabs-canvas.md-center-tabs .md-tab{float:none;display:inline-block}md-pagination-wrapper{height:48px;display:block;transition:-webkit-transform .5s cubic-bezier(.35,0,.25,1);transition:transform .5s cubic-bezier(.35,0,.25,1);position:absolute;width:999999px;left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-pagination-wrapper:after{content:'';display:table;clear:both}[dir=rtl] md-pagination-wrapper{left:0;left:auto;left:initial;right:0}md-pagination-wrapper.md-center-tabs{position:relative;width:initial;margin:0 auto}md-tabs-content-wrapper{display:block;position:absolute;top:48px;left:0;right:0;bottom:0;overflow:hidden}md-tab-content{display:block;position:absolute;top:0;left:0;right:0;bottom:0;transition:-webkit-transform .5s cubic-bezier(.35,0,.25,1);transition:transform .5s cubic-bezier(.35,0,.25,1);overflow:auto;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-tab-content.md-no-scroll{bottom:auto;overflow:hidden}md-tab-content.md-no-transition,md-tab-content.ng-leave{transition:none}md-tab-content.md-left:not(.md-active){-webkit-transform:translateX(-100%);transform:translateX(-100%);-webkit-animation:1s md-tab-content-hide;animation:1s md-tab-content-hide;opacity:0}[dir=rtl] md-tab-content.md-left:not(.md-active){-webkit-transform:translateX(100%);transform:translateX(100%)}md-tab-content.md-left:not(.md-active) *{transition:visibility 0s linear;transition-delay:.5s;visibility:hidden}md-tab-content.md-right:not(.md-active){-webkit-transform:translateX(100%);transform:translateX(100%);-webkit-animation:1s md-tab-content-hide;animation:1s md-tab-content-hide;opacity:0}[dir=rtl] md-tab-content.md-right:not(.md-active){-webkit-transform:translateX(-100%);transform:translateX(-100%)}md-tab-content.md-right:not(.md-active) *{transition:visibility 0s linear;transition-delay:.5s;visibility:hidden}md-tab-content>div.ng-leave{-webkit-animation:1s md-tab-content-hide;animation:1s md-tab-content-hide}md-ink-bar{position:absolute;left:auto;right:auto;bottom:0;height:2px}md-ink-bar.md-left{transition:left .125s cubic-bezier(.35,0,.25,1),right .25s cubic-bezier(.35,0,.25,1)}md-ink-bar.md-right{transition:left .25s cubic-bezier(.35,0,.25,1),right .125s cubic-bezier(.35,0,.25,1)}md-tab{position:absolute;z-index:-1;left:-9999px}.md-tab{font-size:14px;text-align:center;line-height:24px;padding:12px 24px;transition:background-color .35s cubic-bezier(.35,0,.25,1);cursor:pointer;white-space:nowrap;position:relative;text-transform:uppercase;float:left;font-weight:500;box-sizing:border-box;overflow:hidden;text-overflow:ellipsis}[dir=rtl] .md-tab{float:right}.md-tab.md-focused{box-shadow:none;outline:0}.md-tab.md-active{cursor:default}.md-tab.md-disabled{pointer-events:none;-ms-touch-action:pan-y;touch-action:pan-y;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-user-drag:none;opacity:.5;cursor:default}.md-tab.ng-leave{transition:none}md-toolbar+md-tabs{border-top-left-radius:0;border-top-right-radius:0}.md-toast-text{padding:0 6px}md-toast{position:absolute;z-index:105;box-sizing:border-box;cursor:default;overflow:hidden;padding:8px;opacity:1;transition:all .4s cubic-bezier(.25,.8,.25,1)}md-toast .md-toast-content{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;max-height:168px;max-width:100%;min-height:48px;padding:0 18px;box-shadow:0 2px 5px 0 rgba(0,0,0,.26);border-radius:2px;font-size:14px;overflow:hidden;-webkit-transform:translate3d(0,0,0) rotateZ(0deg);transform:translate3d(0,0,0) rotateZ(0deg);transition:all .4s cubic-bezier(.25,.8,.25,1);-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}md-toast .md-toast-content::before{content:'';min-height:48px;visibility:hidden;display:inline-block}[dir=rtl] md-toast .md-toast-content{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}md-toast.md-capsule,md-toast.md-capsule .md-toast-content{border-radius:24px}md-toast.ng-leave-active .md-toast-content{transition:all .3s cubic-bezier(.55,0,.55,.2)}md-toast._md-swipedown .md-toast-content,md-toast._md-swipeleft .md-toast-content,md-toast._md-swiperight .md-toast-content,md-toast._md-swipeup .md-toast-content{transition:all .4s cubic-bezier(.25,.8,.25,1)}md-toast.ng-enter{opacity:0}md-toast.ng-enter .md-toast-content{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}md-toast.ng-enter._md-top .md-toast-content{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}md-toast.ng-enter.ng-enter-active{opacity:1}md-toast.ng-enter.ng-enter-active .md-toast-content{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}md-toast.ng-leave.ng-leave-active .md-toast-content{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}md-toast.ng-leave.ng-leave-active._md-swipeup .md-toast-content{-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0)}md-toast.ng-leave.ng-leave-active._md-swipedown .md-toast-content{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0)}md-toast.ng-leave.ng-leave-active._md-top .md-toast-content{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}md-toast .md-action{line-height:19px;margin-left:24px;margin-right:0;cursor:pointer;text-transform:uppercase;float:right}md-toast .md-button{min-width:0;margin-right:0;margin-left:12px}[dir=rtl] md-toast .md-button{margin-right:12px;margin-left:0}@media (max-width:959px){md-toast{left:0;right:0;width:100%;max-width:100%;min-width:0;border-radius:0;bottom:0;padding:0}md-toast.ng-leave.ng-leave-active._md-swipeup .md-toast-content{-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0)}md-toast.ng-leave.ng-leave-active._md-swipedown .md-toast-content{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0)}}@media (min-width:960px){md-toast{min-width:304px}md-toast._md-bottom{bottom:0}md-toast._md-left{left:0}md-toast._md-right{right:0}md-toast._md-top{top:0}md-toast._md-start{left:0}[dir=rtl] md-toast._md-start{left:0;left:auto;left:initial;right:0}md-toast._md-end{right:0}[dir=rtl] md-toast._md-end{right:0;right:auto;right:initial;left:0}md-toast.ng-leave.ng-leave-active._md-swipeleft .md-toast-content{-webkit-transform:translate3d(-50%,0,0);transform:translate3d(-50%,0,0)}md-toast.ng-leave.ng-leave-active._md-swiperight .md-toast-content{-webkit-transform:translate3d(50%,0,0);transform:translate3d(50%,0,0)}}@media (min-width:1920px){md-toast .md-toast-content{max-width:568px}}@media screen and (-ms-high-contrast:active){md-toast{border:1px solid #fff}}._md-toast-animating{overflow:hidden!important}md-tooltip{position:absolute;z-index:100;overflow:hidden;pointer-events:none;border-radius:4px;font-weight:500;font-size:14px}@media (min-width:960px){md-tooltip{font-size:10px}}md-tooltip ._md-content{position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-transform-origin:center top;transform-origin:center top;-webkit-transform:scale(0);transform:scale(0);opacity:0;height:32px;line-height:32px;padding-left:16px;padding-right:16px}@media (min-width:960px){md-tooltip ._md-content{height:22px;line-height:22px;padding-left:8px;padding-right:8px}}md-tooltip ._md-content._md-show-add{transition:all .4s cubic-bezier(.25,.8,.25,1);transition-duration:.2s;-webkit-transform:scale(0);transform:scale(0);opacity:0}md-tooltip ._md-content._md-show,md-tooltip ._md-content._md-show-add-active{-webkit-transform:scale(1);transform:scale(1);opacity:1;-webkit-transform-origin:center top;transform-origin:center top}md-tooltip ._md-content._md-show-remove{transition:all .4s cubic-bezier(.25,.8,.25,1);transition-duration:.2s}md-tooltip ._md-content._md-show-remove._md-show-remove-active{-webkit-transform:scale(0);transform:scale(0);opacity:0}md-tooltip._md-hide{transition:all .3s cubic-bezier(.55,0,.55,.2)}md-tooltip._md-show{transition:all .4s cubic-bezier(.25,.8,.25,1);pointer-events:auto}.md-virtual-repeat-container{box-sizing:border-box;display:block;margin:0;overflow:hidden;padding:0;position:relative}.md-virtual-repeat-container .md-virtual-repeat-scroller{bottom:0;box-sizing:border-box;left:0;margin:0;overflow-x:hidden;padding:0;position:absolute;right:0;top:0}.md-virtual-repeat-container .md-virtual-repeat-sizer{box-sizing:border-box;height:1px;display:block;margin:0;padding:0;width:1px}.md-virtual-repeat-container .md-virtual-repeat-offsetter{box-sizing:border-box;left:0;margin:0;padding:0;position:absolute;right:0;top:0}.md-virtual-repeat-container.md-orient-horizontal .md-virtual-repeat-scroller{overflow-x:auto;overflow-y:hidden}.md-virtual-repeat-container.md-orient-horizontal .md-virtual-repeat-offsetter{bottom:16px;right:auto;white-space:nowrap}[dir=rtl] .md-virtual-repeat-container.md-orient-horizontal .md-virtual-repeat-offsetter{right:0;right:auto;right:initial;left:auto}md-toolbar{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;position:relative;z-index:2;font-size:20px;min-height:64px;width:100%;transition-duration:.5s;transition-timing-function:cubic-bezier(.35,0,.25,1);transition-property:background-color,fill,color}md-toolbar.md-whiteframe-z1-add,md-toolbar.md-whiteframe-z1-remove{transition:box-shadow .5s linear}md-toolbar md-toolbar-filler{width:72px}md-toolbar *,md-toolbar :after,md-toolbar :before{box-sizing:border-box}md-toolbar.ng-animate{transition:none}md-toolbar.md-tall{height:128px;min-height:128px;max-height:128px}md-toolbar.md-medium-tall{height:88px;min-height:88px;max-height:88px}md-toolbar.md-medium-tall .md-toolbar-tools{height:48px;min-height:48px;max-height:48px}md-toolbar>.md-indent{margin-left:64px}[dir=rtl] md-toolbar>.md-indent{margin-left:auto;margin-left:initial;margin-right:64px}md-toolbar~md-content>md-list{padding:0}md-toolbar~md-content>md-list md-list-item:last-child md-divider{display:none}.md-toolbar-tools{font-size:20px;letter-spacing:.005em;box-sizing:border-box;font-weight:400;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;width:100%;height:64px;max-height:64px;padding:0 16px;margin:0}.md-toolbar-tools h1,.md-toolbar-tools h2,.md-toolbar-tools h3{font-size:inherit;font-weight:inherit;margin:inherit}.md-toolbar-tools a{color:inherit;text-decoration:none}.md-toolbar-tools .fill-height{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.md-toolbar-tools .md-button{margin-top:0;margin-bottom:0}.md-toolbar-tools .md-button,.md-toolbar-tools .md-button.md-icon-button md-icon{transition-duration:.5s;transition-timing-function:cubic-bezier(.35,0,.25,1);transition-property:background-color,fill,color}.md-toolbar-tools .md-button.md-icon-button md-icon.ng-animate,.md-toolbar-tools .md-button.ng-animate{transition:none}.md-toolbar-tools>.md-button:first-child{margin-left:-8px}[dir=rtl] .md-toolbar-tools>.md-button:first-child{margin-left:auto;margin-left:initial;margin-right:-8px}.md-toolbar-tools>.md-button:last-child{margin-right:-8px}[dir=rtl] .md-toolbar-tools>.md-button:last-child{margin-right:auto;margin-right:initial;margin-left:-8px}.md-toolbar-tools>md-menu:last-child{margin-right:-8px}[dir=rtl] .md-toolbar-tools>md-menu:last-child{margin-right:auto;margin-right:initial;margin-left:-8px}.md-toolbar-tools>md-menu:last-child>.md-button{margin-right:0}[dir=rtl] .md-toolbar-tools>md-menu:last-child>.md-button{margin-right:auto;margin-right:initial;margin-left:0}@media screen and (-ms-high-contrast:active){.md-toolbar-tools{border-bottom:1px solid #fff}}@media (min-width:0) and (max-width:959px) and (orientation:portrait){md-toolbar{min-height:56px}.md-toolbar-tools{height:56px;max-height:56px}}@media (min-width:0) and (max-width:959px) and (orientation:landscape){md-toolbar{min-height:48px}.md-toolbar-tools{height:48px;max-height:48px}}.md-whiteframe-1dp,.md-whiteframe-z1{box-shadow:0 1px 3px 0 rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 2px 1px -1px rgba(0,0,0,.12)}.md-whiteframe-2dp{box-shadow:0 1px 5px 0 rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.12)}.md-whiteframe-3dp{box-shadow:0 1px 8px 0 rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.12)}.md-whiteframe-4dp,.md-whiteframe-z2{box-shadow:0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12)}.md-whiteframe-5dp{box-shadow:0 3px 5px -1px rgba(0,0,0,.2),0 5px 8px 0 rgba(0,0,0,.14),0 1px 14px 0 rgba(0,0,0,.12)}.md-whiteframe-6dp{box-shadow:0 3px 5px -1px rgba(0,0,0,.2),0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12)}.md-whiteframe-7dp,.md-whiteframe-z3{box-shadow:0 4px 5px -2px rgba(0,0,0,.2),0 7px 10px 1px rgba(0,0,0,.14),0 2px 16px 1px rgba(0,0,0,.12)}.md-whiteframe-8dp{box-shadow:0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12)}.md-whiteframe-9dp{box-shadow:0 5px 6px -3px rgba(0,0,0,.2),0 9px 12px 1px rgba(0,0,0,.14),0 3px 16px 2px rgba(0,0,0,.12)}.md-whiteframe-10dp,.md-whiteframe-z4{box-shadow:0 6px 6px -3px rgba(0,0,0,.2),0 10px 14px 1px rgba(0,0,0,.14),0 4px 18px 3px rgba(0,0,0,.12)}.md-whiteframe-11dp{box-shadow:0 6px 7px -4px rgba(0,0,0,.2),0 11px 15px 1px rgba(0,0,0,.14),0 4px 20px 3px rgba(0,0,0,.12)}.md-whiteframe-12dp{box-shadow:0 7px 8px -4px rgba(0,0,0,.2),0 12px 17px 2px rgba(0,0,0,.14),0 5px 22px 4px rgba(0,0,0,.12)}.md-whiteframe-13dp,.md-whiteframe-z5{box-shadow:0 7px 8px -4px rgba(0,0,0,.2),0 13px 19px 2px rgba(0,0,0,.14),0 5px 24px 4px rgba(0,0,0,.12)}.md-whiteframe-14dp{box-shadow:0 7px 9px -4px rgba(0,0,0,.2),0 14px 21px 2px rgba(0,0,0,.14),0 5px 26px 4px rgba(0,0,0,.12)}.md-whiteframe-15dp{box-shadow:0 8px 9px -5px rgba(0,0,0,.2),0 15px 22px 2px rgba(0,0,0,.14),0 6px 28px 5px rgba(0,0,0,.12)}.md-whiteframe-16dp{box-shadow:0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12)}.md-whiteframe-17dp{box-shadow:0 8px 11px -5px rgba(0,0,0,.2),0 17px 26px 2px rgba(0,0,0,.14),0 6px 32px 5px rgba(0,0,0,.12)}.md-whiteframe-18dp{box-shadow:0 9px 11px -5px rgba(0,0,0,.2),0 18px 28px 2px rgba(0,0,0,.14),0 7px 34px 6px rgba(0,0,0,.12)}.md-whiteframe-19dp{box-shadow:0 9px 12px -6px rgba(0,0,0,.2),0 19px 29px 2px rgba(0,0,0,.14),0 7px 36px 6px rgba(0,0,0,.12)}.md-whiteframe-20dp{box-shadow:0 10px 13px -6px rgba(0,0,0,.2),0 20px 31px 3px rgba(0,0,0,.14),0 8px 38px 7px rgba(0,0,0,.12)}.md-whiteframe-21dp{box-shadow:0 10px 13px -6px rgba(0,0,0,.2),0 21px 33px 3px rgba(0,0,0,.14),0 8px 40px 7px rgba(0,0,0,.12)}.md-whiteframe-22dp{box-shadow:0 10px 14px -6px rgba(0,0,0,.2),0 22px 35px 3px rgba(0,0,0,.14),0 8px 42px 7px rgba(0,0,0,.12)}.md-whiteframe-23dp{box-shadow:0 11px 14px -7px rgba(0,0,0,.2),0 23px 36px 3px rgba(0,0,0,.14),0 9px 44px 8px rgba(0,0,0,.12)}.md-whiteframe-24dp{box-shadow:0 11px 15px -7px rgba(0,0,0,.2),0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12)}@media screen and (-ms-high-contrast:active){md-whiteframe{border:1px solid #fff}}@media print{[md-whiteframe],md-whiteframe{background-color:#fff}}.ng-cloak,.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}@-moz-document url-prefix(){.layout-fill{margin:0;width:100%;min-height:100%;height:100%}}.flex-order{-webkit-order:0;-ms-flex-order:0;order:0}.flex-order--20{-webkit-order:-20;-ms-flex-order:-20;order:-20}.flex-order--19{-webkit-order:-19;-ms-flex-order:-19;order:-19}.flex-order--18{-webkit-order:-18;-ms-flex-order:-18;order:-18}.flex-order--17{-webkit-order:-17;-ms-flex-order:-17;order:-17}.flex-order--16{-webkit-order:-16;-ms-flex-order:-16;order:-16}.flex-order--15{-webkit-order:-15;-ms-flex-order:-15;order:-15}.flex-order--14{-webkit-order:-14;-ms-flex-order:-14;order:-14}.flex-order--13{-webkit-order:-13;-ms-flex-order:-13;order:-13}.flex-order--12{-webkit-order:-12;-ms-flex-order:-12;order:-12}.flex-order--11{-webkit-order:-11;-ms-flex-order:-11;order:-11}.flex-order--10{-webkit-order:-10;-ms-flex-order:-10;order:-10}.flex-order--9{-webkit-order:-9;-ms-flex-order:-9;order:-9}.flex-order--8{-webkit-order:-8;-ms-flex-order:-8;order:-8}.flex-order--7{-webkit-order:-7;-ms-flex-order:-7;order:-7}.flex-order--6{-webkit-order:-6;-ms-flex-order:-6;order:-6}.flex-order--5{-webkit-order:-5;-ms-flex-order:-5;order:-5}.flex-order--4{-webkit-order:-4;-ms-flex-order:-4;order:-4}.flex-order--3{-webkit-order:-3;-ms-flex-order:-3;order:-3}.flex-order--2{-webkit-order:-2;-ms-flex-order:-2;order:-2}.flex-order--1{-webkit-order:-1;-ms-flex-order:-1;order:-1}.flex-order-0{-webkit-order:0;-ms-flex-order:0;order:0}.flex-order-1{-webkit-order:1;-ms-flex-order:1;order:1}.flex-order-2{-webkit-order:2;-ms-flex-order:2;order:2}.flex-order-3{-webkit-order:3;-ms-flex-order:3;order:3}.flex-order-4{-webkit-order:4;-ms-flex-order:4;order:4}.flex-order-5{-webkit-order:5;-ms-flex-order:5;order:5}.flex-order-6{-webkit-order:6;-ms-flex-order:6;order:6}.flex-order-7{-webkit-order:7;-ms-flex-order:7;order:7}.flex-order-8{-webkit-order:8;-ms-flex-order:8;order:8}.flex-order-9{-webkit-order:9;-ms-flex-order:9;order:9}.flex-order-10{-webkit-order:10;-ms-flex-order:10;order:10}.flex-order-11{-webkit-order:11;-ms-flex-order:11;order:11}.flex-order-12{-webkit-order:12;-ms-flex-order:12;order:12}.flex-order-13{-webkit-order:13;-ms-flex-order:13;order:13}.flex-order-14{-webkit-order:14;-ms-flex-order:14;order:14}.flex-order-15{-webkit-order:15;-ms-flex-order:15;order:15}.flex-order-16{-webkit-order:16;-ms-flex-order:16;order:16}.flex-order-17{-webkit-order:17;-ms-flex-order:17;order:17}.flex-order-18{-webkit-order:18;-ms-flex-order:18;order:18}.flex-order-19{-webkit-order:19;-ms-flex-order:19;order:19}.flex-order-20{-webkit-order:20;-ms-flex-order:20;order:20}.flex-offset-0,.offset-0{margin-left:0}[dir=rtl] .flex-offset-0,[dir=rtl] .offset-0{margin-left:auto;margin-left:initial;margin-right:0}.flex-offset-5,.offset-5{margin-left:5%}[dir=rtl] .flex-offset-5,[dir=rtl] .offset-5{margin-left:auto;margin-left:initial;margin-right:5%}.flex-offset-10,.offset-10{margin-left:10%}[dir=rtl] .flex-offset-10,[dir=rtl] .offset-10{margin-left:auto;margin-left:initial;margin-right:10%}.flex-offset-15,.offset-15{margin-left:15%}[dir=rtl] .flex-offset-15,[dir=rtl] .offset-15{margin-left:auto;margin-left:initial;margin-right:15%}.flex-offset-20,.offset-20{margin-left:20%}[dir=rtl] .flex-offset-20,[dir=rtl] .offset-20{margin-left:auto;margin-left:initial;margin-right:20%}.flex-offset-25,.offset-25{margin-left:25%}[dir=rtl] .flex-offset-25,[dir=rtl] .offset-25{margin-left:auto;margin-left:initial;margin-right:25%}.flex-offset-30,.offset-30{margin-left:30%}[dir=rtl] .flex-offset-30,[dir=rtl] .offset-30{margin-left:auto;margin-left:initial;margin-right:30%}.flex-offset-35,.offset-35{margin-left:35%}[dir=rtl] .flex-offset-35,[dir=rtl] .offset-35{margin-left:auto;margin-left:initial;margin-right:35%}.flex-offset-40,.offset-40{margin-left:40%}[dir=rtl] .flex-offset-40,[dir=rtl] .offset-40{margin-left:auto;margin-left:initial;margin-right:40%}.flex-offset-45,.offset-45{margin-left:45%}[dir=rtl] .flex-offset-45,[dir=rtl] .offset-45{margin-left:auto;margin-left:initial;margin-right:45%}.flex-offset-50,.offset-50{margin-left:50%}[dir=rtl] .flex-offset-50,[dir=rtl] .offset-50{margin-left:auto;margin-left:initial;margin-right:50%}.flex-offset-55,.offset-55{margin-left:55%}[dir=rtl] .flex-offset-55,[dir=rtl] .offset-55{margin-left:auto;margin-left:initial;margin-right:55%}.flex-offset-60,.offset-60{margin-left:60%}[dir=rtl] .flex-offset-60,[dir=rtl] .offset-60{margin-left:auto;margin-left:initial;margin-right:60%}.flex-offset-65,.offset-65{margin-left:65%}[dir=rtl] .flex-offset-65,[dir=rtl] .offset-65{margin-left:auto;margin-left:initial;margin-right:65%}.flex-offset-70,.offset-70{margin-left:70%}[dir=rtl] .flex-offset-70,[dir=rtl] .offset-70{margin-left:auto;margin-left:initial;margin-right:70%}.flex-offset-75,.offset-75{margin-left:75%}[dir=rtl] .flex-offset-75,[dir=rtl] .offset-75{margin-left:auto;margin-left:initial;margin-right:75%}.flex-offset-80,.offset-80{margin-left:80%}[dir=rtl] .flex-offset-80,[dir=rtl] .offset-80{margin-left:auto;margin-left:initial;margin-right:80%}.flex-offset-85,.offset-85{margin-left:85%}[dir=rtl] .flex-offset-85,[dir=rtl] .offset-85{margin-left:auto;margin-left:initial;margin-right:85%}.flex-offset-90,.offset-90{margin-left:90%}[dir=rtl] .flex-offset-90,[dir=rtl] .offset-90{margin-left:auto;margin-left:initial;margin-right:90%}.flex-offset-95,.offset-95{margin-left:95%}[dir=rtl] .flex-offset-95,[dir=rtl] .offset-95{margin-left:auto;margin-left:initial;margin-right:95%}.flex-offset-33,.offset-33{margin-left:calc(100% / 3)}.flex-offset-66,.offset-66{margin-left:calc(200% / 3)}[dir=rtl] .flex-offset-66,[dir=rtl] .offset-66{margin-left:auto;margin-left:initial;margin-right:calc(200% / 3)}.layout-align{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.layout-align-start,.layout-align-start-center,.layout-align-start-end,.layout-align-start-start,.layout-align-start-stretch{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.layout-align-center,.layout-align-center-center,.layout-align-center-end,.layout-align-center-start,.layout-align-center-stretch{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.layout-align-end,.layout-align-end-center,.layout-align-end-end,.layout-align-end-start,.layout-align-end-stretch{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.layout-align-space-around,.layout-align-space-around-center,.layout-align-space-around-end,.layout-align-space-around-start,.layout-align-space-around-stretch{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.layout-align-space-between,.layout-align-space-between-center,.layout-align-space-between-end,.layout-align-space-between-start,.layout-align-space-between-stretch{-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.layout-align-center-start,.layout-align-end-start,.layout-align-space-around-start,.layout-align-space-between-start,.layout-align-start-start{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}.layout-align-center-center,.layout-align-end-center,.layout-align-space-around-center,.layout-align-space-between-center,.layout-align-start-center{-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;max-width:100%}.layout-align-center-center>*,.layout-align-end-center>*,.layout-align-space-around-center>*,.layout-align-space-between-center>*,.layout-align-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-center-end,.layout-align-end-end,.layout-align-space-around-end,.layout-align-space-between-end,.layout-align-start-end{-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;-webkit-align-content:flex-end;-ms-flex-line-pack:end;align-content:flex-end}.layout-align-center-stretch,.layout-align-end-stretch,.layout-align-space-around-stretch,.layout-align-space-between-stretch,.layout-align-start-stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch}.flex{-webkit-flex:1;-ms-flex:1;flex:1;box-sizing:border-box}.flex-grow{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;box-sizing:border-box}.flex-initial{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-auto{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-none{-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-noshrink{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-nogrow{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box;min-width:0}.layout-column>.flex-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:100%;max-height:0;box-sizing:border-box;min-height:0}.flex-5,.layout-row>.flex-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:5%;max-height:100%;box-sizing:border-box}.layout-column>.flex-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:100%;max-height:5%;box-sizing:border-box}.flex-10,.layout-row>.flex-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:10%;max-height:100%;box-sizing:border-box}.layout-column>.flex-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:100%;max-height:10%;box-sizing:border-box}.flex-15,.layout-row>.flex-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:15%;max-height:100%;box-sizing:border-box}.layout-column>.flex-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:100%;max-height:15%;box-sizing:border-box}.flex-20,.layout-row>.flex-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:20%;max-height:100%;box-sizing:border-box}.layout-column>.flex-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:100%;max-height:20%;box-sizing:border-box}.flex-25,.layout-row>.flex-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:25%;max-height:100%;box-sizing:border-box}.layout-column>.flex-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:100%;max-height:25%;box-sizing:border-box}.flex-30,.layout-row>.flex-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:30%;max-height:100%;box-sizing:border-box}.layout-column>.flex-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:100%;max-height:30%;box-sizing:border-box}.flex-35,.layout-row>.flex-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:35%;max-height:100%;box-sizing:border-box}.layout-column>.flex-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:100%;max-height:35%;box-sizing:border-box}.flex-40,.layout-row>.flex-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:40%;max-height:100%;box-sizing:border-box}.layout-column>.flex-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:100%;max-height:40%;box-sizing:border-box}.flex-45,.layout-row>.flex-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:45%;max-height:100%;box-sizing:border-box}.layout-column>.flex-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:100%;max-height:45%;box-sizing:border-box}.flex-50,.layout-row>.flex-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:50%;max-height:100%;box-sizing:border-box}.layout-column>.flex-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:100%;max-height:50%;box-sizing:border-box}.flex-55,.layout-row>.flex-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:55%;max-height:100%;box-sizing:border-box}.layout-column>.flex-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:100%;max-height:55%;box-sizing:border-box}.flex-60,.layout-row>.flex-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:60%;max-height:100%;box-sizing:border-box}.layout-column>.flex-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:100%;max-height:60%;box-sizing:border-box}.flex-65,.layout-row>.flex-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:65%;max-height:100%;box-sizing:border-box}.layout-column>.flex-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:100%;max-height:65%;box-sizing:border-box}.flex-70,.layout-row>.flex-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:70%;max-height:100%;box-sizing:border-box}.layout-column>.flex-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:100%;max-height:70%;box-sizing:border-box}.flex-75,.layout-row>.flex-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:75%;max-height:100%;box-sizing:border-box}.layout-column>.flex-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:100%;max-height:75%;box-sizing:border-box}.flex-80,.layout-row>.flex-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:80%;max-height:100%;box-sizing:border-box}.layout-column>.flex-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:100%;max-height:80%;box-sizing:border-box}.flex-85,.layout-row>.flex-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:85%;max-height:100%;box-sizing:border-box}.layout-column>.flex-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:100%;max-height:85%;box-sizing:border-box}.flex-90,.layout-row>.flex-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:90%;max-height:100%;box-sizing:border-box}.layout-column>.flex-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:100%;max-height:90%;box-sizing:border-box}.flex-95,.layout-row>.flex-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:95%;max-height:100%;box-sizing:border-box}.layout-column>.flex-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:100%;max-height:95%;box-sizing:border-box}.flex-100,.layout-column>.flex-100,.layout-row>.flex-100{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-row>.flex{min-width:0}.layout-column>.flex-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-column>.flex{min-height:0}.layout,.layout-column,.layout-row{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.layout-column{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.layout-row{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.layout-padding-sm>*,.layout-padding>.flex-sm{padding:4px}.layout-padding,.layout-padding-gt-sm,.layout-padding-gt-sm>*,.layout-padding-md,.layout-padding-md>*,.layout-padding>*,.layout-padding>.flex,.layout-padding>.flex-gt-sm,.layout-padding>.flex-md{padding:8px}.layout-padding-gt-lg>*,.layout-padding-gt-md>*,.layout-padding-lg>*,.layout-padding>.flex-gt-lg,.layout-padding>.flex-gt-md,.layout-padding>.flex-lg{padding:16px}.layout-margin-sm>*,.layout-margin>.flex-sm{margin:4px}.layout-margin,.layout-margin-gt-sm,.layout-margin-gt-sm>*,.layout-margin-md,.layout-margin-md>*,.layout-margin>*,.layout-margin>.flex,.layout-margin>.flex-gt-sm,.layout-margin>.flex-md{margin:8px}.layout-margin-gt-lg>*,.layout-margin-gt-md>*,.layout-margin-lg>*,.layout-margin>.flex-gt-lg,.layout-margin>.flex-gt-md,.layout-margin>.flex-lg{margin:16px}.layout-wrap{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.layout-nowrap{-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.layout-fill{margin:0;width:100%;min-height:100%;height:100%}@media (max-width:599px){.hide-xs:not(.show-xs):not(.show),.hide:not(.show-xs):not(.show){display:none}.flex-order-xs--20{-webkit-order:-20;-ms-flex-order:-20;order:-20}.flex-order-xs--19{-webkit-order:-19;-ms-flex-order:-19;order:-19}.flex-order-xs--18{-webkit-order:-18;-ms-flex-order:-18;order:-18}.flex-order-xs--17{-webkit-order:-17;-ms-flex-order:-17;order:-17}.flex-order-xs--16{-webkit-order:-16;-ms-flex-order:-16;order:-16}.flex-order-xs--15{-webkit-order:-15;-ms-flex-order:-15;order:-15}.flex-order-xs--14{-webkit-order:-14;-ms-flex-order:-14;order:-14}.flex-order-xs--13{-webkit-order:-13;-ms-flex-order:-13;order:-13}.flex-order-xs--12{-webkit-order:-12;-ms-flex-order:-12;order:-12}.flex-order-xs--11{-webkit-order:-11;-ms-flex-order:-11;order:-11}.flex-order-xs--10{-webkit-order:-10;-ms-flex-order:-10;order:-10}.flex-order-xs--9{-webkit-order:-9;-ms-flex-order:-9;order:-9}.flex-order-xs--8{-webkit-order:-8;-ms-flex-order:-8;order:-8}.flex-order-xs--7{-webkit-order:-7;-ms-flex-order:-7;order:-7}.flex-order-xs--6{-webkit-order:-6;-ms-flex-order:-6;order:-6}.flex-order-xs--5{-webkit-order:-5;-ms-flex-order:-5;order:-5}.flex-order-xs--4{-webkit-order:-4;-ms-flex-order:-4;order:-4}.flex-order-xs--3{-webkit-order:-3;-ms-flex-order:-3;order:-3}.flex-order-xs--2{-webkit-order:-2;-ms-flex-order:-2;order:-2}.flex-order-xs--1{-webkit-order:-1;-ms-flex-order:-1;order:-1}.flex-order-xs-0{-webkit-order:0;-ms-flex-order:0;order:0}.flex-order-xs-1{-webkit-order:1;-ms-flex-order:1;order:1}.flex-order-xs-2{-webkit-order:2;-ms-flex-order:2;order:2}.flex-order-xs-3{-webkit-order:3;-ms-flex-order:3;order:3}.flex-order-xs-4{-webkit-order:4;-ms-flex-order:4;order:4}.flex-order-xs-5{-webkit-order:5;-ms-flex-order:5;order:5}.flex-order-xs-6{-webkit-order:6;-ms-flex-order:6;order:6}.flex-order-xs-7{-webkit-order:7;-ms-flex-order:7;order:7}.flex-order-xs-8{-webkit-order:8;-ms-flex-order:8;order:8}.flex-order-xs-9{-webkit-order:9;-ms-flex-order:9;order:9}.flex-order-xs-10{-webkit-order:10;-ms-flex-order:10;order:10}.flex-order-xs-11{-webkit-order:11;-ms-flex-order:11;order:11}.flex-order-xs-12{-webkit-order:12;-ms-flex-order:12;order:12}.flex-order-xs-13{-webkit-order:13;-ms-flex-order:13;order:13}.flex-order-xs-14{-webkit-order:14;-ms-flex-order:14;order:14}.flex-order-xs-15{-webkit-order:15;-ms-flex-order:15;order:15}.flex-order-xs-16{-webkit-order:16;-ms-flex-order:16;order:16}.flex-order-xs-17{-webkit-order:17;-ms-flex-order:17;order:17}.flex-order-xs-18{-webkit-order:18;-ms-flex-order:18;order:18}.flex-order-xs-19{-webkit-order:19;-ms-flex-order:19;order:19}.flex-order-xs-20{-webkit-order:20;-ms-flex-order:20;order:20}.flex-offset-xs-0,.offset-xs-0{margin-left:0}[dir=rtl] .flex-offset-xs-0,[dir=rtl] .offset-xs-0{margin-left:auto;margin-left:initial;margin-right:0}.flex-offset-xs-5,.offset-xs-5{margin-left:5%}[dir=rtl] .flex-offset-xs-5,[dir=rtl] .offset-xs-5{margin-left:auto;margin-left:initial;margin-right:5%}.flex-offset-xs-10,.offset-xs-10{margin-left:10%}[dir=rtl] .flex-offset-xs-10,[dir=rtl] .offset-xs-10{margin-left:auto;margin-left:initial;margin-right:10%}.flex-offset-xs-15,.offset-xs-15{margin-left:15%}[dir=rtl] .flex-offset-xs-15,[dir=rtl] .offset-xs-15{margin-left:auto;margin-left:initial;margin-right:15%}.flex-offset-xs-20,.offset-xs-20{margin-left:20%}[dir=rtl] .flex-offset-xs-20,[dir=rtl] .offset-xs-20{margin-left:auto;margin-left:initial;margin-right:20%}.flex-offset-xs-25,.offset-xs-25{margin-left:25%}[dir=rtl] .flex-offset-xs-25,[dir=rtl] .offset-xs-25{margin-left:auto;margin-left:initial;margin-right:25%}.flex-offset-xs-30,.offset-xs-30{margin-left:30%}[dir=rtl] .flex-offset-xs-30,[dir=rtl] .offset-xs-30{margin-left:auto;margin-left:initial;margin-right:30%}.flex-offset-xs-35,.offset-xs-35{margin-left:35%}[dir=rtl] .flex-offset-xs-35,[dir=rtl] .offset-xs-35{margin-left:auto;margin-left:initial;margin-right:35%}.flex-offset-xs-40,.offset-xs-40{margin-left:40%}[dir=rtl] .flex-offset-xs-40,[dir=rtl] .offset-xs-40{margin-left:auto;margin-left:initial;margin-right:40%}.flex-offset-xs-45,.offset-xs-45{margin-left:45%}[dir=rtl] .flex-offset-xs-45,[dir=rtl] .offset-xs-45{margin-left:auto;margin-left:initial;margin-right:45%}.flex-offset-xs-50,.offset-xs-50{margin-left:50%}[dir=rtl] .flex-offset-xs-50,[dir=rtl] .offset-xs-50{margin-left:auto;margin-left:initial;margin-right:50%}.flex-offset-xs-55,.offset-xs-55{margin-left:55%}[dir=rtl] .flex-offset-xs-55,[dir=rtl] .offset-xs-55{margin-left:auto;margin-left:initial;margin-right:55%}.flex-offset-xs-60,.offset-xs-60{margin-left:60%}[dir=rtl] .flex-offset-xs-60,[dir=rtl] .offset-xs-60{margin-left:auto;margin-left:initial;margin-right:60%}.flex-offset-xs-65,.offset-xs-65{margin-left:65%}[dir=rtl] .flex-offset-xs-65,[dir=rtl] .offset-xs-65{margin-left:auto;margin-left:initial;margin-right:65%}.flex-offset-xs-70,.offset-xs-70{margin-left:70%}[dir=rtl] .flex-offset-xs-70,[dir=rtl] .offset-xs-70{margin-left:auto;margin-left:initial;margin-right:70%}.flex-offset-xs-75,.offset-xs-75{margin-left:75%}[dir=rtl] .flex-offset-xs-75,[dir=rtl] .offset-xs-75{margin-left:auto;margin-left:initial;margin-right:75%}.flex-offset-xs-80,.offset-xs-80{margin-left:80%}[dir=rtl] .flex-offset-xs-80,[dir=rtl] .offset-xs-80{margin-left:auto;margin-left:initial;margin-right:80%}.flex-offset-xs-85,.offset-xs-85{margin-left:85%}[dir=rtl] .flex-offset-xs-85,[dir=rtl] .offset-xs-85{margin-left:auto;margin-left:initial;margin-right:85%}.flex-offset-xs-90,.offset-xs-90{margin-left:90%}[dir=rtl] .flex-offset-xs-90,[dir=rtl] .offset-xs-90{margin-left:auto;margin-left:initial;margin-right:90%}.flex-offset-xs-95,.offset-xs-95{margin-left:95%}[dir=rtl] .flex-offset-xs-95,[dir=rtl] .offset-xs-95{margin-left:auto;margin-left:initial;margin-right:95%}.flex-offset-xs-33,.offset-xs-33{margin-left:calc(100% / 3)}.flex-offset-xs-66,.offset-xs-66{margin-left:calc(200% / 3)}[dir=rtl] .flex-offset-xs-66,[dir=rtl] .offset-xs-66{margin-left:auto;margin-left:initial;margin-right:calc(200% / 3)}.layout-align-xs{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.layout-align-xs-start,.layout-align-xs-start-center,.layout-align-xs-start-end,.layout-align-xs-start-start,.layout-align-xs-start-stretch{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.layout-align-xs-center,.layout-align-xs-center-center,.layout-align-xs-center-end,.layout-align-xs-center-start,.layout-align-xs-center-stretch{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.layout-align-xs-end,.layout-align-xs-end-center,.layout-align-xs-end-end,.layout-align-xs-end-start,.layout-align-xs-end-stretch{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.layout-align-xs-space-around,.layout-align-xs-space-around-center,.layout-align-xs-space-around-end,.layout-align-xs-space-around-start,.layout-align-xs-space-around-stretch{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.layout-align-xs-space-between,.layout-align-xs-space-between-center,.layout-align-xs-space-between-end,.layout-align-xs-space-between-start,.layout-align-xs-space-between-stretch{-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.layout-align-xs-center-start,.layout-align-xs-end-start,.layout-align-xs-space-around-start,.layout-align-xs-space-between-start,.layout-align-xs-start-start{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}.layout-align-xs-center-center,.layout-align-xs-end-center,.layout-align-xs-space-around-center,.layout-align-xs-space-between-center,.layout-align-xs-start-center{-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;max-width:100%}.layout-align-xs-center-center>*,.layout-align-xs-end-center>*,.layout-align-xs-space-around-center>*,.layout-align-xs-space-between-center>*,.layout-align-xs-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-xs-center-end,.layout-align-xs-end-end,.layout-align-xs-space-around-end,.layout-align-xs-space-between-end,.layout-align-xs-start-end{-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;-webkit-align-content:flex-end;-ms-flex-line-pack:end;align-content:flex-end}.layout-align-xs-center-stretch,.layout-align-xs-end-stretch,.layout-align-xs-space-around-stretch,.layout-align-xs-space-between-stretch,.layout-align-xs-start-stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch}.flex-xs{-webkit-flex:1;-ms-flex:1;flex:1;box-sizing:border-box}.flex-xs-grow{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;box-sizing:border-box}.flex-xs-initial{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-xs-auto{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-xs-none{-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-xs-noshrink{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-xs-nogrow{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-xs-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-xs-0,.layout-xs-row>.flex-xs-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box;min-width:0}.layout-column>.flex-xs-0,.layout-xs-column>.flex-xs-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:100%;max-height:0;box-sizing:border-box;min-height:0}.flex-xs-5,.layout-row>.flex-xs-5,.layout-xs-row>.flex-xs-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:5%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-5,.layout-xs-column>.flex-xs-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:100%;max-height:5%;box-sizing:border-box}.flex-xs-10,.layout-row>.flex-xs-10,.layout-xs-row>.flex-xs-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:10%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-10,.layout-xs-column>.flex-xs-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:100%;max-height:10%;box-sizing:border-box}.flex-xs-15,.layout-row>.flex-xs-15,.layout-xs-row>.flex-xs-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:15%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-15,.layout-xs-column>.flex-xs-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:100%;max-height:15%;box-sizing:border-box}.flex-xs-20,.layout-row>.flex-xs-20,.layout-xs-row>.flex-xs-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:20%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-20,.layout-xs-column>.flex-xs-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:100%;max-height:20%;box-sizing:border-box}.flex-xs-25,.layout-row>.flex-xs-25,.layout-xs-row>.flex-xs-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:25%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-25,.layout-xs-column>.flex-xs-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:100%;max-height:25%;box-sizing:border-box}.flex-xs-30,.layout-row>.flex-xs-30,.layout-xs-row>.flex-xs-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:30%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-30,.layout-xs-column>.flex-xs-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:100%;max-height:30%;box-sizing:border-box}.flex-xs-35,.layout-row>.flex-xs-35,.layout-xs-row>.flex-xs-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:35%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-35,.layout-xs-column>.flex-xs-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:100%;max-height:35%;box-sizing:border-box}.flex-xs-40,.layout-row>.flex-xs-40,.layout-xs-row>.flex-xs-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:40%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-40,.layout-xs-column>.flex-xs-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:100%;max-height:40%;box-sizing:border-box}.flex-xs-45,.layout-row>.flex-xs-45,.layout-xs-row>.flex-xs-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:45%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-45,.layout-xs-column>.flex-xs-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:100%;max-height:45%;box-sizing:border-box}.flex-xs-50,.layout-row>.flex-xs-50,.layout-xs-row>.flex-xs-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:50%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-50,.layout-xs-column>.flex-xs-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:100%;max-height:50%;box-sizing:border-box}.flex-xs-55,.layout-row>.flex-xs-55,.layout-xs-row>.flex-xs-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:55%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-55,.layout-xs-column>.flex-xs-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:100%;max-height:55%;box-sizing:border-box}.flex-xs-60,.layout-row>.flex-xs-60,.layout-xs-row>.flex-xs-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:60%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-60,.layout-xs-column>.flex-xs-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:100%;max-height:60%;box-sizing:border-box}.flex-xs-65,.layout-row>.flex-xs-65,.layout-xs-row>.flex-xs-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:65%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-65,.layout-xs-column>.flex-xs-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:100%;max-height:65%;box-sizing:border-box}.flex-xs-70,.layout-row>.flex-xs-70,.layout-xs-row>.flex-xs-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:70%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-70,.layout-xs-column>.flex-xs-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:100%;max-height:70%;box-sizing:border-box}.flex-xs-75,.layout-row>.flex-xs-75,.layout-xs-row>.flex-xs-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:75%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-75,.layout-xs-column>.flex-xs-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:100%;max-height:75%;box-sizing:border-box}.flex-xs-80,.layout-row>.flex-xs-80,.layout-xs-row>.flex-xs-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:80%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-80,.layout-xs-column>.flex-xs-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:100%;max-height:80%;box-sizing:border-box}.flex-xs-85,.layout-row>.flex-xs-85,.layout-xs-row>.flex-xs-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:85%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-85,.layout-xs-column>.flex-xs-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:100%;max-height:85%;box-sizing:border-box}.flex-xs-90,.layout-row>.flex-xs-90,.layout-xs-row>.flex-xs-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:90%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-90,.layout-xs-column>.flex-xs-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:100%;max-height:90%;box-sizing:border-box}.flex-xs-95,.layout-row>.flex-xs-95,.layout-xs-row>.flex-xs-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:95%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xs-95,.layout-xs-column>.flex-xs-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:100%;max-height:95%;box-sizing:border-box}.flex-xs-100,.layout-column>.flex-xs-100,.layout-row>.flex-xs-100,.layout-xs-column>.flex-xs-100,.layout-xs-row>.flex-xs-100{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-xs-33,.layout-xs-row>.flex-xs-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-xs-66,.layout-xs-row>.flex-xs-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-row>.flex,.layout-xs-row>.flex{min-width:0}.layout-column>.flex-xs-33,.layout-xs-column>.flex-xs-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-xs-66,.layout-xs-column>.flex-xs-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-column>.flex,.layout-xs-column>.flex{min-height:0}.layout-xs,.layout-xs-column,.layout-xs-row{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.layout-xs-column{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.layout-xs-row{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}}@media (min-width:600px){.flex-order-gt-xs--20{-webkit-order:-20;-ms-flex-order:-20;order:-20}.flex-order-gt-xs--19{-webkit-order:-19;-ms-flex-order:-19;order:-19}.flex-order-gt-xs--18{-webkit-order:-18;-ms-flex-order:-18;order:-18}.flex-order-gt-xs--17{-webkit-order:-17;-ms-flex-order:-17;order:-17}.flex-order-gt-xs--16{-webkit-order:-16;-ms-flex-order:-16;order:-16}.flex-order-gt-xs--15{-webkit-order:-15;-ms-flex-order:-15;order:-15}.flex-order-gt-xs--14{-webkit-order:-14;-ms-flex-order:-14;order:-14}.flex-order-gt-xs--13{-webkit-order:-13;-ms-flex-order:-13;order:-13}.flex-order-gt-xs--12{-webkit-order:-12;-ms-flex-order:-12;order:-12}.flex-order-gt-xs--11{-webkit-order:-11;-ms-flex-order:-11;order:-11}.flex-order-gt-xs--10{-webkit-order:-10;-ms-flex-order:-10;order:-10}.flex-order-gt-xs--9{-webkit-order:-9;-ms-flex-order:-9;order:-9}.flex-order-gt-xs--8{-webkit-order:-8;-ms-flex-order:-8;order:-8}.flex-order-gt-xs--7{-webkit-order:-7;-ms-flex-order:-7;order:-7}.flex-order-gt-xs--6{-webkit-order:-6;-ms-flex-order:-6;order:-6}.flex-order-gt-xs--5{-webkit-order:-5;-ms-flex-order:-5;order:-5}.flex-order-gt-xs--4{-webkit-order:-4;-ms-flex-order:-4;order:-4}.flex-order-gt-xs--3{-webkit-order:-3;-ms-flex-order:-3;order:-3}.flex-order-gt-xs--2{-webkit-order:-2;-ms-flex-order:-2;order:-2}.flex-order-gt-xs--1{-webkit-order:-1;-ms-flex-order:-1;order:-1}.flex-order-gt-xs-0{-webkit-order:0;-ms-flex-order:0;order:0}.flex-order-gt-xs-1{-webkit-order:1;-ms-flex-order:1;order:1}.flex-order-gt-xs-2{-webkit-order:2;-ms-flex-order:2;order:2}.flex-order-gt-xs-3{-webkit-order:3;-ms-flex-order:3;order:3}.flex-order-gt-xs-4{-webkit-order:4;-ms-flex-order:4;order:4}.flex-order-gt-xs-5{-webkit-order:5;-ms-flex-order:5;order:5}.flex-order-gt-xs-6{-webkit-order:6;-ms-flex-order:6;order:6}.flex-order-gt-xs-7{-webkit-order:7;-ms-flex-order:7;order:7}.flex-order-gt-xs-8{-webkit-order:8;-ms-flex-order:8;order:8}.flex-order-gt-xs-9{-webkit-order:9;-ms-flex-order:9;order:9}.flex-order-gt-xs-10{-webkit-order:10;-ms-flex-order:10;order:10}.flex-order-gt-xs-11{-webkit-order:11;-ms-flex-order:11;order:11}.flex-order-gt-xs-12{-webkit-order:12;-ms-flex-order:12;order:12}.flex-order-gt-xs-13{-webkit-order:13;-ms-flex-order:13;order:13}.flex-order-gt-xs-14{-webkit-order:14;-ms-flex-order:14;order:14}.flex-order-gt-xs-15{-webkit-order:15;-ms-flex-order:15;order:15}.flex-order-gt-xs-16{-webkit-order:16;-ms-flex-order:16;order:16}.flex-order-gt-xs-17{-webkit-order:17;-ms-flex-order:17;order:17}.flex-order-gt-xs-18{-webkit-order:18;-ms-flex-order:18;order:18}.flex-order-gt-xs-19{-webkit-order:19;-ms-flex-order:19;order:19}.flex-order-gt-xs-20{-webkit-order:20;-ms-flex-order:20;order:20}.flex-offset-gt-xs-0,.offset-gt-xs-0{margin-left:0}[dir=rtl] .flex-offset-gt-xs-0,[dir=rtl] .offset-gt-xs-0{margin-left:auto;margin-left:initial;margin-right:0}.flex-offset-gt-xs-5,.offset-gt-xs-5{margin-left:5%}[dir=rtl] .flex-offset-gt-xs-5,[dir=rtl] .offset-gt-xs-5{margin-left:auto;margin-left:initial;margin-right:5%}.flex-offset-gt-xs-10,.offset-gt-xs-10{margin-left:10%}[dir=rtl] .flex-offset-gt-xs-10,[dir=rtl] .offset-gt-xs-10{margin-left:auto;margin-left:initial;margin-right:10%}.flex-offset-gt-xs-15,.offset-gt-xs-15{margin-left:15%}[dir=rtl] .flex-offset-gt-xs-15,[dir=rtl] .offset-gt-xs-15{margin-left:auto;margin-left:initial;margin-right:15%}.flex-offset-gt-xs-20,.offset-gt-xs-20{margin-left:20%}[dir=rtl] .flex-offset-gt-xs-20,[dir=rtl] .offset-gt-xs-20{margin-left:auto;margin-left:initial;margin-right:20%}.flex-offset-gt-xs-25,.offset-gt-xs-25{margin-left:25%}[dir=rtl] .flex-offset-gt-xs-25,[dir=rtl] .offset-gt-xs-25{margin-left:auto;margin-left:initial;margin-right:25%}.flex-offset-gt-xs-30,.offset-gt-xs-30{margin-left:30%}[dir=rtl] .flex-offset-gt-xs-30,[dir=rtl] .offset-gt-xs-30{margin-left:auto;margin-left:initial;margin-right:30%}.flex-offset-gt-xs-35,.offset-gt-xs-35{margin-left:35%}[dir=rtl] .flex-offset-gt-xs-35,[dir=rtl] .offset-gt-xs-35{margin-left:auto;margin-left:initial;margin-right:35%}.flex-offset-gt-xs-40,.offset-gt-xs-40{margin-left:40%}[dir=rtl] .flex-offset-gt-xs-40,[dir=rtl] .offset-gt-xs-40{margin-left:auto;margin-left:initial;margin-right:40%}.flex-offset-gt-xs-45,.offset-gt-xs-45{margin-left:45%}[dir=rtl] .flex-offset-gt-xs-45,[dir=rtl] .offset-gt-xs-45{margin-left:auto;margin-left:initial;margin-right:45%}.flex-offset-gt-xs-50,.offset-gt-xs-50{margin-left:50%}[dir=rtl] .flex-offset-gt-xs-50,[dir=rtl] .offset-gt-xs-50{margin-left:auto;margin-left:initial;margin-right:50%}.flex-offset-gt-xs-55,.offset-gt-xs-55{margin-left:55%}[dir=rtl] .flex-offset-gt-xs-55,[dir=rtl] .offset-gt-xs-55{margin-left:auto;margin-left:initial;margin-right:55%}.flex-offset-gt-xs-60,.offset-gt-xs-60{margin-left:60%}[dir=rtl] .flex-offset-gt-xs-60,[dir=rtl] .offset-gt-xs-60{margin-left:auto;margin-left:initial;margin-right:60%}.flex-offset-gt-xs-65,.offset-gt-xs-65{margin-left:65%}[dir=rtl] .flex-offset-gt-xs-65,[dir=rtl] .offset-gt-xs-65{margin-left:auto;margin-left:initial;margin-right:65%}.flex-offset-gt-xs-70,.offset-gt-xs-70{margin-left:70%}[dir=rtl] .flex-offset-gt-xs-70,[dir=rtl] .offset-gt-xs-70{margin-left:auto;margin-left:initial;margin-right:70%}.flex-offset-gt-xs-75,.offset-gt-xs-75{margin-left:75%}[dir=rtl] .flex-offset-gt-xs-75,[dir=rtl] .offset-gt-xs-75{margin-left:auto;margin-left:initial;margin-right:75%}.flex-offset-gt-xs-80,.offset-gt-xs-80{margin-left:80%}[dir=rtl] .flex-offset-gt-xs-80,[dir=rtl] .offset-gt-xs-80{margin-left:auto;margin-left:initial;margin-right:80%}.flex-offset-gt-xs-85,.offset-gt-xs-85{margin-left:85%}[dir=rtl] .flex-offset-gt-xs-85,[dir=rtl] .offset-gt-xs-85{margin-left:auto;margin-left:initial;margin-right:85%}.flex-offset-gt-xs-90,.offset-gt-xs-90{margin-left:90%}[dir=rtl] .flex-offset-gt-xs-90,[dir=rtl] .offset-gt-xs-90{margin-left:auto;margin-left:initial;margin-right:90%}.flex-offset-gt-xs-95,.offset-gt-xs-95{margin-left:95%}[dir=rtl] .flex-offset-gt-xs-95,[dir=rtl] .offset-gt-xs-95{margin-left:auto;margin-left:initial;margin-right:95%}.flex-offset-gt-xs-33,.offset-gt-xs-33{margin-left:calc(100% / 3)}.flex-offset-gt-xs-66,.offset-gt-xs-66{margin-left:calc(200% / 3)}[dir=rtl] .flex-offset-gt-xs-66,[dir=rtl] .offset-gt-xs-66{margin-left:auto;margin-left:initial;margin-right:calc(200% / 3)}.layout-align-gt-xs{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.layout-align-gt-xs-start,.layout-align-gt-xs-start-center,.layout-align-gt-xs-start-end,.layout-align-gt-xs-start-start,.layout-align-gt-xs-start-stretch{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.layout-align-gt-xs-center,.layout-align-gt-xs-center-center,.layout-align-gt-xs-center-end,.layout-align-gt-xs-center-start,.layout-align-gt-xs-center-stretch{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.layout-align-gt-xs-end,.layout-align-gt-xs-end-center,.layout-align-gt-xs-end-end,.layout-align-gt-xs-end-start,.layout-align-gt-xs-end-stretch{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.layout-align-gt-xs-space-around,.layout-align-gt-xs-space-around-center,.layout-align-gt-xs-space-around-end,.layout-align-gt-xs-space-around-start,.layout-align-gt-xs-space-around-stretch{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.layout-align-gt-xs-space-between,.layout-align-gt-xs-space-between-center,.layout-align-gt-xs-space-between-end,.layout-align-gt-xs-space-between-start,.layout-align-gt-xs-space-between-stretch{-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.layout-align-gt-xs-center-start,.layout-align-gt-xs-end-start,.layout-align-gt-xs-space-around-start,.layout-align-gt-xs-space-between-start,.layout-align-gt-xs-start-start{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}.layout-align-gt-xs-center-center,.layout-align-gt-xs-end-center,.layout-align-gt-xs-space-around-center,.layout-align-gt-xs-space-between-center,.layout-align-gt-xs-start-center{-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;max-width:100%}.layout-align-gt-xs-center-center>*,.layout-align-gt-xs-end-center>*,.layout-align-gt-xs-space-around-center>*,.layout-align-gt-xs-space-between-center>*,.layout-align-gt-xs-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-gt-xs-center-end,.layout-align-gt-xs-end-end,.layout-align-gt-xs-space-around-end,.layout-align-gt-xs-space-between-end,.layout-align-gt-xs-start-end{-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;-webkit-align-content:flex-end;-ms-flex-line-pack:end;align-content:flex-end}.layout-align-gt-xs-center-stretch,.layout-align-gt-xs-end-stretch,.layout-align-gt-xs-space-around-stretch,.layout-align-gt-xs-space-between-stretch,.layout-align-gt-xs-start-stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch}.flex-gt-xs{-webkit-flex:1;-ms-flex:1;flex:1;box-sizing:border-box}.flex-gt-xs-grow{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;box-sizing:border-box}.flex-gt-xs-initial{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-xs-auto{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-gt-xs-none{-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-gt-xs-noshrink{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-gt-xs-nogrow{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-xs-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box}.layout-gt-xs-row>.flex-gt-xs-0,.layout-row>.flex-gt-xs-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box;min-width:0}.layout-column>.flex-gt-xs-0,.layout-gt-xs-column>.flex-gt-xs-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:100%;max-height:0;box-sizing:border-box;min-height:0}.flex-gt-xs-5,.layout-gt-xs-row>.flex-gt-xs-5,.layout-row>.flex-gt-xs-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:5%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-5,.layout-gt-xs-column>.flex-gt-xs-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:100%;max-height:5%;box-sizing:border-box}.flex-gt-xs-10,.layout-gt-xs-row>.flex-gt-xs-10,.layout-row>.flex-gt-xs-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:10%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-10,.layout-gt-xs-column>.flex-gt-xs-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:100%;max-height:10%;box-sizing:border-box}.flex-gt-xs-15,.layout-gt-xs-row>.flex-gt-xs-15,.layout-row>.flex-gt-xs-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:15%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-15,.layout-gt-xs-column>.flex-gt-xs-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:100%;max-height:15%;box-sizing:border-box}.flex-gt-xs-20,.layout-gt-xs-row>.flex-gt-xs-20,.layout-row>.flex-gt-xs-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:20%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-20,.layout-gt-xs-column>.flex-gt-xs-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:100%;max-height:20%;box-sizing:border-box}.flex-gt-xs-25,.layout-gt-xs-row>.flex-gt-xs-25,.layout-row>.flex-gt-xs-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:25%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-25,.layout-gt-xs-column>.flex-gt-xs-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:100%;max-height:25%;box-sizing:border-box}.flex-gt-xs-30,.layout-gt-xs-row>.flex-gt-xs-30,.layout-row>.flex-gt-xs-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:30%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-30,.layout-gt-xs-column>.flex-gt-xs-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:100%;max-height:30%;box-sizing:border-box}.flex-gt-xs-35,.layout-gt-xs-row>.flex-gt-xs-35,.layout-row>.flex-gt-xs-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:35%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-35,.layout-gt-xs-column>.flex-gt-xs-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:100%;max-height:35%;box-sizing:border-box}.flex-gt-xs-40,.layout-gt-xs-row>.flex-gt-xs-40,.layout-row>.flex-gt-xs-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:40%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-40,.layout-gt-xs-column>.flex-gt-xs-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:100%;max-height:40%;box-sizing:border-box}.flex-gt-xs-45,.layout-gt-xs-row>.flex-gt-xs-45,.layout-row>.flex-gt-xs-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:45%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-45,.layout-gt-xs-column>.flex-gt-xs-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:100%;max-height:45%;box-sizing:border-box}.flex-gt-xs-50,.layout-gt-xs-row>.flex-gt-xs-50,.layout-row>.flex-gt-xs-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:50%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-50,.layout-gt-xs-column>.flex-gt-xs-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:100%;max-height:50%;box-sizing:border-box}.flex-gt-xs-55,.layout-gt-xs-row>.flex-gt-xs-55,.layout-row>.flex-gt-xs-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:55%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-55,.layout-gt-xs-column>.flex-gt-xs-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:100%;max-height:55%;box-sizing:border-box}.flex-gt-xs-60,.layout-gt-xs-row>.flex-gt-xs-60,.layout-row>.flex-gt-xs-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:60%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-60,.layout-gt-xs-column>.flex-gt-xs-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:100%;max-height:60%;box-sizing:border-box}.flex-gt-xs-65,.layout-gt-xs-row>.flex-gt-xs-65,.layout-row>.flex-gt-xs-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:65%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-65,.layout-gt-xs-column>.flex-gt-xs-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:100%;max-height:65%;box-sizing:border-box}.flex-gt-xs-70,.layout-gt-xs-row>.flex-gt-xs-70,.layout-row>.flex-gt-xs-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:70%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-70,.layout-gt-xs-column>.flex-gt-xs-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:100%;max-height:70%;box-sizing:border-box}.flex-gt-xs-75,.layout-gt-xs-row>.flex-gt-xs-75,.layout-row>.flex-gt-xs-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:75%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-75,.layout-gt-xs-column>.flex-gt-xs-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:100%;max-height:75%;box-sizing:border-box}.flex-gt-xs-80,.layout-gt-xs-row>.flex-gt-xs-80,.layout-row>.flex-gt-xs-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:80%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-80,.layout-gt-xs-column>.flex-gt-xs-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:100%;max-height:80%;box-sizing:border-box}.flex-gt-xs-85,.layout-gt-xs-row>.flex-gt-xs-85,.layout-row>.flex-gt-xs-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:85%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-85,.layout-gt-xs-column>.flex-gt-xs-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:100%;max-height:85%;box-sizing:border-box}.flex-gt-xs-90,.layout-gt-xs-row>.flex-gt-xs-90,.layout-row>.flex-gt-xs-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:90%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-90,.layout-gt-xs-column>.flex-gt-xs-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:100%;max-height:90%;box-sizing:border-box}.flex-gt-xs-95,.layout-gt-xs-row>.flex-gt-xs-95,.layout-row>.flex-gt-xs-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:95%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-xs-95,.layout-gt-xs-column>.flex-gt-xs-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:100%;max-height:95%;box-sizing:border-box}.flex-gt-xs-100,.layout-column>.flex-gt-xs-100,.layout-gt-xs-column>.flex-gt-xs-100,.layout-gt-xs-row>.flex-gt-xs-100,.layout-row>.flex-gt-xs-100{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-gt-xs-row>.flex-gt-xs-33,.layout-row>.flex-gt-xs-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-gt-xs-row>.flex-gt-xs-66,.layout-row>.flex-gt-xs-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-gt-xs-row>.flex,.layout-row>.flex{min-width:0}.layout-column>.flex-gt-xs-33,.layout-gt-xs-column>.flex-gt-xs-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-gt-xs-66,.layout-gt-xs-column>.flex-gt-xs-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-column>.flex,.layout-gt-xs-column>.flex{min-height:0}.layout-gt-xs,.layout-gt-xs-column,.layout-gt-xs-row{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.layout-gt-xs-column{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.layout-gt-xs-row{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}}@media (min-width:600px) and (max-width:959px){.hide-gt-xs:not(.show-gt-xs):not(.show-sm):not(.show),.hide-sm:not(.show-gt-xs):not(.show-sm):not(.show),.hide:not(.show-gt-xs):not(.show-sm):not(.show){display:none}.flex-order-sm--20{-webkit-order:-20;-ms-flex-order:-20;order:-20}.flex-order-sm--19{-webkit-order:-19;-ms-flex-order:-19;order:-19}.flex-order-sm--18{-webkit-order:-18;-ms-flex-order:-18;order:-18}.flex-order-sm--17{-webkit-order:-17;-ms-flex-order:-17;order:-17}.flex-order-sm--16{-webkit-order:-16;-ms-flex-order:-16;order:-16}.flex-order-sm--15{-webkit-order:-15;-ms-flex-order:-15;order:-15}.flex-order-sm--14{-webkit-order:-14;-ms-flex-order:-14;order:-14}.flex-order-sm--13{-webkit-order:-13;-ms-flex-order:-13;order:-13}.flex-order-sm--12{-webkit-order:-12;-ms-flex-order:-12;order:-12}.flex-order-sm--11{-webkit-order:-11;-ms-flex-order:-11;order:-11}.flex-order-sm--10{-webkit-order:-10;-ms-flex-order:-10;order:-10}.flex-order-sm--9{-webkit-order:-9;-ms-flex-order:-9;order:-9}.flex-order-sm--8{-webkit-order:-8;-ms-flex-order:-8;order:-8}.flex-order-sm--7{-webkit-order:-7;-ms-flex-order:-7;order:-7}.flex-order-sm--6{-webkit-order:-6;-ms-flex-order:-6;order:-6}.flex-order-sm--5{-webkit-order:-5;-ms-flex-order:-5;order:-5}.flex-order-sm--4{-webkit-order:-4;-ms-flex-order:-4;order:-4}.flex-order-sm--3{-webkit-order:-3;-ms-flex-order:-3;order:-3}.flex-order-sm--2{-webkit-order:-2;-ms-flex-order:-2;order:-2}.flex-order-sm--1{-webkit-order:-1;-ms-flex-order:-1;order:-1}.flex-order-sm-0{-webkit-order:0;-ms-flex-order:0;order:0}.flex-order-sm-1{-webkit-order:1;-ms-flex-order:1;order:1}.flex-order-sm-2{-webkit-order:2;-ms-flex-order:2;order:2}.flex-order-sm-3{-webkit-order:3;-ms-flex-order:3;order:3}.flex-order-sm-4{-webkit-order:4;-ms-flex-order:4;order:4}.flex-order-sm-5{-webkit-order:5;-ms-flex-order:5;order:5}.flex-order-sm-6{-webkit-order:6;-ms-flex-order:6;order:6}.flex-order-sm-7{-webkit-order:7;-ms-flex-order:7;order:7}.flex-order-sm-8{-webkit-order:8;-ms-flex-order:8;order:8}.flex-order-sm-9{-webkit-order:9;-ms-flex-order:9;order:9}.flex-order-sm-10{-webkit-order:10;-ms-flex-order:10;order:10}.flex-order-sm-11{-webkit-order:11;-ms-flex-order:11;order:11}.flex-order-sm-12{-webkit-order:12;-ms-flex-order:12;order:12}.flex-order-sm-13{-webkit-order:13;-ms-flex-order:13;order:13}.flex-order-sm-14{-webkit-order:14;-ms-flex-order:14;order:14}.flex-order-sm-15{-webkit-order:15;-ms-flex-order:15;order:15}.flex-order-sm-16{-webkit-order:16;-ms-flex-order:16;order:16}.flex-order-sm-17{-webkit-order:17;-ms-flex-order:17;order:17}.flex-order-sm-18{-webkit-order:18;-ms-flex-order:18;order:18}.flex-order-sm-19{-webkit-order:19;-ms-flex-order:19;order:19}.flex-order-sm-20{-webkit-order:20;-ms-flex-order:20;order:20}.flex-offset-sm-0,.offset-sm-0{margin-left:0}[dir=rtl] .flex-offset-sm-0,[dir=rtl] .offset-sm-0{margin-left:auto;margin-left:initial;margin-right:0}.flex-offset-sm-5,.offset-sm-5{margin-left:5%}[dir=rtl] .flex-offset-sm-5,[dir=rtl] .offset-sm-5{margin-left:auto;margin-left:initial;margin-right:5%}.flex-offset-sm-10,.offset-sm-10{margin-left:10%}[dir=rtl] .flex-offset-sm-10,[dir=rtl] .offset-sm-10{margin-left:auto;margin-left:initial;margin-right:10%}.flex-offset-sm-15,.offset-sm-15{margin-left:15%}[dir=rtl] .flex-offset-sm-15,[dir=rtl] .offset-sm-15{margin-left:auto;margin-left:initial;margin-right:15%}.flex-offset-sm-20,.offset-sm-20{margin-left:20%}[dir=rtl] .flex-offset-sm-20,[dir=rtl] .offset-sm-20{margin-left:auto;margin-left:initial;margin-right:20%}.flex-offset-sm-25,.offset-sm-25{margin-left:25%}[dir=rtl] .flex-offset-sm-25,[dir=rtl] .offset-sm-25{margin-left:auto;margin-left:initial;margin-right:25%}.flex-offset-sm-30,.offset-sm-30{margin-left:30%}[dir=rtl] .flex-offset-sm-30,[dir=rtl] .offset-sm-30{margin-left:auto;margin-left:initial;margin-right:30%}.flex-offset-sm-35,.offset-sm-35{margin-left:35%}[dir=rtl] .flex-offset-sm-35,[dir=rtl] .offset-sm-35{margin-left:auto;margin-left:initial;margin-right:35%}.flex-offset-sm-40,.offset-sm-40{margin-left:40%}[dir=rtl] .flex-offset-sm-40,[dir=rtl] .offset-sm-40{margin-left:auto;margin-left:initial;margin-right:40%}.flex-offset-sm-45,.offset-sm-45{margin-left:45%}[dir=rtl] .flex-offset-sm-45,[dir=rtl] .offset-sm-45{margin-left:auto;margin-left:initial;margin-right:45%}.flex-offset-sm-50,.offset-sm-50{margin-left:50%}[dir=rtl] .flex-offset-sm-50,[dir=rtl] .offset-sm-50{margin-left:auto;margin-left:initial;margin-right:50%}.flex-offset-sm-55,.offset-sm-55{margin-left:55%}[dir=rtl] .flex-offset-sm-55,[dir=rtl] .offset-sm-55{margin-left:auto;margin-left:initial;margin-right:55%}.flex-offset-sm-60,.offset-sm-60{margin-left:60%}[dir=rtl] .flex-offset-sm-60,[dir=rtl] .offset-sm-60{margin-left:auto;margin-left:initial;margin-right:60%}.flex-offset-sm-65,.offset-sm-65{margin-left:65%}[dir=rtl] .flex-offset-sm-65,[dir=rtl] .offset-sm-65{margin-left:auto;margin-left:initial;margin-right:65%}.flex-offset-sm-70,.offset-sm-70{margin-left:70%}[dir=rtl] .flex-offset-sm-70,[dir=rtl] .offset-sm-70{margin-left:auto;margin-left:initial;margin-right:70%}.flex-offset-sm-75,.offset-sm-75{margin-left:75%}[dir=rtl] .flex-offset-sm-75,[dir=rtl] .offset-sm-75{margin-left:auto;margin-left:initial;margin-right:75%}.flex-offset-sm-80,.offset-sm-80{margin-left:80%}[dir=rtl] .flex-offset-sm-80,[dir=rtl] .offset-sm-80{margin-left:auto;margin-left:initial;margin-right:80%}.flex-offset-sm-85,.offset-sm-85{margin-left:85%}[dir=rtl] .flex-offset-sm-85,[dir=rtl] .offset-sm-85{margin-left:auto;margin-left:initial;margin-right:85%}.flex-offset-sm-90,.offset-sm-90{margin-left:90%}[dir=rtl] .flex-offset-sm-90,[dir=rtl] .offset-sm-90{margin-left:auto;margin-left:initial;margin-right:90%}.flex-offset-sm-95,.offset-sm-95{margin-left:95%}[dir=rtl] .flex-offset-sm-95,[dir=rtl] .offset-sm-95{margin-left:auto;margin-left:initial;margin-right:95%}.flex-offset-sm-33,.offset-sm-33{margin-left:calc(100% / 3)}.flex-offset-sm-66,.offset-sm-66{margin-left:calc(200% / 3)}[dir=rtl] .flex-offset-sm-66,[dir=rtl] .offset-sm-66{margin-left:auto;margin-left:initial;margin-right:calc(200% / 3)}.layout-align-sm{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.layout-align-sm-start,.layout-align-sm-start-center,.layout-align-sm-start-end,.layout-align-sm-start-start,.layout-align-sm-start-stretch{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.layout-align-sm-center,.layout-align-sm-center-center,.layout-align-sm-center-end,.layout-align-sm-center-start,.layout-align-sm-center-stretch{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.layout-align-sm-end,.layout-align-sm-end-center,.layout-align-sm-end-end,.layout-align-sm-end-start,.layout-align-sm-end-stretch{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.layout-align-sm-space-around,.layout-align-sm-space-around-center,.layout-align-sm-space-around-end,.layout-align-sm-space-around-start,.layout-align-sm-space-around-stretch{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.layout-align-sm-space-between,.layout-align-sm-space-between-center,.layout-align-sm-space-between-end,.layout-align-sm-space-between-start,.layout-align-sm-space-between-stretch{-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.layout-align-sm-center-start,.layout-align-sm-end-start,.layout-align-sm-space-around-start,.layout-align-sm-space-between-start,.layout-align-sm-start-start{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}.layout-align-sm-center-center,.layout-align-sm-end-center,.layout-align-sm-space-around-center,.layout-align-sm-space-between-center,.layout-align-sm-start-center{-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;max-width:100%}.layout-align-sm-center-center>*,.layout-align-sm-end-center>*,.layout-align-sm-space-around-center>*,.layout-align-sm-space-between-center>*,.layout-align-sm-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-sm-center-end,.layout-align-sm-end-end,.layout-align-sm-space-around-end,.layout-align-sm-space-between-end,.layout-align-sm-start-end{-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;-webkit-align-content:flex-end;-ms-flex-line-pack:end;align-content:flex-end}.layout-align-sm-center-stretch,.layout-align-sm-end-stretch,.layout-align-sm-space-around-stretch,.layout-align-sm-space-between-stretch,.layout-align-sm-start-stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch}.flex-sm{-webkit-flex:1;-ms-flex:1;flex:1;box-sizing:border-box}.flex-sm-grow{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;box-sizing:border-box}.flex-sm-initial{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-sm-auto{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-sm-none{-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-sm-noshrink{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-sm-nogrow{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-sm-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-sm-0,.layout-sm-row>.flex-sm-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box;min-width:0}.layout-column>.flex-sm-0,.layout-sm-column>.flex-sm-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:100%;max-height:0;box-sizing:border-box;min-height:0}.flex-sm-5,.layout-row>.flex-sm-5,.layout-sm-row>.flex-sm-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:5%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-5,.layout-sm-column>.flex-sm-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:100%;max-height:5%;box-sizing:border-box}.flex-sm-10,.layout-row>.flex-sm-10,.layout-sm-row>.flex-sm-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:10%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-10,.layout-sm-column>.flex-sm-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:100%;max-height:10%;box-sizing:border-box}.flex-sm-15,.layout-row>.flex-sm-15,.layout-sm-row>.flex-sm-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:15%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-15,.layout-sm-column>.flex-sm-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:100%;max-height:15%;box-sizing:border-box}.flex-sm-20,.layout-row>.flex-sm-20,.layout-sm-row>.flex-sm-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:20%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-20,.layout-sm-column>.flex-sm-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:100%;max-height:20%;box-sizing:border-box}.flex-sm-25,.layout-row>.flex-sm-25,.layout-sm-row>.flex-sm-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:25%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-25,.layout-sm-column>.flex-sm-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:100%;max-height:25%;box-sizing:border-box}.flex-sm-30,.layout-row>.flex-sm-30,.layout-sm-row>.flex-sm-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:30%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-30,.layout-sm-column>.flex-sm-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:100%;max-height:30%;box-sizing:border-box}.flex-sm-35,.layout-row>.flex-sm-35,.layout-sm-row>.flex-sm-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:35%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-35,.layout-sm-column>.flex-sm-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:100%;max-height:35%;box-sizing:border-box}.flex-sm-40,.layout-row>.flex-sm-40,.layout-sm-row>.flex-sm-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:40%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-40,.layout-sm-column>.flex-sm-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:100%;max-height:40%;box-sizing:border-box}.flex-sm-45,.layout-row>.flex-sm-45,.layout-sm-row>.flex-sm-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:45%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-45,.layout-sm-column>.flex-sm-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:100%;max-height:45%;box-sizing:border-box}.flex-sm-50,.layout-row>.flex-sm-50,.layout-sm-row>.flex-sm-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:50%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-50,.layout-sm-column>.flex-sm-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:100%;max-height:50%;box-sizing:border-box}.flex-sm-55,.layout-row>.flex-sm-55,.layout-sm-row>.flex-sm-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:55%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-55,.layout-sm-column>.flex-sm-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:100%;max-height:55%;box-sizing:border-box}.flex-sm-60,.layout-row>.flex-sm-60,.layout-sm-row>.flex-sm-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:60%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-60,.layout-sm-column>.flex-sm-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:100%;max-height:60%;box-sizing:border-box}.flex-sm-65,.layout-row>.flex-sm-65,.layout-sm-row>.flex-sm-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:65%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-65,.layout-sm-column>.flex-sm-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:100%;max-height:65%;box-sizing:border-box}.flex-sm-70,.layout-row>.flex-sm-70,.layout-sm-row>.flex-sm-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:70%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-70,.layout-sm-column>.flex-sm-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:100%;max-height:70%;box-sizing:border-box}.flex-sm-75,.layout-row>.flex-sm-75,.layout-sm-row>.flex-sm-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:75%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-75,.layout-sm-column>.flex-sm-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:100%;max-height:75%;box-sizing:border-box}.flex-sm-80,.layout-row>.flex-sm-80,.layout-sm-row>.flex-sm-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:80%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-80,.layout-sm-column>.flex-sm-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:100%;max-height:80%;box-sizing:border-box}.flex-sm-85,.layout-row>.flex-sm-85,.layout-sm-row>.flex-sm-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:85%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-85,.layout-sm-column>.flex-sm-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:100%;max-height:85%;box-sizing:border-box}.flex-sm-90,.layout-row>.flex-sm-90,.layout-sm-row>.flex-sm-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:90%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-90,.layout-sm-column>.flex-sm-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:100%;max-height:90%;box-sizing:border-box}.flex-sm-95,.layout-row>.flex-sm-95,.layout-sm-row>.flex-sm-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:95%;max-height:100%;box-sizing:border-box}.layout-column>.flex-sm-95,.layout-sm-column>.flex-sm-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:100%;max-height:95%;box-sizing:border-box}.flex-sm-100,.layout-column>.flex-sm-100,.layout-row>.flex-sm-100,.layout-sm-column>.flex-sm-100,.layout-sm-row>.flex-sm-100{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-sm-33,.layout-sm-row>.flex-sm-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-sm-66,.layout-sm-row>.flex-sm-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-row>.flex,.layout-sm-row>.flex{min-width:0}.layout-column>.flex-sm-33,.layout-sm-column>.flex-sm-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-sm-66,.layout-sm-column>.flex-sm-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-column>.flex,.layout-sm-column>.flex{min-height:0}.layout-sm,.layout-sm-column,.layout-sm-row{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.layout-sm-column{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.layout-sm-row{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}}@media (min-width:960px){.flex-order-gt-sm--20{-webkit-order:-20;-ms-flex-order:-20;order:-20}.flex-order-gt-sm--19{-webkit-order:-19;-ms-flex-order:-19;order:-19}.flex-order-gt-sm--18{-webkit-order:-18;-ms-flex-order:-18;order:-18}.flex-order-gt-sm--17{-webkit-order:-17;-ms-flex-order:-17;order:-17}.flex-order-gt-sm--16{-webkit-order:-16;-ms-flex-order:-16;order:-16}.flex-order-gt-sm--15{-webkit-order:-15;-ms-flex-order:-15;order:-15}.flex-order-gt-sm--14{-webkit-order:-14;-ms-flex-order:-14;order:-14}.flex-order-gt-sm--13{-webkit-order:-13;-ms-flex-order:-13;order:-13}.flex-order-gt-sm--12{-webkit-order:-12;-ms-flex-order:-12;order:-12}.flex-order-gt-sm--11{-webkit-order:-11;-ms-flex-order:-11;order:-11}.flex-order-gt-sm--10{-webkit-order:-10;-ms-flex-order:-10;order:-10}.flex-order-gt-sm--9{-webkit-order:-9;-ms-flex-order:-9;order:-9}.flex-order-gt-sm--8{-webkit-order:-8;-ms-flex-order:-8;order:-8}.flex-order-gt-sm--7{-webkit-order:-7;-ms-flex-order:-7;order:-7}.flex-order-gt-sm--6{-webkit-order:-6;-ms-flex-order:-6;order:-6}.flex-order-gt-sm--5{-webkit-order:-5;-ms-flex-order:-5;order:-5}.flex-order-gt-sm--4{-webkit-order:-4;-ms-flex-order:-4;order:-4}.flex-order-gt-sm--3{-webkit-order:-3;-ms-flex-order:-3;order:-3}.flex-order-gt-sm--2{-webkit-order:-2;-ms-flex-order:-2;order:-2}.flex-order-gt-sm--1{-webkit-order:-1;-ms-flex-order:-1;order:-1}.flex-order-gt-sm-0{-webkit-order:0;-ms-flex-order:0;order:0}.flex-order-gt-sm-1{-webkit-order:1;-ms-flex-order:1;order:1}.flex-order-gt-sm-2{-webkit-order:2;-ms-flex-order:2;order:2}.flex-order-gt-sm-3{-webkit-order:3;-ms-flex-order:3;order:3}.flex-order-gt-sm-4{-webkit-order:4;-ms-flex-order:4;order:4}.flex-order-gt-sm-5{-webkit-order:5;-ms-flex-order:5;order:5}.flex-order-gt-sm-6{-webkit-order:6;-ms-flex-order:6;order:6}.flex-order-gt-sm-7{-webkit-order:7;-ms-flex-order:7;order:7}.flex-order-gt-sm-8{-webkit-order:8;-ms-flex-order:8;order:8}.flex-order-gt-sm-9{-webkit-order:9;-ms-flex-order:9;order:9}.flex-order-gt-sm-10{-webkit-order:10;-ms-flex-order:10;order:10}.flex-order-gt-sm-11{-webkit-order:11;-ms-flex-order:11;order:11}.flex-order-gt-sm-12{-webkit-order:12;-ms-flex-order:12;order:12}.flex-order-gt-sm-13{-webkit-order:13;-ms-flex-order:13;order:13}.flex-order-gt-sm-14{-webkit-order:14;-ms-flex-order:14;order:14}.flex-order-gt-sm-15{-webkit-order:15;-ms-flex-order:15;order:15}.flex-order-gt-sm-16{-webkit-order:16;-ms-flex-order:16;order:16}.flex-order-gt-sm-17{-webkit-order:17;-ms-flex-order:17;order:17}.flex-order-gt-sm-18{-webkit-order:18;-ms-flex-order:18;order:18}.flex-order-gt-sm-19{-webkit-order:19;-ms-flex-order:19;order:19}.flex-order-gt-sm-20{-webkit-order:20;-ms-flex-order:20;order:20}.flex-offset-gt-sm-0,.offset-gt-sm-0{margin-left:0}[dir=rtl] .flex-offset-gt-sm-0,[dir=rtl] .offset-gt-sm-0{margin-left:auto;margin-left:initial;margin-right:0}.flex-offset-gt-sm-5,.offset-gt-sm-5{margin-left:5%}[dir=rtl] .flex-offset-gt-sm-5,[dir=rtl] .offset-gt-sm-5{margin-left:auto;margin-left:initial;margin-right:5%}.flex-offset-gt-sm-10,.offset-gt-sm-10{margin-left:10%}[dir=rtl] .flex-offset-gt-sm-10,[dir=rtl] .offset-gt-sm-10{margin-left:auto;margin-left:initial;margin-right:10%}.flex-offset-gt-sm-15,.offset-gt-sm-15{margin-left:15%}[dir=rtl] .flex-offset-gt-sm-15,[dir=rtl] .offset-gt-sm-15{margin-left:auto;margin-left:initial;margin-right:15%}.flex-offset-gt-sm-20,.offset-gt-sm-20{margin-left:20%}[dir=rtl] .flex-offset-gt-sm-20,[dir=rtl] .offset-gt-sm-20{margin-left:auto;margin-left:initial;margin-right:20%}.flex-offset-gt-sm-25,.offset-gt-sm-25{margin-left:25%}[dir=rtl] .flex-offset-gt-sm-25,[dir=rtl] .offset-gt-sm-25{margin-left:auto;margin-left:initial;margin-right:25%}.flex-offset-gt-sm-30,.offset-gt-sm-30{margin-left:30%}[dir=rtl] .flex-offset-gt-sm-30,[dir=rtl] .offset-gt-sm-30{margin-left:auto;margin-left:initial;margin-right:30%}.flex-offset-gt-sm-35,.offset-gt-sm-35{margin-left:35%}[dir=rtl] .flex-offset-gt-sm-35,[dir=rtl] .offset-gt-sm-35{margin-left:auto;margin-left:initial;margin-right:35%}.flex-offset-gt-sm-40,.offset-gt-sm-40{margin-left:40%}[dir=rtl] .flex-offset-gt-sm-40,[dir=rtl] .offset-gt-sm-40{margin-left:auto;margin-left:initial;margin-right:40%}.flex-offset-gt-sm-45,.offset-gt-sm-45{margin-left:45%}[dir=rtl] .flex-offset-gt-sm-45,[dir=rtl] .offset-gt-sm-45{margin-left:auto;margin-left:initial;margin-right:45%}.flex-offset-gt-sm-50,.offset-gt-sm-50{margin-left:50%}[dir=rtl] .flex-offset-gt-sm-50,[dir=rtl] .offset-gt-sm-50{margin-left:auto;margin-left:initial;margin-right:50%}.flex-offset-gt-sm-55,.offset-gt-sm-55{margin-left:55%}[dir=rtl] .flex-offset-gt-sm-55,[dir=rtl] .offset-gt-sm-55{margin-left:auto;margin-left:initial;margin-right:55%}.flex-offset-gt-sm-60,.offset-gt-sm-60{margin-left:60%}[dir=rtl] .flex-offset-gt-sm-60,[dir=rtl] .offset-gt-sm-60{margin-left:auto;margin-left:initial;margin-right:60%}.flex-offset-gt-sm-65,.offset-gt-sm-65{margin-left:65%}[dir=rtl] .flex-offset-gt-sm-65,[dir=rtl] .offset-gt-sm-65{margin-left:auto;margin-left:initial;margin-right:65%}.flex-offset-gt-sm-70,.offset-gt-sm-70{margin-left:70%}[dir=rtl] .flex-offset-gt-sm-70,[dir=rtl] .offset-gt-sm-70{margin-left:auto;margin-left:initial;margin-right:70%}.flex-offset-gt-sm-75,.offset-gt-sm-75{margin-left:75%}[dir=rtl] .flex-offset-gt-sm-75,[dir=rtl] .offset-gt-sm-75{margin-left:auto;margin-left:initial;margin-right:75%}.flex-offset-gt-sm-80,.offset-gt-sm-80{margin-left:80%}[dir=rtl] .flex-offset-gt-sm-80,[dir=rtl] .offset-gt-sm-80{margin-left:auto;margin-left:initial;margin-right:80%}.flex-offset-gt-sm-85,.offset-gt-sm-85{margin-left:85%}[dir=rtl] .flex-offset-gt-sm-85,[dir=rtl] .offset-gt-sm-85{margin-left:auto;margin-left:initial;margin-right:85%}.flex-offset-gt-sm-90,.offset-gt-sm-90{margin-left:90%}[dir=rtl] .flex-offset-gt-sm-90,[dir=rtl] .offset-gt-sm-90{margin-left:auto;margin-left:initial;margin-right:90%}.flex-offset-gt-sm-95,.offset-gt-sm-95{margin-left:95%}[dir=rtl] .flex-offset-gt-sm-95,[dir=rtl] .offset-gt-sm-95{margin-left:auto;margin-left:initial;margin-right:95%}.flex-offset-gt-sm-33,.offset-gt-sm-33{margin-left:calc(100% / 3)}.flex-offset-gt-sm-66,.offset-gt-sm-66{margin-left:calc(200% / 3)}[dir=rtl] .flex-offset-gt-sm-66,[dir=rtl] .offset-gt-sm-66{margin-left:auto;margin-left:initial;margin-right:calc(200% / 3)}.layout-align-gt-sm{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.layout-align-gt-sm-start,.layout-align-gt-sm-start-center,.layout-align-gt-sm-start-end,.layout-align-gt-sm-start-start,.layout-align-gt-sm-start-stretch{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.layout-align-gt-sm-center,.layout-align-gt-sm-center-center,.layout-align-gt-sm-center-end,.layout-align-gt-sm-center-start,.layout-align-gt-sm-center-stretch{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.layout-align-gt-sm-end,.layout-align-gt-sm-end-center,.layout-align-gt-sm-end-end,.layout-align-gt-sm-end-start,.layout-align-gt-sm-end-stretch{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.layout-align-gt-sm-space-around,.layout-align-gt-sm-space-around-center,.layout-align-gt-sm-space-around-end,.layout-align-gt-sm-space-around-start,.layout-align-gt-sm-space-around-stretch{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.layout-align-gt-sm-space-between,.layout-align-gt-sm-space-between-center,.layout-align-gt-sm-space-between-end,.layout-align-gt-sm-space-between-start,.layout-align-gt-sm-space-between-stretch{-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.layout-align-gt-sm-center-start,.layout-align-gt-sm-end-start,.layout-align-gt-sm-space-around-start,.layout-align-gt-sm-space-between-start,.layout-align-gt-sm-start-start{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}.layout-align-gt-sm-center-center,.layout-align-gt-sm-end-center,.layout-align-gt-sm-space-around-center,.layout-align-gt-sm-space-between-center,.layout-align-gt-sm-start-center{-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;max-width:100%}.layout-align-gt-sm-center-center>*,.layout-align-gt-sm-end-center>*,.layout-align-gt-sm-space-around-center>*,.layout-align-gt-sm-space-between-center>*,.layout-align-gt-sm-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-gt-sm-center-end,.layout-align-gt-sm-end-end,.layout-align-gt-sm-space-around-end,.layout-align-gt-sm-space-between-end,.layout-align-gt-sm-start-end{-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;-webkit-align-content:flex-end;-ms-flex-line-pack:end;align-content:flex-end}.layout-align-gt-sm-center-stretch,.layout-align-gt-sm-end-stretch,.layout-align-gt-sm-space-around-stretch,.layout-align-gt-sm-space-between-stretch,.layout-align-gt-sm-start-stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch}.flex-gt-sm{-webkit-flex:1;-ms-flex:1;flex:1;box-sizing:border-box}.flex-gt-sm-grow{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;box-sizing:border-box}.flex-gt-sm-initial{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-sm-auto{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-gt-sm-none{-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-gt-sm-noshrink{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-gt-sm-nogrow{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-sm-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box}.layout-gt-sm-row>.flex-gt-sm-0,.layout-row>.flex-gt-sm-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box;min-width:0}.layout-column>.flex-gt-sm-0,.layout-gt-sm-column>.flex-gt-sm-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:100%;max-height:0;box-sizing:border-box;min-height:0}.flex-gt-sm-5,.layout-gt-sm-row>.flex-gt-sm-5,.layout-row>.flex-gt-sm-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:5%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-5,.layout-gt-sm-column>.flex-gt-sm-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:100%;max-height:5%;box-sizing:border-box}.flex-gt-sm-10,.layout-gt-sm-row>.flex-gt-sm-10,.layout-row>.flex-gt-sm-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:10%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-10,.layout-gt-sm-column>.flex-gt-sm-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:100%;max-height:10%;box-sizing:border-box}.flex-gt-sm-15,.layout-gt-sm-row>.flex-gt-sm-15,.layout-row>.flex-gt-sm-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:15%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-15,.layout-gt-sm-column>.flex-gt-sm-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:100%;max-height:15%;box-sizing:border-box}.flex-gt-sm-20,.layout-gt-sm-row>.flex-gt-sm-20,.layout-row>.flex-gt-sm-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:20%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-20,.layout-gt-sm-column>.flex-gt-sm-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:100%;max-height:20%;box-sizing:border-box}.flex-gt-sm-25,.layout-gt-sm-row>.flex-gt-sm-25,.layout-row>.flex-gt-sm-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:25%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-25,.layout-gt-sm-column>.flex-gt-sm-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:100%;max-height:25%;box-sizing:border-box}.flex-gt-sm-30,.layout-gt-sm-row>.flex-gt-sm-30,.layout-row>.flex-gt-sm-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:30%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-30,.layout-gt-sm-column>.flex-gt-sm-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:100%;max-height:30%;box-sizing:border-box}.flex-gt-sm-35,.layout-gt-sm-row>.flex-gt-sm-35,.layout-row>.flex-gt-sm-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:35%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-35,.layout-gt-sm-column>.flex-gt-sm-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:100%;max-height:35%;box-sizing:border-box}.flex-gt-sm-40,.layout-gt-sm-row>.flex-gt-sm-40,.layout-row>.flex-gt-sm-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:40%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-40,.layout-gt-sm-column>.flex-gt-sm-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:100%;max-height:40%;box-sizing:border-box}.flex-gt-sm-45,.layout-gt-sm-row>.flex-gt-sm-45,.layout-row>.flex-gt-sm-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:45%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-45,.layout-gt-sm-column>.flex-gt-sm-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:100%;max-height:45%;box-sizing:border-box}.flex-gt-sm-50,.layout-gt-sm-row>.flex-gt-sm-50,.layout-row>.flex-gt-sm-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:50%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-50,.layout-gt-sm-column>.flex-gt-sm-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:100%;max-height:50%;box-sizing:border-box}.flex-gt-sm-55,.layout-gt-sm-row>.flex-gt-sm-55,.layout-row>.flex-gt-sm-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:55%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-55,.layout-gt-sm-column>.flex-gt-sm-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:100%;max-height:55%;box-sizing:border-box}.flex-gt-sm-60,.layout-gt-sm-row>.flex-gt-sm-60,.layout-row>.flex-gt-sm-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:60%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-60,.layout-gt-sm-column>.flex-gt-sm-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:100%;max-height:60%;box-sizing:border-box}.flex-gt-sm-65,.layout-gt-sm-row>.flex-gt-sm-65,.layout-row>.flex-gt-sm-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:65%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-65,.layout-gt-sm-column>.flex-gt-sm-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:100%;max-height:65%;box-sizing:border-box}.flex-gt-sm-70,.layout-gt-sm-row>.flex-gt-sm-70,.layout-row>.flex-gt-sm-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:70%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-70,.layout-gt-sm-column>.flex-gt-sm-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:100%;max-height:70%;box-sizing:border-box}.flex-gt-sm-75,.layout-gt-sm-row>.flex-gt-sm-75,.layout-row>.flex-gt-sm-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:75%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-75,.layout-gt-sm-column>.flex-gt-sm-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:100%;max-height:75%;box-sizing:border-box}.flex-gt-sm-80,.layout-gt-sm-row>.flex-gt-sm-80,.layout-row>.flex-gt-sm-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:80%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-80,.layout-gt-sm-column>.flex-gt-sm-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:100%;max-height:80%;box-sizing:border-box}.flex-gt-sm-85,.layout-gt-sm-row>.flex-gt-sm-85,.layout-row>.flex-gt-sm-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:85%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-85,.layout-gt-sm-column>.flex-gt-sm-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:100%;max-height:85%;box-sizing:border-box}.flex-gt-sm-90,.layout-gt-sm-row>.flex-gt-sm-90,.layout-row>.flex-gt-sm-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:90%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-90,.layout-gt-sm-column>.flex-gt-sm-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:100%;max-height:90%;box-sizing:border-box}.flex-gt-sm-95,.layout-gt-sm-row>.flex-gt-sm-95,.layout-row>.flex-gt-sm-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:95%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-sm-95,.layout-gt-sm-column>.flex-gt-sm-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:100%;max-height:95%;box-sizing:border-box}.flex-gt-sm-100,.layout-column>.flex-gt-sm-100,.layout-gt-sm-column>.flex-gt-sm-100,.layout-gt-sm-row>.flex-gt-sm-100,.layout-row>.flex-gt-sm-100{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-gt-sm-row>.flex-gt-sm-33,.layout-row>.flex-gt-sm-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-gt-sm-row>.flex-gt-sm-66,.layout-row>.flex-gt-sm-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-gt-sm-row>.flex,.layout-row>.flex{min-width:0}.layout-column>.flex-gt-sm-33,.layout-gt-sm-column>.flex-gt-sm-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-gt-sm-66,.layout-gt-sm-column>.flex-gt-sm-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-column>.flex,.layout-gt-sm-column>.flex{min-height:0}.layout-gt-sm,.layout-gt-sm-column,.layout-gt-sm-row{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.layout-gt-sm-column{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.layout-gt-sm-row{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}}@media (min-width:960px) and (max-width:1279px){.hide-gt-sm:not(.show-gt-xs):not(.show-gt-sm):not(.show-md):not(.show),.hide-gt-xs:not(.show-gt-xs):not(.show-gt-sm):not(.show-md):not(.show),.hide-md:not(.show-md):not(.show-gt-sm):not(.show-gt-xs):not(.show),.hide:not(.show-gt-xs):not(.show-gt-sm):not(.show-md):not(.show){display:none}.flex-order-md--20{-webkit-order:-20;-ms-flex-order:-20;order:-20}.flex-order-md--19{-webkit-order:-19;-ms-flex-order:-19;order:-19}.flex-order-md--18{-webkit-order:-18;-ms-flex-order:-18;order:-18}.flex-order-md--17{-webkit-order:-17;-ms-flex-order:-17;order:-17}.flex-order-md--16{-webkit-order:-16;-ms-flex-order:-16;order:-16}.flex-order-md--15{-webkit-order:-15;-ms-flex-order:-15;order:-15}.flex-order-md--14{-webkit-order:-14;-ms-flex-order:-14;order:-14}.flex-order-md--13{-webkit-order:-13;-ms-flex-order:-13;order:-13}.flex-order-md--12{-webkit-order:-12;-ms-flex-order:-12;order:-12}.flex-order-md--11{-webkit-order:-11;-ms-flex-order:-11;order:-11}.flex-order-md--10{-webkit-order:-10;-ms-flex-order:-10;order:-10}.flex-order-md--9{-webkit-order:-9;-ms-flex-order:-9;order:-9}.flex-order-md--8{-webkit-order:-8;-ms-flex-order:-8;order:-8}.flex-order-md--7{-webkit-order:-7;-ms-flex-order:-7;order:-7}.flex-order-md--6{-webkit-order:-6;-ms-flex-order:-6;order:-6}.flex-order-md--5{-webkit-order:-5;-ms-flex-order:-5;order:-5}.flex-order-md--4{-webkit-order:-4;-ms-flex-order:-4;order:-4}.flex-order-md--3{-webkit-order:-3;-ms-flex-order:-3;order:-3}.flex-order-md--2{-webkit-order:-2;-ms-flex-order:-2;order:-2}.flex-order-md--1{-webkit-order:-1;-ms-flex-order:-1;order:-1}.flex-order-md-0{-webkit-order:0;-ms-flex-order:0;order:0}.flex-order-md-1{-webkit-order:1;-ms-flex-order:1;order:1}.flex-order-md-2{-webkit-order:2;-ms-flex-order:2;order:2}.flex-order-md-3{-webkit-order:3;-ms-flex-order:3;order:3}.flex-order-md-4{-webkit-order:4;-ms-flex-order:4;order:4}.flex-order-md-5{-webkit-order:5;-ms-flex-order:5;order:5}.flex-order-md-6{-webkit-order:6;-ms-flex-order:6;order:6}.flex-order-md-7{-webkit-order:7;-ms-flex-order:7;order:7}.flex-order-md-8{-webkit-order:8;-ms-flex-order:8;order:8}.flex-order-md-9{-webkit-order:9;-ms-flex-order:9;order:9}.flex-order-md-10{-webkit-order:10;-ms-flex-order:10;order:10}.flex-order-md-11{-webkit-order:11;-ms-flex-order:11;order:11}.flex-order-md-12{-webkit-order:12;-ms-flex-order:12;order:12}.flex-order-md-13{-webkit-order:13;-ms-flex-order:13;order:13}.flex-order-md-14{-webkit-order:14;-ms-flex-order:14;order:14}.flex-order-md-15{-webkit-order:15;-ms-flex-order:15;order:15}.flex-order-md-16{-webkit-order:16;-ms-flex-order:16;order:16}.flex-order-md-17{-webkit-order:17;-ms-flex-order:17;order:17}.flex-order-md-18{-webkit-order:18;-ms-flex-order:18;order:18}.flex-order-md-19{-webkit-order:19;-ms-flex-order:19;order:19}.flex-order-md-20{-webkit-order:20;-ms-flex-order:20;order:20}.flex-offset-md-0,.offset-md-0{margin-left:0}[dir=rtl] .flex-offset-md-0,[dir=rtl] .offset-md-0{margin-left:auto;margin-left:initial;margin-right:0}.flex-offset-md-5,.offset-md-5{margin-left:5%}[dir=rtl] .flex-offset-md-5,[dir=rtl] .offset-md-5{margin-left:auto;margin-left:initial;margin-right:5%}.flex-offset-md-10,.offset-md-10{margin-left:10%}[dir=rtl] .flex-offset-md-10,[dir=rtl] .offset-md-10{margin-left:auto;margin-left:initial;margin-right:10%}.flex-offset-md-15,.offset-md-15{margin-left:15%}[dir=rtl] .flex-offset-md-15,[dir=rtl] .offset-md-15{margin-left:auto;margin-left:initial;margin-right:15%}.flex-offset-md-20,.offset-md-20{margin-left:20%}[dir=rtl] .flex-offset-md-20,[dir=rtl] .offset-md-20{margin-left:auto;margin-left:initial;margin-right:20%}.flex-offset-md-25,.offset-md-25{margin-left:25%}[dir=rtl] .flex-offset-md-25,[dir=rtl] .offset-md-25{margin-left:auto;margin-left:initial;margin-right:25%}.flex-offset-md-30,.offset-md-30{margin-left:30%}[dir=rtl] .flex-offset-md-30,[dir=rtl] .offset-md-30{margin-left:auto;margin-left:initial;margin-right:30%}.flex-offset-md-35,.offset-md-35{margin-left:35%}[dir=rtl] .flex-offset-md-35,[dir=rtl] .offset-md-35{margin-left:auto;margin-left:initial;margin-right:35%}.flex-offset-md-40,.offset-md-40{margin-left:40%}[dir=rtl] .flex-offset-md-40,[dir=rtl] .offset-md-40{margin-left:auto;margin-left:initial;margin-right:40%}.flex-offset-md-45,.offset-md-45{margin-left:45%}[dir=rtl] .flex-offset-md-45,[dir=rtl] .offset-md-45{margin-left:auto;margin-left:initial;margin-right:45%}.flex-offset-md-50,.offset-md-50{margin-left:50%}[dir=rtl] .flex-offset-md-50,[dir=rtl] .offset-md-50{margin-left:auto;margin-left:initial;margin-right:50%}.flex-offset-md-55,.offset-md-55{margin-left:55%}[dir=rtl] .flex-offset-md-55,[dir=rtl] .offset-md-55{margin-left:auto;margin-left:initial;margin-right:55%}.flex-offset-md-60,.offset-md-60{margin-left:60%}[dir=rtl] .flex-offset-md-60,[dir=rtl] .offset-md-60{margin-left:auto;margin-left:initial;margin-right:60%}.flex-offset-md-65,.offset-md-65{margin-left:65%}[dir=rtl] .flex-offset-md-65,[dir=rtl] .offset-md-65{margin-left:auto;margin-left:initial;margin-right:65%}.flex-offset-md-70,.offset-md-70{margin-left:70%}[dir=rtl] .flex-offset-md-70,[dir=rtl] .offset-md-70{margin-left:auto;margin-left:initial;margin-right:70%}.flex-offset-md-75,.offset-md-75{margin-left:75%}[dir=rtl] .flex-offset-md-75,[dir=rtl] .offset-md-75{margin-left:auto;margin-left:initial;margin-right:75%}.flex-offset-md-80,.offset-md-80{margin-left:80%}[dir=rtl] .flex-offset-md-80,[dir=rtl] .offset-md-80{margin-left:auto;margin-left:initial;margin-right:80%}.flex-offset-md-85,.offset-md-85{margin-left:85%}[dir=rtl] .flex-offset-md-85,[dir=rtl] .offset-md-85{margin-left:auto;margin-left:initial;margin-right:85%}.flex-offset-md-90,.offset-md-90{margin-left:90%}[dir=rtl] .flex-offset-md-90,[dir=rtl] .offset-md-90{margin-left:auto;margin-left:initial;margin-right:90%}.flex-offset-md-95,.offset-md-95{margin-left:95%}[dir=rtl] .flex-offset-md-95,[dir=rtl] .offset-md-95{margin-left:auto;margin-left:initial;margin-right:95%}.flex-offset-md-33,.offset-md-33{margin-left:calc(100% / 3)}.flex-offset-md-66,.offset-md-66{margin-left:calc(200% / 3)}[dir=rtl] .flex-offset-md-66,[dir=rtl] .offset-md-66{margin-left:auto;margin-left:initial;margin-right:calc(200% / 3)}.layout-align-md{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.layout-align-md-start,.layout-align-md-start-center,.layout-align-md-start-end,.layout-align-md-start-start,.layout-align-md-start-stretch{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.layout-align-md-center,.layout-align-md-center-center,.layout-align-md-center-end,.layout-align-md-center-start,.layout-align-md-center-stretch{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.layout-align-md-end,.layout-align-md-end-center,.layout-align-md-end-end,.layout-align-md-end-start,.layout-align-md-end-stretch{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.layout-align-md-space-around,.layout-align-md-space-around-center,.layout-align-md-space-around-end,.layout-align-md-space-around-start,.layout-align-md-space-around-stretch{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.layout-align-md-space-between,.layout-align-md-space-between-center,.layout-align-md-space-between-end,.layout-align-md-space-between-start,.layout-align-md-space-between-stretch{-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.layout-align-md-center-start,.layout-align-md-end-start,.layout-align-md-space-around-start,.layout-align-md-space-between-start,.layout-align-md-start-start{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}.layout-align-md-center-center,.layout-align-md-end-center,.layout-align-md-space-around-center,.layout-align-md-space-between-center,.layout-align-md-start-center{-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;max-width:100%}.layout-align-md-center-center>*,.layout-align-md-end-center>*,.layout-align-md-space-around-center>*,.layout-align-md-space-between-center>*,.layout-align-md-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-md-center-end,.layout-align-md-end-end,.layout-align-md-space-around-end,.layout-align-md-space-between-end,.layout-align-md-start-end{-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;-webkit-align-content:flex-end;-ms-flex-line-pack:end;align-content:flex-end}.layout-align-md-center-stretch,.layout-align-md-end-stretch,.layout-align-md-space-around-stretch,.layout-align-md-space-between-stretch,.layout-align-md-start-stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch}.flex-md{-webkit-flex:1;-ms-flex:1;flex:1;box-sizing:border-box}.flex-md-grow{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;box-sizing:border-box}.flex-md-initial{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-md-auto{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-md-none{-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-md-noshrink{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-md-nogrow{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-md-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box}.layout-md-row>.flex-md-0,.layout-row>.flex-md-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box;min-width:0}.layout-column>.flex-md-0,.layout-md-column>.flex-md-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:100%;max-height:0;box-sizing:border-box;min-height:0}.flex-md-5,.layout-md-row>.flex-md-5,.layout-row>.flex-md-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:5%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-5,.layout-md-column>.flex-md-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:100%;max-height:5%;box-sizing:border-box}.flex-md-10,.layout-md-row>.flex-md-10,.layout-row>.flex-md-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:10%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-10,.layout-md-column>.flex-md-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:100%;max-height:10%;box-sizing:border-box}.flex-md-15,.layout-md-row>.flex-md-15,.layout-row>.flex-md-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:15%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-15,.layout-md-column>.flex-md-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:100%;max-height:15%;box-sizing:border-box}.flex-md-20,.layout-md-row>.flex-md-20,.layout-row>.flex-md-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:20%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-20,.layout-md-column>.flex-md-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:100%;max-height:20%;box-sizing:border-box}.flex-md-25,.layout-md-row>.flex-md-25,.layout-row>.flex-md-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:25%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-25,.layout-md-column>.flex-md-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:100%;max-height:25%;box-sizing:border-box}.flex-md-30,.layout-md-row>.flex-md-30,.layout-row>.flex-md-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:30%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-30,.layout-md-column>.flex-md-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:100%;max-height:30%;box-sizing:border-box}.flex-md-35,.layout-md-row>.flex-md-35,.layout-row>.flex-md-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:35%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-35,.layout-md-column>.flex-md-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:100%;max-height:35%;box-sizing:border-box}.flex-md-40,.layout-md-row>.flex-md-40,.layout-row>.flex-md-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:40%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-40,.layout-md-column>.flex-md-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:100%;max-height:40%;box-sizing:border-box}.flex-md-45,.layout-md-row>.flex-md-45,.layout-row>.flex-md-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:45%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-45,.layout-md-column>.flex-md-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:100%;max-height:45%;box-sizing:border-box}.flex-md-50,.layout-md-row>.flex-md-50,.layout-row>.flex-md-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:50%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-50,.layout-md-column>.flex-md-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:100%;max-height:50%;box-sizing:border-box}.flex-md-55,.layout-md-row>.flex-md-55,.layout-row>.flex-md-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:55%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-55,.layout-md-column>.flex-md-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:100%;max-height:55%;box-sizing:border-box}.flex-md-60,.layout-md-row>.flex-md-60,.layout-row>.flex-md-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:60%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-60,.layout-md-column>.flex-md-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:100%;max-height:60%;box-sizing:border-box}.flex-md-65,.layout-md-row>.flex-md-65,.layout-row>.flex-md-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:65%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-65,.layout-md-column>.flex-md-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:100%;max-height:65%;box-sizing:border-box}.flex-md-70,.layout-md-row>.flex-md-70,.layout-row>.flex-md-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:70%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-70,.layout-md-column>.flex-md-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:100%;max-height:70%;box-sizing:border-box}.flex-md-75,.layout-md-row>.flex-md-75,.layout-row>.flex-md-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:75%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-75,.layout-md-column>.flex-md-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:100%;max-height:75%;box-sizing:border-box}.flex-md-80,.layout-md-row>.flex-md-80,.layout-row>.flex-md-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:80%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-80,.layout-md-column>.flex-md-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:100%;max-height:80%;box-sizing:border-box}.flex-md-85,.layout-md-row>.flex-md-85,.layout-row>.flex-md-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:85%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-85,.layout-md-column>.flex-md-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:100%;max-height:85%;box-sizing:border-box}.flex-md-90,.layout-md-row>.flex-md-90,.layout-row>.flex-md-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:90%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-90,.layout-md-column>.flex-md-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:100%;max-height:90%;box-sizing:border-box}.flex-md-95,.layout-md-row>.flex-md-95,.layout-row>.flex-md-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:95%;max-height:100%;box-sizing:border-box}.layout-column>.flex-md-95,.layout-md-column>.flex-md-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:100%;max-height:95%;box-sizing:border-box}.flex-md-100,.layout-column>.flex-md-100,.layout-md-column>.flex-md-100,.layout-md-row>.flex-md-100,.layout-row>.flex-md-100{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-md-row>.flex-md-33,.layout-row>.flex-md-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-md-row>.flex-md-66,.layout-row>.flex-md-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-md-row>.flex,.layout-row>.flex{min-width:0}.layout-column>.flex-md-33,.layout-md-column>.flex-md-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-md-66,.layout-md-column>.flex-md-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-column>.flex,.layout-md-column>.flex{min-height:0}.layout-md,.layout-md-column,.layout-md-row{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.layout-md-column{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.layout-md-row{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}}@media (min-width:1280px){.flex-order-gt-md--20{-webkit-order:-20;-ms-flex-order:-20;order:-20}.flex-order-gt-md--19{-webkit-order:-19;-ms-flex-order:-19;order:-19}.flex-order-gt-md--18{-webkit-order:-18;-ms-flex-order:-18;order:-18}.flex-order-gt-md--17{-webkit-order:-17;-ms-flex-order:-17;order:-17}.flex-order-gt-md--16{-webkit-order:-16;-ms-flex-order:-16;order:-16}.flex-order-gt-md--15{-webkit-order:-15;-ms-flex-order:-15;order:-15}.flex-order-gt-md--14{-webkit-order:-14;-ms-flex-order:-14;order:-14}.flex-order-gt-md--13{-webkit-order:-13;-ms-flex-order:-13;order:-13}.flex-order-gt-md--12{-webkit-order:-12;-ms-flex-order:-12;order:-12}.flex-order-gt-md--11{-webkit-order:-11;-ms-flex-order:-11;order:-11}.flex-order-gt-md--10{-webkit-order:-10;-ms-flex-order:-10;order:-10}.flex-order-gt-md--9{-webkit-order:-9;-ms-flex-order:-9;order:-9}.flex-order-gt-md--8{-webkit-order:-8;-ms-flex-order:-8;order:-8}.flex-order-gt-md--7{-webkit-order:-7;-ms-flex-order:-7;order:-7}.flex-order-gt-md--6{-webkit-order:-6;-ms-flex-order:-6;order:-6}.flex-order-gt-md--5{-webkit-order:-5;-ms-flex-order:-5;order:-5}.flex-order-gt-md--4{-webkit-order:-4;-ms-flex-order:-4;order:-4}.flex-order-gt-md--3{-webkit-order:-3;-ms-flex-order:-3;order:-3}.flex-order-gt-md--2{-webkit-order:-2;-ms-flex-order:-2;order:-2}.flex-order-gt-md--1{-webkit-order:-1;-ms-flex-order:-1;order:-1}.flex-order-gt-md-0{-webkit-order:0;-ms-flex-order:0;order:0}.flex-order-gt-md-1{-webkit-order:1;-ms-flex-order:1;order:1}.flex-order-gt-md-2{-webkit-order:2;-ms-flex-order:2;order:2}.flex-order-gt-md-3{-webkit-order:3;-ms-flex-order:3;order:3}.flex-order-gt-md-4{-webkit-order:4;-ms-flex-order:4;order:4}.flex-order-gt-md-5{-webkit-order:5;-ms-flex-order:5;order:5}.flex-order-gt-md-6{-webkit-order:6;-ms-flex-order:6;order:6}.flex-order-gt-md-7{-webkit-order:7;-ms-flex-order:7;order:7}.flex-order-gt-md-8{-webkit-order:8;-ms-flex-order:8;order:8}.flex-order-gt-md-9{-webkit-order:9;-ms-flex-order:9;order:9}.flex-order-gt-md-10{-webkit-order:10;-ms-flex-order:10;order:10}.flex-order-gt-md-11{-webkit-order:11;-ms-flex-order:11;order:11}.flex-order-gt-md-12{-webkit-order:12;-ms-flex-order:12;order:12}.flex-order-gt-md-13{-webkit-order:13;-ms-flex-order:13;order:13}.flex-order-gt-md-14{-webkit-order:14;-ms-flex-order:14;order:14}.flex-order-gt-md-15{-webkit-order:15;-ms-flex-order:15;order:15}.flex-order-gt-md-16{-webkit-order:16;-ms-flex-order:16;order:16}.flex-order-gt-md-17{-webkit-order:17;-ms-flex-order:17;order:17}.flex-order-gt-md-18{-webkit-order:18;-ms-flex-order:18;order:18}.flex-order-gt-md-19{-webkit-order:19;-ms-flex-order:19;order:19}.flex-order-gt-md-20{-webkit-order:20;-ms-flex-order:20;order:20}.flex-offset-gt-md-0,.offset-gt-md-0{margin-left:0}[dir=rtl] .flex-offset-gt-md-0,[dir=rtl] .offset-gt-md-0{margin-left:auto;margin-left:initial;margin-right:0}.flex-offset-gt-md-5,.offset-gt-md-5{margin-left:5%}[dir=rtl] .flex-offset-gt-md-5,[dir=rtl] .offset-gt-md-5{margin-left:auto;margin-left:initial;margin-right:5%}.flex-offset-gt-md-10,.offset-gt-md-10{margin-left:10%}[dir=rtl] .flex-offset-gt-md-10,[dir=rtl] .offset-gt-md-10{margin-left:auto;margin-left:initial;margin-right:10%}.flex-offset-gt-md-15,.offset-gt-md-15{margin-left:15%}[dir=rtl] .flex-offset-gt-md-15,[dir=rtl] .offset-gt-md-15{margin-left:auto;margin-left:initial;margin-right:15%}.flex-offset-gt-md-20,.offset-gt-md-20{margin-left:20%}[dir=rtl] .flex-offset-gt-md-20,[dir=rtl] .offset-gt-md-20{margin-left:auto;margin-left:initial;margin-right:20%}.flex-offset-gt-md-25,.offset-gt-md-25{margin-left:25%}[dir=rtl] .flex-offset-gt-md-25,[dir=rtl] .offset-gt-md-25{margin-left:auto;margin-left:initial;margin-right:25%}.flex-offset-gt-md-30,.offset-gt-md-30{margin-left:30%}[dir=rtl] .flex-offset-gt-md-30,[dir=rtl] .offset-gt-md-30{margin-left:auto;margin-left:initial;margin-right:30%}.flex-offset-gt-md-35,.offset-gt-md-35{margin-left:35%}[dir=rtl] .flex-offset-gt-md-35,[dir=rtl] .offset-gt-md-35{margin-left:auto;margin-left:initial;margin-right:35%}.flex-offset-gt-md-40,.offset-gt-md-40{margin-left:40%}[dir=rtl] .flex-offset-gt-md-40,[dir=rtl] .offset-gt-md-40{margin-left:auto;margin-left:initial;margin-right:40%}.flex-offset-gt-md-45,.offset-gt-md-45{margin-left:45%}[dir=rtl] .flex-offset-gt-md-45,[dir=rtl] .offset-gt-md-45{margin-left:auto;margin-left:initial;margin-right:45%}.flex-offset-gt-md-50,.offset-gt-md-50{margin-left:50%}[dir=rtl] .flex-offset-gt-md-50,[dir=rtl] .offset-gt-md-50{margin-left:auto;margin-left:initial;margin-right:50%}.flex-offset-gt-md-55,.offset-gt-md-55{margin-left:55%}[dir=rtl] .flex-offset-gt-md-55,[dir=rtl] .offset-gt-md-55{margin-left:auto;margin-left:initial;margin-right:55%}.flex-offset-gt-md-60,.offset-gt-md-60{margin-left:60%}[dir=rtl] .flex-offset-gt-md-60,[dir=rtl] .offset-gt-md-60{margin-left:auto;margin-left:initial;margin-right:60%}.flex-offset-gt-md-65,.offset-gt-md-65{margin-left:65%}[dir=rtl] .flex-offset-gt-md-65,[dir=rtl] .offset-gt-md-65{margin-left:auto;margin-left:initial;margin-right:65%}.flex-offset-gt-md-70,.offset-gt-md-70{margin-left:70%}[dir=rtl] .flex-offset-gt-md-70,[dir=rtl] .offset-gt-md-70{margin-left:auto;margin-left:initial;margin-right:70%}.flex-offset-gt-md-75,.offset-gt-md-75{margin-left:75%}[dir=rtl] .flex-offset-gt-md-75,[dir=rtl] .offset-gt-md-75{margin-left:auto;margin-left:initial;margin-right:75%}.flex-offset-gt-md-80,.offset-gt-md-80{margin-left:80%}[dir=rtl] .flex-offset-gt-md-80,[dir=rtl] .offset-gt-md-80{margin-left:auto;margin-left:initial;margin-right:80%}.flex-offset-gt-md-85,.offset-gt-md-85{margin-left:85%}[dir=rtl] .flex-offset-gt-md-85,[dir=rtl] .offset-gt-md-85{margin-left:auto;margin-left:initial;margin-right:85%}.flex-offset-gt-md-90,.offset-gt-md-90{margin-left:90%}[dir=rtl] .flex-offset-gt-md-90,[dir=rtl] .offset-gt-md-90{margin-left:auto;margin-left:initial;margin-right:90%}.flex-offset-gt-md-95,.offset-gt-md-95{margin-left:95%}[dir=rtl] .flex-offset-gt-md-95,[dir=rtl] .offset-gt-md-95{margin-left:auto;margin-left:initial;margin-right:95%}.flex-offset-gt-md-33,.offset-gt-md-33{margin-left:calc(100% / 3)}.flex-offset-gt-md-66,.offset-gt-md-66{margin-left:calc(200% / 3)}[dir=rtl] .flex-offset-gt-md-66,[dir=rtl] .offset-gt-md-66{margin-left:auto;margin-left:initial;margin-right:calc(200% / 3)}.layout-align-gt-md{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.layout-align-gt-md-start,.layout-align-gt-md-start-center,.layout-align-gt-md-start-end,.layout-align-gt-md-start-start,.layout-align-gt-md-start-stretch{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.layout-align-gt-md-center,.layout-align-gt-md-center-center,.layout-align-gt-md-center-end,.layout-align-gt-md-center-start,.layout-align-gt-md-center-stretch{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.layout-align-gt-md-end,.layout-align-gt-md-end-center,.layout-align-gt-md-end-end,.layout-align-gt-md-end-start,.layout-align-gt-md-end-stretch{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.layout-align-gt-md-space-around,.layout-align-gt-md-space-around-center,.layout-align-gt-md-space-around-end,.layout-align-gt-md-space-around-start,.layout-align-gt-md-space-around-stretch{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.layout-align-gt-md-space-between,.layout-align-gt-md-space-between-center,.layout-align-gt-md-space-between-end,.layout-align-gt-md-space-between-start,.layout-align-gt-md-space-between-stretch{-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.layout-align-gt-md-center-start,.layout-align-gt-md-end-start,.layout-align-gt-md-space-around-start,.layout-align-gt-md-space-between-start,.layout-align-gt-md-start-start{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}.layout-align-gt-md-center-center,.layout-align-gt-md-end-center,.layout-align-gt-md-space-around-center,.layout-align-gt-md-space-between-center,.layout-align-gt-md-start-center{-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;max-width:100%}.layout-align-gt-md-center-center>*,.layout-align-gt-md-end-center>*,.layout-align-gt-md-space-around-center>*,.layout-align-gt-md-space-between-center>*,.layout-align-gt-md-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-gt-md-center-end,.layout-align-gt-md-end-end,.layout-align-gt-md-space-around-end,.layout-align-gt-md-space-between-end,.layout-align-gt-md-start-end{-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;-webkit-align-content:flex-end;-ms-flex-line-pack:end;align-content:flex-end}.layout-align-gt-md-center-stretch,.layout-align-gt-md-end-stretch,.layout-align-gt-md-space-around-stretch,.layout-align-gt-md-space-between-stretch,.layout-align-gt-md-start-stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch}.flex-gt-md{-webkit-flex:1;-ms-flex:1;flex:1;box-sizing:border-box}.flex-gt-md-grow{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;box-sizing:border-box}.flex-gt-md-initial{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-md-auto{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-gt-md-none{-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-gt-md-noshrink{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-gt-md-nogrow{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-md-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box}.layout-gt-md-row>.flex-gt-md-0,.layout-row>.flex-gt-md-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box;min-width:0}.layout-column>.flex-gt-md-0,.layout-gt-md-column>.flex-gt-md-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:100%;max-height:0;box-sizing:border-box;min-height:0}.flex-gt-md-5,.layout-gt-md-row>.flex-gt-md-5,.layout-row>.flex-gt-md-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:5%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-5,.layout-gt-md-column>.flex-gt-md-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:100%;max-height:5%;box-sizing:border-box}.flex-gt-md-10,.layout-gt-md-row>.flex-gt-md-10,.layout-row>.flex-gt-md-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:10%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-10,.layout-gt-md-column>.flex-gt-md-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:100%;max-height:10%;box-sizing:border-box}.flex-gt-md-15,.layout-gt-md-row>.flex-gt-md-15,.layout-row>.flex-gt-md-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:15%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-15,.layout-gt-md-column>.flex-gt-md-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:100%;max-height:15%;box-sizing:border-box}.flex-gt-md-20,.layout-gt-md-row>.flex-gt-md-20,.layout-row>.flex-gt-md-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:20%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-20,.layout-gt-md-column>.flex-gt-md-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:100%;max-height:20%;box-sizing:border-box}.flex-gt-md-25,.layout-gt-md-row>.flex-gt-md-25,.layout-row>.flex-gt-md-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:25%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-25,.layout-gt-md-column>.flex-gt-md-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:100%;max-height:25%;box-sizing:border-box}.flex-gt-md-30,.layout-gt-md-row>.flex-gt-md-30,.layout-row>.flex-gt-md-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:30%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-30,.layout-gt-md-column>.flex-gt-md-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:100%;max-height:30%;box-sizing:border-box}.flex-gt-md-35,.layout-gt-md-row>.flex-gt-md-35,.layout-row>.flex-gt-md-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:35%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-35,.layout-gt-md-column>.flex-gt-md-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:100%;max-height:35%;box-sizing:border-box}.flex-gt-md-40,.layout-gt-md-row>.flex-gt-md-40,.layout-row>.flex-gt-md-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:40%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-40,.layout-gt-md-column>.flex-gt-md-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:100%;max-height:40%;box-sizing:border-box}.flex-gt-md-45,.layout-gt-md-row>.flex-gt-md-45,.layout-row>.flex-gt-md-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:45%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-45,.layout-gt-md-column>.flex-gt-md-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:100%;max-height:45%;box-sizing:border-box}.flex-gt-md-50,.layout-gt-md-row>.flex-gt-md-50,.layout-row>.flex-gt-md-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:50%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-50,.layout-gt-md-column>.flex-gt-md-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:100%;max-height:50%;box-sizing:border-box}.flex-gt-md-55,.layout-gt-md-row>.flex-gt-md-55,.layout-row>.flex-gt-md-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:55%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-55,.layout-gt-md-column>.flex-gt-md-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:100%;max-height:55%;box-sizing:border-box}.flex-gt-md-60,.layout-gt-md-row>.flex-gt-md-60,.layout-row>.flex-gt-md-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:60%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-60,.layout-gt-md-column>.flex-gt-md-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:100%;max-height:60%;box-sizing:border-box}.flex-gt-md-65,.layout-gt-md-row>.flex-gt-md-65,.layout-row>.flex-gt-md-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:65%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-65,.layout-gt-md-column>.flex-gt-md-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:100%;max-height:65%;box-sizing:border-box}.flex-gt-md-70,.layout-gt-md-row>.flex-gt-md-70,.layout-row>.flex-gt-md-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:70%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-70,.layout-gt-md-column>.flex-gt-md-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:100%;max-height:70%;box-sizing:border-box}.flex-gt-md-75,.layout-gt-md-row>.flex-gt-md-75,.layout-row>.flex-gt-md-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:75%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-75,.layout-gt-md-column>.flex-gt-md-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:100%;max-height:75%;box-sizing:border-box}.flex-gt-md-80,.layout-gt-md-row>.flex-gt-md-80,.layout-row>.flex-gt-md-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:80%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-80,.layout-gt-md-column>.flex-gt-md-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:100%;max-height:80%;box-sizing:border-box}.flex-gt-md-85,.layout-gt-md-row>.flex-gt-md-85,.layout-row>.flex-gt-md-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:85%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-85,.layout-gt-md-column>.flex-gt-md-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:100%;max-height:85%;box-sizing:border-box}.flex-gt-md-90,.layout-gt-md-row>.flex-gt-md-90,.layout-row>.flex-gt-md-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:90%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-90,.layout-gt-md-column>.flex-gt-md-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:100%;max-height:90%;box-sizing:border-box}.flex-gt-md-95,.layout-gt-md-row>.flex-gt-md-95,.layout-row>.flex-gt-md-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:95%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-md-95,.layout-gt-md-column>.flex-gt-md-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:100%;max-height:95%;box-sizing:border-box}.flex-gt-md-100,.layout-column>.flex-gt-md-100,.layout-gt-md-column>.flex-gt-md-100,.layout-gt-md-row>.flex-gt-md-100,.layout-row>.flex-gt-md-100{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-gt-md-row>.flex-gt-md-33,.layout-row>.flex-gt-md-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-gt-md-row>.flex-gt-md-66,.layout-row>.flex-gt-md-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-gt-md-row>.flex,.layout-row>.flex{min-width:0}.layout-column>.flex-gt-md-33,.layout-gt-md-column>.flex-gt-md-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-gt-md-66,.layout-gt-md-column>.flex-gt-md-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-column>.flex,.layout-gt-md-column>.flex{min-height:0}.layout-gt-md,.layout-gt-md-column,.layout-gt-md-row{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.layout-gt-md-column{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.layout-gt-md-row{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}}@media (min-width:1280px) and (max-width:1919px){.hide-gt-md:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-lg):not(.show),.hide-gt-sm:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-lg):not(.show),.hide-gt-xs:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-lg):not(.show),.hide-lg:not(.show-lg):not(.show-gt-md):not(.show-gt-sm):not(.show-gt-xs):not(.show),.hide:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-lg):not(.show){display:none}.flex-order-lg--20{-webkit-order:-20;-ms-flex-order:-20;order:-20}.flex-order-lg--19{-webkit-order:-19;-ms-flex-order:-19;order:-19}.flex-order-lg--18{-webkit-order:-18;-ms-flex-order:-18;order:-18}.flex-order-lg--17{-webkit-order:-17;-ms-flex-order:-17;order:-17}.flex-order-lg--16{-webkit-order:-16;-ms-flex-order:-16;order:-16}.flex-order-lg--15{-webkit-order:-15;-ms-flex-order:-15;order:-15}.flex-order-lg--14{-webkit-order:-14;-ms-flex-order:-14;order:-14}.flex-order-lg--13{-webkit-order:-13;-ms-flex-order:-13;order:-13}.flex-order-lg--12{-webkit-order:-12;-ms-flex-order:-12;order:-12}.flex-order-lg--11{-webkit-order:-11;-ms-flex-order:-11;order:-11}.flex-order-lg--10{-webkit-order:-10;-ms-flex-order:-10;order:-10}.flex-order-lg--9{-webkit-order:-9;-ms-flex-order:-9;order:-9}.flex-order-lg--8{-webkit-order:-8;-ms-flex-order:-8;order:-8}.flex-order-lg--7{-webkit-order:-7;-ms-flex-order:-7;order:-7}.flex-order-lg--6{-webkit-order:-6;-ms-flex-order:-6;order:-6}.flex-order-lg--5{-webkit-order:-5;-ms-flex-order:-5;order:-5}.flex-order-lg--4{-webkit-order:-4;-ms-flex-order:-4;order:-4}.flex-order-lg--3{-webkit-order:-3;-ms-flex-order:-3;order:-3}.flex-order-lg--2{-webkit-order:-2;-ms-flex-order:-2;order:-2}.flex-order-lg--1{-webkit-order:-1;-ms-flex-order:-1;order:-1}.flex-order-lg-0{-webkit-order:0;-ms-flex-order:0;order:0}.flex-order-lg-1{-webkit-order:1;-ms-flex-order:1;order:1}.flex-order-lg-2{-webkit-order:2;-ms-flex-order:2;order:2}.flex-order-lg-3{-webkit-order:3;-ms-flex-order:3;order:3}.flex-order-lg-4{-webkit-order:4;-ms-flex-order:4;order:4}.flex-order-lg-5{-webkit-order:5;-ms-flex-order:5;order:5}.flex-order-lg-6{-webkit-order:6;-ms-flex-order:6;order:6}.flex-order-lg-7{-webkit-order:7;-ms-flex-order:7;order:7}.flex-order-lg-8{-webkit-order:8;-ms-flex-order:8;order:8}.flex-order-lg-9{-webkit-order:9;-ms-flex-order:9;order:9}.flex-order-lg-10{-webkit-order:10;-ms-flex-order:10;order:10}.flex-order-lg-11{-webkit-order:11;-ms-flex-order:11;order:11}.flex-order-lg-12{-webkit-order:12;-ms-flex-order:12;order:12}.flex-order-lg-13{-webkit-order:13;-ms-flex-order:13;order:13}.flex-order-lg-14{-webkit-order:14;-ms-flex-order:14;order:14}.flex-order-lg-15{-webkit-order:15;-ms-flex-order:15;order:15}.flex-order-lg-16{-webkit-order:16;-ms-flex-order:16;order:16}.flex-order-lg-17{-webkit-order:17;-ms-flex-order:17;order:17}.flex-order-lg-18{-webkit-order:18;-ms-flex-order:18;order:18}.flex-order-lg-19{-webkit-order:19;-ms-flex-order:19;order:19}.flex-order-lg-20{-webkit-order:20;-ms-flex-order:20;order:20}.flex-offset-lg-0,.offset-lg-0{margin-left:0}[dir=rtl] .flex-offset-lg-0,[dir=rtl] .offset-lg-0{margin-left:auto;margin-left:initial;margin-right:0}.flex-offset-lg-5,.offset-lg-5{margin-left:5%}[dir=rtl] .flex-offset-lg-5,[dir=rtl] .offset-lg-5{margin-left:auto;margin-left:initial;margin-right:5%}.flex-offset-lg-10,.offset-lg-10{margin-left:10%}[dir=rtl] .flex-offset-lg-10,[dir=rtl] .offset-lg-10{margin-left:auto;margin-left:initial;margin-right:10%}.flex-offset-lg-15,.offset-lg-15{margin-left:15%}[dir=rtl] .flex-offset-lg-15,[dir=rtl] .offset-lg-15{margin-left:auto;margin-left:initial;margin-right:15%}.flex-offset-lg-20,.offset-lg-20{margin-left:20%}[dir=rtl] .flex-offset-lg-20,[dir=rtl] .offset-lg-20{margin-left:auto;margin-left:initial;margin-right:20%}.flex-offset-lg-25,.offset-lg-25{margin-left:25%}[dir=rtl] .flex-offset-lg-25,[dir=rtl] .offset-lg-25{margin-left:auto;margin-left:initial;margin-right:25%}.flex-offset-lg-30,.offset-lg-30{margin-left:30%}[dir=rtl] .flex-offset-lg-30,[dir=rtl] .offset-lg-30{margin-left:auto;margin-left:initial;margin-right:30%}.flex-offset-lg-35,.offset-lg-35{margin-left:35%}[dir=rtl] .flex-offset-lg-35,[dir=rtl] .offset-lg-35{margin-left:auto;margin-left:initial;margin-right:35%}.flex-offset-lg-40,.offset-lg-40{margin-left:40%}[dir=rtl] .flex-offset-lg-40,[dir=rtl] .offset-lg-40{margin-left:auto;margin-left:initial;margin-right:40%}.flex-offset-lg-45,.offset-lg-45{margin-left:45%}[dir=rtl] .flex-offset-lg-45,[dir=rtl] .offset-lg-45{margin-left:auto;margin-left:initial;margin-right:45%}.flex-offset-lg-50,.offset-lg-50{margin-left:50%}[dir=rtl] .flex-offset-lg-50,[dir=rtl] .offset-lg-50{margin-left:auto;margin-left:initial;margin-right:50%}.flex-offset-lg-55,.offset-lg-55{margin-left:55%}[dir=rtl] .flex-offset-lg-55,[dir=rtl] .offset-lg-55{margin-left:auto;margin-left:initial;margin-right:55%}.flex-offset-lg-60,.offset-lg-60{margin-left:60%}[dir=rtl] .flex-offset-lg-60,[dir=rtl] .offset-lg-60{margin-left:auto;margin-left:initial;margin-right:60%}.flex-offset-lg-65,.offset-lg-65{margin-left:65%}[dir=rtl] .flex-offset-lg-65,[dir=rtl] .offset-lg-65{margin-left:auto;margin-left:initial;margin-right:65%}.flex-offset-lg-70,.offset-lg-70{margin-left:70%}[dir=rtl] .flex-offset-lg-70,[dir=rtl] .offset-lg-70{margin-left:auto;margin-left:initial;margin-right:70%}.flex-offset-lg-75,.offset-lg-75{margin-left:75%}[dir=rtl] .flex-offset-lg-75,[dir=rtl] .offset-lg-75{margin-left:auto;margin-left:initial;margin-right:75%}.flex-offset-lg-80,.offset-lg-80{margin-left:80%}[dir=rtl] .flex-offset-lg-80,[dir=rtl] .offset-lg-80{margin-left:auto;margin-left:initial;margin-right:80%}.flex-offset-lg-85,.offset-lg-85{margin-left:85%}[dir=rtl] .flex-offset-lg-85,[dir=rtl] .offset-lg-85{margin-left:auto;margin-left:initial;margin-right:85%}.flex-offset-lg-90,.offset-lg-90{margin-left:90%}[dir=rtl] .flex-offset-lg-90,[dir=rtl] .offset-lg-90{margin-left:auto;margin-left:initial;margin-right:90%}.flex-offset-lg-95,.offset-lg-95{margin-left:95%}[dir=rtl] .flex-offset-lg-95,[dir=rtl] .offset-lg-95{margin-left:auto;margin-left:initial;margin-right:95%}.flex-offset-lg-33,.offset-lg-33{margin-left:calc(100% / 3)}.flex-offset-lg-66,.offset-lg-66{margin-left:calc(200% / 3)}[dir=rtl] .flex-offset-lg-66,[dir=rtl] .offset-lg-66{margin-left:auto;margin-left:initial;margin-right:calc(200% / 3)}.layout-align-lg{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.layout-align-lg-start,.layout-align-lg-start-center,.layout-align-lg-start-end,.layout-align-lg-start-start,.layout-align-lg-start-stretch{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.layout-align-lg-center,.layout-align-lg-center-center,.layout-align-lg-center-end,.layout-align-lg-center-start,.layout-align-lg-center-stretch{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.layout-align-lg-end,.layout-align-lg-end-center,.layout-align-lg-end-end,.layout-align-lg-end-start,.layout-align-lg-end-stretch{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.layout-align-lg-space-around,.layout-align-lg-space-around-center,.layout-align-lg-space-around-end,.layout-align-lg-space-around-start,.layout-align-lg-space-around-stretch{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.layout-align-lg-space-between,.layout-align-lg-space-between-center,.layout-align-lg-space-between-end,.layout-align-lg-space-between-start,.layout-align-lg-space-between-stretch{-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.layout-align-lg-center-start,.layout-align-lg-end-start,.layout-align-lg-space-around-start,.layout-align-lg-space-between-start,.layout-align-lg-start-start{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}.layout-align-lg-center-center,.layout-align-lg-end-center,.layout-align-lg-space-around-center,.layout-align-lg-space-between-center,.layout-align-lg-start-center{-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;max-width:100%}.layout-align-lg-center-center>*,.layout-align-lg-end-center>*,.layout-align-lg-space-around-center>*,.layout-align-lg-space-between-center>*,.layout-align-lg-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-lg-center-end,.layout-align-lg-end-end,.layout-align-lg-space-around-end,.layout-align-lg-space-between-end,.layout-align-lg-start-end{-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;-webkit-align-content:flex-end;-ms-flex-line-pack:end;align-content:flex-end}.layout-align-lg-center-stretch,.layout-align-lg-end-stretch,.layout-align-lg-space-around-stretch,.layout-align-lg-space-between-stretch,.layout-align-lg-start-stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch}.flex-lg{-webkit-flex:1;-ms-flex:1;flex:1;box-sizing:border-box}.flex-lg-grow{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;box-sizing:border-box}.flex-lg-initial{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-lg-auto{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-lg-none{-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-lg-noshrink{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-lg-nogrow{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-lg-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box}.layout-lg-row>.flex-lg-0,.layout-row>.flex-lg-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box;min-width:0}.layout-column>.flex-lg-0,.layout-lg-column>.flex-lg-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:100%;max-height:0;box-sizing:border-box;min-height:0}.flex-lg-5,.layout-lg-row>.flex-lg-5,.layout-row>.flex-lg-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:5%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-5,.layout-lg-column>.flex-lg-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:100%;max-height:5%;box-sizing:border-box}.flex-lg-10,.layout-lg-row>.flex-lg-10,.layout-row>.flex-lg-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:10%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-10,.layout-lg-column>.flex-lg-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:100%;max-height:10%;box-sizing:border-box}.flex-lg-15,.layout-lg-row>.flex-lg-15,.layout-row>.flex-lg-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:15%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-15,.layout-lg-column>.flex-lg-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:100%;max-height:15%;box-sizing:border-box}.flex-lg-20,.layout-lg-row>.flex-lg-20,.layout-row>.flex-lg-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:20%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-20,.layout-lg-column>.flex-lg-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:100%;max-height:20%;box-sizing:border-box}.flex-lg-25,.layout-lg-row>.flex-lg-25,.layout-row>.flex-lg-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:25%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-25,.layout-lg-column>.flex-lg-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:100%;max-height:25%;box-sizing:border-box}.flex-lg-30,.layout-lg-row>.flex-lg-30,.layout-row>.flex-lg-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:30%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-30,.layout-lg-column>.flex-lg-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:100%;max-height:30%;box-sizing:border-box}.flex-lg-35,.layout-lg-row>.flex-lg-35,.layout-row>.flex-lg-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:35%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-35,.layout-lg-column>.flex-lg-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:100%;max-height:35%;box-sizing:border-box}.flex-lg-40,.layout-lg-row>.flex-lg-40,.layout-row>.flex-lg-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:40%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-40,.layout-lg-column>.flex-lg-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:100%;max-height:40%;box-sizing:border-box}.flex-lg-45,.layout-lg-row>.flex-lg-45,.layout-row>.flex-lg-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:45%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-45,.layout-lg-column>.flex-lg-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:100%;max-height:45%;box-sizing:border-box}.flex-lg-50,.layout-lg-row>.flex-lg-50,.layout-row>.flex-lg-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:50%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-50,.layout-lg-column>.flex-lg-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:100%;max-height:50%;box-sizing:border-box}.flex-lg-55,.layout-lg-row>.flex-lg-55,.layout-row>.flex-lg-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:55%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-55,.layout-lg-column>.flex-lg-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:100%;max-height:55%;box-sizing:border-box}.flex-lg-60,.layout-lg-row>.flex-lg-60,.layout-row>.flex-lg-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:60%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-60,.layout-lg-column>.flex-lg-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:100%;max-height:60%;box-sizing:border-box}.flex-lg-65,.layout-lg-row>.flex-lg-65,.layout-row>.flex-lg-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:65%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-65,.layout-lg-column>.flex-lg-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:100%;max-height:65%;box-sizing:border-box}.flex-lg-70,.layout-lg-row>.flex-lg-70,.layout-row>.flex-lg-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:70%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-70,.layout-lg-column>.flex-lg-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:100%;max-height:70%;box-sizing:border-box}.flex-lg-75,.layout-lg-row>.flex-lg-75,.layout-row>.flex-lg-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:75%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-75,.layout-lg-column>.flex-lg-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:100%;max-height:75%;box-sizing:border-box}.flex-lg-80,.layout-lg-row>.flex-lg-80,.layout-row>.flex-lg-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:80%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-80,.layout-lg-column>.flex-lg-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:100%;max-height:80%;box-sizing:border-box}.flex-lg-85,.layout-lg-row>.flex-lg-85,.layout-row>.flex-lg-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:85%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-85,.layout-lg-column>.flex-lg-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:100%;max-height:85%;box-sizing:border-box}.flex-lg-90,.layout-lg-row>.flex-lg-90,.layout-row>.flex-lg-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:90%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-90,.layout-lg-column>.flex-lg-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:100%;max-height:90%;box-sizing:border-box}.flex-lg-95,.layout-lg-row>.flex-lg-95,.layout-row>.flex-lg-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:95%;max-height:100%;box-sizing:border-box}.layout-column>.flex-lg-95,.layout-lg-column>.flex-lg-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:100%;max-height:95%;box-sizing:border-box}.flex-lg-100,.layout-column>.flex-lg-100,.layout-lg-column>.flex-lg-100,.layout-lg-row>.flex-lg-100,.layout-row>.flex-lg-100{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-lg-row>.flex-lg-33,.layout-row>.flex-lg-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-lg-row>.flex-lg-66,.layout-row>.flex-lg-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-lg-row>.flex,.layout-row>.flex{min-width:0}.layout-column>.flex-lg-33,.layout-lg-column>.flex-lg-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-lg-66,.layout-lg-column>.flex-lg-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-column>.flex,.layout-lg-column>.flex{min-height:0}.layout-lg,.layout-lg-column,.layout-lg-row{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.layout-lg-column{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.layout-lg-row{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}}@media (min-width:1920px){.flex-order-gt-lg--20{-webkit-order:-20;-ms-flex-order:-20;order:-20}.flex-order-gt-lg--19{-webkit-order:-19;-ms-flex-order:-19;order:-19}.flex-order-gt-lg--18{-webkit-order:-18;-ms-flex-order:-18;order:-18}.flex-order-gt-lg--17{-webkit-order:-17;-ms-flex-order:-17;order:-17}.flex-order-gt-lg--16{-webkit-order:-16;-ms-flex-order:-16;order:-16}.flex-order-gt-lg--15{-webkit-order:-15;-ms-flex-order:-15;order:-15}.flex-order-gt-lg--14{-webkit-order:-14;-ms-flex-order:-14;order:-14}.flex-order-gt-lg--13{-webkit-order:-13;-ms-flex-order:-13;order:-13}.flex-order-gt-lg--12{-webkit-order:-12;-ms-flex-order:-12;order:-12}.flex-order-gt-lg--11{-webkit-order:-11;-ms-flex-order:-11;order:-11}.flex-order-gt-lg--10{-webkit-order:-10;-ms-flex-order:-10;order:-10}.flex-order-gt-lg--9{-webkit-order:-9;-ms-flex-order:-9;order:-9}.flex-order-gt-lg--8{-webkit-order:-8;-ms-flex-order:-8;order:-8}.flex-order-gt-lg--7{-webkit-order:-7;-ms-flex-order:-7;order:-7}.flex-order-gt-lg--6{-webkit-order:-6;-ms-flex-order:-6;order:-6}.flex-order-gt-lg--5{-webkit-order:-5;-ms-flex-order:-5;order:-5}.flex-order-gt-lg--4{-webkit-order:-4;-ms-flex-order:-4;order:-4}.flex-order-gt-lg--3{-webkit-order:-3;-ms-flex-order:-3;order:-3}.flex-order-gt-lg--2{-webkit-order:-2;-ms-flex-order:-2;order:-2}.flex-order-gt-lg--1{-webkit-order:-1;-ms-flex-order:-1;order:-1}.flex-order-gt-lg-0{-webkit-order:0;-ms-flex-order:0;order:0}.flex-order-gt-lg-1{-webkit-order:1;-ms-flex-order:1;order:1}.flex-order-gt-lg-2{-webkit-order:2;-ms-flex-order:2;order:2}.flex-order-gt-lg-3{-webkit-order:3;-ms-flex-order:3;order:3}.flex-order-gt-lg-4{-webkit-order:4;-ms-flex-order:4;order:4}.flex-order-gt-lg-5{-webkit-order:5;-ms-flex-order:5;order:5}.flex-order-gt-lg-6{-webkit-order:6;-ms-flex-order:6;order:6}.flex-order-gt-lg-7{-webkit-order:7;-ms-flex-order:7;order:7}.flex-order-gt-lg-8{-webkit-order:8;-ms-flex-order:8;order:8}.flex-order-gt-lg-9{-webkit-order:9;-ms-flex-order:9;order:9}.flex-order-gt-lg-10{-webkit-order:10;-ms-flex-order:10;order:10}.flex-order-gt-lg-11{-webkit-order:11;-ms-flex-order:11;order:11}.flex-order-gt-lg-12{-webkit-order:12;-ms-flex-order:12;order:12}.flex-order-gt-lg-13{-webkit-order:13;-ms-flex-order:13;order:13}.flex-order-gt-lg-14{-webkit-order:14;-ms-flex-order:14;order:14}.flex-order-gt-lg-15{-webkit-order:15;-ms-flex-order:15;order:15}.flex-order-gt-lg-16{-webkit-order:16;-ms-flex-order:16;order:16}.flex-order-gt-lg-17{-webkit-order:17;-ms-flex-order:17;order:17}.flex-order-gt-lg-18{-webkit-order:18;-ms-flex-order:18;order:18}.flex-order-gt-lg-19{-webkit-order:19;-ms-flex-order:19;order:19}.flex-order-gt-lg-20{-webkit-order:20;-ms-flex-order:20;order:20}.flex-offset-gt-lg-0,.offset-gt-lg-0{margin-left:0}[dir=rtl] .flex-offset-gt-lg-0,[dir=rtl] .offset-gt-lg-0{margin-left:auto;margin-left:initial;margin-right:0}.flex-offset-gt-lg-5,.offset-gt-lg-5{margin-left:5%}[dir=rtl] .flex-offset-gt-lg-5,[dir=rtl] .offset-gt-lg-5{margin-left:auto;margin-left:initial;margin-right:5%}.flex-offset-gt-lg-10,.offset-gt-lg-10{margin-left:10%}[dir=rtl] .flex-offset-gt-lg-10,[dir=rtl] .offset-gt-lg-10{margin-left:auto;margin-left:initial;margin-right:10%}.flex-offset-gt-lg-15,.offset-gt-lg-15{margin-left:15%}[dir=rtl] .flex-offset-gt-lg-15,[dir=rtl] .offset-gt-lg-15{margin-left:auto;margin-left:initial;margin-right:15%}.flex-offset-gt-lg-20,.offset-gt-lg-20{margin-left:20%}[dir=rtl] .flex-offset-gt-lg-20,[dir=rtl] .offset-gt-lg-20{margin-left:auto;margin-left:initial;margin-right:20%}.flex-offset-gt-lg-25,.offset-gt-lg-25{margin-left:25%}[dir=rtl] .flex-offset-gt-lg-25,[dir=rtl] .offset-gt-lg-25{margin-left:auto;margin-left:initial;margin-right:25%}.flex-offset-gt-lg-30,.offset-gt-lg-30{margin-left:30%}[dir=rtl] .flex-offset-gt-lg-30,[dir=rtl] .offset-gt-lg-30{margin-left:auto;margin-left:initial;margin-right:30%}.flex-offset-gt-lg-35,.offset-gt-lg-35{margin-left:35%}[dir=rtl] .flex-offset-gt-lg-35,[dir=rtl] .offset-gt-lg-35{margin-left:auto;margin-left:initial;margin-right:35%}.flex-offset-gt-lg-40,.offset-gt-lg-40{margin-left:40%}[dir=rtl] .flex-offset-gt-lg-40,[dir=rtl] .offset-gt-lg-40{margin-left:auto;margin-left:initial;margin-right:40%}.flex-offset-gt-lg-45,.offset-gt-lg-45{margin-left:45%}[dir=rtl] .flex-offset-gt-lg-45,[dir=rtl] .offset-gt-lg-45{margin-left:auto;margin-left:initial;margin-right:45%}.flex-offset-gt-lg-50,.offset-gt-lg-50{margin-left:50%}[dir=rtl] .flex-offset-gt-lg-50,[dir=rtl] .offset-gt-lg-50{margin-left:auto;margin-left:initial;margin-right:50%}.flex-offset-gt-lg-55,.offset-gt-lg-55{margin-left:55%}[dir=rtl] .flex-offset-gt-lg-55,[dir=rtl] .offset-gt-lg-55{margin-left:auto;margin-left:initial;margin-right:55%}.flex-offset-gt-lg-60,.offset-gt-lg-60{margin-left:60%}[dir=rtl] .flex-offset-gt-lg-60,[dir=rtl] .offset-gt-lg-60{margin-left:auto;margin-left:initial;margin-right:60%}.flex-offset-gt-lg-65,.offset-gt-lg-65{margin-left:65%}[dir=rtl] .flex-offset-gt-lg-65,[dir=rtl] .offset-gt-lg-65{margin-left:auto;margin-left:initial;margin-right:65%}.flex-offset-gt-lg-70,.offset-gt-lg-70{margin-left:70%}[dir=rtl] .flex-offset-gt-lg-70,[dir=rtl] .offset-gt-lg-70{margin-left:auto;margin-left:initial;margin-right:70%}.flex-offset-gt-lg-75,.offset-gt-lg-75{margin-left:75%}[dir=rtl] .flex-offset-gt-lg-75,[dir=rtl] .offset-gt-lg-75{margin-left:auto;margin-left:initial;margin-right:75%}.flex-offset-gt-lg-80,.offset-gt-lg-80{margin-left:80%}[dir=rtl] .flex-offset-gt-lg-80,[dir=rtl] .offset-gt-lg-80{margin-left:auto;margin-left:initial;margin-right:80%}.flex-offset-gt-lg-85,.offset-gt-lg-85{margin-left:85%}[dir=rtl] .flex-offset-gt-lg-85,[dir=rtl] .offset-gt-lg-85{margin-left:auto;margin-left:initial;margin-right:85%}.flex-offset-gt-lg-90,.offset-gt-lg-90{margin-left:90%}[dir=rtl] .flex-offset-gt-lg-90,[dir=rtl] .offset-gt-lg-90{margin-left:auto;margin-left:initial;margin-right:90%}.flex-offset-gt-lg-95,.offset-gt-lg-95{margin-left:95%}[dir=rtl] .flex-offset-gt-lg-95,[dir=rtl] .offset-gt-lg-95{margin-left:auto;margin-left:initial;margin-right:95%}.flex-offset-gt-lg-33,.offset-gt-lg-33{margin-left:calc(100% / 3)}.flex-offset-gt-lg-66,.offset-gt-lg-66{margin-left:calc(200% / 3)}[dir=rtl] .flex-offset-gt-lg-66,[dir=rtl] .offset-gt-lg-66{margin-left:auto;margin-left:initial;margin-right:calc(200% / 3)}.layout-align-gt-lg{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.layout-align-gt-lg-start,.layout-align-gt-lg-start-center,.layout-align-gt-lg-start-end,.layout-align-gt-lg-start-start,.layout-align-gt-lg-start-stretch{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.layout-align-gt-lg-center,.layout-align-gt-lg-center-center,.layout-align-gt-lg-center-end,.layout-align-gt-lg-center-start,.layout-align-gt-lg-center-stretch{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.layout-align-gt-lg-end,.layout-align-gt-lg-end-center,.layout-align-gt-lg-end-end,.layout-align-gt-lg-end-start,.layout-align-gt-lg-end-stretch{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.layout-align-gt-lg-space-around,.layout-align-gt-lg-space-around-center,.layout-align-gt-lg-space-around-end,.layout-align-gt-lg-space-around-start,.layout-align-gt-lg-space-around-stretch{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.layout-align-gt-lg-space-between,.layout-align-gt-lg-space-between-center,.layout-align-gt-lg-space-between-end,.layout-align-gt-lg-space-between-start,.layout-align-gt-lg-space-between-stretch{-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.layout-align-gt-lg-center-start,.layout-align-gt-lg-end-start,.layout-align-gt-lg-space-around-start,.layout-align-gt-lg-space-between-start,.layout-align-gt-lg-start-start{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}.layout-align-gt-lg-center-center,.layout-align-gt-lg-end-center,.layout-align-gt-lg-space-around-center,.layout-align-gt-lg-space-between-center,.layout-align-gt-lg-start-center{-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;max-width:100%}.layout-align-gt-lg-center-center>*,.layout-align-gt-lg-end-center>*,.layout-align-gt-lg-space-around-center>*,.layout-align-gt-lg-space-between-center>*,.layout-align-gt-lg-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-gt-lg-center-end,.layout-align-gt-lg-end-end,.layout-align-gt-lg-space-around-end,.layout-align-gt-lg-space-between-end,.layout-align-gt-lg-start-end{-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;-webkit-align-content:flex-end;-ms-flex-line-pack:end;align-content:flex-end}.layout-align-gt-lg-center-stretch,.layout-align-gt-lg-end-stretch,.layout-align-gt-lg-space-around-stretch,.layout-align-gt-lg-space-between-stretch,.layout-align-gt-lg-start-stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch}.flex-gt-lg{-webkit-flex:1;-ms-flex:1;flex:1;box-sizing:border-box}.flex-gt-lg-grow{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;box-sizing:border-box}.flex-gt-lg-initial{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-lg-auto{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-gt-lg-none{-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-gt-lg-noshrink{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-gt-lg-nogrow{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-gt-lg-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box}.layout-gt-lg-row>.flex-gt-lg-0,.layout-row>.flex-gt-lg-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box;min-width:0}.layout-column>.flex-gt-lg-0,.layout-gt-lg-column>.flex-gt-lg-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:100%;max-height:0;box-sizing:border-box;min-height:0}.flex-gt-lg-5,.layout-gt-lg-row>.flex-gt-lg-5,.layout-row>.flex-gt-lg-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:5%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-5,.layout-gt-lg-column>.flex-gt-lg-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:100%;max-height:5%;box-sizing:border-box}.flex-gt-lg-10,.layout-gt-lg-row>.flex-gt-lg-10,.layout-row>.flex-gt-lg-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:10%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-10,.layout-gt-lg-column>.flex-gt-lg-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:100%;max-height:10%;box-sizing:border-box}.flex-gt-lg-15,.layout-gt-lg-row>.flex-gt-lg-15,.layout-row>.flex-gt-lg-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:15%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-15,.layout-gt-lg-column>.flex-gt-lg-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:100%;max-height:15%;box-sizing:border-box}.flex-gt-lg-20,.layout-gt-lg-row>.flex-gt-lg-20,.layout-row>.flex-gt-lg-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:20%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-20,.layout-gt-lg-column>.flex-gt-lg-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:100%;max-height:20%;box-sizing:border-box}.flex-gt-lg-25,.layout-gt-lg-row>.flex-gt-lg-25,.layout-row>.flex-gt-lg-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:25%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-25,.layout-gt-lg-column>.flex-gt-lg-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:100%;max-height:25%;box-sizing:border-box}.flex-gt-lg-30,.layout-gt-lg-row>.flex-gt-lg-30,.layout-row>.flex-gt-lg-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:30%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-30,.layout-gt-lg-column>.flex-gt-lg-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:100%;max-height:30%;box-sizing:border-box}.flex-gt-lg-35,.layout-gt-lg-row>.flex-gt-lg-35,.layout-row>.flex-gt-lg-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:35%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-35,.layout-gt-lg-column>.flex-gt-lg-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:100%;max-height:35%;box-sizing:border-box}.flex-gt-lg-40,.layout-gt-lg-row>.flex-gt-lg-40,.layout-row>.flex-gt-lg-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:40%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-40,.layout-gt-lg-column>.flex-gt-lg-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:100%;max-height:40%;box-sizing:border-box}.flex-gt-lg-45,.layout-gt-lg-row>.flex-gt-lg-45,.layout-row>.flex-gt-lg-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:45%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-45,.layout-gt-lg-column>.flex-gt-lg-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:100%;max-height:45%;box-sizing:border-box}.flex-gt-lg-50,.layout-gt-lg-row>.flex-gt-lg-50,.layout-row>.flex-gt-lg-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:50%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-50,.layout-gt-lg-column>.flex-gt-lg-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:100%;max-height:50%;box-sizing:border-box}.flex-gt-lg-55,.layout-gt-lg-row>.flex-gt-lg-55,.layout-row>.flex-gt-lg-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:55%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-55,.layout-gt-lg-column>.flex-gt-lg-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:100%;max-height:55%;box-sizing:border-box}.flex-gt-lg-60,.layout-gt-lg-row>.flex-gt-lg-60,.layout-row>.flex-gt-lg-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:60%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-60,.layout-gt-lg-column>.flex-gt-lg-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:100%;max-height:60%;box-sizing:border-box}.flex-gt-lg-65,.layout-gt-lg-row>.flex-gt-lg-65,.layout-row>.flex-gt-lg-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:65%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-65,.layout-gt-lg-column>.flex-gt-lg-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:100%;max-height:65%;box-sizing:border-box}.flex-gt-lg-70,.layout-gt-lg-row>.flex-gt-lg-70,.layout-row>.flex-gt-lg-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:70%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-70,.layout-gt-lg-column>.flex-gt-lg-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:100%;max-height:70%;box-sizing:border-box}.flex-gt-lg-75,.layout-gt-lg-row>.flex-gt-lg-75,.layout-row>.flex-gt-lg-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:75%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-75,.layout-gt-lg-column>.flex-gt-lg-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:100%;max-height:75%;box-sizing:border-box}.flex-gt-lg-80,.layout-gt-lg-row>.flex-gt-lg-80,.layout-row>.flex-gt-lg-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:80%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-80,.layout-gt-lg-column>.flex-gt-lg-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:100%;max-height:80%;box-sizing:border-box}.flex-gt-lg-85,.layout-gt-lg-row>.flex-gt-lg-85,.layout-row>.flex-gt-lg-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:85%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-85,.layout-gt-lg-column>.flex-gt-lg-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:100%;max-height:85%;box-sizing:border-box}.flex-gt-lg-90,.layout-gt-lg-row>.flex-gt-lg-90,.layout-row>.flex-gt-lg-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:90%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-90,.layout-gt-lg-column>.flex-gt-lg-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:100%;max-height:90%;box-sizing:border-box}.flex-gt-lg-95,.layout-gt-lg-row>.flex-gt-lg-95,.layout-row>.flex-gt-lg-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:95%;max-height:100%;box-sizing:border-box}.layout-column>.flex-gt-lg-95,.layout-gt-lg-column>.flex-gt-lg-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:100%;max-height:95%;box-sizing:border-box}.flex-gt-lg-100,.layout-column>.flex-gt-lg-100,.layout-gt-lg-column>.flex-gt-lg-100,.layout-gt-lg-row>.flex-gt-lg-100,.layout-row>.flex-gt-lg-100{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-gt-lg-row>.flex-gt-lg-33,.layout-row>.flex-gt-lg-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-gt-lg-row>.flex-gt-lg-66,.layout-row>.flex-gt-lg-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-gt-lg-row>.flex,.layout-row>.flex{min-width:0}.layout-column>.flex-gt-lg-33,.layout-gt-lg-column>.flex-gt-lg-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-gt-lg-66,.layout-gt-lg-column>.flex-gt-lg-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-column>.flex,.layout-gt-lg-column>.flex{min-height:0}.layout-gt-lg,.layout-gt-lg-column,.layout-gt-lg-row{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.layout-gt-lg-column{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.layout-gt-lg-row{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.flex-order-xl--20{-webkit-order:-20;-ms-flex-order:-20;order:-20}.flex-order-xl--19{-webkit-order:-19;-ms-flex-order:-19;order:-19}.flex-order-xl--18{-webkit-order:-18;-ms-flex-order:-18;order:-18}.flex-order-xl--17{-webkit-order:-17;-ms-flex-order:-17;order:-17}.flex-order-xl--16{-webkit-order:-16;-ms-flex-order:-16;order:-16}.flex-order-xl--15{-webkit-order:-15;-ms-flex-order:-15;order:-15}.flex-order-xl--14{-webkit-order:-14;-ms-flex-order:-14;order:-14}.flex-order-xl--13{-webkit-order:-13;-ms-flex-order:-13;order:-13}.flex-order-xl--12{-webkit-order:-12;-ms-flex-order:-12;order:-12}.flex-order-xl--11{-webkit-order:-11;-ms-flex-order:-11;order:-11}.flex-order-xl--10{-webkit-order:-10;-ms-flex-order:-10;order:-10}.flex-order-xl--9{-webkit-order:-9;-ms-flex-order:-9;order:-9}.flex-order-xl--8{-webkit-order:-8;-ms-flex-order:-8;order:-8}.flex-order-xl--7{-webkit-order:-7;-ms-flex-order:-7;order:-7}.flex-order-xl--6{-webkit-order:-6;-ms-flex-order:-6;order:-6}.flex-order-xl--5{-webkit-order:-5;-ms-flex-order:-5;order:-5}.flex-order-xl--4{-webkit-order:-4;-ms-flex-order:-4;order:-4}.flex-order-xl--3{-webkit-order:-3;-ms-flex-order:-3;order:-3}.flex-order-xl--2{-webkit-order:-2;-ms-flex-order:-2;order:-2}.flex-order-xl--1{-webkit-order:-1;-ms-flex-order:-1;order:-1}.flex-order-xl-0{-webkit-order:0;-ms-flex-order:0;order:0}.flex-order-xl-1{-webkit-order:1;-ms-flex-order:1;order:1}.flex-order-xl-2{-webkit-order:2;-ms-flex-order:2;order:2}.flex-order-xl-3{-webkit-order:3;-ms-flex-order:3;order:3}.flex-order-xl-4{-webkit-order:4;-ms-flex-order:4;order:4}.flex-order-xl-5{-webkit-order:5;-ms-flex-order:5;order:5}.flex-order-xl-6{-webkit-order:6;-ms-flex-order:6;order:6}.flex-order-xl-7{-webkit-order:7;-ms-flex-order:7;order:7}.flex-order-xl-8{-webkit-order:8;-ms-flex-order:8;order:8}.flex-order-xl-9{-webkit-order:9;-ms-flex-order:9;order:9}.flex-order-xl-10{-webkit-order:10;-ms-flex-order:10;order:10}.flex-order-xl-11{-webkit-order:11;-ms-flex-order:11;order:11}.flex-order-xl-12{-webkit-order:12;-ms-flex-order:12;order:12}.flex-order-xl-13{-webkit-order:13;-ms-flex-order:13;order:13}.flex-order-xl-14{-webkit-order:14;-ms-flex-order:14;order:14}.flex-order-xl-15{-webkit-order:15;-ms-flex-order:15;order:15}.flex-order-xl-16{-webkit-order:16;-ms-flex-order:16;order:16}.flex-order-xl-17{-webkit-order:17;-ms-flex-order:17;order:17}.flex-order-xl-18{-webkit-order:18;-ms-flex-order:18;order:18}.flex-order-xl-19{-webkit-order:19;-ms-flex-order:19;order:19}.flex-order-xl-20{-webkit-order:20;-ms-flex-order:20;order:20}.flex-offset-xl-0,.offset-xl-0{margin-left:0}[dir=rtl] .flex-offset-xl-0,[dir=rtl] .offset-xl-0{margin-left:auto;margin-left:initial;margin-right:0}.flex-offset-xl-5,.offset-xl-5{margin-left:5%}[dir=rtl] .flex-offset-xl-5,[dir=rtl] .offset-xl-5{margin-left:auto;margin-left:initial;margin-right:5%}.flex-offset-xl-10,.offset-xl-10{margin-left:10%}[dir=rtl] .flex-offset-xl-10,[dir=rtl] .offset-xl-10{margin-left:auto;margin-left:initial;margin-right:10%}.flex-offset-xl-15,.offset-xl-15{margin-left:15%}[dir=rtl] .flex-offset-xl-15,[dir=rtl] .offset-xl-15{margin-left:auto;margin-left:initial;margin-right:15%}.flex-offset-xl-20,.offset-xl-20{margin-left:20%}[dir=rtl] .flex-offset-xl-20,[dir=rtl] .offset-xl-20{margin-left:auto;margin-left:initial;margin-right:20%}.flex-offset-xl-25,.offset-xl-25{margin-left:25%}[dir=rtl] .flex-offset-xl-25,[dir=rtl] .offset-xl-25{margin-left:auto;margin-left:initial;margin-right:25%}.flex-offset-xl-30,.offset-xl-30{margin-left:30%}[dir=rtl] .flex-offset-xl-30,[dir=rtl] .offset-xl-30{margin-left:auto;margin-left:initial;margin-right:30%}.flex-offset-xl-35,.offset-xl-35{margin-left:35%}[dir=rtl] .flex-offset-xl-35,[dir=rtl] .offset-xl-35{margin-left:auto;margin-left:initial;margin-right:35%}.flex-offset-xl-40,.offset-xl-40{margin-left:40%}[dir=rtl] .flex-offset-xl-40,[dir=rtl] .offset-xl-40{margin-left:auto;margin-left:initial;margin-right:40%}.flex-offset-xl-45,.offset-xl-45{margin-left:45%}[dir=rtl] .flex-offset-xl-45,[dir=rtl] .offset-xl-45{margin-left:auto;margin-left:initial;margin-right:45%}.flex-offset-xl-50,.offset-xl-50{margin-left:50%}[dir=rtl] .flex-offset-xl-50,[dir=rtl] .offset-xl-50{margin-left:auto;margin-left:initial;margin-right:50%}.flex-offset-xl-55,.offset-xl-55{margin-left:55%}[dir=rtl] .flex-offset-xl-55,[dir=rtl] .offset-xl-55{margin-left:auto;margin-left:initial;margin-right:55%}.flex-offset-xl-60,.offset-xl-60{margin-left:60%}[dir=rtl] .flex-offset-xl-60,[dir=rtl] .offset-xl-60{margin-left:auto;margin-left:initial;margin-right:60%}.flex-offset-xl-65,.offset-xl-65{margin-left:65%}[dir=rtl] .flex-offset-xl-65,[dir=rtl] .offset-xl-65{margin-left:auto;margin-left:initial;margin-right:65%}.flex-offset-xl-70,.offset-xl-70{margin-left:70%}[dir=rtl] .flex-offset-xl-70,[dir=rtl] .offset-xl-70{margin-left:auto;margin-left:initial;margin-right:70%}.flex-offset-xl-75,.offset-xl-75{margin-left:75%}[dir=rtl] .flex-offset-xl-75,[dir=rtl] .offset-xl-75{margin-left:auto;margin-left:initial;margin-right:75%}.flex-offset-xl-80,.offset-xl-80{margin-left:80%}[dir=rtl] .flex-offset-xl-80,[dir=rtl] .offset-xl-80{margin-left:auto;margin-left:initial;margin-right:80%}.flex-offset-xl-85,.offset-xl-85{margin-left:85%}[dir=rtl] .flex-offset-xl-85,[dir=rtl] .offset-xl-85{margin-left:auto;margin-left:initial;margin-right:85%}.flex-offset-xl-90,.offset-xl-90{margin-left:90%}[dir=rtl] .flex-offset-xl-90,[dir=rtl] .offset-xl-90{margin-left:auto;margin-left:initial;margin-right:90%}.flex-offset-xl-95,.offset-xl-95{margin-left:95%}[dir=rtl] .flex-offset-xl-95,[dir=rtl] .offset-xl-95{margin-left:auto;margin-left:initial;margin-right:95%}.flex-offset-xl-33,.offset-xl-33{margin-left:calc(100% / 3)}.flex-offset-xl-66,.offset-xl-66{margin-left:calc(200% / 3)}[dir=rtl] .flex-offset-xl-66,[dir=rtl] .offset-xl-66{margin-left:auto;margin-left:initial;margin-right:calc(200% / 3)}.layout-align-xl{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.layout-align-xl-start,.layout-align-xl-start-center,.layout-align-xl-start-end,.layout-align-xl-start-start,.layout-align-xl-start-stretch{-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.layout-align-xl-center,.layout-align-xl-center-center,.layout-align-xl-center-end,.layout-align-xl-center-start,.layout-align-xl-center-stretch{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.layout-align-xl-end,.layout-align-xl-end-center,.layout-align-xl-end-end,.layout-align-xl-end-start,.layout-align-xl-end-stretch{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.layout-align-xl-space-around,.layout-align-xl-space-around-center,.layout-align-xl-space-around-end,.layout-align-xl-space-around-start,.layout-align-xl-space-around-stretch{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.layout-align-xl-space-between,.layout-align-xl-space-between-center,.layout-align-xl-space-between-end,.layout-align-xl-space-between-start,.layout-align-xl-space-between-stretch{-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.layout-align-xl-center-start,.layout-align-xl-end-start,.layout-align-xl-space-around-start,.layout-align-xl-space-between-start,.layout-align-xl-start-start{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}.layout-align-xl-center-center,.layout-align-xl-end-center,.layout-align-xl-space-around-center,.layout-align-xl-space-between-center,.layout-align-xl-start-center{-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;max-width:100%}.layout-align-xl-center-center>*,.layout-align-xl-end-center>*,.layout-align-xl-space-around-center>*,.layout-align-xl-space-between-center>*,.layout-align-xl-start-center>*{max-width:100%;box-sizing:border-box}.layout-align-xl-center-end,.layout-align-xl-end-end,.layout-align-xl-space-around-end,.layout-align-xl-space-between-end,.layout-align-xl-start-end{-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;-webkit-align-content:flex-end;-ms-flex-line-pack:end;align-content:flex-end}.layout-align-xl-center-stretch,.layout-align-xl-end-stretch,.layout-align-xl-space-around-stretch,.layout-align-xl-space-between-stretch,.layout-align-xl-start-stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch}.flex-xl{-webkit-flex:1;-ms-flex:1;flex:1;box-sizing:border-box}.flex-xl-grow{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;box-sizing:border-box}.flex-xl-initial{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-xl-auto{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;box-sizing:border-box}.flex-xl-none{-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;box-sizing:border-box}.flex-xl-noshrink{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;box-sizing:border-box}.flex-xl-nogrow{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;box-sizing:border-box}.flex-xl-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box}.layout-row>.flex-xl-0,.layout-xl-row>.flex-xl-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:0;max-height:100%;box-sizing:border-box;min-width:0}.layout-column>.flex-xl-0,.layout-xl-column>.flex-xl-0{-webkit-flex:1 1 0;-ms-flex:1 1 0;flex:1 1 0;max-width:100%;max-height:0;box-sizing:border-box;min-height:0}.flex-xl-5,.layout-row>.flex-xl-5,.layout-xl-row>.flex-xl-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:5%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-5,.layout-xl-column>.flex-xl-5{-webkit-flex:1 1 5%;-ms-flex:1 1 5%;flex:1 1 5%;max-width:100%;max-height:5%;box-sizing:border-box}.flex-xl-10,.layout-row>.flex-xl-10,.layout-xl-row>.flex-xl-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:10%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-10,.layout-xl-column>.flex-xl-10{-webkit-flex:1 1 10%;-ms-flex:1 1 10%;flex:1 1 10%;max-width:100%;max-height:10%;box-sizing:border-box}.flex-xl-15,.layout-row>.flex-xl-15,.layout-xl-row>.flex-xl-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:15%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-15,.layout-xl-column>.flex-xl-15{-webkit-flex:1 1 15%;-ms-flex:1 1 15%;flex:1 1 15%;max-width:100%;max-height:15%;box-sizing:border-box}.flex-xl-20,.layout-row>.flex-xl-20,.layout-xl-row>.flex-xl-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:20%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-20,.layout-xl-column>.flex-xl-20{-webkit-flex:1 1 20%;-ms-flex:1 1 20%;flex:1 1 20%;max-width:100%;max-height:20%;box-sizing:border-box}.flex-xl-25,.layout-row>.flex-xl-25,.layout-xl-row>.flex-xl-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:25%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-25,.layout-xl-column>.flex-xl-25{-webkit-flex:1 1 25%;-ms-flex:1 1 25%;flex:1 1 25%;max-width:100%;max-height:25%;box-sizing:border-box}.flex-xl-30,.layout-row>.flex-xl-30,.layout-xl-row>.flex-xl-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:30%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-30,.layout-xl-column>.flex-xl-30{-webkit-flex:1 1 30%;-ms-flex:1 1 30%;flex:1 1 30%;max-width:100%;max-height:30%;box-sizing:border-box}.flex-xl-35,.layout-row>.flex-xl-35,.layout-xl-row>.flex-xl-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:35%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-35,.layout-xl-column>.flex-xl-35{-webkit-flex:1 1 35%;-ms-flex:1 1 35%;flex:1 1 35%;max-width:100%;max-height:35%;box-sizing:border-box}.flex-xl-40,.layout-row>.flex-xl-40,.layout-xl-row>.flex-xl-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:40%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-40,.layout-xl-column>.flex-xl-40{-webkit-flex:1 1 40%;-ms-flex:1 1 40%;flex:1 1 40%;max-width:100%;max-height:40%;box-sizing:border-box}.flex-xl-45,.layout-row>.flex-xl-45,.layout-xl-row>.flex-xl-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:45%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-45,.layout-xl-column>.flex-xl-45{-webkit-flex:1 1 45%;-ms-flex:1 1 45%;flex:1 1 45%;max-width:100%;max-height:45%;box-sizing:border-box}.flex-xl-50,.layout-row>.flex-xl-50,.layout-xl-row>.flex-xl-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:50%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-50,.layout-xl-column>.flex-xl-50{-webkit-flex:1 1 50%;-ms-flex:1 1 50%;flex:1 1 50%;max-width:100%;max-height:50%;box-sizing:border-box}.flex-xl-55,.layout-row>.flex-xl-55,.layout-xl-row>.flex-xl-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:55%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-55,.layout-xl-column>.flex-xl-55{-webkit-flex:1 1 55%;-ms-flex:1 1 55%;flex:1 1 55%;max-width:100%;max-height:55%;box-sizing:border-box}.flex-xl-60,.layout-row>.flex-xl-60,.layout-xl-row>.flex-xl-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:60%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-60,.layout-xl-column>.flex-xl-60{-webkit-flex:1 1 60%;-ms-flex:1 1 60%;flex:1 1 60%;max-width:100%;max-height:60%;box-sizing:border-box}.flex-xl-65,.layout-row>.flex-xl-65,.layout-xl-row>.flex-xl-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:65%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-65,.layout-xl-column>.flex-xl-65{-webkit-flex:1 1 65%;-ms-flex:1 1 65%;flex:1 1 65%;max-width:100%;max-height:65%;box-sizing:border-box}.flex-xl-70,.layout-row>.flex-xl-70,.layout-xl-row>.flex-xl-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:70%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-70,.layout-xl-column>.flex-xl-70{-webkit-flex:1 1 70%;-ms-flex:1 1 70%;flex:1 1 70%;max-width:100%;max-height:70%;box-sizing:border-box}.flex-xl-75,.layout-row>.flex-xl-75,.layout-xl-row>.flex-xl-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:75%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-75,.layout-xl-column>.flex-xl-75{-webkit-flex:1 1 75%;-ms-flex:1 1 75%;flex:1 1 75%;max-width:100%;max-height:75%;box-sizing:border-box}.flex-xl-80,.layout-row>.flex-xl-80,.layout-xl-row>.flex-xl-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:80%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-80,.layout-xl-column>.flex-xl-80{-webkit-flex:1 1 80%;-ms-flex:1 1 80%;flex:1 1 80%;max-width:100%;max-height:80%;box-sizing:border-box}.flex-xl-85,.layout-row>.flex-xl-85,.layout-xl-row>.flex-xl-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:85%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-85,.layout-xl-column>.flex-xl-85{-webkit-flex:1 1 85%;-ms-flex:1 1 85%;flex:1 1 85%;max-width:100%;max-height:85%;box-sizing:border-box}.flex-xl-90,.layout-row>.flex-xl-90,.layout-xl-row>.flex-xl-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:90%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-90,.layout-xl-column>.flex-xl-90{-webkit-flex:1 1 90%;-ms-flex:1 1 90%;flex:1 1 90%;max-width:100%;max-height:90%;box-sizing:border-box}.flex-xl-95,.layout-row>.flex-xl-95,.layout-xl-row>.flex-xl-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:95%;max-height:100%;box-sizing:border-box}.layout-column>.flex-xl-95,.layout-xl-column>.flex-xl-95{-webkit-flex:1 1 95%;-ms-flex:1 1 95%;flex:1 1 95%;max-width:100%;max-height:95%;box-sizing:border-box}.flex-xl-100,.layout-column>.flex-xl-100,.layout-row>.flex-xl-100,.layout-xl-column>.flex-xl-100,.layout-xl-row>.flex-xl-100{-webkit-flex:1 1 100%;-ms-flex:1 1 100%;flex:1 1 100%;max-width:100%;max-height:100%;box-sizing:border-box}.layout-row>.flex-xl-33,.layout-xl-row>.flex-xl-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:33.33%;max-height:100%;box-sizing:border-box}.layout-row>.flex-xl-66,.layout-xl-row>.flex-xl-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:66.66%;max-height:100%;box-sizing:border-box}.layout-row>.flex,.layout-xl-row>.flex{min-width:0}.layout-column>.flex-xl-33,.layout-xl-column>.flex-xl-33{-webkit-flex:1 1 33.33%;-ms-flex:1 1 33.33%;flex:1 1 33.33%;max-width:100%;max-height:33.33%;box-sizing:border-box}.layout-column>.flex-xl-66,.layout-xl-column>.flex-xl-66{-webkit-flex:1 1 66.66%;-ms-flex:1 1 66.66%;flex:1 1 66.66%;max-width:100%;max-height:66.66%;box-sizing:border-box}.layout-column>.flex,.layout-xl-column>.flex{min-height:0}.layout-xl,.layout-xl-column,.layout-xl-row{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.layout-xl-column{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.layout-xl-row{-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.hide-gt-lg:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-gt-lg):not(.show-xl):not(.show),.hide-gt-md:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-gt-lg):not(.show-xl):not(.show),.hide-gt-sm:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-gt-lg):not(.show-xl):not(.show),.hide-gt-xs:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-gt-lg):not(.show-xl):not(.show),.hide-xl:not(.show-xl):not(.show-gt-lg):not(.show-gt-md):not(.show-gt-sm):not(.show-gt-xs):not(.show),.hide:not(.show-gt-xs):not(.show-gt-sm):not(.show-gt-md):not(.show-gt-lg):not(.show-xl):not(.show){display:none}}@media print{.hide-print:not(.show-print):not(.show){display:none!important}} \ No newline at end of file diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-material/angular-material.min.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-material/angular-material.min.js new file mode 100644 index 00000000000..b9ad5a213d1 --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-material/angular-material.min.js @@ -0,0 +1,17 @@ +/*! + * Angular Material Design + * https://github.com/angular/material + * @license MIT + * v1.1.0-rc.5 + */ +!function(e,t,n){"use strict";!function(){t.module("ngMaterial",["ng","ngAnimate","ngAria","material.core","material.core.gestures","material.core.layout","material.core.theming.palette","material.core.theming","material.core.animate","material.components.autocomplete","material.components.backdrop","material.components.button","material.components.bottomSheet","material.components.checkbox","material.components.chips","material.components.card","material.components.colors","material.components.content","material.components.datepicker","material.components.dialog","material.components.divider","material.components.fabActions","material.components.fabShared","material.components.fabSpeedDial","material.components.fabToolbar","material.components.fabTrigger","material.components.icon","material.components.gridList","material.components.input","material.components.list","material.components.menu","material.components.menuBar","material.components.navBar","material.components.panel","material.components.progressLinear","material.components.progressCircular","material.components.radioButton","material.components.select","material.components.showHide","material.components.sidenav","material.components.slider","material.components.sticky","material.components.subheader","material.components.swipe","material.components.switch","material.components.tabs","material.components.toast","material.components.tooltip","material.components.virtualRepeat","material.components.toolbar","material.components.whiteframe"])}(),function(){function e(e,t){if(t.has("$swipe")){var n="You are using the ngTouch module. \nAngular Material already has mobile click, tap, and swipe support... \nngTouch is not supported with Angular Material!";e.warn(n)}}function n(e,t){e.decorator("$$rAF",["$delegate",o]),t.theme("default").primaryPalette("indigo").accentPalette("pink").warnPalette("deep-orange").backgroundPalette("grey")}function o(e){return e.throttle=function(t){var n,o,i,r;return function(){n=arguments,r=this,i=t,o||(o=!0,e(function(){i.apply(r,Array.prototype.slice.call(n)),o=!1}))}},e}t.module("material.core",["ngAnimate","material.core.animate","material.core.layout","material.core.gestures","material.core.theming"]).config(n).run(e),e.$inject=["$log","$injector"],n.$inject=["$provide","$mdThemingProvider"],o.$inject=["$delegate"]}(),function(){function e(){return{restrict:"A",link:n}}function n(e,t,n){var o=n.mdAutoFocus||n.mdAutofocus||n.mdSidenavFocus;e.$watch(o,function(e){t.toggleClass("_md-autofocus",e)})}t.module("material.core").directive("mdAutofocus",e).directive("mdAutoFocus",e).directive("mdSidenavFocus",e)}(),function(){function e(){function e(e){var t="#"===e[0]?e.substr(1):e,n=t.length/3,o=t.substr(0,n),i=t.substr(n,n),r=t.substr(2*n);return 1===n&&(o+=o,i+=i,r+=r),"rgba("+parseInt(o,16)+","+parseInt(i,16)+","+parseInt(r,16)+",0.1)"}function t(e){e=e.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);var t=e&&4===e.length?"#"+("0"+parseInt(e[1],10).toString(16)).slice(-2)+("0"+parseInt(e[2],10).toString(16)).slice(-2)+("0"+parseInt(e[3],10).toString(16)).slice(-2):"";return t.toUpperCase()}function n(e){return e.replace(")",", 0.1)").replace("(","a(")}function o(e){return e?e.replace("rgba","rgb").replace(/,[^\),]+\)/,")"):"rgb(0,0,0)"}return{rgbaToHex:t,hexToRgba:e,rgbToRgba:n,rgbaToRgb:o}}t.module("material.core").factory("$mdColorUtil",e)}(),function(){function e(e){function n(e){var t=r+"-"+e,n=i(t),a=n.charAt(0).toLowerCase()+n.substring(1);return o(e)?e:o(n)?n:o(a)?a:e}function o(e){return t.isDefined(s.style[e])}function i(e){return e.replace(d,function(e,t,n,o){return o?n.toUpperCase():n})}var r=e.vendorPrefix,a=/webkit/i.test(r),d=/([:\-_]+(.))/g,s=document.createElement("div");return{KEY_CODE:{COMMA:188,SEMICOLON:186,ENTER:13,ESCAPE:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT_ARROW:37,UP_ARROW:38,RIGHT_ARROW:39,DOWN_ARROW:40,TAB:9,BACKSPACE:8,DELETE:46},CSS:{TRANSITIONEND:"transitionend"+(a?" webkitTransitionEnd":""),ANIMATIONEND:"animationend"+(a?" webkitAnimationEnd":""),TRANSFORM:n("transform"),TRANSFORM_ORIGIN:n("transformOrigin"),TRANSITION:n("transition"),TRANSITION_DURATION:n("transitionDuration"),ANIMATION_PLAY_STATE:n("animationPlayState"),ANIMATION_DURATION:n("animationDuration"),ANIMATION_NAME:n("animationName"),ANIMATION_TIMING:n("animationTimingFunction"),ANIMATION_DIRECTION:n("animationDirection")},MEDIA:{xs:"(max-width: 599px)","gt-xs":"(min-width: 600px)",sm:"(min-width: 600px) and (max-width: 959px)","gt-sm":"(min-width: 960px)",md:"(min-width: 960px) and (max-width: 1279px)","gt-md":"(min-width: 1280px)",lg:"(min-width: 1280px) and (max-width: 1919px)","gt-lg":"(min-width: 1920px)",xl:"(min-width: 1920px)",landscape:"(orientation: landscape)",portrait:"(orientation: portrait)",print:"print"},MEDIA_PRIORITY:["xl","gt-lg","lg","gt-md","md","gt-sm","sm","gt-xs","xs","landscape","portrait","print"]}}t.module("material.core").factory("$mdConstant",e),e.$inject=["$sniffer"]}(),function(){function e(e,n){function o(){return[].concat(v)}function i(){return v.length}function r(e){return v.length&&e>-1&&e<v.length}function a(e){return e?r(u(e)+1):!1}function d(e){return e?r(u(e)-1):!1}function s(e){return r(e)?v[e]:null}function c(e,t){return v.filter(function(n){return n[e]===t})}function l(e,n){return e?(t.isNumber(n)||(n=v.length),v.splice(n,0,e),u(e)):-1}function m(e){h(e)&&v.splice(u(e),1)}function u(e){return v.indexOf(e)}function h(e){return e&&u(e)>-1}function p(){return v.length?v[0]:null}function f(){return v.length?v[v.length-1]:null}function g(e,o,i,a){i=i||b;for(var d=u(o);;){if(!r(d))return null;var s=d+(e?-1:1),c=null;if(r(s)?c=v[s]:n&&(c=e?f():p(),s=u(c)),null===c||s===a)return null;if(i(c))return c;t.isUndefined(a)&&(a=s),d=s}}var b=function(){return!0};e&&!t.isArray(e)&&(e=Array.prototype.slice.call(e)),n=!!n;var v=e||[];return{items:o,count:i,inRange:r,contains:h,indexOf:u,itemAt:s,findBy:c,add:l,remove:m,first:p,last:f,next:t.bind(null,g,!1),previous:t.bind(null,g,!0),hasPrevious:d,hasNext:a}}t.module("material.core").config(["$provide",function(t){t.decorator("$mdUtil",["$delegate",function(t){return t.iterator=e,t}])}])}(),function(){function e(e,n,o){function i(e){var n=u[e];t.isUndefined(n)&&(n=u[e]=r(e));var o=p[n];return t.isUndefined(o)&&(o=a(n)),o}function r(t){return e.MEDIA[t]||("("!==t.charAt(0)?"("+t+")":t)}function a(e){var t=h[e];return t||(t=h[e]=o.matchMedia(e)),t.addListener(d),p[t.media]=!!t.matches}function d(e){n.$evalAsync(function(){p[e.media]=!!e.matches})}function s(e){return h[e]}function c(t,n){for(var o=0;o<e.MEDIA_PRIORITY.length;o++){var i=e.MEDIA_PRIORITY[o];if(h[u[i]].matches){var r=m(t,n+"-"+i);if(t[r])return t[r]}}return t[m(t,n)]}function l(n,o,i){var r=[];return n.forEach(function(n){var a=m(o,n);t.isDefined(o[a])&&r.push(o.$observe(a,t.bind(void 0,i,null)));for(var d in e.MEDIA)a=m(o,n+"-"+d),t.isDefined(o[a])&&r.push(o.$observe(a,t.bind(void 0,i,d)))}),function(){r.forEach(function(e){e()})}}function m(e,t){return f[t]||(f[t]=e.$normalize(t))}var u={},h={},p={},f={};return i.getResponsiveAttribute=c,i.getQuery=s,i.watchResponsiveAttributes=l,i}t.module("material.core").factory("$mdMedia",e),e.$inject=["$mdConstant","$rootScope","$window"]}(),function(){function e(e,n){function o(e){return e=t.isArray(e)?e:[e],e.forEach(function(t){a.forEach(function(n){e.push(n+"-"+t)})}),e}function i(e){return e=t.isArray(e)?e:[e],o(e).map(function(e){return"["+e+"]"}).join(",")}function r(e,t){e=e[0]||e;for(var n=o(t),i=0;i<n.length;i++)if(e.hasAttribute(n[i]))return!0;return!1}var a=["data","x"];return e?n?i(e):o(e):{buildList:o,buildSelector:i,hasAttribute:r}}t.module("material.core").config(["$provide",function(t){t.decorator("$mdUtil",["$delegate",function(t){return t.prefixer=e,t}])}])}(),function(){function o(o,r,a,d,s,c,l,m,u){function h(e){return e[0]||e}var p=c.startSymbol(),f=c.endSymbol(),g="{{"===p&&"}}"===f,b=function(e,n,o){var i=!1;if(e&&e.length){var r=u.getComputedStyle(e[0]);i=t.isDefined(r[n])&&(o?r[n]==o:!0)}return i},v={dom:{},now:e.performance?t.bind(e.performance,e.performance.now):Date.now||function(){return(new Date).getTime()},bidi:function(e,n,i,r){function a(e){return e?d(e)?e:e+"px":"0"}function d(e){return String(e).indexOf("px")>-1}var s=!("rtl"==o[0].dir||"rtl"==o[0].body.dir);return 0==arguments.length?s?"ltr":"rtl":void(s&&t.isDefined(i)?t.element(e).css(n,a(i)):!s&&t.isDefined(r)&&t.element(e).css(n,a(r)))},clientRect:function(e,t,n){var o=h(e);t=h(t||o.offsetParent||document.body);var i=o.getBoundingClientRect(),r=n?t.getBoundingClientRect():{left:0,top:0,width:0,height:0};return{left:i.left-r.left,top:i.top-r.top,width:i.width,height:i.height}},offsetRect:function(e,t){return v.clientRect(e,t,!0)},nodesToArray:function(e){e=e||[];for(var t=[],n=0;n<e.length;++n)t.push(e.item(n));return t},scrollTop:function(e){e=t.element(e||o[0].body);var i=e[0]==o[0].body?o[0].body:n,r=i?i.scrollTop+i.parentElement.scrollTop:0;return r||Math.abs(e[0].getBoundingClientRect().top)},findFocusTarget:function(e,n){function o(e,n){var o,i=e[0].querySelectorAll(n);return i&&i.length&&i.length&&t.forEach(i,function(e){e=t.element(e);var n=e.hasClass("_md-autofocus");n&&(o=e)}),o}var i,r=this.prefixer("md-autofocus",!0);return i=o(e,n||r),i||n==r||(i=o(e,this.prefixer("md-auto-focus",!0)),i||(i=o(e,r))),i},disableScrollAround:function(e,n){function i(e){function n(e){e.preventDefault()}e=t.element(e||d)[0];var o=t.element('<div class="md-scroll-mask"> <div class="md-scroll-mask-bar"></div></div>');return e.appendChild(o[0]),o.on("wheel",n),o.on("touchmove",n),function(){o.off("wheel"),o.off("touchmove"),o[0].parentNode.removeChild(o[0]),delete v.disableScrollAround._enableScrolling}}function r(){var e=d.parentNode,t=e.style.cssText||"",n=d.style.cssText||"",o=v.scrollTop(d),i=d.clientWidth;return d.scrollHeight>d.clientHeight+1&&(a(d,{position:"fixed",width:"100%",top:-o+"px"}),e.style.overflowY="scroll"),d.clientWidth<i&&a(d,{overflow:"hidden"}),function(){d.style.cssText=n,e.style.cssText=t,d.scrollTop=o,e.scrollTop=o}}function a(e,t){for(var n in t)e.style[n]=t[n]}if(v.disableScrollAround._count=v.disableScrollAround._count||0,++v.disableScrollAround._count,v.disableScrollAround._enableScrolling)return v.disableScrollAround._enableScrolling;var d=o[0].body,s=r(),c=i(n);return v.disableScrollAround._enableScrolling=function(){--v.disableScrollAround._count||(s(),c(),delete v.disableScrollAround._enableScrolling)}},enableScrolling:function(){var e=this.disableScrollAround._enableScrolling;e&&e()},floatingScrollbars:function(){if(this.floatingScrollbars.cached===n){var e=t.element("<div><div></div></div>").css({width:"100%","z-index":-1,position:"absolute",height:"35px","overflow-y":"scroll"});e.children().css("height","60px"),o[0].body.appendChild(e[0]),this.floatingScrollbars.cached=e[0].offsetWidth==e[0].childNodes[0].offsetWidth,e.remove()}return this.floatingScrollbars.cached},forceFocus:function(t){var n=t[0]||t;document.addEventListener("click",function i(e){e.target===n&&e.$focus&&(n.focus(),e.stopImmediatePropagation(),e.preventDefault(),n.removeEventListener("click",i))},!0);var o=document.createEvent("MouseEvents");o.initMouseEvent("click",!1,!0,e,{},0,0,0,0,!1,!1,!1,!1,0,null),o.$material=!0,o.$focus=!0,n.dispatchEvent(o)},createBackdrop:function(e,t){return a(v.supplant('<md-backdrop class="{0}">',[t]))(e)},supplant:function(e,t,n){return n=n||/\{([^\{\}]*)\}/g,e.replace(n,function(e,n){var o=n.split("."),i=t;try{for(var r in o)o.hasOwnProperty(r)&&(i=i[o[r]])}catch(a){i=e}return"string"==typeof i||"number"==typeof i?i:e})},fakeNgModel:function(){return{$fake:!0,$setTouched:t.noop,$setViewValue:function(e){this.$viewValue=e,this.$render(e),this.$viewChangeListeners.forEach(function(e){e()})},$isEmpty:function(e){return 0===(""+e).length},$parsers:[],$formatters:[],$viewChangeListeners:[],$render:t.noop}},debounce:function(e,t,o,i){var a;return function(){var d=o,s=Array.prototype.slice.call(arguments);r.cancel(a),a=r(function(){a=n,e.apply(d,s)},t||10,i)}},throttle:function(e,t){var n;return function(){var o=this,i=arguments,r=v.now();(!n||r-n>t)&&(e.apply(o,i),n=r)}},time:function(e){var t=v.now();return e(),v.now()-t},valueOnUse:function(e,t,n){var o=null,i=Array.prototype.slice.call(arguments),r=i.length>3?i.slice(3):[];Object.defineProperty(e,t,{get:function(){return null===o&&(o=n.apply(e,r)),o}})},nextUid:function(){return""+i++},disconnectScope:function(e){if(e&&e.$root!==e&&!e.$$destroyed){var t=e.$parent;e.$$disconnected=!0,t.$$childHead===e&&(t.$$childHead=e.$$nextSibling),t.$$childTail===e&&(t.$$childTail=e.$$prevSibling),e.$$prevSibling&&(e.$$prevSibling.$$nextSibling=e.$$nextSibling),e.$$nextSibling&&(e.$$nextSibling.$$prevSibling=e.$$prevSibling),e.$$nextSibling=e.$$prevSibling=null}},reconnectScope:function(e){if(e&&e.$root!==e&&e.$$disconnected){var t=e,n=t.$parent;t.$$disconnected=!1,t.$$prevSibling=n.$$childTail,n.$$childHead?(n.$$childTail.$$nextSibling=t,n.$$childTail=t):n.$$childHead=n.$$childTail=t}},getClosest:function(e,n,o){if(e instanceof t.element&&(e=e[0]),n=n.toUpperCase(),o&&(e=e.parentNode),!e)return null;do if(e.nodeName===n)return e;while(e=e.parentNode);return null},elementContains:function(n,o){var i=e.Node&&e.Node.prototype&&Node.prototype.contains,r=i?t.bind(n,n.contains):t.bind(n,function(e){return n===o||!!(16&this.compareDocumentPosition(e))});return r(o)},extractElementByName:function(e,n,o,i){function r(e){return a(e)||(o?d(e):null)}function a(e){if(e)for(var t=0,o=e.length;o>t;t++)if(e[t].nodeName.toLowerCase()===n)return e[t];return null}function d(e){var t;if(e)for(var n=0,o=e.length;o>n;n++){var i=e[n];if(!t)for(var a=0,d=i.childNodes.length;d>a;a++)t=t||r([i.childNodes[a]])}return t}var s=r(e);return!s&&i&&l.warn(v.supplant("Unable to find node '{0}' in element '{1}'.",[n,e[0].outerHTML])),t.element(s||e)},initOptionalProperties:function(e,n,o){o=o||{},t.forEach(e.$$isolateBindings,function(i,r){if(i.optional&&t.isUndefined(e[r])){var a=t.isDefined(n[i.attrName]);e[r]=t.isDefined(o[r])?o[r]:a}})},nextTick:function(e,t,n){function o(){var e=i.queue,t=i.digest;i.queue=[],i.timeout=null,i.digest=!1,e.forEach(function(e){var t=e.scope&&e.scope.$$destroyed;t||e.callback()}),t&&d.$digest()}var i=v.nextTick,a=i.timeout,s=i.queue||[];return s.push({scope:n,callback:e}),null==t&&(t=!0),i.digest=i.digest||t,i.queue=s,a||(i.timeout=r(o,0,!1))},processTemplate:function(e){return g?e:e&&t.isString(e)?e.replace(/\{\{/g,p).replace(/}}/g,f):e},getParentWithPointerEvents:function(e){for(var t=e.parent();b(t,"pointer-events","none");)t=t.parent();return t},getNearestContentElement:function(e){for(var t=e.parent()[0];t&&t!==m[0]&&t!==document.body&&"MD-CONTENT"!==t.nodeName.toUpperCase();)t=t.parentNode;return t},parseAttributeBoolean:function(e,t){return""===e||!!e&&(t===!1||"false"!==e&&"0"!==e)},hasComputedStyle:b};return v.dom.animator=s(v),v}var i=0;t.module("material.core").factory("$mdUtil",o),o.$inject=["$document","$timeout","$compile","$rootScope","$$mdAnimate","$interpolate","$log","$rootElement","$window"],t.element.prototype.focus=t.element.prototype.focus||function(){return this.length&&this[0].focus(),this},t.element.prototype.blur=t.element.prototype.blur||function(){return this.length&&this[0].blur(),this}}(),function(){function e(e,n,o,i){function r(e,o,i){var r=t.element(e)[0]||e;!r||r.hasAttribute(o)&&0!==r.getAttribute(o).length||c(r,o)||(i=t.isString(i)?i.trim():"",i.length?e.attr(o,i):n.warn('ARIA: Attribute "',o,'", required for accessibility, is missing on node:',r))}function a(t,n,o){e(function(){r(t,n,o())})}function d(e,t){var n=s(e)||"",o=n.indexOf(i.startSymbol())>-1;o?a(e,t,function(){return s(e)}):r(e,t,n)}function s(e){function t(t){for(;t.parentNode&&(t=t.parentNode)!==e;)if(t.getAttribute&&"true"===t.getAttribute("aria-hidden"))return!0}e=e[0]||e;for(var n,o=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,null,!1),i="";n=o.nextNode();)t(n)||(i+=n.textContent);return i.trim()||""}function c(e,t){function n(e){var t=e.currentStyle?e.currentStyle:o.getComputedStyle(e);return"none"===t.display}var i=e.hasChildNodes(),r=!1;if(i)for(var a=e.childNodes,d=0;d<a.length;d++){var s=a[d];1===s.nodeType&&s.hasAttribute(t)&&(n(s)||(r=!0))}return r}return{expect:r,expectAsync:a,expectWithText:d}}t.module("material.core").service("$mdAria",e),e.$inject=["$$rAF","$log","$window","$interpolate"]}(),function(){function e(e,n,o,i,r){this.compile=function(a){var d=a.templateUrl,s=a.template||"",c=a.controller,l=a.controllerAs,m=t.extend({},a.resolve||{}),u=t.extend({},a.locals||{}),h=a.transformTemplate||t.identity,p=a.bindToController;return t.forEach(m,function(e,n){t.isString(e)?m[n]=o.get(e):m[n]=o.invoke(e)}),t.extend(m,u),d?m.$template=n(d).then(function(e){return e}):m.$template=e.when(s),e.all(m).then(function(e){var n,o=h(e.$template,a),d=a.element||t.element("<div>").html(o.trim()).contents(),s=i(d);return n={locals:e,element:d,link:function(o){if(e.$scope=o,c){var i=r(c,e,!0);p&&t.extend(i.instance,e);var a=i();d.data("$ngControllerController",a),d.children().data("$ngControllerController",a),l&&(o[l]=a),n.controller=a}return s(o)}}})}}t.module("material.core").service("$mdCompiler",e),e.$inject=["$q","$templateRequest","$injector","$compile","$controller"]}(),function(){function n(){}function o(n,o,i){function r(e){return function(t,n){n.distance<this.state.options.maxDistance&&this.dispatchEvent(t,e,n)}}function a(e,t,n){var o=p[t.replace(/^\$md./,"")];if(!o)throw new Error("Failed to register element with handler "+t+". Available handlers: "+Object.keys(p).join(", "));return o.registerElement(e,n)}function s(e,o){var i=new n(e);return t.extend(i,o),p[e]=i,g}var c=navigator.userAgent||navigator.vendor||e.opera,m=c.match(/ipad|iphone|ipod/i),u=c.match(/android/i),h="undefined"!=typeof e.jQuery&&t.element===e.jQuery,g={handler:s,register:a,isHijackingClicks:(m||u)&&!h&&!f};if(g.isHijackingClicks){var b=6;g.handler("click",{options:{maxDistance:b},onEnd:r("click")}),g.handler("focus",{options:{maxDistance:b},onEnd:function(e,t){function n(e){var t=["INPUT","SELECT","BUTTON","TEXTAREA","VIDEO","AUDIO"];return"-1"!=e.getAttribute("tabindex")&&!e.hasAttribute("DISABLED")&&(e.hasAttribute("tabindex")||e.hasAttribute("href")||-1!=t.indexOf(e.nodeName))}t.distance<this.state.options.maxDistance&&n(e.target)&&(this.dispatchEvent(e,"focus",t),e.target.focus())}}),g.handler("mouseup",{options:{maxDistance:b},onEnd:r("mouseup")}),g.handler("mousedown",{onStart:function(e){this.dispatchEvent(e,"mousedown")}})}return g.handler("press",{onStart:function(e,t){this.dispatchEvent(e,"$md.pressdown")},onEnd:function(e,t){this.dispatchEvent(e,"$md.pressup")}}).handler("hold",{options:{maxDistance:6,delay:500},onCancel:function(){i.cancel(this.state.timeout)},onStart:function(e,n){return this.state.registeredParent?(this.state.pos={x:n.x,y:n.y},void(this.state.timeout=i(t.bind(this,function(){this.dispatchEvent(e,"$md.hold"),this.cancel()}),this.state.options.delay,!1))):this.cancel()},onMove:function(e,t){e.preventDefault();var n=this.state.pos.x-t.x,o=this.state.pos.y-t.y;Math.sqrt(n*n+o*o)>this.options.maxDistance&&this.cancel()},onEnd:function(){this.onCancel()}}).handler("drag",{options:{minDistance:6,horizontal:!0,cancelMultiplier:1.5},onStart:function(e){this.state.registeredParent||this.cancel()},onMove:function(e,t){var n,o;e.preventDefault(),this.state.dragPointer?this.dispatchDragMove(e):(this.state.options.horizontal?(n=Math.abs(t.distanceX)>this.state.options.minDistance,o=Math.abs(t.distanceY)>this.state.options.minDistance*this.state.options.cancelMultiplier):(n=Math.abs(t.distanceY)>this.state.options.minDistance,o=Math.abs(t.distanceX)>this.state.options.minDistance*this.state.options.cancelMultiplier),n?(this.state.dragPointer=d(e),l(e,this.state.dragPointer),this.dispatchEvent(e,"$md.dragstart",this.state.dragPointer)):o&&this.cancel())},dispatchDragMove:o.throttle(function(e){this.state.isRunning&&(l(e,this.state.dragPointer),this.dispatchEvent(e,"$md.drag",this.state.dragPointer))}),onEnd:function(e,t){this.state.dragPointer&&(l(e,this.state.dragPointer),this.dispatchEvent(e,"$md.dragend",this.state.dragPointer))}}).handler("swipe",{options:{minVelocity:.65,minDistance:10},onEnd:function(e,t){var n;Math.abs(t.velocityX)>this.state.options.minVelocity&&Math.abs(t.distanceX)>this.state.options.minDistance?(n="left"==t.directionX?"$md.swipeleft":"$md.swiperight",this.dispatchEvent(e,n)):Math.abs(t.velocityY)>this.state.options.minVelocity&&Math.abs(t.distanceY)>this.state.options.minDistance&&(n="up"==t.directionY?"$md.swipeup":"$md.swipedown",this.dispatchEvent(e,n))}})}function i(e){this.name=e,this.state={}}function r(){function n(e,n,o){o=o||u;var i=new t.element.Event(n);i.$material=!0,i.pointer=o,i.srcEvent=e,t.extend(i,{clientX:o.x,clientY:o.y,screenX:o.x,screenY:o.y,pageX:o.x,pageY:o.y,ctrlKey:e.ctrlKey,altKey:e.altKey,shiftKey:e.shiftKey,metaKey:e.metaKey}),t.element(o.target).trigger(i)}function o(t,n,o){o=o||u;var i;"click"===n||"mouseup"==n||"mousedown"==n?(i=document.createEvent("MouseEvents"),i.initMouseEvent(n,!0,!0,e,t.detail,o.x,o.y,o.x,o.y,t.ctrlKey,t.altKey,t.shiftKey,t.metaKey,t.button,t.relatedTarget||null)):(i=document.createEvent("CustomEvent"),i.initCustomEvent(n,!0,!0,{})),i.$material=!0,i.pointer=o,i.srcEvent=t,o.target.dispatchEvent(i)}var r="undefined"!=typeof e.jQuery&&t.element===e.jQuery;return i.prototype={options:{},dispatchEvent:r?n:o,onStart:t.noop,onMove:t.noop,onEnd:t.noop,onCancel:t.noop,start:function(e,n){if(!this.state.isRunning){var o=this.getNearestParent(e.target),i=o&&o.$mdGesture[this.name]||{};this.state={isRunning:!0,options:t.extend({},this.options,i),registeredParent:o},this.onStart(e,n)}},move:function(e,t){this.state.isRunning&&this.onMove(e,t)},end:function(e,t){this.state.isRunning&&(this.onEnd(e,t),this.state.isRunning=!1)},cancel:function(e,t){this.onCancel(e,t),this.state={}},getNearestParent:function(e){for(var t=e;t;){if((t.$mdGesture||{})[this.name])return t;t=t.parentNode}return null},registerElement:function(e,t){function n(){delete e[0].$mdGesture[o.name],e.off("$destroy",n)}var o=this;return e[0].$mdGesture=e[0].$mdGesture||{},e[0].$mdGesture[this.name]=t||{},e.on("$destroy",n),n}},i}function a(e,n){function o(e){var t=!e.clientX&&!e.clientY;t||e.$material||e.isIonicTap||c(e)||(e.preventDefault(),e.stopPropagation())}function i(e){var t=0===e.clientX&&0===e.clientY;t||e.$material||e.isIonicTap||c(e)?(g=null,"label"==e.target.tagName.toLowerCase()&&(g={x:e.x,y:e.y})):(e.preventDefault(),e.stopPropagation(),g=null)}function r(e,t){var o;for(var i in p)o=p[i],o instanceof n&&("start"===e&&o.cancel(),o[e](t,u))}function a(e){if(!u){var t=+Date.now();h&&!s(e,h)&&t-h.endTime<1500||(u=d(e),r("start",e))}}function m(e){u&&s(e,u)&&(l(e,u),r("move",e))}function f(e){u&&s(e,u)&&(l(e,u),u.endTime=+Date.now(),r("end",e),h=u,u=null)}document.contains||(document.contains=function(e){return document.body.contains(e)}),!b&&e.isHijackingClicks&&(document.addEventListener("click",i,!0),document.addEventListener("mouseup",o,!0),document.addEventListener("mousedown",o,!0),document.addEventListener("focus",o,!0),b=!0);var v="mousedown touchstart pointerdown",E="mousemove touchmove pointermove",$="mouseup mouseleave touchend touchcancel pointerup pointercancel";t.element(document).on(v,a).on(E,m).on($,f).on("$$mdGestureReset",function(){h=u=null})}function d(e){var t=m(e),n={startTime:+Date.now(),target:e.target,type:e.type.charAt(0)};return n.startX=n.x=t.pageX,n.startY=n.y=t.pageY,n}function s(e,t){return e&&t&&e.type.charAt(0)===t.type}function c(e){return g&&g.x==e.x&&g.y==e.y}function l(e,t){var n=m(e),o=t.x=n.pageX,i=t.y=n.pageY;t.distanceX=o-t.startX,t.distanceY=i-t.startY,t.distance=Math.sqrt(t.distanceX*t.distanceX+t.distanceY*t.distanceY),t.directionX=t.distanceX>0?"right":t.distanceX<0?"left":"",t.directionY=t.distanceY>0?"down":t.distanceY<0?"up":"",t.duration=+Date.now()-t.startTime,t.velocityX=t.distanceX/t.duration,t.velocityY=t.distanceY/t.duration}function m(e){return e=e.originalEvent||e,e.touches&&e.touches[0]||e.changedTouches&&e.changedTouches[0]||e}var u,h,p={},f=!1,g=null,b=!1;t.module("material.core.gestures",[]).provider("$mdGesture",n).factory("$$MdGestureHandler",r).run(a),n.prototype={skipClickHijack:function(){return f=!0},$get:["$$MdGestureHandler","$$rAF","$timeout",function(e,t,n){return new o(e,t,n)}]},o.$inject=["$$MdGestureHandler","$$rAF","$timeout"],a.$inject=["$mdGesture","$$MdGestureHandler"]}(),function(){function e(){function e(e){function n(e){return s.optionsFactory=e.options,s.methods=(e.methods||[]).concat(a),c}function o(e,t){return d[e]=t,c}function i(t,n){if(n=n||{},n.methods=n.methods||[],n.options=n.options||function(){return{}},/^cancel|hide|show$/.test(t))throw new Error("Preset '"+t+"' in "+e+" is reserved!");if(n.methods.indexOf("_options")>-1)throw new Error("Method '_options' in "+e+" is reserved!");return s.presets[t]={methods:n.methods.concat(a),optionsFactory:n.options,argOption:n.argOption},c}function r(n,o){function i(e){return e=e||{},e._options&&(e=e._options),m.show(t.extend({},l,e))}function r(e){return m.destroy(e)}function a(t,n){var i={};return i[e]=u,o.invoke(t||function(){return n},{},i)}var c,l,m=n(),u={hide:m.hide,cancel:m.cancel,show:i,destroy:r};return c=s.methods||[],l=a(s.optionsFactory,{}),t.forEach(d,function(e,t){u[t]=e}),t.forEach(s.presets,function(e,n){function o(e){this._options=t.extend({},i,e)}var i=a(e.optionsFactory,{}),r=(e.methods||[]).concat(c);if(t.extend(i,{$type:n}),t.forEach(r,function(e){o.prototype[e]=function(t){return this._options[e]=t,this}}),e.argOption){var d="show"+n.charAt(0).toUpperCase()+n.slice(1);u[d]=function(e){var t=u[n](e);return u.show(t)}}u[n]=function(n){return arguments.length&&e.argOption&&!t.isObject(n)&&!t.isArray(n)?(new o)[e.argOption](n):new o(n)}}),u}var a=["onHide","onShow","onRemove"],d={},s={presets:{}},c={setDefaults:n,addPreset:i,addMethod:o,$get:r};return c.addPreset("build",{methods:["controller","controllerAs","resolve","template","templateUrl","themable","transformTemplate","parent"]}),r.$inject=["$$interimElement","$injector"],c}function o(e,o,i,r,a,d,s,c,l,m,u){return function(){function h(e){e=e||{};var t=new b(e||{}),n=!e.skipHide&&$.length?v.cancel():o.when(!0);return n["finally"](function(){$.push(t),t.show()["catch"](function(e){return e})}),t.deferred.promise}function p(e,t){function i(n){return n.remove(e,!1,t||{})["catch"](function(e){return e}),n.deferred.promise}if(!$.length)return o.when(e);if(t=t||{},t.closeAll){var r=o.all($.reverse().map(i));return $=[],r}if(t.closeTo!==n)return o.all($.splice(t.closeTo).map(i));var a=$.pop();return i(a)}function f(e,n){var i=$.pop();return i?(i.remove(e,!0,n||{})["catch"](function(e){return e}),i.deferred.promise["catch"](t.noop)):o.when(e)}function g(e){var n=e?null:$.shift(),i=t.element(e).length?t.element(e)[0].parentNode:null;if(i){var r=$.filter(function(e){var t=e.options.element[0];return t===i});r.length>0&&(n=r[0],$.splice($.indexOf(n),1))}return n?n.remove(E,!1,{$destroy:!0}):o.when(E)}function b(u){function h(){return o(function(e,t){function n(e){M.deferred.reject(e),t(e)}g(u).then(function(t){_=b(t,u),A=C(_,u,t.controller).then(e,n)},n)})}function p(e,n,i){function r(e){M.deferred.resolve(e)}function a(e){M.deferred.reject(e)}return _?(u=t.extend(u||{},i||{}),u.cancelAutoHide&&u.cancelAutoHide(),u.element.triggerHandler("$mdInterimElementRemove"),u.$destroy===!0?y(u.element,u).then(function(){n&&a(e)||r(e)}):(o.when(A)["finally"](function(){y(u.element,u).then(function(){n&&a(e)||r(e)},a)}),M.deferred.promise)):o.when(!1)}function f(e){return e=e||{},e.template&&(e.template=c.processTemplate(e.template)),t.extend({preserveScope:!1,cancelAutoHide:t.noop,scope:e.scope||r.$new(e.isolateScope),onShow:function(e,t,n){return s.enter(t,n.parent)},onRemove:function(e,t){return t&&s.leave(t)||o.when()}},e)}function g(e){var t=e.skipCompile?null:l.compile(e);return t||o(function(t){t({locals:{},link:function(){return e.element}})})}function b(e,n){t.extend(e.locals,n);var o=e.link(n.scope);return n.element=o,n.parent=E(o,n),n.themable&&m(o),o}function E(n,o){var i=o.parent;if(i=t.isFunction(i)?i(o.scope,n,o):t.isString(i)?t.element(e[0].querySelector(i)):t.element(i),!(i||{}).length){var r;return d[0]&&d[0].querySelector&&(r=d[0].querySelector(":not(svg) > body")),r||(r=d[0]),"#comment"==r.nodeName&&(r=e[0].body),t.element(r)}return i}function $(){var e,o=t.noop;u.hideDelay&&(e=a(v.hide,u.hideDelay),o=function(){a.cancel(e)}),u.cancelAutoHide=function(){o(),u.cancelAutoHide=n}}function C(e,n,i){var r=n.onShowing||t.noop,a=n.onComplete||t.noop;return r(n.scope,e,n,i),o(function(t,r){try{o.when(n.onShow(n.scope,e,n,i)).then(function(){a(n.scope,e,n),$(),t(e)},r)}catch(d){r(d.message)}})}function y(e,n){var o=n.onRemoving||t.noop;return i(function(t,r){try{var a=i.when(n.onRemove(n.scope,e,n)||!0);o(e,a),1==n.$destroy?t(e):a.then(function(){!n.preserveScope&&n.scope&&n.scope.$destroy(),t(e)},r)}catch(d){r(d)}})}var M,_,A=o.when(!0);return u=f(u),M={options:u,deferred:o.defer(),show:h,remove:p}}var v,E=!1,$=[];return v={show:h,hide:p,cancel:f,destroy:g,$injector_:u}}}return e.$get=o,o.$inject=["$document","$q","$$q","$rootScope","$timeout","$rootElement","$animate","$mdUtil","$mdCompiler","$mdTheming","$injector"],e}t.module("material.core").provider("$$interimElement",e)}(),function(){!function(){function e(e){function a(e){return e.replace(s,"").replace(c,function(e,t,n,o){return o?n.toUpperCase():n})}var s=/^((?:x|data)[\:\-_])/i,c=/([\:\-\_]+(.))/g,l=["","xs","gt-xs","sm","gt-sm","md","gt-md","lg","gt-lg","xl","print"],m=["layout","flex","flex-order","flex-offset","layout-align"],u=["show","hide","layout-padding","layout-margin"];t.forEach(l,function(n){t.forEach(m,function(t){var o=n?t+"-"+n:t;e.directive(a(o),i(o))}),t.forEach(u,function(t){var o=n?t+"-"+n:t;e.directive(a(o),r(o))})}),e.directive("mdLayoutCss",n).directive("ngCloak",o("ng-cloak")).directive("layoutWrap",r("layout-wrap")).directive("layoutNowrap",r("layout-nowrap")).directive("layoutNoWrap",r("layout-no-wrap")).directive("layoutFill",r("layout-fill")).directive("layoutLtMd",d("layout-lt-md",!0)).directive("layoutLtLg",d("layout-lt-lg",!0)).directive("flexLtMd",d("flex-lt-md",!0)).directive("flexLtLg",d("flex-lt-lg",!0)).directive("layoutAlignLtMd",d("layout-align-lt-md")).directive("layoutAlignLtLg",d("layout-align-lt-lg")).directive("flexOrderLtMd",d("flex-order-lt-md")).directive("flexOrderLtLg",d("flex-order-lt-lg")).directive("offsetLtMd",d("flex-offset-lt-md")).directive("offsetLtLg",d("flex-offset-lt-lg")).directive("hideLtMd",d("hide-lt-md")).directive("hideLtLg",d("hide-lt-lg")).directive("showLtMd",d("show-lt-md")).directive("showLtLg",d("show-lt-lg"))}function n(){return{restrict:"A",priority:"900",compile:function(e,n){return _.enabled=!1,t.noop}}}function o(e){return["$timeout",function(n){return{restrict:"A",priority:-10,compile:function(o){return _.enabled?(o.addClass(e),function(t,o){n(function(){o.removeClass(e)},10,!1)}):t.noop}}}]}function i(e){function n(t,n,o){var i=a(n,e,o),r=o.$observe(o.$normalize(e),i);i(u(e,o,"")),t.$on("$destroy",function(){r()})}return["$mdUtil","$interpolate","$log",function(o,i,r){return f=o,g=i,b=r,{restrict:"A",compile:function(o,i){var r;return _.enabled&&(s(e,i,o,b),c(e,u(e,i,""),l(o,e,i)),r=n),r||t.noop}}}]}function r(e){function n(t,n){n.addClass(e)}return["$mdUtil","$interpolate","$log",function(o,i,r){return f=o,g=i,b=r,{restrict:"A",compile:function(o,i){var r;return _.enabled&&(c(e,u(e,i,""),l(o,e,i)),n(null,o),r=n),r||t.noop}}}]}function a(e,n){var o;return function(i){var r=c(n,i||"");t.isDefined(r)&&(o&&e.removeClass(o),o=r?n+"-"+r.replace(E,"-"):n,e.addClass(o))}}function d(e){var n=e.split("-");return["$log",function(o){return o.warn(e+"has been deprecated. Please use a `"+n[0]+"-gt-<xxx>` variant."), +t.noop}]}function s(e,t,n,o){var i,r,a,d=n[0].nodeName.toLowerCase();switch(e.replace(v,"")){case"flex":"md-button"!=d&&"fieldset"!=d||(r="<"+d+" "+e+"></"+d+">",a="https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers",i="Markup '{0}' may not work as expected in IE Browsers. Consult '{1}' for details.",o.warn(f.supplant(i,[r,a])))}}function c(e,n,o){var i=n;if(!m(n)){switch(e.replace(v,"")){case"layout":h(n,C)||(n=C[0]);break;case"flex":h(n,$)||isNaN(n)&&(n="");break;case"flex-offset":case"flex-order":n&&!isNaN(+n)||(n="0");break;case"layout-align":var r=p(n);n=f.supplant("{main}-{cross}",r);break;case"layout-padding":case"layout-margin":case"layout-fill":case"layout-wrap":case"layout-nowrap":case"layout-nowrap":n=""}n!=i&&(o||t.noop)(n)}return n}function l(e,t,n){return function(e){m(e)||(n[n.$normalize(t)]=e)}}function m(e){return(e||"").indexOf(g.startSymbol())>-1}function u(e,t,n){var o=t.$normalize(e);return t[o]?t[o].replace(E,"-"):n||null}function h(e,t,n){e=n&&e?e.replace(E,n):e;var o=!1;return e&&t.forEach(function(t){t=n?t.replace(E,n):t,o=o||t===e}),o}function p(e){var t,n={main:"start",cross:"stretch"};return e=e||"",0!=e.indexOf("-")&&0!=e.indexOf(" ")||(e="none"+e),t=e.toLowerCase().trim().replace(E,"-").split("-"),t.length&&"space"===t[0]&&(t=[t[0]+"-"+t[1],t[2]]),t.length>0&&(n.main=t[0]||n.main),t.length>1&&(n.cross=t[1]||n.cross),y.indexOf(n.main)<0&&(n.main="start"),M.indexOf(n.cross)<0&&(n.cross="stretch"),n}var f,g,b,v=/(-gt)?-(sm|md|lg|print)/g,E=/\s+/g,$=["grow","initial","auto","none","noshrink","nogrow"],C=["row","column"],y=["","start","center","end","stretch","space-around","space-between"],M=["","start","center","end","stretch"],_={enabled:!0,breakpoints:[]};e(t.module("material.core.layout",["ng"]))}()}(),function(){function e(e,o){function i(e){return e&&""!==e}var r,a=[],d={};return r={notFoundError:function(t,n){e.error((n||"")+"No instance found for handle",t)},getInstances:function(){return a},get:function(e){if(!i(e))return null;var t,n,o;for(t=0,n=a.length;n>t;t++)if(o=a[t],o.$$mdHandle===e)return o;return null},register:function(e,n){function o(){var t=a.indexOf(e);-1!==t&&a.splice(t,1)}function i(){var t=d[n];t&&(t.forEach(function(t){t.resolve(e)}),delete d[n])}return n?(e.$$mdHandle=n,a.push(e),i(),o):t.noop},when:function(e){if(i(e)){var t=o.defer(),a=r.get(e);return a?t.resolve(a):(d[e]===n&&(d[e]=[]),d[e].push(t)),t.promise}return o.reject("Invalid `md-component-id` value.")}}}t.module("material.core").factory("$mdComponentRegistry",e),e.$inject=["$log","$q"]}(),function(){!function(){function e(e){function n(e){return e.hasClass("md-icon-button")?{isMenuItem:e.hasClass("md-menu-item"),fitRipple:!0,center:!0}:{isMenuItem:e.hasClass("md-menu-item"),dimBackground:!0}}return{attach:function(o,i,r){return r=t.extend(n(i),r),e.attach(o,i,r)}}}t.module("material.core").factory("$mdButtonInkRipple",e),e.$inject=["$mdInkRipple"]}()}(),function(){!function(){function e(e){function n(n,o,i){return e.attach(n,o,t.extend({center:!0,dimBackground:!1,fitRipple:!0},i))}return{attach:n}}t.module("material.core").factory("$mdCheckboxInkRipple",e),e.$inject=["$mdInkRipple"]}()}(),function(){!function(){function e(e){function n(n,o,i){return e.attach(n,o,t.extend({center:!1,dimBackground:!0,outline:!1,rippleSize:"full"},i))}return{attach:n}}t.module("material.core").factory("$mdListInkRipple",e),e.$inject=["$mdInkRipple"]}()}(),function(){function e(e,n){return{controller:t.noop,link:function(t,o,i){i.hasOwnProperty("mdInkRippleCheckbox")?n.attach(t,o):e.attach(t,o)}}}function n(){function e(){n=!0}var n=!1;return{disableInkRipple:e,$get:["$injector",function(e){function i(i,r,a){return n||r.controller("mdNoInk")?t.noop:e.instantiate(o,{$scope:i,$element:r,rippleOptions:a})}return{attach:i}}]}}function o(e,n,o,i,r,a,d){this.$window=i,this.$timeout=r,this.$mdUtil=a,this.$mdColorUtil=d,this.$scope=e,this.$element=n,this.options=o,this.mousedown=!1,this.ripples=[],this.timeout=null,this.lastRipple=null,a.valueOnUse(this,"container",this.createContainer),this.$element.addClass("md-ink-ripple"),(n.controller("mdInkRipple")||{}).createRipple=t.bind(this,this.createRipple),(n.controller("mdInkRipple")||{}).setColor=t.bind(this,this.color),this.bindEvents()}function i(e,n){(e.mousedown||e.lastRipple)&&(e.mousedown=!1,e.$mdUtil.nextTick(t.bind(e,n),!1))}function r(){return{controller:t.noop}}t.module("material.core").provider("$mdInkRipple",n).directive("mdInkRipple",e).directive("mdNoInk",r).directive("mdNoBar",r).directive("mdNoStretch",r);var a=450;e.$inject=["$mdButtonInkRipple","$mdCheckboxInkRipple"],o.$inject=["$scope","$element","rippleOptions","$window","$timeout","$mdUtil","$mdColorUtil"],o.prototype.color=function(e){function n(){var e=o.options&&o.options.colorElement?o.options.colorElement:[],t=e.length?e[0]:o.$element[0];return t?o.$window.getComputedStyle(t).color:"rgb(0,0,0)"}var o=this;return t.isDefined(e)&&(o._color=o._parseColor(e)),o._color||o._parseColor(o.inkRipple())||o._parseColor(n())},o.prototype.calculateColor=function(){return this.color()},o.prototype._parseColor=function(e,t){t=t||1;var n=this.$mdColorUtil;if(e)return 0===e.indexOf("rgba")?e.replace(/\d?\.?\d*\s*\)\s*$/,(.1*t).toString()+")"):0===e.indexOf("rgb")?n.rgbToRgba(e):0===e.indexOf("#")?n.hexToRgba(e):void 0},o.prototype.bindEvents=function(){this.$element.on("mousedown",t.bind(this,this.handleMousedown)),this.$element.on("mouseup touchend",t.bind(this,this.handleMouseup)),this.$element.on("mouseleave",t.bind(this,this.handleMouseup)),this.$element.on("touchmove",t.bind(this,this.handleTouchmove))},o.prototype.handleMousedown=function(e){if(!this.mousedown)if(e.hasOwnProperty("originalEvent")&&(e=e.originalEvent),this.mousedown=!0,this.options.center)this.createRipple(this.container.prop("clientWidth")/2,this.container.prop("clientWidth")/2);else if(e.srcElement!==this.$element[0]){var t=this.$element[0].getBoundingClientRect(),n=e.clientX-t.left,o=e.clientY-t.top;this.createRipple(n,o)}else this.createRipple(e.offsetX,e.offsetY)},o.prototype.handleMouseup=function(){i(this,this.clearRipples)},o.prototype.handleTouchmove=function(){i(this,this.deleteRipples)},o.prototype.deleteRipples=function(){for(var e=0;e<this.ripples.length;e++)this.ripples[e].remove()},o.prototype.clearRipples=function(){for(var e=0;e<this.ripples.length;e++)this.fadeInComplete(this.ripples[e])},o.prototype.createContainer=function(){var e=t.element('<div class="md-ripple-container"></div>');return this.$element.append(e),e},o.prototype.clearTimeout=function(){this.timeout&&(this.$timeout.cancel(this.timeout),this.timeout=null)},o.prototype.isRippleAllowed=function(){var e=this.$element[0];do{if(!e.tagName||"BODY"===e.tagName)break;if(e&&t.isFunction(e.hasAttribute)){if(e.hasAttribute("disabled"))return!1;if("false"===this.inkRipple()||"0"===this.inkRipple())return!1}}while(e=e.parentNode);return!0},o.prototype.inkRipple=function(){return this.$element.attr("md-ink-ripple")},o.prototype.createRipple=function(e,n){function o(e,t,n){return e?Math.max(t,n):Math.sqrt(Math.pow(t,2)+Math.pow(n,2))}if(this.isRippleAllowed()){var i=this,r=i.$mdColorUtil,d=t.element('<div class="md-ripple"></div>'),s=this.$element.prop("clientWidth"),c=this.$element.prop("clientHeight"),l=2*Math.max(Math.abs(s-e),e),m=2*Math.max(Math.abs(c-n),n),u=o(this.options.fitRipple,l,m),h=this.calculateColor();d.css({left:e+"px",top:n+"px",background:"black",width:u+"px",height:u+"px",backgroundColor:r.rgbaToRgb(h),borderColor:r.rgbaToRgb(h)}),this.lastRipple=d,this.clearTimeout(),this.timeout=this.$timeout(function(){i.clearTimeout(),i.mousedown||i.fadeInComplete(d)},.35*a,!1),this.options.dimBackground&&this.container.css({backgroundColor:h}),this.container.append(d),this.ripples.push(d),d.addClass("md-ripple-placed"),this.$mdUtil.nextTick(function(){d.addClass("md-ripple-scaled md-ripple-active"),i.$timeout(function(){i.clearRipples()},a,!1)},!1)}},o.prototype.fadeInComplete=function(e){this.lastRipple===e?this.timeout||this.mousedown||this.removeRipple(e):this.removeRipple(e)},o.prototype.removeRipple=function(e){var t=this,n=this.ripples.indexOf(e);0>n||(this.ripples.splice(this.ripples.indexOf(e),1),e.removeClass("md-ripple-active"),0===this.ripples.length&&this.container.css({backgroundColor:""}),this.$timeout(function(){t.fadeOutComplete(e)},a,!1))},o.prototype.fadeOutComplete=function(e){e.remove(),this.lastRipple=null}}(),function(){!function(){function e(e){function n(n,o,i){return e.attach(n,o,t.extend({center:!1,dimBackground:!0,outline:!1,rippleSize:"full"},i))}return{attach:n}}t.module("material.core").factory("$mdTabInkRipple",e),e.$inject=["$mdInkRipple"]}()}(),function(){t.module("material.core.theming.palette",[]).constant("$mdColorPalette",{red:{50:"#ffebee",100:"#ffcdd2",200:"#ef9a9a",300:"#e57373",400:"#ef5350",500:"#f44336",600:"#e53935",700:"#d32f2f",800:"#c62828",900:"#b71c1c",A100:"#ff8a80",A200:"#ff5252",A400:"#ff1744",A700:"#d50000",contrastDefaultColor:"light",contrastDarkColors:"50 100 200 300 A100",contrastStrongLightColors:"400 500 600 700 A200 A400 A700"},pink:{50:"#fce4ec",100:"#f8bbd0",200:"#f48fb1",300:"#f06292",400:"#ec407a",500:"#e91e63",600:"#d81b60",700:"#c2185b",800:"#ad1457",900:"#880e4f",A100:"#ff80ab",A200:"#ff4081",A400:"#f50057",A700:"#c51162",contrastDefaultColor:"light",contrastDarkColors:"50 100 200 A100",contrastStrongLightColors:"500 600 A200 A400 A700"},purple:{50:"#f3e5f5",100:"#e1bee7",200:"#ce93d8",300:"#ba68c8",400:"#ab47bc",500:"#9c27b0",600:"#8e24aa",700:"#7b1fa2",800:"#6a1b9a",900:"#4a148c",A100:"#ea80fc",A200:"#e040fb",A400:"#d500f9",A700:"#aa00ff",contrastDefaultColor:"light",contrastDarkColors:"50 100 200 A100",contrastStrongLightColors:"300 400 A200 A400 A700"},"deep-purple":{50:"#ede7f6",100:"#d1c4e9",200:"#b39ddb",300:"#9575cd",400:"#7e57c2",500:"#673ab7",600:"#5e35b1",700:"#512da8",800:"#4527a0",900:"#311b92",A100:"#b388ff",A200:"#7c4dff",A400:"#651fff",A700:"#6200ea",contrastDefaultColor:"light",contrastDarkColors:"50 100 200 A100",contrastStrongLightColors:"300 400 A200"},indigo:{50:"#e8eaf6",100:"#c5cae9",200:"#9fa8da",300:"#7986cb",400:"#5c6bc0",500:"#3f51b5",600:"#3949ab",700:"#303f9f",800:"#283593",900:"#1a237e",A100:"#8c9eff",A200:"#536dfe",A400:"#3d5afe",A700:"#304ffe",contrastDefaultColor:"light",contrastDarkColors:"50 100 200 A100",contrastStrongLightColors:"300 400 A200 A400"},blue:{50:"#e3f2fd",100:"#bbdefb",200:"#90caf9",300:"#64b5f6",400:"#42a5f5",500:"#2196f3",600:"#1e88e5",700:"#1976d2",800:"#1565c0",900:"#0d47a1",A100:"#82b1ff",A200:"#448aff",A400:"#2979ff",A700:"#2962ff",contrastDefaultColor:"light",contrastDarkColors:"50 100 200 300 400 A100",contrastStrongLightColors:"500 600 700 A200 A400 A700"},"light-blue":{50:"#e1f5fe",100:"#b3e5fc",200:"#81d4fa",300:"#4fc3f7",400:"#29b6f6",500:"#03a9f4",600:"#039be5",700:"#0288d1",800:"#0277bd",900:"#01579b",A100:"#80d8ff",A200:"#40c4ff",A400:"#00b0ff",A700:"#0091ea",contrastDefaultColor:"dark",contrastLightColors:"600 700 800 900 A700",contrastStrongLightColors:"600 700 800 A700"},cyan:{50:"#e0f7fa",100:"#b2ebf2",200:"#80deea",300:"#4dd0e1",400:"#26c6da",500:"#00bcd4",600:"#00acc1",700:"#0097a7",800:"#00838f",900:"#006064",A100:"#84ffff",A200:"#18ffff",A400:"#00e5ff",A700:"#00b8d4",contrastDefaultColor:"dark",contrastLightColors:"700 800 900",contrastStrongLightColors:"700 800 900"},teal:{50:"#e0f2f1",100:"#b2dfdb",200:"#80cbc4",300:"#4db6ac",400:"#26a69a",500:"#009688",600:"#00897b",700:"#00796b",800:"#00695c",900:"#004d40",A100:"#a7ffeb",A200:"#64ffda",A400:"#1de9b6",A700:"#00bfa5",contrastDefaultColor:"dark",contrastLightColors:"500 600 700 800 900",contrastStrongLightColors:"500 600 700"},green:{50:"#e8f5e9",100:"#c8e6c9",200:"#a5d6a7",300:"#81c784",400:"#66bb6a",500:"#4caf50",600:"#43a047",700:"#388e3c",800:"#2e7d32",900:"#1b5e20",A100:"#b9f6ca",A200:"#69f0ae",A400:"#00e676",A700:"#00c853",contrastDefaultColor:"dark",contrastLightColors:"500 600 700 800 900",contrastStrongLightColors:"500 600 700"},"light-green":{50:"#f1f8e9",100:"#dcedc8",200:"#c5e1a5",300:"#aed581",400:"#9ccc65",500:"#8bc34a",600:"#7cb342",700:"#689f38",800:"#558b2f",900:"#33691e",A100:"#ccff90",A200:"#b2ff59",A400:"#76ff03",A700:"#64dd17",contrastDefaultColor:"dark",contrastLightColors:"700 800 900",contrastStrongLightColors:"700 800 900"},lime:{50:"#f9fbe7",100:"#f0f4c3",200:"#e6ee9c",300:"#dce775",400:"#d4e157",500:"#cddc39",600:"#c0ca33",700:"#afb42b",800:"#9e9d24",900:"#827717",A100:"#f4ff81",A200:"#eeff41",A400:"#c6ff00",A700:"#aeea00",contrastDefaultColor:"dark",contrastLightColors:"900",contrastStrongLightColors:"900"},yellow:{50:"#fffde7",100:"#fff9c4",200:"#fff59d",300:"#fff176",400:"#ffee58",500:"#ffeb3b",600:"#fdd835",700:"#fbc02d",800:"#f9a825",900:"#f57f17",A100:"#ffff8d",A200:"#ffff00",A400:"#ffea00",A700:"#ffd600",contrastDefaultColor:"dark"},amber:{50:"#fff8e1",100:"#ffecb3",200:"#ffe082",300:"#ffd54f",400:"#ffca28",500:"#ffc107",600:"#ffb300",700:"#ffa000",800:"#ff8f00",900:"#ff6f00",A100:"#ffe57f",A200:"#ffd740",A400:"#ffc400",A700:"#ffab00",contrastDefaultColor:"dark"},orange:{50:"#fff3e0",100:"#ffe0b2",200:"#ffcc80",300:"#ffb74d",400:"#ffa726",500:"#ff9800",600:"#fb8c00",700:"#f57c00",800:"#ef6c00",900:"#e65100",A100:"#ffd180",A200:"#ffab40",A400:"#ff9100",A700:"#ff6d00",contrastDefaultColor:"dark",contrastLightColors:"800 900",contrastStrongLightColors:"800 900"},"deep-orange":{50:"#fbe9e7",100:"#ffccbc",200:"#ffab91",300:"#ff8a65",400:"#ff7043",500:"#ff5722",600:"#f4511e",700:"#e64a19",800:"#d84315",900:"#bf360c",A100:"#ff9e80",A200:"#ff6e40",A400:"#ff3d00",A700:"#dd2c00",contrastDefaultColor:"light",contrastDarkColors:"50 100 200 300 400 A100 A200",contrastStrongLightColors:"500 600 700 800 900 A400 A700"},brown:{50:"#efebe9",100:"#d7ccc8",200:"#bcaaa4",300:"#a1887f",400:"#8d6e63",500:"#795548",600:"#6d4c41",700:"#5d4037",800:"#4e342e",900:"#3e2723",A100:"#d7ccc8",A200:"#bcaaa4",A400:"#8d6e63",A700:"#5d4037",contrastDefaultColor:"light",contrastDarkColors:"50 100 200 A100 A200",contrastStrongLightColors:"300 400"},grey:{50:"#fafafa",100:"#f5f5f5",200:"#eeeeee",300:"#e0e0e0",400:"#bdbdbd",500:"#9e9e9e",600:"#757575",700:"#616161",800:"#424242",900:"#212121",A100:"#ffffff",A200:"#000000",A400:"#303030",A700:"#616161",contrastDefaultColor:"dark",contrastLightColors:"600 700 800 900 A200 A400 A700"},"blue-grey":{50:"#eceff1",100:"#cfd8dc",200:"#b0bec5",300:"#90a4ae",400:"#78909c",500:"#607d8b",600:"#546e7a",700:"#455a64",800:"#37474f",900:"#263238",A100:"#cfd8dc",A200:"#b0bec5",A400:"#78909c",A700:"#455a64",contrastDefaultColor:"light",contrastDarkColors:"50 100 200 300 A100 A200",contrastStrongLightColors:"400 500 700"}})}(),function(){function e(e){function o(e,t){return t=t||{},m[e]=a(e,t),b}function i(e,n){return a(e,t.extend({},m[e]||{},n))}function a(e,t){var n=_.filter(function(e){return!t[e]});if(n.length)throw new Error("Missing colors %1 in palette %2!".replace("%1",n.join(", ")).replace("%2",e));return t}function s(e,n){if(v[e])return v[e];n=n||"default";var o="string"==typeof n?v[n]:n,i=new c(e);return o&&t.forEach(o.colors,function(e,n){i.colors[n]={name:e.name,hues:t.extend({},e.hues)}}),v[e]=i,i}function c(e){function n(e){if(e=0===arguments.length?!0:!!e,e!==o.isDark){o.isDark=e,o.foregroundPalette=o.isDark?p:h,o.foregroundShadow=o.isDark?f:g;var n=o.isDark?M:y,i=o.isDark?y:M;return t.forEach(n,function(e,t){var n=o.colors[t],r=i[t];if(n)for(var a in n.hues)n.hues[a]===r[a]&&(n.hues[a]=e[a])}),o}}var o=this;o.name=e,o.colors={},o.dark=n,n(!1),$.forEach(function(e){var n=(o.isDark?M:y)[e];o[e+"Palette"]=function(i,r){var a=o.colors[e]={name:i,hues:t.extend({},n,r)};return Object.keys(a.hues).forEach(function(e){if(!n[e])throw new Error("Invalid hue name '%1' in theme %2's %3 color %4. Available hue names: %4".replace("%1",e).replace("%2",o.name).replace("%3",i).replace("%4",Object.keys(n).join(", ")))}),Object.keys(a.hues).map(function(e){return a.hues[e]}).forEach(function(t){if(-1==_.indexOf(t))throw new Error("Invalid hue value '%1' in theme %2's %3 color %4. Available hue values: %5".replace("%1",t).replace("%2",o.name).replace("%3",e).replace("%4",i).replace("%5",_.join(", ")))}),o},o[e+"Color"]=function(){var t=Array.prototype.slice.call(arguments);return console.warn("$mdThemingProviderTheme."+e+"Color() has been deprecated. Use $mdThemingProviderTheme."+e+"Palette() instead."),o[e+"Palette"].apply(o,t)}})}function u(e,o){function i(e){return e===n||""===e?!0:a.THEMES[e]!==n}function r(n,r){function a(){return s=r.controller("mdTheme")||n.data("$mdThemeController"),s&&s.$mdTheme||("default"==E?"":E)}function d(e){if(e){i(e)||o.warn("Attempted to use unregistered theme '"+e+"'. Register it with $mdThemingProvider.theme().");var t=n.data("$mdThemeName");t&&n.removeClass("md-"+t+"-theme"),n.addClass("md-"+e+"-theme"),n.data("$mdThemeName",e),s&&n.data("$mdThemeController",s)}}var s=r.controller("mdTheme"),c=n.attr("md-theme-watch"),l=(C||t.isDefined(c))&&"false"!=c;d(a()),n.on("$destroy",l?e.$watch(a,d):t.noop)}var a=function(t,o){o===n&&(o=t,t=n),t===n&&(t=e),a.inherit(o,o)};return a.THEMES=t.extend({},v),a.PALETTES=t.extend({},m),a.inherit=r,a.registered=i,a.defaultTheme=function(){return E},a.generateTheme=function(e){d(v[e],e,T)},a}m={};var b,v={},E="default",C=!1;return t.extend(m,e),u.$inject=["$rootScope","$log"],b={definePalette:o,extendPalette:i,theme:s,disableTheming:function(){w=!0},setNonce:function(e){T=e},setDefaultTheme:function(e){E=e},alwaysWatchTheme:function(e){C=e},generateThemesOnDemand:function(e){A=e},$get:u,_LIGHT_DEFAULT_HUES:y,_DARK_DEFAULT_HUES:M,_PALETTES:m,_THEMES:v,_parseRules:r,_rgba:l}}function o(e,n,o){return{priority:100,link:{pre:function(i,r,a){var d=[],s={registerChanges:function(e,n){return n&&(e=t.bind(n,e)),d.push(e),function(){var t=d.indexOf(e);t>-1&&d.splice(t,1)}},$setTheme:function(t){e.registered(t)||o.warn("attempted to use unregistered theme '"+t+"'"),s.$mdTheme=t,d.forEach(function(e){e()})}};r.data("$mdThemeController",s),s.$setTheme(n(a.mdTheme)(i)),a.$observe("mdTheme",s.$setTheme)}}}}function i(e){return e}function r(e,n,o){s(e,n),o=o.replace(/THEME_NAME/g,e.name);var i=[],r=e.colors[n],a=new RegExp(".md-"+e.name+"-theme","g"),d=new RegExp("('|\")?{{\\s*("+n+")-(color|contrast)-?(\\d\\.?\\d*)?\\s*}}(\"|')?","g"),c=/'?"?\{\{\s*([a-zA-Z]+)-(A?\d+|hue\-[0-3]|shadow|default)-?(\d\.?\d*)?(contrast)?\s*\}\}'?"?/g,u=m[r.name];return o=o.replace(c,function(t,n,o,i,r){return"foreground"===n?"shadow"==o?e.foregroundShadow:e.foregroundPalette[o]||e.foregroundPalette[1]:(0!==o.indexOf("hue")&&"default"!==o||(o=e.colors[n].hues[o]),l((m[e.colors[n].name][o]||"")[r?"contrast":"value"],i))}),t.forEach(r.hues,function(t,n){var r=o.replace(d,function(e,n,o,i,r){return l(u[t]["color"===i?"value":"contrast"],r)});if("default"!==n&&(r=r.replace(a,".md-"+e.name+"-theme.md-"+n)),"default"==e.name){var s=/((?:(?:(?: |>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)+) )?)((?:(?:\w|\.|-)+)?)\.md-default-theme((?: |>|\.|\w|-|:|\(|\)|\[|\]|"|'|=)*)/g;r=r.replace(s,function(e,t,n,o){return e+", "+t+n+o})}i.push(r)}),i}function a(e,n){function o(e,n){var o=e.contrastDefaultColor,i=e.contrastLightColors||[],r=e.contrastStrongLightColors||[],a=e.contrastDarkColors||[];"string"==typeof i&&(i=i.split(" ")),"string"==typeof r&&(r=r.split(" ")),"string"==typeof a&&(a=a.split(" ")),delete e.contrastDefaultColor,delete e.contrastLightColors,delete e.contrastStrongLightColors,delete e.contrastDarkColors,t.forEach(e,function(n,d){function s(){return"light"===o?a.indexOf(d)>-1?b:r.indexOf(d)>-1?E:v:i.indexOf(d)>-1?r.indexOf(d)>-1?E:v:b}if(!t.isObject(n)){var l=c(n);if(!l)throw new Error("Color %1, in palette %2's hue %3, is invalid. Hex or rgb(a) color expected.".replace("%1",n).replace("%2",e.name).replace("%3",d));e[d]={value:l,contrast:s()}}})}var i=document.head,r=i?i.firstElementChild:null,a=!w&&e.has("$MD_THEME_CSS")?e.get("$MD_THEME_CSS"):"";if(r&&0!==a.length){t.forEach(m,o);var s=a.split(/\}(?!(\}|'|"|;))/).filter(function(e){return e&&e.length}).map(function(e){return e.trim()+"}"}),l=new RegExp("md-("+$.join("|")+")","g");$.forEach(function(e){k[e]=""}),s.forEach(function(e){for(var t,n=(e.match(l),0);t=$[n];n++)if(e.indexOf(".md-"+t)>-1)return k[t]+=e;for(n=0;t=$[n];n++)if(e.indexOf(t)>-1)return k[t]+=e;return k[C]+=e}),A||t.forEach(n.THEMES,function(e){u[e.name]||"default"!==n.defaultTheme()&&"default"===e.name||d(e,e.name,T)})}}function d(e,t,n){var o=document.head,i=o?o.firstElementChild:null;u[t]||($.forEach(function(t){for(var a=r(e,t,k[t]);a.length;){var d=a.shift();if(d){var s=document.createElement("style");s.setAttribute("md-theme-style",""),n&&s.setAttribute("nonce",n),s.appendChild(document.createTextNode(d)),o.insertBefore(s,i)}}}),u[e.name]=!0)}function s(e,t){if(!m[(e.colors[t]||{}).name])throw new Error("You supplied an invalid color palette for theme %1's %2 palette. Available palettes: %3".replace("%1",e.name).replace("%2",t).replace("%3",Object.keys(m).join(", ")))}function c(e){if(t.isArray(e)&&3==e.length)return e;if(/^rgb/.test(e))return e.replace(/(^\s*rgba?\(|\)\s*$)/g,"").split(",").map(function(e,t){return 3==t?parseFloat(e,10):parseInt(e,10)});if("#"==e.charAt(0)&&(e=e.substring(1)),/^([a-fA-F0-9]{3}){1,2}$/g.test(e)){var n=e.length/3,o=e.substr(0,n),i=e.substr(n,n),r=e.substr(2*n);return 1===n&&(o+=o,i+=i,r+=r),[parseInt(o,16),parseInt(i,16),parseInt(r,16)]}}function l(e,n){return e?(4==e.length&&(e=t.copy(e),n?e.pop():n=e.pop()),n&&("number"==typeof n||"string"==typeof n&&n.length)?"rgba("+e.join(",")+","+n+")":"rgb("+e.join(",")+")"):"rgb('0,0,0')"}t.module("material.core.theming",["material.core.theming.palette"]).directive("mdTheme",o).directive("mdThemable",i).provider("$mdTheming",e).run(a);var m,u={},h={name:"dark",1:"rgba(0,0,0,0.87)",2:"rgba(0,0,0,0.54)",3:"rgba(0,0,0,0.38)",4:"rgba(0,0,0,0.12)"},p={name:"light",1:"rgba(255,255,255,1.0)",2:"rgba(255,255,255,0.7)",3:"rgba(255,255,255,0.5)",4:"rgba(255,255,255,0.12)"},f="1px 1px 0px rgba(0,0,0,0.4), -1px -1px 0px rgba(0,0,0,0.4)",g="",b=c("rgba(0,0,0,0.87)"),v=c("rgba(255,255,255,0.87)"),E=c("rgb(255,255,255)"),$=["primary","accent","warn","background"],C="primary",y={accent:{"default":"A200","hue-1":"A100","hue-2":"A400","hue-3":"A700"},background:{"default":"50","hue-1":"A100","hue-2":"100","hue-3":"300"}},M={background:{"default":"A400","hue-1":"800","hue-2":"900","hue-3":"A200"}};$.forEach(function(e){var t={"default":"500","hue-1":"300","hue-2":"800","hue-3":"A100"};y[e]||(y[e]=t),M[e]||(M[e]=t)});var _=["50","100","200","300","400","500","600","700","800","900","A100","A200","A400","A700"],A=!1,T=null,w=!1;e.$inject=["$mdColorPalette"],o.$inject=["$mdTheming","$interpolate","$log"],i.$inject=["$mdTheming"];var k={};a.$inject=["$injector","$mdTheming"]}(),function(){function n(n,o,i,r,a){var d;return d={translate3d:function(e,t,n,o){function i(n){return a(e,{to:n||t,addClass:o.transitionOutClass,removeClass:o.transitionInClass}).start()}return a(e,{from:t,to:n,addClass:o.transitionInClass,removeClass:o.transitionOutClass}).start().then(function(){return i})},waitTransitionEnd:function(t,n){var a=3e3;return o(function(o,d){function s(e){e&&e.target!==t[0]||(e&&i.cancel(l),t.off(r.CSS.TRANSITIONEND,s),o())}function c(n){return n=n||e.getComputedStyle(t[0]),"0s"==n.transitionDuration||!n.transition&&!n.transitionProperty}n=n||{},c(n.cachedTransitionStyles)&&(a=0);var l=i(s,n.timeout||a);t.on(r.CSS.TRANSITIONEND,s)})},calculateTransformValues:function(e,t){function n(){var t=e?e.parent():null,n=t?t.parent():null;return n?d.clientRect(n):null}var o=t.element,i=t.bounds;if(o||i){var r=o?d.clientRect(o)||n():d.copyRect(i),a=d.copyRect(e[0].getBoundingClientRect()),s=d.centerPointFor(a),c=d.centerPointFor(r);return{centerX:c.x-s.x,centerY:c.y-s.y,scaleX:Math.round(100*Math.min(.5,r.width/a.width))/100,scaleY:Math.round(100*Math.min(.5,r.height/a.height))/100}}return{centerX:0,centerY:0,scaleX:.5,scaleY:.5}},calculateZoomToOrigin:function(e,o){var i="translate3d( {centerX}px, {centerY}px, 0 ) scale( {scaleX}, {scaleY} )",r=t.bind(null,n.supplant,i);return r(d.calculateTransformValues(e,o))},calculateSlideToOrigin:function(e,o){var i="translate3d( {centerX}px, {centerY}px, 0 )",r=t.bind(null,n.supplant,i);return r(d.calculateTransformValues(e,o))},toCss:function(e){function n(e,n,i){t.forEach(n.split(" "),function(e){o[e]=i})}var o={},i="left top right bottom width height x y min-width min-height max-width max-height";return t.forEach(e,function(e,a){if(!t.isUndefined(e))if(i.indexOf(a)>=0)o[a]=e+"px";else switch(a){case"transition":n(a,r.CSS.TRANSITION,e);break;case"transform":n(a,r.CSS.TRANSFORM,e);break;case"transformOrigin":n(a,r.CSS.TRANSFORM_ORIGIN,e)}}),o},toTransformCss:function(e,n,o){var i={};return t.forEach(r.CSS.TRANSFORM.split(" "),function(t){i[t]=e}),n&&(o=o||"all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) !important",i.transition=o),i},copyRect:function(e,n){return e?(n=n||{},t.forEach("left top right bottom width height".split(" "),function(t){n[t]=Math.round(e[t])}),n.width=n.width||n.right-n.left,n.height=n.height||n.bottom-n.top,n):null},clientRect:function(e){var n=t.element(e)[0].getBoundingClientRect(),o=function(e){return e&&e.width>0&&e.height>0};return o(n)?d.copyRect(n):null},centerPointFor:function(e){return e?{x:Math.round(e.left+e.width/2),y:Math.round(e.top+e.height/2)}:{x:0,y:0}}}}t.module("material.core").factory("$$mdAnimate",["$q","$timeout","$mdConstant","$animateCss",function(e,t,o,i){return function(r){return n(r,e,t,o,i)}}])}(),function(){t.version.minor>=4?t.module("material.core.animate",[]):!function(){function e(e){return e.replace(/-[a-z]/g,function(e){return e.charAt(1).toUpperCase()})}var n=t.forEach,o=t.isDefined(document.documentElement.style.WebkitAppearance),i=o?"-webkit-":"",r=(o?"webkitTransitionEnd ":"")+"transitionend",a=(o?"webkitAnimationEnd ":"")+"animationend",d=["$document",function(e){return function(){return e[0].body.clientWidth+1}}],s=["$$rAF",function(e){return function(){var t=!1;return e(function(){t=!0}),function(n){t?n():e(n)}}}],c=["$q","$$rAFMutex",function(e,o){function i(e){this.setHost(e),this._doneCallbacks=[],this._runInAnimationFrame=o(),this._state=0}var r=0,a=1,d=2;return i.prototype={setHost:function(e){this.host=e||{}},done:function(e){this._state===d?e():this._doneCallbacks.push(e)},progress:t.noop,getPromise:function(){if(!this.promise){var t=this;this.promise=e(function(e,n){t.done(function(t){t===!1?n():e()})})}return this.promise},then:function(e,t){return this.getPromise().then(e,t)},"catch":function(e){return this.getPromise()["catch"](e)},"finally":function(e){return this.getPromise()["finally"](e)},pause:function(){this.host.pause&&this.host.pause()},resume:function(){this.host.resume&&this.host.resume()},end:function(){this.host.end&&this.host.end(),this._resolve(!0)},cancel:function(){this.host.cancel&&this.host.cancel(),this._resolve(!1)},complete:function(e){var t=this;t._state===r&&(t._state=a,t._runInAnimationFrame(function(){t._resolve(e)}))},_resolve:function(e){this._state!==d&&(n(this._doneCallbacks,function(t){t(e)}),this._doneCallbacks.length=0,this._state=d)}},i}];t.module("material.core.animate",[]).factory("$$forceReflow",d).factory("$$AnimateRunner",c).factory("$$rAFMutex",s).factory("$animateCss",["$window","$$rAF","$$AnimateRunner","$$forceReflow","$$jqLite","$timeout","$animate",function(t,d,s,c,l,m,u){function h(o,d){var c=[],l=C(o),h=l&&u.enabled(),g=!1,M=!1;h&&(d.transitionStyle&&c.push([i+"transition",d.transitionStyle]),d.keyframeStyle&&c.push([i+"animation",d.keyframeStyle]),d.delay&&c.push([i+"transition-delay",d.delay+"s"]),d.duration&&c.push([i+"transition-duration",d.duration+"s"]),g=d.keyframeStyle||d.to&&(d.duration>0||d.transitionStyle),M=!!d.addClass||!!d.removeClass,y(o,!0));var _=h&&(g||M);E(o,d);var A,T,w=!1;return{close:t.close,start:function(){function t(){return w?void 0:(w=!0,A&&T&&o.off(A,T),p(o,d),v(o,d),n(c,function(t){l.style[e(t[0])]=""}),u.complete(!0),u)}var u=new s;return b(function(){if(y(o,!1),!_)return t();n(c,function(t){var n=t[0],o=t[1];l.style[e(n)]=o}),p(o,d);var s=f(o);if(0===s.duration)return t();var u=[];d.easing&&(s.transitionDuration&&u.push([i+"transition-timing-function",d.easing]),s.animationDuration&&u.push([i+"animation-timing-function",d.easing])),d.delay&&s.animationDelay&&u.push([i+"animation-delay",d.delay+"s"]),d.duration&&s.animationDuration&&u.push([i+"animation-duration",d.duration+"s"]),n(u,function(t){var n=t[0],o=t[1];l.style[e(n)]=o,c.push(t)});var h=s.delay,g=1e3*h,b=s.duration,v=1e3*b,E=Date.now();A=[],s.transitionDuration&&A.push(r),s.animationDuration&&A.push(a),A=A.join(" "),T=function(e){e.stopPropagation();var n=e.originalEvent||e,o=n.timeStamp||Date.now(),i=parseFloat(n.elapsedTime.toFixed(3));Math.max(o-E,0)>=g&&i>=b&&t()},o.on(A,T),$(o,d),m(t,g+1.5*v,!1)}),u}}}function p(e,t){t.addClass&&(l.addClass(e,t.addClass),t.addClass=null),t.removeClass&&(l.removeClass(e,t.removeClass),t.removeClass=null)}function f(e){function n(e){return o?"Webkit"+e.charAt(0).toUpperCase()+e.substr(1):e}var i=C(e),r=t.getComputedStyle(i),a=g(r[n("transitionDuration")]),d=g(r[n("animationDuration")]),s=g(r[n("transitionDelay")]),c=g(r[n("animationDelay")]);d*=parseInt(r[n("animationIterationCount")],10)||1;var l=Math.max(d,a),m=Math.max(c,s);return{duration:l,delay:m,animationDuration:d,transitionDuration:a,animationDelay:c,transitionDelay:s}}function g(e){var t=0,o=(e||"").split(/\s*,\s*/);return n(o,function(e){"s"==e.charAt(e.length-1)&&(e=e.substring(0,e.length-1)),e=parseFloat(e)||0,t=t?Math.max(e,t):e}),t}function b(e){M&&M(),_.push(e),M=d(function(){M=null;for(var e=c(),t=0;t<_.length;t++)_[t](e);_.length=0})}function v(e,t){E(e,t),$(e,t)}function E(e,t){t.from&&(e.css(t.from),t.from=null)}function $(e,t){t.to&&(e.css(t.to),t.to=null)}function C(e){for(var t=0;t<e.length;t++)if(1===e[t].nodeType)return e[t]}function y(t,n){var o=C(t),r=e(i+"transition-delay");o.style[r]=n?"-9999s":""}var M,_=[];return h}])}()}(),function(){t.module("material.components.autocomplete",["material.core","material.components.icon","material.components.virtualRepeat"])}(),function(){t.module("material.components.backdrop",["material.core"]).directive("mdBackdrop",["$mdTheming","$mdUtil","$animate","$rootElement","$window","$log","$$rAF","$document",function(e,t,n,o,i,r,a,d){function s(t,s,l){n.pin&&n.pin(s,o),a(function(){var t=i.getComputedStyle(d[0].body);if("fixed"==t.position){var n=parseInt(t.height,10)+Math.abs(parseInt(t.top,10));s.css({height:n+"px"})}var o=s.parent()[0];if(o){"BODY"==o.nodeName&&s.css({position:"fixed"});var a=i.getComputedStyle(o);"static"==a.position&&r.warn(c)}s.parent().length&&e.inherit(s,s.parent())})}var c="<md-backdrop> may not work properly in a scrolled, static-positioned parent container.";return{restrict:"E",link:s}}])}(),function(){function e(e){return{restrict:"E",link:function(t,n){e(n)}}}function n(e,n,o,i){function r(e){return t.isDefined(e.href)||t.isDefined(e.ngHref)||t.isDefined(e.ngLink)||t.isDefined(e.uiSref)}function a(e,t){if(r(t))return'<a class="md-button" ng-transclude></a>';var n="undefined"==typeof t.type?"button":t.type;return'<button class="md-button" type="'+n+'" ng-transclude></button>'}function d(a,d,s){n(d),e.attach(a,d),o.expectWithText(d,"aria-label"),r(s)&&t.isDefined(s.ngDisabled)&&a.$watch(s.ngDisabled,function(e){d.attr("tabindex",e?-1:0)}),d.on("click",function(e){s.disabled===!0&&(e.preventDefault(),e.stopImmediatePropagation())}),t.isDefined(s.mdNoFocusStyle)||(a.mouseActive=!1,d.on("mousedown",function(){a.mouseActive=!0,i(function(){a.mouseActive=!1},100)}).on("focus",function(){a.mouseActive===!1&&d.addClass("md-focused")}).on("blur",function(e){d.removeClass("md-focused")}))}return{restrict:"EA",replace:!0,transclude:!0,template:a,link:d}}t.module("material.components.button",["material.core"]).directive("mdButton",n).directive("a",e),e.$inject=["$mdTheming"],n.$inject=["$mdButtonInkRipple","$mdTheming","$mdAria","$timeout"]; +}(),function(){function e(e){return{restrict:"E",link:function(t,n){n.addClass("_md"),t.$on("$destroy",function(){e.destroy()})}}}function n(e){function n(e,n,r,a,d,s,c){function l(o,i,c,l){i=r.extractElementByName(i,"md-bottom-sheet"),i.attr("tabindex","-1"),c.disableBackdrop||(h=r.createBackdrop(o,"_md-bottom-sheet-backdrop md-opaque"),h[0].tabIndex=-1,c.clickOutsideToClose&&h.on("click",function(){r.nextTick(d.cancel,!0)}),a.inherit(h,c.parent),e.enter(h,c.parent,null));var m=new u(i,c.parent);return c.bottomSheet=m,a.inherit(m.element,c.parent),c.disableParentScroll&&(c.restoreScroll=r.disableScrollAround(m.element,c.parent)),e.enter(m.element,c.parent,h).then(function(){var e=r.findFocusTarget(i)||t.element(i[0].querySelector("button")||i[0].querySelector("a")||i[0].querySelector(r.prefixer("ng-click",!0)))||h;c.escapeToClose&&(c.rootElementKeyupCallback=function(e){e.keyCode===n.KEY_CODE.ESCAPE&&r.nextTick(d.cancel,!0)},s.on("keyup",c.rootElementKeyupCallback),e&&e.focus())})}function m(t,n,o){var i=o.bottomSheet;return o.disableBackdrop||e.leave(h),e.leave(i.element).then(function(){o.disableParentScroll&&(o.restoreScroll(),delete o.restoreScroll),i.cleanup()})}function u(e,t){function a(t){e.css(n.CSS.TRANSITION_DURATION,"0ms")}function s(t){var o=t.pointer.distanceY;5>o&&(o=Math.max(-i,o/2)),e.css(n.CSS.TRANSFORM,"translate3d(0,"+(i+o)+"px,0)")}function l(t){if(t.pointer.distanceY>0&&(t.pointer.distanceY>20||Math.abs(t.pointer.velocityY)>o)){var i=e.prop("offsetHeight")-t.pointer.distanceY,a=Math.min(i/t.pointer.velocityY*.75,500);e.css(n.CSS.TRANSITION_DURATION,a+"ms"),r.nextTick(d.cancel,!0)}else e.css(n.CSS.TRANSITION_DURATION,""),e.css(n.CSS.TRANSFORM,"")}var m=c.register(t,"drag",{horizontal:!1});return t.on("$md.dragstart",a).on("$md.drag",s).on("$md.dragend",l),{element:e,cleanup:function(){m(),t.off("$md.dragstart",a),t.off("$md.drag",s),t.off("$md.dragend",l)}}}var h;return{themable:!0,onShow:l,onRemove:m,disableBackdrop:!1,escapeToClose:!0,clickOutsideToClose:!0,disableParentScroll:!0}}var o=.5,i=80;return n.$inject=["$animate","$mdConstant","$mdUtil","$mdTheming","$mdBottomSheet","$rootElement","$mdGesture"],e("$mdBottomSheet").setDefaults({methods:["disableParentScroll","escapeToClose","clickOutsideToClose"],options:n})}t.module("material.components.bottomSheet",["material.core","material.components.backdrop"]).directive("mdBottomSheet",e).provider("$mdBottomSheet",n),e.$inject=["$mdBottomSheet"],n.$inject=["$$interimElementProvider"]}(),function(){function e(e,n,o,i,r,a){function d(d,c){var l=d.children(),m=r.parseAttributeBoolean(c.mdIndeterminate);return c.$set("tabindex",c.tabindex||"0"),c.$set("type","checkbox"),c.$set("role",c.type),d.on("click",function(e){this.hasAttribute("disabled")&&e.stopImmediatePropagation()}),l.on("focus",function(){d.focus()}),function(d,c,l,u){function h(e,t,n){l[e]&&d.$watch(l[e],function(e){n[e]&&c.attr(t,n[e])})}function p(e){var t=e.which||e.keyCode;t!==o.KEY_CODE.SPACE&&t!==o.KEY_CODE.ENTER||(e.preventDefault(),c.hasClass("md-focused")||c.addClass("md-focused"),f(e))}function f(e){c[0].hasAttribute("disabled")||d.$apply(function(){var t=l.ngChecked?l.checked:!u.$viewValue;u.$setViewValue(t,e&&e.type),u.$render()})}function g(){u.$viewValue&&!v?c.addClass(s):c.removeClass(s)}function b(e){v=e!==!1,v&&c.attr("aria-checked","mixed"),c.toggleClass("md-indeterminate",v)}var v;u=u||r.fakeNgModel(),i(c),m&&(b(),d.$watch(l.mdIndeterminate,b)),l.ngChecked&&d.$watch(d.$eval.bind(d,l.ngChecked),u.$setViewValue.bind(u)),h("ngDisabled","tabindex",{"true":"-1","false":l.tabindex}),n.expectWithText(c,"aria-label"),e.link.pre(d,{on:t.noop,0:{}},l,[u]),d.mouseActive=!1,c.on("click",f).on("keypress",p).on("mousedown",function(){d.mouseActive=!0,a(function(){d.mouseActive=!1},100)}).on("focus",function(){d.mouseActive===!1&&c.addClass("md-focused")}).on("blur",function(){c.removeClass("md-focused")}),u.$render=g}}e=e[0];var s="md-checked";return{restrict:"E",transclude:!0,require:"?ngModel",priority:210,template:'<div class="_md-container" md-ink-ripple md-ink-ripple-checkbox><div class="_md-icon"></div></div><div ng-transclude class="_md-label"></div>',compile:d}}t.module("material.components.checkbox",["material.core"]).directive("mdCheckbox",e),e.$inject=["inputDirective","$mdAria","$mdConstant","$mdTheming","$mdUtil","$timeout"]}(),function(){t.module("material.components.chips",["material.core","material.components.autocomplete"])}(),function(){function e(e){return{restrict:"E",link:function(t,n,o){n.addClass("_md"),e(n)}}}t.module("material.components.card",["material.core"]).directive("mdCard",e),e.$inject=["$mdTheming"]}(),function(){!function(){function e(e,n,o){function i(e,t){try{e.css(s(t))}catch(n){o.error(n.message)}}function a(e){var t=l(e);return d(t)}function d(t,o){o=o||!1;var i=e.PALETTES[t.palette][t.hue];return i=o?i.contrast:i.value,n.supplant("rgba( {0}, {1}, {2}, {3} )",[i[0],i[1],i[2],i[3]||t.opacity])}function s(e){var n={},o=e.hasOwnProperty("color");return t.forEach(e,function(e,t){var i=l(e),r=t.indexOf("background")>-1;n[t]=d(i),r&&!o&&(n.color=d(i,!0))}),n}function c(n){return t.isDefined(e.THEMES[n.split("-")[0]])}function l(n){var o=n.split("-"),i=t.isDefined(e.THEMES[o[0]]),r=i?o.splice(0,1)[0]:e.defaultTheme();return{theme:r,palette:m(o,r),hue:u(o,r),opacity:o[2]||1}}function m(t,o){var i=t.length>1&&-1!==r.indexOf(t[1]),a=t[0].replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase();if(i&&(a=t[0]+"-"+t.splice(1,1)),-1===r.indexOf(a)){var d=e.THEMES[o].colors[a];if(!d)throw new Error(n.supplant("mdColors: couldn't find '{palette}' in the palettes.",{palette:a}));a=d.name}return a}function u(t,o){var i=e.THEMES[o].colors;if("hue"===t[1]){var r=parseInt(t.splice(2,1)[0],10);if(1>r||r>3)throw new Error(n.supplant("mdColors: 'hue-{hueNumber}' is not a valid hue, can be only 'hue-1', 'hue-2' and 'hue-3'",{hueNumber:r}));if(t[1]="hue-"+r,!(t[0]in i))throw new Error(n.supplant("mdColors: 'hue-x' can only be used with [{availableThemes}], but was used with '{usedTheme}'",{availableThemes:Object.keys(i).join(", "),usedTheme:t[0]}));return i[t[0]].hues[t[1]]}return t[1]||i[t[0]in i?t[0]:"primary"].hues["default"]}return r=r||Object.keys(e.PALETTES),{applyThemeColors:i,getThemeColor:a,hasTheme:c}}function o(e,n,o,r){return{restrict:"A",require:["^?mdTheme"],compile:function(a,d){function s(){var e=d.mdColors,o=e.indexOf("::")>-1,r=o?!0:i.test(d.mdColors);d.mdColors=e.replace("::","");var a=t.isDefined(d.mdColorsWatch);return o||r?!1:a?n.parseAttributeBoolean(d.mdColorsWatch):!0}var c=s();return function(n,i,a,d){var s=d[0],l=function(t){var o=r(a.mdColors)(n);return s&&Object.keys(o).forEach(function(n){var i=o[n];e.hasTheme(i)||(o[n]=(t||s.$mdTheme)+"-"+i)}),o},m=t.noop;s&&(m=s.registerChanges(function(t){e.applyThemeColors(i,l(t))})),n.$on("destroy",function(){m()});try{c?n.$watch(l,t.bind(this,e.applyThemeColors,i),!0):e.applyThemeColors(i,l())}catch(u){o.error(u.message)}}}}}var i=/^{((\s|,)*?["'a-zA-Z-]+?\s*?:\s*?('|")[a-zA-Z0-9-.]*('|"))+\s*}$/,r=n;t.module("material.components.colors",["material.core"]).directive("mdColors",o).service("$mdColors",e),e.$inject=["$mdTheming","$mdUtil","$log"],o.$inject=["$mdColors","$mdUtil","$log","$parse"]}()}(),function(){function e(e){function t(e,t){this.$scope=e,this.$element=t}return{restrict:"E",controller:["$scope","$element",t],link:function(t,o){o.addClass("_md"),e(o),t.$broadcast("$mdContentLoaded",o),n(o[0])}}}function n(e){t.element(e).on("$md.pressdown",function(t){"t"===t.pointer.type&&(t.$materialScrollFixed||(t.$materialScrollFixed=!0,0===e.scrollTop?e.scrollTop=1:e.scrollHeight===e.scrollTop+e.offsetHeight&&(e.scrollTop-=1)))})}t.module("material.components.content",["material.core"]).directive("mdContent",e),e.$inject=["$mdTheming"]}(),function(){!function(){function e(){return{template:function(e,t){var n=t.hasOwnProperty("ngIf")?"":'ng-if="calendarCtrl.isInitialized"',o='<div ng-switch="calendarCtrl.currentView" '+n+'><md-calendar-year ng-switch-when="year"></md-calendar-year><md-calendar-month ng-switch-default></md-calendar-month></div>';return o},scope:{minDate:"=mdMinDate",maxDate:"=mdMaxDate",dateFilter:"=mdDateFilter"},require:["ngModel","mdCalendar"],controller:n,controllerAs:"calendarCtrl",bindToController:!0,link:function(e,t,n,o){var i=o[0],r=o[1];r.configureNgModel(i)}}}function n(e,n,o,r,a,d,s,c){d(e),this.$element=e,this.$scope=n,this.dateUtil=o,this.$mdUtil=r,this.keyCode=a.KEY_CODE,this.$$rAF=s,this.today=this.dateUtil.createDateAtMidnight(),this.ngModelCtrl=null,this.currentView="month",this.SELECTED_DATE_CLASS="md-calendar-selected-date",this.TODAY_CLASS="md-calendar-date-today",this.FOCUSED_DATE_CLASS="md-focus",this.id=i++,this.displayDate=null,this.selectedDate=null,this.isInitialized=!1,this.width=0,this.scrollbarWidth=0,c.tabindex||e.attr("tabindex","-1"),e.on("keydown",t.bind(this,this.handleKeyEvent))}t.module("material.components.datepicker",["material.core","material.components.icon","material.components.virtualRepeat"]).directive("mdCalendar",e);var o=340,i=0;n.$inject=["$element","$scope","$$mdDateUtil","$mdUtil","$mdConstant","$mdTheming","$$rAF","$attrs"],n.prototype.configureNgModel=function(e){var t=this;t.ngModelCtrl=e,t.$mdUtil.nextTick(function(){t.isInitialized=!0}),e.$render=function(){var e=this.$viewValue;t.$scope.$broadcast("md-calendar-parent-changed",e),t.selectedDate||(t.selectedDate=e),t.displayDate||(t.displayDate=t.selectedDate||t.today)}},n.prototype.setNgModelValue=function(e){var t=this.dateUtil.createDateAtMidnight(e);return this.focus(t),this.$scope.$emit("md-calendar-change",t),this.ngModelCtrl.$setViewValue(t),this.ngModelCtrl.$render(),t},n.prototype.setCurrentView=function(e,n){var o=this;o.$mdUtil.nextTick(function(){o.currentView=e,n&&(o.displayDate=t.isDate(n)?n:new Date(n))})},n.prototype.focus=function(e){if(this.dateUtil.isValidDate(e)){var t=this.$element[0].querySelector(".md-focus");t&&t.classList.remove(this.FOCUSED_DATE_CLASS);var n=this.getDateId(e,this.currentView),o=document.getElementById(n);o&&(o.classList.add(this.FOCUSED_DATE_CLASS),o.focus(),this.displayDate=e)}else{var i=this.$element[0].querySelector("[ng-switch]");i&&i.focus()}},n.prototype.getActionFromKeyEvent=function(e){var t=this.keyCode;switch(e.which){case t.ENTER:return"select";case t.RIGHT_ARROW:return"move-right";case t.LEFT_ARROW:return"move-left";case t.DOWN_ARROW:return e.metaKey?"move-page-down":"move-row-down";case t.UP_ARROW:return e.metaKey?"move-page-up":"move-row-up";case t.PAGE_DOWN:return"move-page-down";case t.PAGE_UP:return"move-page-up";case t.HOME:return"start";case t.END:return"end";default:return null}},n.prototype.handleKeyEvent=function(e){var t=this;this.$scope.$apply(function(){if(e.which==t.keyCode.ESCAPE||e.which==t.keyCode.TAB)return t.$scope.$emit("md-calendar-close"),void(e.which==t.keyCode.TAB&&e.preventDefault());var n=t.getActionFromKeyEvent(e);n&&(e.preventDefault(),e.stopPropagation(),t.$scope.$broadcast("md-calendar-parent-action",n))})},n.prototype.hideVerticalScrollbar=function(e){function t(){var t=n.width||o,i=n.scrollbarWidth,a=e.calendarScroller;r.style.width=t+"px",a.style.width=t+i+"px",a.style.paddingRight=i+"px"}var n=this,i=e.$element[0],r=i.querySelector(".md-calendar-scroll-mask");n.width>0?t():n.$$rAF(function(){var o=e.calendarScroller;n.scrollbarWidth=o.offsetWidth-o.clientWidth,n.width=i.querySelector("table").offsetWidth,t()})},n.prototype.getDateId=function(e,t){if(!t)throw new Error("A namespace for the date id has to be specified.");return["md",this.id,t,e.getFullYear(),e.getMonth(),e.getDate()].join("-")}}()}(),function(){!function(){function e(){return{template:'<table aria-hidden="true" class="md-calendar-day-header"><thead></thead></table><div class="md-calendar-scroll-mask"><md-virtual-repeat-container class="md-calendar-scroll-container" md-offset-size="'+(i-o)+'"><table role="grid" tabindex="0" class="md-calendar" aria-readonly="true"><tbody md-calendar-month-body role="rowgroup" md-virtual-repeat="i in monthCtrl.items" md-month-offset="$index" class="md-calendar-month" md-start-index="monthCtrl.getSelectedMonthIndex()" md-item-size="'+o+'"></tbody></table></md-virtual-repeat-container></div>',require:["^^mdCalendar","mdCalendarMonth"],controller:n,controllerAs:"monthCtrl",bindToController:!0,link:function(e,t,n,o){var i=o[0],r=o[1];r.initialize(i)}}}function n(e,t,n,o,i,r){this.$element=e,this.$scope=t,this.$animate=n,this.$q=o,this.dateUtil=i,this.dateLocale=r,this.calendarScroller=e[0].querySelector(".md-virtual-repeat-scroller"),this.firstRenderableDate=null,this.isInitialized=!1,this.isMonthTransitionInProgress=!1;var a=this;this.cellClickHandler=function(){var e=i.getTimestampFromNode(this);a.$scope.$apply(function(){a.calendarCtrl.setNgModelValue(e)})},this.headerClickHandler=function(){a.calendarCtrl.setCurrentView("year",i.getTimestampFromNode(this))}}t.module("material.components.datepicker").directive("mdCalendarMonth",e);var o=265,i=45;n.$inject=["$element","$scope","$animate","$q","$$mdDateUtil","$mdDateLocale"],n.prototype.initialize=function(e){var t=e.minDate,n=e.maxDate;if(this.calendarCtrl=e,this.items={length:2e3},n&&t){var o=this.dateUtil.getMonthDistance(t,n)+1;o=Math.max(o,1),o+=1,this.items.length=o}if(this.firstRenderableDate=this.dateUtil.incrementMonths(e.today,-this.items.length/2),t&&t>this.firstRenderableDate)this.firstRenderableDate=t;else if(n){this.items.length-2;this.firstRenderableDate=this.dateUtil.incrementMonths(n,-(this.items.length-2))}this.attachScopeListeners(),e.ngModelCtrl&&e.ngModelCtrl.$render()},n.prototype.getSelectedMonthIndex=function(){var e=this.calendarCtrl;return this.dateUtil.getMonthDistance(this.firstRenderableDate,e.displayDate||e.selectedDate||e.today)},n.prototype.changeSelectedDate=function(e){var t=this,n=t.calendarCtrl,o=n.selectedDate;n.selectedDate=e,this.changeDisplayDate(e).then(function(){var t=n.SELECTED_DATE_CLASS,i="month";if(o){var r=document.getElementById(n.getDateId(o,i));r&&(r.classList.remove(t),r.setAttribute("aria-selected","false"))}if(e){var a=document.getElementById(n.getDateId(e,i));a&&(a.classList.add(t),a.setAttribute("aria-selected","true"))}})},n.prototype.changeDisplayDate=function(e){if(!this.isInitialized)return this.buildWeekHeader(),this.calendarCtrl.hideVerticalScrollbar(this),this.isInitialized=!0,this.$q.when();if(!this.dateUtil.isValidDate(e)||this.isMonthTransitionInProgress)return this.$q.when();this.isMonthTransitionInProgress=!0;var t=this.animateDateChange(e);this.calendarCtrl.displayDate=e;var n=this;return t.then(function(){n.isMonthTransitionInProgress=!1}),t},n.prototype.animateDateChange=function(e){if(this.dateUtil.isValidDate(e)){var t=this.dateUtil.getMonthDistance(this.firstRenderableDate,e);this.calendarScroller.scrollTop=t*o}return this.$q.when()},n.prototype.buildWeekHeader=function(){for(var e=this.dateLocale.firstDayOfWeek,t=this.dateLocale.shortDays,n=document.createElement("tr"),o=0;7>o;o++){var i=document.createElement("th");i.textContent=t[(o+e)%7],n.appendChild(i)}this.$element.find("thead").append(n)},n.prototype.attachScopeListeners=function(){var e=this;e.$scope.$on("md-calendar-parent-changed",function(t,n){e.changeSelectedDate(n)}),e.$scope.$on("md-calendar-parent-action",t.bind(this,this.handleKeyEvent))},n.prototype.handleKeyEvent=function(e,t){var n=this.calendarCtrl,o=n.displayDate;if("select"===t)n.setNgModelValue(o);else{var i=null,r=this.dateUtil;switch(t){case"move-right":i=r.incrementDays(o,1);break;case"move-left":i=r.incrementDays(o,-1);break;case"move-page-down":i=r.incrementMonths(o,1);break;case"move-page-up":i=r.incrementMonths(o,-1);break;case"move-row-down":i=r.incrementDays(o,7);break;case"move-row-up":i=r.incrementDays(o,-7);break;case"start":i=r.getFirstDateOfMonth(o);break;case"end":i=r.getLastDateOfMonth(o)}i&&(i=this.dateUtil.clampDate(i,n.minDate,n.maxDate),this.changeDisplayDate(i).then(function(){n.focus(i)}))}}}()}(),function(){!function(){function e(){return{require:["^^mdCalendar","^^mdCalendarMonth","mdCalendarMonthBody"],scope:{offset:"=mdMonthOffset"},controller:n,controllerAs:"mdMonthBodyCtrl",bindToController:!0,link:function(e,t,n,o){var i=o[0],r=o[1],a=o[2];a.calendarCtrl=i,a.monthCtrl=r,a.generateContent(),e.$watch(function(){return a.offset},function(e,t){e!=t&&a.generateContent()})}}}function n(e,t,n){this.$element=e,this.dateUtil=t,this.dateLocale=n,this.monthCtrl=null,this.calendarCtrl=null,this.offset=null,this.focusAfterAppend=null}t.module("material.components.datepicker").directive("mdCalendarMonthBody",e),n.$inject=["$element","$$mdDateUtil","$mdDateLocale"],n.prototype.generateContent=function(){var e=this.dateUtil.incrementMonths(this.monthCtrl.firstRenderableDate,this.offset);this.$element.empty(),this.$element.append(this.buildCalendarForMonth(e)),this.focusAfterAppend&&(this.focusAfterAppend.classList.add(this.calendarCtrl.FOCUSED_DATE_CLASS),this.focusAfterAppend.focus(),this.focusAfterAppend=null)},n.prototype.buildDateCell=function(e){var t=this.monthCtrl,n=this.calendarCtrl,o=document.createElement("td");if(o.tabIndex=-1,o.classList.add("md-calendar-date"),o.setAttribute("role","gridcell"),e){o.setAttribute("tabindex","-1"),o.setAttribute("aria-label",this.dateLocale.longDateFormatter(e)),o.id=n.getDateId(e,"month"),o.setAttribute("data-timestamp",e.getTime()),this.dateUtil.isSameDay(e,n.today)&&o.classList.add(n.TODAY_CLASS),this.dateUtil.isValidDate(n.selectedDate)&&this.dateUtil.isSameDay(e,n.selectedDate)&&(o.classList.add(n.SELECTED_DATE_CLASS),o.setAttribute("aria-selected","true"));var i=this.dateLocale.dates[e.getDate()];if(this.isDateEnabled(e)){var r=document.createElement("span");r.classList.add("md-calendar-date-selection-indicator"),r.textContent=i,o.appendChild(r),o.addEventListener("click",t.cellClickHandler),n.displayDate&&this.dateUtil.isSameDay(e,n.displayDate)&&(this.focusAfterAppend=o)}else o.classList.add("md-calendar-date-disabled"),o.textContent=i}return o},n.prototype.isDateEnabled=function(e){return this.dateUtil.isDateWithinRange(e,this.calendarCtrl.minDate,this.calendarCtrl.maxDate)&&(!t.isFunction(this.calendarCtrl.dateFilter)||this.calendarCtrl.dateFilter(e))},n.prototype.buildDateRow=function(e){var t=document.createElement("tr");return t.setAttribute("role","row"),t.setAttribute("aria-label",this.dateLocale.weekNumberFormatter(e)),t},n.prototype.buildCalendarForMonth=function(e){var t=this.dateUtil.isValidDate(e)?e:new Date,n=this.dateUtil.getFirstDateOfMonth(t),o=this.getLocaleDay_(n),i=this.dateUtil.getNumberOfDaysInMonth(t),r=document.createDocumentFragment(),a=1,d=this.buildDateRow(a);r.appendChild(d);var s=this.offset===this.monthCtrl.items.length-1,c=0,l=document.createElement("td");if(l.textContent=this.dateLocale.monthHeaderFormatter(t),l.classList.add("md-calendar-month-label"),this.calendarCtrl.maxDate&&n>this.calendarCtrl.maxDate?l.classList.add("md-calendar-month-label-disabled"):(l.addEventListener("click",this.monthCtrl.headerClickHandler),l.setAttribute("data-timestamp",n.getTime()),l.setAttribute("aria-label",this.dateLocale.monthFormatter(t))),2>=o){l.setAttribute("colspan","7");var m=this.buildDateRow();if(m.appendChild(l),r.insertBefore(m,d),s)return r}else c=2,l.setAttribute("colspan","2"),d.appendChild(l);for(var u=c;o>u;u++)d.appendChild(this.buildDateCell());for(var h=o,p=n,f=1;i>=f;f++){if(7===h){if(s)return r;h=0,a++,d=this.buildDateRow(a),r.appendChild(d)}p.setDate(f);var g=this.buildDateCell(p);d.appendChild(g),h++}for(;d.childNodes.length<7;)d.appendChild(this.buildDateCell());for(;r.childNodes.length<6;){for(var b=this.buildDateRow(),v=0;7>v;v++)b.appendChild(this.buildDateCell());r.appendChild(b)}return r},n.prototype.getLocaleDay_=function(e){return(e.getDay()+(7-this.dateLocale.firstDayOfWeek))%7}}()}(),function(){!function(){function e(){return{template:'<div class="md-calendar-scroll-mask"><md-virtual-repeat-container class="md-calendar-scroll-container"><table role="grid" tabindex="0" class="md-calendar" aria-readonly="true"><tbody md-calendar-year-body role="rowgroup" md-virtual-repeat="i in yearCtrl.items" md-year-offset="$index" class="md-calendar-year" md-start-index="yearCtrl.getFocusedYearIndex()" md-item-size="'+o+'"></tbody></table></md-virtual-repeat-container></div>',require:["^^mdCalendar","mdCalendarYear"],controller:n,controllerAs:"yearCtrl",bindToController:!0,link:function(e,t,n,o){var i=o[0],r=o[1];r.initialize(i)}}}function n(e,t,n,o,i,r){this.$element=e,this.$scope=t,this.$animate=n,this.$q=o,this.dateUtil=i,this.$timeout=r,this.calendarScroller=e[0].querySelector(".md-virtual-repeat-scroller"),this.firstRenderableDate=null,this.isInitialized=!1,this.isMonthTransitionInProgress=!1;var a=this;this.cellClickHandler=function(){a.calendarCtrl.setCurrentView("month",i.getTimestampFromNode(this))}}t.module("material.components.datepicker").directive("mdCalendarYear",e);var o=88;n.$inject=["$element","$scope","$animate","$q","$$mdDateUtil","$timeout"],n.prototype.initialize=function(e){var t=e.minDate,n=e.maxDate;if(this.calendarCtrl=e,this.items={length:400},n&&t){var o=this.dateUtil.getYearDistance(t,n)+1;this.items.length=Math.max(o,1)}this.firstRenderableDate=this.dateUtil.incrementYears(e.today,-(this.items.length/2)),t&&t>this.firstRenderableDate?this.firstRenderableDate=t:n&&(this.firstRenderableDate=this.dateUtil.incrementMonths(n,-(this.items.length-1))),(t||n)&&this.$timeout(),this.attachScopeListeners(),e.ngModelCtrl&&e.ngModelCtrl.$render()},n.prototype.getFocusedYearIndex=function(){var e=this.calendarCtrl;return this.dateUtil.getYearDistance(this.firstRenderableDate,e.displayDate||e.selectedDate||e.today)},n.prototype.changeDate=function(e){if(!this.isInitialized)return this.calendarCtrl.hideVerticalScrollbar(this),this.isInitialized=!0,this.$q.when();if(this.dateUtil.isValidDate(e)&&!this.isMonthTransitionInProgress){var t=this,n=this.animateDateChange(e);return t.isMonthTransitionInProgress=!0,t.calendarCtrl.displayDate=e,n.then(function(){t.isMonthTransitionInProgress=!1})}},n.prototype.animateDateChange=function(e){if(this.dateUtil.isValidDate(e)){var t=this.dateUtil.getYearDistance(this.firstRenderableDate,e);this.calendarScroller.scrollTop=t*o}return this.$q.when()},n.prototype.handleKeyEvent=function(e,t){var n=this.calendarCtrl,o=n.displayDate;if("select"===t)this.changeDate(o).then(function(){n.setCurrentView("month",o),n.focus(o)});else{var i=null,r=this.dateUtil;switch(t){case"move-right":i=r.incrementMonths(o,1);break;case"move-left":i=r.incrementMonths(o,-1);break;case"move-row-down":i=r.incrementMonths(o,6);break;case"move-row-up":i=r.incrementMonths(o,-6)}if(i){var a=n.minDate?r.incrementMonths(r.getFirstDateOfMonth(n.minDate),1):null,d=n.maxDate?r.getFirstDateOfMonth(n.maxDate):null;i=r.getFirstDateOfMonth(this.dateUtil.clampDate(i,a,d)),this.changeDate(i).then(function(){n.focus(i)})}}},n.prototype.attachScopeListeners=function(){var e=this;e.$scope.$on("md-calendar-parent-changed",function(t,n){e.changeDate(n)}),e.$scope.$on("md-calendar-parent-action",t.bind(e,e.handleKeyEvent))}}()}(),function(){!function(){function e(){return{require:["^^mdCalendar","^^mdCalendarYear","mdCalendarYearBody"],scope:{offset:"=mdYearOffset"},controller:n,controllerAs:"mdYearBodyCtrl",bindToController:!0,link:function(e,t,n,o){var i=o[0],r=o[1],a=o[2];a.calendarCtrl=i,a.yearCtrl=r,a.generateContent(),e.$watch(function(){return a.offset},function(e,t){e!=t&&a.generateContent()})}}}function n(e,t,n){this.$element=e,this.dateUtil=t,this.dateLocale=n,this.calendarCtrl=null,this.yearCtrl=null,this.offset=null,this.focusAfterAppend=null}t.module("material.components.datepicker").directive("mdCalendarYearBody",e),n.$inject=["$element","$$mdDateUtil","$mdDateLocale"],n.prototype.generateContent=function(){var e=this.dateUtil.incrementYears(this.yearCtrl.firstRenderableDate,this.offset);this.$element.empty(),this.$element.append(this.buildCalendarForYear(e)),this.focusAfterAppend&&(this.focusAfterAppend.classList.add(this.calendarCtrl.FOCUSED_DATE_CLASS),this.focusAfterAppend.focus(),this.focusAfterAppend=null)},n.prototype.buildMonthCell=function(e,t){var n=this.calendarCtrl,o=this.yearCtrl,i=this.buildBlankCell(),r=new Date(e,t,1);i.setAttribute("aria-label",this.dateLocale.monthFormatter(r)),i.id=n.getDateId(r,"year"),i.setAttribute("data-timestamp",r.getTime()),this.dateUtil.isSameMonthAndYear(r,n.today)&&i.classList.add(n.TODAY_CLASS),this.dateUtil.isValidDate(n.selectedDate)&&this.dateUtil.isSameMonthAndYear(r,n.selectedDate)&&(i.classList.add(n.SELECTED_DATE_CLASS),i.setAttribute("aria-selected","true"));var a=this.dateLocale.shortMonths[t];if(this.dateUtil.isDateWithinRange(r,n.minDate,n.maxDate)){var d=document.createElement("span");d.classList.add("md-calendar-date-selection-indicator"),d.textContent=a,i.appendChild(d),i.addEventListener("click",o.cellClickHandler),n.displayDate&&this.dateUtil.isSameMonthAndYear(r,n.displayDate)&&(this.focusAfterAppend=i)}else i.classList.add("md-calendar-date-disabled"),i.textContent=a;return i},n.prototype.buildBlankCell=function(){var e=document.createElement("td");return e.tabIndex=-1,e.classList.add("md-calendar-date"),e.setAttribute("role","gridcell"),e.setAttribute("tabindex","-1"),e},n.prototype.buildCalendarForYear=function(e){var t,n=e.getFullYear(),o=document.createDocumentFragment(),i=document.createElement("tr"),r=document.createElement("td");for(r.className="md-calendar-month-label",r.textContent=n,i.appendChild(r),t=0;6>t;t++)i.appendChild(this.buildMonthCell(n,t));o.appendChild(i);var a=document.createElement("tr");for(a.appendChild(this.buildBlankCell()),t=6;12>t;t++)a.appendChild(this.buildMonthCell(n,t));return o.appendChild(a),o}}()}(),function(){!function(){t.module("material.components.datepicker").config(["$provide",function(e){function t(){this.months=null,this.shortMonths=null,this.days=null,this.shortDays=null,this.dates=null,this.firstDayOfWeek=0,this.formatDate=null,this.parseDate=null,this.monthHeaderFormatter=null,this.weekNumberFormatter=null,this.longDateFormatter=null,this.msgCalendar="",this.msgOpenCalendar=""}t.prototype.$get=function(e,t){function n(e){if(!e)return"";var n=e.toLocaleTimeString(),o=e;return 0!=e.getHours()||-1===n.indexOf("11:")&&-1===n.indexOf("23:")||(o=new Date(e.getFullYear(),e.getMonth(),e.getDate(),1,0,0)),t("date")(o,"M/d/yyyy")}function o(e){return new Date(e)}function i(e){e=e.trim();var t=/^(([a-zA-Z]{3,}|[0-9]{1,4})([ \.,]+|[\/\-])){2}([a-zA-Z]{3,}|[0-9]{1,4})$/;return t.test(e)}function r(e){return p.shortMonths[e.getMonth()]+" "+e.getFullYear()}function a(e){return p.months[e.getMonth()]+" "+e.getFullYear()}function d(e){return"Week "+e}function s(e){return[p.days[e.getDay()],p.months[e.getMonth()],p.dates[e.getDate()],e.getFullYear()].join(" ")}for(var c=e.DATETIME_FORMATS.DAY.map(function(e){return e[0]}),l=Array(32),m=1;31>=m;m++)l[m]=m;var u="Calendar",h="Open calendar",p={months:this.months||e.DATETIME_FORMATS.MONTH,shortMonths:this.shortMonths||e.DATETIME_FORMATS.SHORTMONTH,days:this.days||e.DATETIME_FORMATS.DAY,shortDays:this.shortDays||c,dates:this.dates||l,firstDayOfWeek:this.firstDayOfWeek||0,formatDate:this.formatDate||n,parseDate:this.parseDate||o,isDateComplete:this.isDateComplete||i,monthHeaderFormatter:this.monthHeaderFormatter||r,monthFormatter:this.monthFormatter||a,weekNumberFormatter:this.weekNumberFormatter||d,longDateFormatter:this.longDateFormatter||s,msgCalendar:this.msgCalendar||u,msgOpenCalendar:this.msgOpenCalendar||h};return p},t.prototype.$get.$inject=["$locale","$filter"],e.provider("$mdDateLocale",new t)}])}()}(),function(){!function(){function n(e){return{template:'<md-button class="md-datepicker-button md-icon-button" type="button" tabindex="-1" aria-hidden="true" ng-click="ctrl.openCalendarPane($event)"><md-icon class="md-datepicker-calendar-icon" aria-label="md-calendar" md-svg-src="'+e.mdCalendar+'"></md-icon></md-button><div class="md-datepicker-input-container" ng-class="{\'md-datepicker-focused\': ctrl.isFocused}"><input class="md-datepicker-input" aria-haspopup="true" ng-focus="ctrl.setFocused(true)" ng-blur="ctrl.setFocused(false)"><md-button type="button" md-no-ink class="md-datepicker-triangle-button md-icon-button" ng-click="ctrl.openCalendarPane($event)" aria-label="{{::ctrl.dateLocale.msgOpenCalendar}}"><div class="md-datepicker-expand-triangle"></div></md-button></div><div class="md-datepicker-calendar-pane md-whiteframe-z1"><div class="md-datepicker-input-mask"><div class="md-datepicker-input-mask-opaque"></div></div><div class="md-datepicker-calendar"><md-calendar role="dialog" aria-label="{{::ctrl.dateLocale.msgCalendar}}" md-min-date="ctrl.minDate" md-max-date="ctrl.maxDate"md-date-filter="ctrl.dateFilter"ng-model="ctrl.date" ng-if="ctrl.isCalendarOpen"></md-calendar></div></div>',require:["ngModel","mdDatepicker","?^mdInputContainer"],scope:{minDate:"=mdMinDate",maxDate:"=mdMaxDate",placeholder:"@mdPlaceholder",dateFilter:"=mdDateFilter"},controller:o,controllerAs:"ctrl",bindToController:!0,link:function(e,t,n,o){var i=o[0],r=o[1],a=o[2];if(a)throw Error("md-datepicker should not be placed inside md-input-container.");r.configureNgModel(i)}}}function o(e,n,o,i,r,a,d,s,c,l,m,u){this.$compile=i,this.$timeout=r,this.$window=a,this.dateLocale=l,this.dateUtil=m,this.$mdConstant=d,this.$mdUtil=c,this.$$rAF=u,this.documentElement=t.element(document.documentElement),this.ngModelCtrl=null,this.inputElement=n[0].querySelector("input"),this.ngInputElement=t.element(this.inputElement),this.inputContainer=n[0].querySelector(".md-datepicker-input-container"),this.calendarPane=n[0].querySelector(".md-datepicker-calendar-pane"),this.calendarButton=n[0].querySelector(".md-datepicker-button"),this.inputMask=n[0].querySelector(".md-datepicker-input-mask-opaque"),this.$element=n,this.$attrs=o,this.$scope=e,this.date=null,this.isFocused=!1,this.isDisabled,this.setDisabled(n[0].disabled||t.isString(o.disabled)),this.isCalendarOpen=!1,this.openOnFocus=o.hasOwnProperty("mdOpenOnFocus"),this.calendarPaneOpenedFrom=null,this.calendarPane.id="md-date-pane"+c.nextUid(),s(n),this.bodyClickHandler=t.bind(this,this.handleBodyClick),this.windowResizeHandler=c.debounce(t.bind(this,this.closeCalendarPane),100),o.tabindex||n.attr("tabindex","-1"),this.installPropertyInterceptors(),this.attachChangeListeners(),this.attachInteractionListeners();var h=this;e.$on("$destroy",function(){h.detachCalendarPane()})}t.module("material.components.datepicker").directive("mdDatepicker",n),n.$inject=["$$mdSvgRegistry"];var i=3,r="md-datepicker-invalid",a=500,d=368,s=360;o.$inject=["$scope","$element","$attrs","$compile","$timeout","$window","$mdConstant","$mdTheming","$mdUtil","$mdDateLocale","$$mdDateUtil","$$rAF"],o.prototype.configureNgModel=function(e){this.ngModelCtrl=e;var t=this;e.$render=function(){var e=t.ngModelCtrl.$viewValue;if(e&&!(e instanceof Date))throw Error("The ng-model for md-datepicker must be a Date instance. Currently the model is a: "+typeof e);t.date=e,t.inputElement.value=t.dateLocale.formatDate(e),t.resizeInputElement(),t.updateErrorState()}},o.prototype.attachChangeListeners=function(){var e=this;e.$scope.$on("md-calendar-change",function(t,n){e.ngModelCtrl.$setViewValue(n),e.date=n,e.inputElement.value=e.dateLocale.formatDate(n),e.closeCalendarPane(),e.resizeInputElement(),e.updateErrorState()}),e.ngInputElement.on("input",t.bind(e,e.resizeInputElement)),e.ngInputElement.on("input",e.$mdUtil.debounce(e.handleInputEvent,a,e))},o.prototype.attachInteractionListeners=function(){var e=this,n=this.$scope,o=this.$mdConstant.KEY_CODE;e.ngInputElement.on("keydown",function(t){t.altKey&&t.keyCode==o.DOWN_ARROW&&(e.openCalendarPane(t),n.$digest())}),e.openOnFocus&&e.ngInputElement.on("focus",t.bind(e,e.openCalendarPane)),n.$on("md-calendar-close",function(){e.closeCalendarPane()})},o.prototype.installPropertyInterceptors=function(){var e=this;if(this.$attrs.ngDisabled){var t=this.$scope.$parent; +t&&t.$watch(this.$attrs.ngDisabled,function(t){e.setDisabled(t)})}Object.defineProperty(this,"placeholder",{get:function(){return e.inputElement.placeholder},set:function(t){e.inputElement.placeholder=t||""}})},o.prototype.setDisabled=function(e){this.isDisabled=e,this.inputElement.disabled=e,this.calendarButton.disabled=e},o.prototype.updateErrorState=function(e){var n=e||this.date;if(this.clearErrorState(),this.dateUtil.isValidDate(n)){if(n=this.dateUtil.createDateAtMidnight(n),this.dateUtil.isValidDate(this.minDate)){var o=this.dateUtil.createDateAtMidnight(this.minDate);this.ngModelCtrl.$setValidity("mindate",n>=o)}if(this.dateUtil.isValidDate(this.maxDate)){var i=this.dateUtil.createDateAtMidnight(this.maxDate);this.ngModelCtrl.$setValidity("maxdate",i>=n)}t.isFunction(this.dateFilter)&&this.ngModelCtrl.$setValidity("filtered",this.dateFilter(n))}else this.ngModelCtrl.$setValidity("valid",null==n);this.ngModelCtrl.$valid||this.inputContainer.classList.add(r)},o.prototype.clearErrorState=function(){this.inputContainer.classList.remove(r),["mindate","maxdate","filtered","valid"].forEach(function(e){this.ngModelCtrl.$setValidity(e,!0)},this)},o.prototype.resizeInputElement=function(){this.inputElement.size=this.inputElement.value.length+i},o.prototype.handleInputEvent=function(){var e=this.inputElement.value,t=e?this.dateLocale.parseDate(e):null;this.dateUtil.setDateTimeToMidnight(t);var n=""==e||this.dateUtil.isValidDate(t)&&this.dateLocale.isDateComplete(e)&&this.isDateEnabled(t);n&&(this.ngModelCtrl.$setViewValue(t),this.date=t),this.updateErrorState(t)},o.prototype.isDateEnabled=function(e){return this.dateUtil.isDateWithinRange(e,this.minDate,this.maxDate)&&(!t.isFunction(this.dateFilter)||this.dateFilter(e))},o.prototype.attachCalendarPane=function(){var e=this.calendarPane,n=document.body;e.style.transform="",this.$element.addClass("md-datepicker-open"),t.element(n).addClass("md-datepicker-is-showing");var o=this.inputContainer.getBoundingClientRect(),i=n.getBoundingClientRect(),r=o.top-i.top,a=o.left-i.left,c=i.top<0&&0==document.body.scrollTop?-i.top:document.body.scrollTop,l=i.left<0&&0==document.body.scrollLeft?-i.left:document.body.scrollLeft,m=c+this.$window.innerHeight,u=l+this.$window.innerWidth;if(a+s>u){if(u-s>0)a=u-s;else{a=l;var h=this.$window.innerWidth/s;e.style.transform="scale("+h+")"}e.classList.add("md-datepicker-pos-adjusted")}r+d>m&&m-d>c&&(r=m-d,e.classList.add("md-datepicker-pos-adjusted")),e.style.left=a+"px",e.style.top=r+"px",document.body.appendChild(e),this.inputMask.style.left=o.width+"px",this.$$rAF(function(){e.classList.add("md-pane-open")})},o.prototype.detachCalendarPane=function(){this.$element.removeClass("md-datepicker-open"),t.element(document.body).removeClass("md-datepicker-is-showing"),this.calendarPane.classList.remove("md-pane-open"),this.calendarPane.classList.remove("md-datepicker-pos-adjusted"),this.isCalendarOpen&&this.$mdUtil.enableScrolling(),this.calendarPane.parentNode&&this.calendarPane.parentNode.removeChild(this.calendarPane)},o.prototype.openCalendarPane=function(t){if(!this.isCalendarOpen&&!this.isDisabled){this.isCalendarOpen=!0,this.calendarPaneOpenedFrom=t.target,this.$mdUtil.disableScrollAround(this.calendarPane),this.attachCalendarPane(),this.focusCalendar();var n=this;this.$mdUtil.nextTick(function(){n.documentElement.on("click touchstart",n.bodyClickHandler)},!1),e.addEventListener("resize",this.windowResizeHandler)}},o.prototype.closeCalendarPane=function(){function t(){n.detachCalendarPane(),n.isCalendarOpen=!1,n.ngModelCtrl.$setTouched(),n.documentElement.off("click touchstart",n.bodyClickHandler),e.removeEventListener("resize",n.windowResizeHandler)}if(this.isCalendarOpen){var n=this;n.calendarPaneOpenedFrom.focus(),n.calendarPaneOpenedFrom=null,n.openOnFocus?this.$mdUtil.nextTick(t):t()}},o.prototype.getCalendarCtrl=function(){return t.element(this.calendarPane.querySelector("md-calendar")).controller("mdCalendar")},o.prototype.focusCalendar=function(){var e=this;this.$mdUtil.nextTick(function(){e.getCalendarCtrl().focus()},!1)},o.prototype.setFocused=function(e){e||this.ngModelCtrl.$setTouched(),this.isFocused=e},o.prototype.handleBodyClick=function(e){if(this.isCalendarOpen){var t=this.$mdUtil.getClosest,n=t(e.target,"md-calendar-year")||t(e.target,"md-calendar-month");n||this.closeCalendarPane(),this.$scope.$digest()}}}()}(),function(){!function(){t.module("material.components.datepicker").factory("$$mdDateUtil",function(){function e(e){return new Date(e.getFullYear(),e.getMonth(),1)}function n(e){return new Date(e.getFullYear(),e.getMonth()+1,0).getDate()}function o(e){return new Date(e.getFullYear(),e.getMonth()+1,1)}function i(e){return new Date(e.getFullYear(),e.getMonth()-1,1)}function r(e,t){return e.getFullYear()===t.getFullYear()&&e.getMonth()===t.getMonth()}function a(e,t){return e.getDate()==t.getDate()&&r(e,t)}function d(e,t){var n=o(e);return r(n,t)}function s(e,t){var n=i(e);return r(t,n)}function c(e,t){return b((e.getTime()+t.getTime())/2)}function l(t){var n=e(t);return Math.floor((n.getDay()+t.getDate()-1)/7)}function m(e,t){return new Date(e.getFullYear(),e.getMonth(),e.getDate()+t)}function u(e,t){var o=new Date(e.getFullYear(),e.getMonth()+t,1),i=n(o);return i<e.getDate()?o.setDate(i):o.setDate(e.getDate()),o}function h(e,t){return 12*(t.getFullYear()-e.getFullYear())+(t.getMonth()-e.getMonth())}function p(e){return new Date(e.getFullYear(),e.getMonth(),n(e))}function f(e){return null!=e&&e.getTime&&!isNaN(e.getTime())}function g(e){f(e)&&e.setHours(0,0,0,0)}function b(e){var n;return n=t.isUndefined(e)?new Date:new Date(e),g(n),n}function v(e,t,n){var o=b(e),i=f(t)?b(t):null,r=f(n)?b(n):null;return(!i||o>=i)&&(!r||r>=o)}function E(e,t){return u(e,12*t)}function $(e,t){return t.getFullYear()-e.getFullYear()}function C(e,t,n){var o=e;return t&&t>e&&(o=new Date(t.getTime())),n&&e>n&&(o=new Date(n.getTime())),o}function y(e){return e&&e.hasAttribute("data-timestamp")?Number(e.getAttribute("data-timestamp")):void 0}return{getFirstDateOfMonth:e,getNumberOfDaysInMonth:n,getDateInNextMonth:o,getDateInPreviousMonth:i,isInNextMonth:d,isInPreviousMonth:s,getDateMidpoint:c,isSameMonthAndYear:r,getWeekOfMonth:l,incrementDays:m,incrementMonths:u,getLastDateOfMonth:p,isSameDay:a,getMonthDistance:h,isValidDate:f,setDateTimeToMidnight:g,createDateAtMidnight:b,isDateWithinRange:v,incrementYears:E,getYearDistance:$,clampDate:C,getTimestampFromNode:y}})}()}(),function(){function e(e,n,o){return{restrict:"E",link:function(i,r){r.addClass("_md"),n(r),e(function(){function e(){r.toggleClass("md-content-overflow",a.scrollHeight>a.clientHeight)}var n,a=r[0].querySelector("md-dialog-content");a&&(n=a.getElementsByTagName("img"),e(),t.element(n).on("load",e)),i.$on("$destroy",function(){o.destroy(r)})})}}}function o(e){function o(e,t,n){return{template:['<md-dialog md-theme="{{ dialog.theme }}" aria-label="{{ dialog.ariaLabel }}" ng-class="dialog.css">',' <md-dialog-content class="md-dialog-content" role="document" tabIndex="-1">',' <h2 class="md-title">{{ dialog.title }}</h2>',' <div ng-if="::dialog.mdHtmlContent" class="_md-dialog-content-body" ',' ng-bind-html="::dialog.mdHtmlContent"></div>',' <div ng-if="::!dialog.mdHtmlContent" class="_md-dialog-content-body">'," <p>{{::dialog.mdTextContent}}</p>"," </div>",' <md-input-container md-no-float ng-if="::dialog.$type == \'prompt\'" class="md-prompt-input-container">',' <input ng-keypress="dialog.keypress($event)" md-autofocus ng-model="dialog.result" placeholder="{{::dialog.placeholder}}">'," </md-input-container>"," </md-dialog-content>"," <md-dialog-actions>",' <md-button ng-if="dialog.$type === \'confirm\' || dialog.$type === \'prompt\'" ng-click="dialog.abort()" class="md-primary">'," {{ dialog.cancel }}"," </md-button>",' <md-button ng-click="dialog.hide()" class="md-primary" md-autofocus="dialog.$type===\'alert\'">'," {{ dialog.ok }}"," </md-button>"," </md-dialog-actions>","</md-dialog>"].join("").replace(/\s\s+/g,""),controller:function(){var t="prompt"==this.$type;t&&this.initialValue&&(this.result=this.initialValue),this.hide=function(){e.hide(t?this.result:!0)},this.abort=function(){e.cancel()},this.keypress=function(t){t.keyCode===n.KEY_CODE.ENTER&&e.hide(this.result)}},controllerAs:"dialog",bindToController:!0,theme:t.defaultTheme()}}function i(e,o,i,d,s,c,l,m,u,h){function p(e,t,n,o){if(o){if(o.mdHtmlContent=o.htmlContent||n.htmlContent||"",o.mdTextContent=o.textContent||n.textContent||o.content||n.content||"",o.mdHtmlContent&&!h.has("$sanitize"))throw Error("The ngSanitize module must be loaded in order to use htmlContent.");if(o.mdHtmlContent&&o.mdTextContent)throw Error("md-dialog cannot have both `htmlContent` and `textContent`")}}function f(e,n,o,r){function a(){n[0].querySelector(".md-actions")&&u.warn("Using a class of md-actions is deprecated, please use <md-dialog-actions>.")}function d(){function e(){var e=n[0].querySelector(".dialog-close");if(!e){var o=n[0].querySelectorAll(".md-actions button, md-dialog-actions button");e=o[o.length-1]}return t.element(e)}if(o.focusOnOpen){var r=i.findFocusTarget(n)||e();r.focus()}}if(t.element(c[0].body).addClass("md-dialog-is-showing"),o.contentElement){var s=o.contentElement;t.isString(s)?(s=document.querySelector(s),o.elementInsertionSibling=s.nextElementSibling,o.elementInsertionParent=s.parentNode):(s=s[0]||s,document.contains(s)&&(o.elementInsertionSibling=s.nextElementSibling,o.elementInsertionParent=s.parentNode)),o.elementInsertionEntry=s,n=t.element(s)}return b(o),$(n.find("md-dialog"),o),E(e,n,o),M(n,o).then(function(){v(n,o),C(n,o),a(),d()})}function g(e,n,o){function i(){return _(n,o)}function d(){o.contentElement&&(o.reverseContainerStretch(),o.elementInsertionParent?o.elementInsertionSibling?o.elementInsertionParent.insertBefore(o.elementInsertionEntry,o.elementInsertionSibling):o.elementInsertionParent.appendChild(o.elementInsertionEntry):o.elementInsertionEntry.parentNode.removeChild(o.elementInsertionEntry))}function s(){t.element(c[0].body).removeClass("md-dialog-is-showing"),o.contentElement?d():n.remove(),o.$destroy||o.origin.focus()}return o.deactivateListeners(),o.unlockScreenReader(),o.hideBackdrop(o.$destroy),r&&r.parentNode&&r.parentNode.removeChild(r),a&&a.parentNode&&a.parentNode.removeChild(a),o.$destroy?s():i().then(s)}function b(e){function o(e,o){var i=t.element(e||{});if(i&&i.length){var r={top:0,left:0,height:0,width:0},a=t.isFunction(i[0].getBoundingClientRect);return t.extend(o||{},{element:a?i:n,bounds:a?i[0].getBoundingClientRect():t.extend({},r,i[0]),focus:t.bind(i,i.focus)})}}function i(e,n){return t.isString(e)&&(e=c[0].querySelector(e)),t.element(e||n)}e.origin=t.extend({element:null,bounds:null,focus:t.noop},e.origin||{}),e.parent=i(e.parent,m),e.closeTo=o(i(e.closeTo)),e.openFrom=o(i(e.openFrom)),e.targetEvent&&(e.origin=o(e.targetEvent.target,e.origin))}function v(n,o){var r=t.element(l),a=i.debounce(function(){y(n,o)},60),s=[],c=function(){var t="alert"==o.$type?e.hide:e.cancel;i.nextTick(t,!0)};if(o.escapeToClose){var m=o.parent,u=function(e){e.keyCode===d.KEY_CODE.ESCAPE&&(e.stopPropagation(),e.preventDefault(),c())};n.on("keydown",u),m.on("keydown",u),s.push(function(){n.off("keydown",u),m.off("keydown",u)})}if(r.on("resize",a),s.push(function(){r.off("resize",a)}),o.clickOutsideToClose){var h,p=n,f=function(e){h=e.target},g=function(e){h===p[0]&&e.target===p[0]&&(e.stopPropagation(),e.preventDefault(),c())};p.on("mousedown",f),p.on("mouseup",g),s.push(function(){p.off("mousedown",f),p.off("mouseup",g)})}o.deactivateListeners=function(){s.forEach(function(e){e()}),o.deactivateListeners=null}}function E(e,t,n){n.disableParentScroll&&(n.restoreScroll=i.disableScrollAround(t,n.parent)),n.hasBackdrop&&(n.backdrop=i.createBackdrop(e,"_md-dialog-backdrop md-opaque"),s.enter(n.backdrop,n.parent)),n.hideBackdrop=function(e){n.backdrop&&(e?n.backdrop.remove():s.leave(n.backdrop)),n.disableParentScroll&&(n.restoreScroll(),delete n.restoreScroll),n.hideBackdrop=null}}function $(e,t){var n="alert"===t.$type?"alertdialog":"dialog",d=e.find("md-dialog-content"),s=e.attr("id"),c="dialogContent_"+(s||i.nextUid());e.attr({role:n,tabIndex:"-1"}),0===d.length&&(d=e,s&&(c=s)),d.attr("id",c),e.attr("aria-describedby",c),t.ariaLabel?o.expect(e,"aria-label",t.ariaLabel):o.expectAsync(e,"aria-label",function(){var e=d.text().split(/\s+/);return e.length>3&&(e=e.slice(0,3).concat("...")),e.join(" ")}),r=document.createElement("div"),r.classList.add("_md-dialog-focus-trap"),r.tabIndex=0,a=r.cloneNode(!1);var l=function(){e.focus()};r.addEventListener("focus",l),a.addEventListener("focus",l),e[0].parentNode.insertBefore(r,e[0]),e.after(a)}function C(e,t){function n(e){for(;e.parentNode;){if(e===document.body)return;for(var t=e.parentNode.children,i=0;i<t.length;i++)e===t[i]||A(t[i],["SCRIPT","STYLE"])||t[i].setAttribute("aria-hidden",o);n(e=e.parentNode)}}var o=!0;n(e[0]),t.unlockScreenReader=function(){o=!1,n(e[0]),t.unlockScreenReader=null}}function y(e,t){var n="fixed"==l.getComputedStyle(c[0].body).position,o=t.backdrop?l.getComputedStyle(t.backdrop[0]):null,r=o?Math.min(c[0].body.clientHeight,Math.ceil(Math.abs(parseInt(o.height,10)))):0,a={top:e.css("top"),height:e.css("height")};return e.css({top:(n?i.scrollTop(t.parent):0)+"px",height:r?r+"px":"100%"}),function(){e.css(a)}}function M(e,t){t.parent.append(e),t.reverseContainerStretch=y(e,t);var n=e.find("md-dialog"),o=i.dom.animator,r=o.calculateZoomToOrigin,a={transitionInClass:"_md-transition-in",transitionOutClass:"_md-transition-out"},d=o.toTransformCss(r(n,t.openFrom||t.origin)),s=o.toTransformCss("");return t.fullscreen&&n.addClass("md-dialog-fullscreen"),o.translate3d(n,d,s,a).then(function(e){return t.reverseAnimate=function(){return delete t.reverseAnimate,t.closeTo?(a={transitionInClass:"_md-transition-out",transitionOutClass:"_md-transition-in"},d=s,s=o.toTransformCss(r(n,t.closeTo)),o.translate3d(n,d,s,a)):e(s=o.toTransformCss(r(n,t.origin)))},t.clearAnimate=function(){return delete t.clearAnimate,o.translate3d(n,s,o.toTransformCss(""),{})},!0})}function _(e,t){return t.reverseAnimate().then(function(){t.contentElement&&t.clearAnimate()})}function A(e,t){return-1!==t.indexOf(e.nodeName)?!0:void 0}return{hasBackdrop:!0,isolateScope:!0,onShow:f,onShowing:p,onRemove:g,clickOutsideToClose:!1,escapeToClose:!0,targetEvent:null,contentElement:null,closeTo:null,openFrom:null,focusOnOpen:!0,disableParentScroll:!0,autoWrap:!0,fullscreen:!1,transformTemplate:function(e,t){function n(e){return t.autoWrap&&!/<\/md-dialog>/g.test(e)?"<md-dialog>"+(e||"")+"</md-dialog>":e||""}return'<div class="md-dialog-container" tabindex="-1">'+n(e)+"</div>"}}}var r,a;return o.$inject=["$mdDialog","$mdTheming","$mdConstant"],i.$inject=["$mdDialog","$mdAria","$mdUtil","$mdConstant","$animate","$document","$window","$rootElement","$log","$injector"],e("$mdDialog").setDefaults({methods:["disableParentScroll","hasBackdrop","clickOutsideToClose","escapeToClose","targetEvent","closeTo","openFrom","parent","fullscreen","contentElement"],options:i}).addPreset("alert",{methods:["title","htmlContent","textContent","content","ariaLabel","ok","theme","css"],options:o}).addPreset("confirm",{methods:["title","htmlContent","textContent","content","ariaLabel","ok","cancel","theme","css"],options:o}).addPreset("prompt",{methods:["title","htmlContent","textContent","initialValue","content","placeholder","ariaLabel","ok","cancel","theme","css"],options:o})}t.module("material.components.dialog",["material.core","material.components.backdrop"]).directive("mdDialog",e).provider("$mdDialog",o),e.$inject=["$$rAF","$mdTheming","$mdDialog"],o.$inject=["$$interimElementProvider"]}(),function(){function e(e){return{restrict:"E",link:e}}t.module("material.components.divider",["material.core"]).directive("mdDivider",e),e.$inject=["$mdTheming"]}(),function(){!function(){function e(e){return{restrict:"E",require:["^?mdFabSpeedDial","^?mdFabToolbar"],compile:function(t,n){var o=t.children(),i=e.prefixer().hasAttribute(o,"ng-repeat");i?o.addClass("md-fab-action-item"):o.wrap('<div class="md-fab-action-item">')}}}t.module("material.components.fabActions",["material.core"]).directive("mdFabActions",e),e.$inject=["$mdUtil"]}()}(),function(){!function(){function e(e,n,o,i,r,a){function d(){N.direction=N.direction||"down",N.isOpen=N.isOpen||!1,l(),n.addClass("_md-animations-waiting")}function s(){var o=["click","focusin","focusout"];t.forEach(o,function(e){n.on(e,c)}),e.$on("$destroy",function(){t.forEach(o,function(e){n.off(e,c)}),p()})}function c(e){"click"==e.type&&w(e),"focusout"!=e.type||D||(D=a(function(){N.close()},100,!1)),"focusin"==e.type&&D&&(a.cancel(D),D=null)}function l(){N.currentActionIndex=-1}function m(){e.$watch("vm.direction",function(e,t){o.removeClass(n,"md-"+t),o.addClass(n,"md-"+e),l()});var t,i;e.$watch("vm.isOpen",function(e){l(),t&&i||(t=k(),i=x()),e?h():p();var r=e?"md-is-open":"",a=e?"":"md-is-open";t.attr("aria-haspopup",!0),t.attr("aria-expanded",e),i.attr("aria-hidden",!e),o.setClass(n,r,a)})}function u(){n[0].scrollHeight>0?o.addClass(n,"_md-animations-ready").then(function(){n.removeClass("_md-animations-waiting")}):10>S&&(a(u,100),S+=1)}function h(){n.on("keydown",g),i.nextTick(function(){t.element(document).on("click touchend",f)})}function p(){n.off("keydown",g),t.element(document).off("click touchend",f)}function f(e){if(e.target){var t=i.getClosest(e.target,"md-fab-trigger"),n=i.getClosest(e.target,"md-fab-actions");t||n||N.close()}}function g(e){switch(e.which){case r.KEY_CODE.ESCAPE:return N.close(),e.preventDefault(),!1;case r.KEY_CODE.LEFT_ARROW:return C(e),!1;case r.KEY_CODE.UP_ARROW:return y(e),!1;case r.KEY_CODE.RIGHT_ARROW:return M(e),!1;case r.KEY_CODE.DOWN_ARROW:return _(e),!1}}function b(e){E(e,-1)}function v(e){E(e,1)}function E(e,n){var o=$();N.currentActionIndex=N.currentActionIndex+n,N.currentActionIndex=Math.min(o.length-1,N.currentActionIndex),N.currentActionIndex=Math.max(0,N.currentActionIndex);var i=t.element(o[N.currentActionIndex]).children()[0];t.element(i).attr("tabindex",0),i.focus(),e.preventDefault(),e.stopImmediatePropagation()}function $(){var e=x()[0].querySelectorAll(".md-fab-action-item");return t.forEach(e,function(e){t.element(t.element(e).children()[0]).attr("tabindex",-1)}),e}function C(e){"left"===N.direction?v(e):b(e)}function y(e){"down"===N.direction?b(e):v(e)}function M(e){"left"===N.direction?b(e):v(e)}function _(e){"up"===N.direction?b(e):v(e)}function A(e){return i.getClosest(e,"md-fab-trigger")}function T(e){return i.getClosest(e,"md-fab-actions")}function w(e){A(e.target)&&N.toggle(),T(e.target)&&N.close()}function k(){return n.find("md-fab-trigger")}function x(){return n.find("md-fab-actions")}var N=this;N.open=function(){e.$evalAsync("vm.isOpen = true")},N.close=function(){e.$evalAsync("vm.isOpen = false"),n.find("md-fab-trigger")[0].focus()},N.toggle=function(){e.$evalAsync("vm.isOpen = !vm.isOpen")},d(),s(),m();var S=0;u();var D}t.module("material.components.fabShared",["material.core"]).controller("MdFabController",e),e.$inject=["$scope","$element","$animate","$mdUtil","$mdConstant","$timeout"]}()}(),function(){!function(){function n(){function e(e,t){t.prepend('<div class="_md-css-variables"></div>')}return{restrict:"E",scope:{direction:"@?mdDirection",isOpen:"=?mdOpen"},bindToController:!0,controller:"MdFabController",controllerAs:"vm",link:e}}function o(n){function o(e){n(e,r,!1)}function i(n){if(!n.hasClass("_md-animations-waiting")||n.hasClass("_md-animations-ready")){var o=n[0],i=n.controller("mdFabSpeedDial"),r=o.querySelectorAll(".md-fab-action-item"),a=o.querySelector("md-fab-trigger"),d=o.querySelector("._md-css-variables"),s=parseInt(e.getComputedStyle(d).zIndex);t.forEach(r,function(e,t){var n=e.style;n.transform=n.webkitTransform="",n.transitionDelay="",n.opacity=1,n.zIndex=r.length-t+s}),a.style.zIndex=s+r.length+1,i.isOpen||t.forEach(r,function(e,t){var n,o,r=e.style,d=(a.clientHeight-e.clientHeight)/2,s=(a.clientWidth-e.clientWidth)/2;switch(i.direction){case"up":n=e.scrollHeight*(t+1)+d,o="Y";break;case"down":n=-(e.scrollHeight*(t+1)+d),o="Y";break;case"left":n=e.scrollWidth*(t+1)+s,o="X";break;case"right":n=-(e.scrollWidth*(t+1)+s),o="X"}var c="translate"+o+"("+n+"px)";r.transform=r.webkitTransform=c})}}return{addClass:function(e,t,n){e.hasClass("md-fling")?(i(e),o(n)):n()},removeClass:function(e,t,n){i(e),o(n)}}}function i(n){function o(e){n(e,r,!1)}function i(n){var o=n[0],i=n.controller("mdFabSpeedDial"),r=o.querySelectorAll(".md-fab-action-item"),d=o.querySelector("._md-css-variables"),s=parseInt(e.getComputedStyle(d).zIndex);t.forEach(r,function(e,t){var n=e.style,o=t*a;n.opacity=i.isOpen?1:0,n.transform=n.webkitTransform=i.isOpen?"scale(1)":"scale(0)",n.transitionDelay=(i.isOpen?o:r.length-o)+"ms",n.zIndex=r.length-t+s})}var a=65;return{addClass:function(e,t,n){i(e),o(n)},removeClass:function(e,t,n){i(e),o(n)}}}var r=300;t.module("material.components.fabSpeedDial",["material.core","material.components.fabShared","material.components.fabTrigger","material.components.fabActions"]).directive("mdFabSpeedDial",n).animation(".md-fling",o).animation(".md-scale",i).service("mdFabSpeedDialFlingAnimation",o).service("mdFabSpeedDialScaleAnimation",i),o.$inject=["$timeout"],i.$inject=["$timeout"]}()}(),function(){!function(){function n(){function e(e,t,n){t.addClass("md-fab-toolbar"),t.find("md-fab-trigger").find("button").prepend('<div class="_md-fab-toolbar-background"></div>')}return{restrict:"E",transclude:!0,template:'<div class="_md-fab-toolbar-wrapper"> <div class="_md-fab-toolbar-content" ng-transclude></div></div>',scope:{direction:"@?mdDirection",isOpen:"=?mdOpen"},bindToController:!0,controller:"MdFabController",controllerAs:"vm",link:e}}function o(){function n(n,o,i){if(o){var r=n[0],a=n.controller("mdFabToolbar"),d=r.querySelector("._md-fab-toolbar-background"),s=r.querySelector("md-fab-trigger button"),c=r.querySelector("md-toolbar"),l=r.querySelector("md-fab-trigger button md-icon"),m=n.find("md-fab-actions").children();if(s&&d){var u=e.getComputedStyle(s).getPropertyValue("background-color"),h=r.offsetWidth,p=(r.offsetHeight,2*(h/s.offsetWidth));d.style.backgroundColor=u,d.style.borderRadius=h+"px",a.isOpen?(c.style.pointerEvents="inherit",d.style.width=s.offsetWidth+"px",d.style.height=s.offsetHeight+"px",d.style.transform="scale("+p+")",d.style.transitionDelay="0ms",l&&(l.style.transitionDelay=".3s"),t.forEach(m,function(e,t){e.style.transitionDelay=25*(m.length-t)+"ms"})):(c.style.pointerEvents="none",d.style.transform="scale(1)",d.style.top="0",n.hasClass("md-right")&&(d.style.left="0",d.style.right=null),n.hasClass("md-left")&&(d.style.right="0",d.style.left=null),d.style.transitionDelay="200ms",l&&(l.style.transitionDelay="0ms"),t.forEach(m,function(e,t){e.style.transitionDelay=200+25*t+"ms"}))}}}return{addClass:function(e,t,o){n(e,t,o),o()},removeClass:function(e,t,o){n(e,t,o),o()}}}t.module("material.components.fabToolbar",["material.core","material.components.fabShared","material.components.fabTrigger","material.components.fabActions"]).directive("mdFabToolbar",n).animation(".md-fab-toolbar",o).service("mdFabToolbarAnimation",o)}()}(),function(){!function(){function e(){return{restrict:"E",require:["^?mdFabSpeedDial","^?mdFabToolbar"]}}t.module("material.components.fabTrigger",["material.core"]).directive("mdFabTrigger",e)}()}(),function(){t.module("material.components.icon",["material.core"])}(),function(){function e(e,o,i,r){function a(n,a,d,s){function c(){for(var e in o.MEDIA)r(e),r.getQuery(o.MEDIA[e]).addListener(M);return r.watchResponsiveAttributes(["md-cols","md-row-height","md-gutter"],d,m)}function l(){s.layoutDelegate=t.noop,_();for(var e in o.MEDIA)r.getQuery(o.MEDIA[e]).removeListener(M)}function m(e){null==e?s.invalidateLayout():r(e)&&s.invalidateLayout()}function u(e){var o=g(),r={tileSpans:b(o),colCount:v(),rowMode:C(),rowHeight:$(),gutter:E()};if(e||!t.equals(r,A)){var d=i(r.colCount,r.tileSpans,o).map(function(e,n){return{grid:{element:a,style:f(r.colCount,n,r.gutter,r.rowMode,r.rowHeight)},tiles:e.map(function(e,i){return{element:t.element(o[i]),style:p(e.position,e.spans,r.colCount,n,r.gutter,r.rowMode,r.rowHeight)}})}}).reflow().performance();n.mdOnLayout({$event:{performance:d}}),A=r}}function h(e){return T+e+w}function p(e,t,n,o,i,r,a){var d=1/n*100,s=(n-1)/n,c=k({share:d,gutterShare:s,gutter:i}),l={left:x({unit:c,offset:e.col,gutter:i}),width:N({unit:c,span:t.col,gutter:i}),paddingTop:"",marginTop:"",top:"",height:""};switch(r){case"fixed":l.top=x({unit:a,offset:e.row,gutter:i}),l.height=N({unit:a,span:t.row,gutter:i});break;case"ratio":var m=d/a,u=k({share:m,gutterShare:s,gutter:i});l.paddingTop=N({unit:u,span:t.row,gutter:i}),l.marginTop=x({unit:u,offset:e.row,gutter:i});break;case"fit":var h=(o-1)/o,m=1/o*100,u=k({share:m,gutterShare:h,gutter:i});l.top=x({unit:u,offset:e.row,gutter:i}),l.height=N({unit:u,span:t.row,gutter:i})}return l}function f(e,t,n,o,i){var r={};switch(o){case"fixed":r.height=N({unit:i,span:t,gutter:n}),r.paddingBottom="";break;case"ratio":var a=1===e?0:(e-1)/e,d=1/e*100,s=d*(1/i),c=k({share:s,gutterShare:a,gutter:n});r.height="",r.paddingBottom=N({unit:c,span:t,gutter:n});break;case"fit":}return r}function g(){return[].filter.call(a.children(),function(e){return"MD-GRID-TILE"==e.tagName&&!e.$$mdDestroyed})}function b(e){return[].map.call(e,function(e){var n=t.element(e).controller("mdGridTile");return{row:parseInt(r.getResponsiveAttribute(n.$attrs,"md-rowspan"),10)||1,col:parseInt(r.getResponsiveAttribute(n.$attrs,"md-colspan"),10)||1}})}function v(){var e=parseInt(r.getResponsiveAttribute(d,"md-cols"),10);if(isNaN(e))throw"md-grid-list: md-cols attribute was not found, or contained a non-numeric value";return e}function E(){return y(r.getResponsiveAttribute(d,"md-gutter")||1)}function $(){var e=r.getResponsiveAttribute(d,"md-row-height");if(!e)throw"md-grid-list: md-row-height attribute was not found";switch(C()){case"fixed":return y(e);case"ratio":var t=e.split(":");return parseFloat(t[0])/parseFloat(t[1]);case"fit":return 0}}function C(){var e=r.getResponsiveAttribute(d,"md-row-height");if(!e)throw"md-grid-list: md-row-height attribute was not found";return"fit"==e?"fit":-1!==e.indexOf(":")?"ratio":"fixed"}function y(e){return/\D$/.test(e)?e:e+"px"}a.addClass("_md"),a.attr("role","list"),s.layoutDelegate=u;var M=t.bind(s,s.invalidateLayout),_=c();n.$on("$destroy",l);var A,T=e.startSymbol(),w=e.endSymbol(),k=e(h("share")+"% - ("+h("gutter")+" * "+h("gutterShare")+")"),x=e("calc(("+h("unit")+" + "+h("gutter")+") * "+h("offset")+")"),N=e("calc(("+h("unit")+") * "+h("span")+" + ("+h("span")+" - 1) * "+h("gutter")+")")}return{restrict:"E",controller:n,scope:{mdOnLayout:"&"},link:a}}function n(e){this.layoutInvalidated=!1,this.tilesInvalidated=!1,this.$timeout_=e.nextTick,this.layoutDelegate=t.noop}function o(e){function n(t,n){var o,a,d,s,c,l;return s=e.time(function(){a=i(t,n)}),o={layoutInfo:function(){return a},map:function(t){return c=e.time(function(){var e=o.layoutInfo();d=t(e.positioning,e.rowCount)}),o},reflow:function(t){return l=e.time(function(){var e=t||r;e(d.grid,d.tiles)}),o},performance:function(){return{tileCount:n.length,layoutTime:s,mapTime:c,reflowTime:l,totalTime:s+c+l}}}}function o(e,t){e.element.css(e.style),t.forEach(function(e){e.element.css(e.style)})}function i(e,t){function n(t,n){if(t.col>e)throw"md-grid-list: Tile at position "+n+" has a colspan ("+t.col+") that exceeds the column count ("+e+")";for(var a=0,l=0;l-a<t.col;)d>=e?o():(a=c.indexOf(0,d),-1!==a&&-1!==(l=r(a+1))?d=l+1:(a=l=0,o()));return i(a,t.col,t.row),d=a+t.col,{col:a,row:s}}function o(){d=0,s++,i(0,e,-1)}function i(e,t,n){for(var o=e;e+t>o;o++)c[o]=Math.max(c[o]+n,0)}function r(e){var t;for(t=e;t<c.length;t++)if(0!==c[t])return t;return t===c.length?t:void 0}function a(){for(var t=[],n=0;e>n;n++)t.push(0);return t}var d=0,s=0,c=a();return{positioning:t.map(function(e,t){return{spans:e,position:n(e,t)}}),rowCount:s+Math.max.apply(Math,c)}}var r=o;return n.animateWith=function(e){r=t.isFunction(e)?e:o},n}function i(e){function n(n,o,i,r){o.attr("role","listitem");var a=e.watchResponsiveAttributes(["md-colspan","md-rowspan"],i,t.bind(r,r.invalidateLayout));r.invalidateTiles(),n.$on("$destroy",function(){o[0].$$mdDestroyed=!0,a(),r.invalidateLayout()}),t.isDefined(n.$parent.$index)&&n.$watch(function(){return n.$parent.$index},function(e,t){e!==t&&r.invalidateTiles()})}return{restrict:"E",require:"^mdGridList",template:"<figure ng-transclude></figure>",transclude:!0,scope:{},controller:["$attrs",function(e){this.$attrs=e}],link:n}}function r(){return{template:"<figcaption ng-transclude></figcaption>",transclude:!0}}t.module("material.components.gridList",["material.core"]).directive("mdGridList",e).directive("mdGridTile",i).directive("mdGridTileFooter",r).directive("mdGridTileHeader",r).factory("$mdGridLayout",o),e.$inject=["$interpolate","$mdConstant","$mdGridLayout","$mdMedia"],n.$inject=["$mdUtil"],n.prototype={invalidateTiles:function(){this.tilesInvalidated=!0,this.invalidateLayout()},invalidateLayout:function(){this.layoutInvalidated||(this.layoutInvalidated=!0,this.$timeout_(t.bind(this,this.layout)))},layout:function(){try{this.layoutDelegate(this.tilesInvalidated)}finally{this.layoutInvalidated=!1,this.tilesInvalidated=!1}}},o.$inject=["$mdUtil"],i.$inject=["$mdMedia"]}(),function(){function n(e,t){function n(t,n){e(n);var o=n[0].querySelector(r),i=n[0].querySelector(a);o&&n.addClass("md-icon-left"),i&&n.addClass("md-icon-right")}function o(e,n,o,i){var r=this;r.isErrorGetter=o.mdIsError&&t(o.mdIsError),r.delegateClick=function(){r.input.focus()},r.element=n,r.setFocused=function(e){n.toggleClass("md-input-focused",!!e)},r.setHasValue=function(e){n.toggleClass("md-input-has-value",!!e)},r.setHasPlaceholder=function(e){n.toggleClass("md-input-has-placeholder",!!e)},r.setInvalid=function(e){e?i.addClass(n,"md-input-invalid"):i.removeClass(n,"md-input-invalid")},e.$watch(function(){return r.label&&r.input},function(e){e&&!r.label.attr("for")&&r.label.attr("for",r.input.attr("id"))})}var i=["INPUT","TEXTAREA","SELECT","MD-SELECT"],r=i.reduce(function(e,t){return e.concat(["md-icon ~ "+t,".md-icon ~ "+t])},[]).join(","),a=i.reduce(function(e,t){return e.concat([t+" ~ md-icon",t+" ~ .md-icon"])},[]).join(",");return o.$inject=["$scope","$element","$attrs","$animate"],{restrict:"E",link:n,controller:o}}function o(){return{restrict:"E",require:"^?mdInputContainer",link:function(e,t,n,o){!o||n.mdNoFloat||t.hasClass("_md-container-ignore")||(o.label=t,e.$on("$destroy",function(){o.label=null}))}}}function i(e,n,o,i,r){function a(a,d,s,c){function l(e){return p.setHasValue(!g.$isEmpty(e)),e}function m(){p.label&&s.$observe("required",function(e){p.label.toggleClass("md-required",e&&!v)})}function u(){p.setHasValue(d.val().length>0||(d[0].validity||{}).badInput)}function h(){function o(){d.attr("rows",1).css("height","auto").addClass("md-no-flex");var e=c();if(E||(E=d.css("padding",0).prop("offsetHeight"),d.css("padding",null)),b&&E&&(e=Math.max(e,E*b)),v&&E){var t=E*v;e>t?(d.attr("md-no-autogrow",""),e=t):d.removeAttr("md-no-autogrow")}E&&d.attr("rows",Math.round(e/E)),d.css("height",e+"px").removeClass("md-no-flex")}function c(){var e=$.offsetHeight,t=$.scrollHeight-e;return e+Math.max(t,0)}function l(t){return e.nextTick(o),t}function m(){if(h&&(h=!1,t.element(n).off("resize",o),d.attr("md-no-autogrow","").off("input",o),f)){var e=g.$formatters.indexOf(l);e>-1&&g.$formatters.splice(e,1)}}function u(){function e(e){e.preventDefault(),l=!0,u=e.clientY,h=parseFloat(d.css("height"))||d.prop("offsetHeight")}function n(e){l&&(e.preventDefault(),m(),f.addClass("md-input-resized"))}function o(e){l&&d.css("height",h+(e.pointer.y-u)+"px")}function i(e){l&&(l=!1,f.removeClass("md-input-resized"))}if(!s.hasOwnProperty("mdNoResize")){var c=t.element('<div class="md-resize-handle"></div>'),l=!1,u=null,h=0,f=p.element,g=r.register(c,"drag",{ +horizontal:!1});d.after(c),c.on("mousedown",e),f.on("$md.dragstart",n).on("$md.drag",o).on("$md.dragend",i),a.$on("$destroy",function(){c.off("mousedown",e).remove(),f.off("$md.dragstart",n).off("$md.drag",o).off("$md.dragend",i),g(),c=null,f=null,g=null})}}var h=!s.hasOwnProperty("mdNoAutogrow");if(u(),h){var b=s.hasOwnProperty("rows")?parseInt(s.rows):NaN,v=s.hasOwnProperty("maxRows")?parseInt(s.maxRows):NaN,E=null,$=d[0];if(i(function(){e.nextTick(o)},10,!1),d.on("input",o),f&&g.$formatters.push(l),b||d.attr("rows",1),t.element(n).on("resize",o),a.$on("$destroy",m),s.hasOwnProperty("mdDetectHidden")){var C=function(){var e=!1;return function(){var t=0===$.offsetHeight;t===!1&&e===!0&&o(),e=t}}();a.$watch(function(){return e.nextTick(C,!1),!0})}}}var p=c[0],f=!!c[1],g=c[1]||e.fakeNgModel(),b=t.isDefined(s.readonly),v=e.parseAttributeBoolean(s.mdNoAsterisk),E=d[0].tagName.toLowerCase();if(p){if("hidden"===s.type)return void d.attr("aria-hidden","true");if(p.input)throw new Error("<md-input-container> can only have *one* <input>, <textarea> or <md-select> child element!");p.input=d,m();var $=t.element('<div class="md-errors-spacer">');d.after($),p.label||o.expect(d,"aria-label",d.attr("placeholder")),d.addClass("md-input"),d.attr("id")||d.attr("id","input_"+e.nextUid()),"input"===E&&"number"===s.type&&s.min&&s.max&&!s.step?d.attr("step","any"):"textarea"===E&&h(),f||u();var C=p.isErrorGetter||function(){return g.$invalid&&(g.$touched||y())},y=function(){var n=e.getClosest(d,"form"),o=n?t.element(n).controller("form"):null;return o?o.$submitted:!1};a.$watch(C,p.setInvalid),g.$parsers.push(l),g.$formatters.push(l),d.on("input",u),b||d.on("focus",function(t){e.nextTick(function(){p.setFocused(!0)})}).on("blur",function(t){e.nextTick(function(){p.setFocused(!1),u()})}),a.$on("$destroy",function(){p.setFocused(!1),p.setHasValue(!1),p.input=null})}}return{restrict:"E",require:["^?mdInputContainer","?ngModel"],link:a}}function r(e,n){function o(o,i,r,a){function d(e){return c.parent?(c.text(String(i.val()||e||"").length+"/"+s),e):e}var s,c,l,m=a[0],u=a[1];n.nextTick(function(){l=t.element(u.element[0].querySelector(".md-errors-spacer")),c=t.element('<div class="md-char-counter">'),l.append(c),r.$set("ngTrim","false"),m.$formatters.push(d),m.$viewChangeListeners.push(d),i.on("input keydown keyup",function(){d()}),o.$watch(r.mdMaxlength,function(n){s=n,t.isNumber(n)&&n>0?(c.parent().length||e.enter(c,l),d()):e.leave(c)}),m.$validators["md-maxlength"]=function(e,n){return!t.isNumber(s)||0>s?!0:(e||i.val()||n||"").length<=s}})}return{restrict:"A",require:["ngModel","^mdInputContainer"],link:o}}function a(e){function t(e,t,n,o){if(o){var i=o.element.find("label"),r=o.element.attr("md-no-float");if(i&&i.length||""===r||e.$eval(r))return void o.setHasPlaceholder(!0);var a=n.placeholder;if(t.removeAttr("placeholder"),o.input&&"MD-SELECT"!=o.input[0].nodeName){var d='<label ng-click="delegateClick()">'+a+"</label>";o.element.addClass("md-icon-float"),o.element.prepend(d)}}}return{restrict:"A",require:"^^?mdInputContainer",priority:200,link:t}}function d(e){function t(t,n,o){function i(){a=!0,e(function(){n[0].select(),a=!1},1,!1)}function r(e){a&&e.preventDefault()}if("INPUT"===n[0].nodeName||"TEXTAREA"===n[0].nodeName){var a=!1;n.on("focus",i).on("mouseup",r),t.$on("$destroy",function(){n.off("focus",i).off("mouseup",r)})}}return{restrict:"A",link:t}}function s(){function e(e,n,o,i){i&&(n.toggleClass("md-input-messages-animation",!0),n.toggleClass("md-auto-hide",!0),("false"==o.mdAutoHide||t(o))&&n.toggleClass("md-auto-hide",!1))}function t(e){return E.some(function(t){return e[t]})}return{restrict:"EA",link:e,require:"^^?mdInputContainer"}}function c(e){function t(t){function n(){for(var e=t[0];e=e.parentNode;)if(e.nodeType===Node.DOCUMENT_FRAGMENT_NODE)return!0;return!1}function o(t){return!!e.getClosest(t,"md-input-container")}function i(e){e.toggleClass("md-input-message-animation",!0)}if(o(t))i(t);else if(n())return function(e,n){o(n)&&i(t)}}return{restrict:"EA",compile:t,priority:100}}function l(e,t){return{addClass:function(n,o,i){var r=v(n);"md-input-invalid"==o&&r.hasClass("md-auto-hide")?h(n,t,e)["finally"](i):i()}}}function m(e,t){return{enter:function(n,o){h(n,t,e)["finally"](o)},leave:function(n,o){p(n,t,e)["finally"](o)},addClass:function(n,o,i){"ng-hide"==o?p(n,t,e)["finally"](i):i()},removeClass:function(n,o,i){"ng-hide"==o?h(n,t,e)["finally"](i):i()}}}function u(e){return{enter:function(t,n){var o=v(t);return o.hasClass("md-auto-hide")?void n():f(t,e)},leave:function(t,n){return g(t,e)}}}function h(e,n,o){var i,r=[],a=v(e);return t.forEach(a.children(),function(e){i=f(t.element(e),n),r.push(i.start())}),o.all(r)}function p(e,n,o){var i,r=[],a=v(e);return t.forEach(a.children(),function(e){i=g(t.element(e),n),r.push(i.start())}),o.all(r)}function f(e,t){var n=e[0].offsetHeight;return t(e,{event:"enter",structural:!0,from:{opacity:0,"margin-top":-n+"px"},to:{opacity:1,"margin-top":"0"},duration:.3})}function g(t,n){var o=t[0].offsetHeight,i=e.getComputedStyle(t[0]);return 0==i.opacity?n(t,{}):n(t,{event:"leave",structural:!0,from:{opacity:1,"margin-top":0},to:{opacity:0,"margin-top":-o+"px"},duration:.3})}function b(e){var t=e.controller("mdInputContainer");return t.element}function v(e){var n=b(e);return t.element(n[0].querySelector(".md-input-messages-animation"))}t.module("material.components.input",["material.core"]).directive("mdInputContainer",n).directive("label",o).directive("input",i).directive("textarea",i).directive("mdMaxlength",r).directive("placeholder",a).directive("ngMessages",s).directive("ngMessage",c).directive("ngMessageExp",c).directive("mdSelectOnFocus",d).animation(".md-input-invalid",l).animation(".md-input-messages-animation",m).animation(".md-input-message-animation",u),n.$inject=["$mdTheming","$parse"],i.$inject=["$mdUtil","$window","$mdAria","$timeout","$mdGesture"],r.$inject=["$animate","$mdUtil"],a.$inject=["$log"],d.$inject=["$timeout"];var E=["ngIf","ngShow","ngHide","ngSwitchWhen","ngSwitchDefault"];c.$inject=["$mdUtil"],l.$inject=["$q","$animateCss"],m.$inject=["$q","$animateCss"],u.$inject=["$animateCss"]}(),function(){function e(e){return{restrict:"E",compile:function(t){return t[0].setAttribute("role","list"),e}}}function n(e,n,o,i){var r=["md-checkbox","md-switch"];return{restrict:"E",controller:"MdListController",compile:function(a,d){function s(){for(var e,t,n=["md-switch","md-checkbox"],o=0;t=n[o];++o)if((e=a.find(t)[0])&&!e.hasAttribute("aria-label")){var i=a.find("p")[0];if(!i)return;e.setAttribute("aria-label","Toggle "+i.textContent)}}function c(e){if("div"==e)$=t.element('<div class="_md-no-style _md-list-item-inner">'),$.append(a.contents()),a.addClass("_md-proxy-focus");else{$=t.element('<div class="md-button _md-no-style"> <div class="_md-list-item-inner"></div></div>');var n=t.element('<md-button class="_md-no-style"></md-button>');n[0].setAttribute("aria-label",a[0].textContent),u(a[0],n[0]),$.prepend(n),$.children().eq(1).append(a.contents()),a.addClass("_md-button-wrap")}a[0].setAttribute("tabindex","-1"),a.append($)}function l(){var e=t.element('<div class="_md-secondary-container">');t.forEach(E,function(t){m(t,e)}),$.append(e)}function m(n,o){if(n&&!p(n)&&n.hasAttribute("ng-click")){e.expect(n,"aria-label");var i=t.element('<md-button class="md-secondary md-icon-button">');u(n,i[0]),n.setAttribute("tabindex","-1"),i.append(n),n=i[0]}n&&(!f(n)||!d.ngClick&&h(n))&&t.element(n).removeClass("md-secondary"),a.addClass("md-with-secondary"),o.append(n)}function u(e,n){var i=o.prefixer(["ng-if","ng-click","ng-dblclick","aria-label","ng-disabled","ui-sref","href","ng-href","target","ng-attr-ui-sref","ui-sref-opts"]);t.forEach(i,function(t){e.hasAttribute(t)&&(n.setAttribute(t,e.getAttribute(t)),e.removeAttribute(t))})}function h(e){return-1!=r.indexOf(e.nodeName.toLowerCase())}function p(e){var t=e.nodeName.toUpperCase();return"MD-BUTTON"==t||"BUTTON"==t}function f(e){for(var t=e.attributes,n=0;n<t.length;n++)if("ngClick"===d.$normalize(t[n].name))return!0;return!1}function g(e,a,d,s){function c(){u&&u.children&&!g&&t.forEach(r,function(e){t.forEach(u.querySelectorAll(e+":not(.md-secondary)"),function(e){m.push(e)})})}function l(){(1==m.length||g)&&(a.addClass("md-clickable"),g||s.attachRipple(e,t.element(a[0].querySelector("._md-no-style"))))}a.addClass("_md");var m=[],u=a[0].firstElementChild,h=a.hasClass("_md-button-wrap"),p=h?u.firstElementChild:u,g=p&&f(p);c(),l(),a.hasClass("_md-proxy-focus")&&m.length&&t.forEach(m,function(n){n=t.element(n),e.mouseActive=!1,n.on("mousedown",function(){e.mouseActive=!0,i(function(){e.mouseActive=!1},100)}).on("focus",function(){e.mouseActive===!1&&a.addClass("md-focused"),n.on("blur",function t(){a.removeClass("md-focused"),n.off("blur",t)})})});var b=function(e){if("INPUT"!=e.target.nodeName&&"TEXTAREA"!=e.target.nodeName&&!e.target.isContentEditable){var t=e.which||e.keyCode;t==n.KEY_CODE.SPACE&&p&&(p.click(),e.preventDefault(),e.stopPropagation())}};g||m.length||p&&p.addEventListener("keypress",b),a.off("click"),a.off("keypress"),1==m.length&&p&&a.children().eq(0).on("click",function(e){var n=o.getClosest(e.target,"BUTTON");!n&&p.contains(e.target)&&t.forEach(m,function(n){e.target===n||n.contains(e.target)||t.element(n).triggerHandler("click")})}),e.$on("$destroy",function(){p&&p.removeEventListener("keypress",b)})}var b,v,E=a[0].querySelectorAll(".md-secondary"),$=a;if(a[0].setAttribute("role","listitem"),d.ngClick||d.ngDblclick||d.ngHref||d.href||d.uiSref||d.ngAttrUiSref)c("button");else{for(var C,y=0;C=r[y];++y)if(v=a[0].querySelector(C)){b=!0;break}b?c("div"):a[0].querySelector("md-button:not(.md-secondary):not(.md-exclude)")||a.addClass("_md-no-proxy")}return l(),s(),g}}}function o(e,t,n){function o(e,t){var o={};n.attach(e,t,o)}var i=this;i.attachRipple=o}t.module("material.components.list",["material.core"]).controller("MdListController",o).directive("mdList",e).directive("mdListItem",n),e.$inject=["$mdTheming"],n.$inject=["$mdAria","$mdConstant","$mdUtil","$timeout"],o.$inject=["$scope","$element","$mdListInkRipple"]}(),function(){t.module("material.components.menu",["material.core","material.components.backdrop"])}(),function(){t.module("material.components.menuBar",["material.core","material.components.menu"])}(),function(){function e(e){return{restrict:"E",transclude:!0,controller:o,controllerAs:"ctrl",bindToController:!0,scope:{mdSelectedNavItem:"=?",navBarAriaLabel:"@?"},template:'<div class="md-nav-bar"><nav role="navigation"><ul class="_md-nav-bar-list" layout="row" ng-transclude role="listbox"tabindex="0"ng-focus="ctrl.onFocus()"ng-blur="ctrl.onBlur()"ng-keydown="ctrl.onKeydown($event)"aria-label="{{ctrl.navBarAriaLabel}}"></ul></nav><md-nav-ink-bar></md-nav-ink-bar></div>',link:function(n,o,i,r){r.navBarAriaLabel||e.expectAsync(o,"aria-label",t.noop)}}}function o(e,t,n,o){this._$timeout=n,this._$scope=t,this._$mdConstant=o,this.mdSelectedNavItem,this.navBarAriaLabel,this._navBarEl=e[0],this._inkbar;var i=this,r=this._$scope.$watch(function(){return i._navBarEl.querySelectorAll("._md-nav-button").length},function(e){e>0&&(i._initTabs(),r())})}function i(e){return{restrict:"E",require:["mdNavItem","^mdNavBar"],controller:r,bindToController:!0,controllerAs:"ctrl",replace:!0,transclude:!0,template:'<li class="md-nav-item" role="option" aria-selected="{{ctrl.isSelected()}}"><md-button ng-if="ctrl.mdNavSref" class="_md-nav-button md-accent"ng-class="ctrl.getNgClassMap()"tabindex="-1"ui-sref="{{ctrl.mdNavSref}}"><span ng-transclude class="_md-nav-button-text"></span></md-button><md-button ng-if="ctrl.mdNavHref" class="_md-nav-button md-accent"ng-class="ctrl.getNgClassMap()"tabindex="-1"ng-href="{{ctrl.mdNavHref}}"><span ng-transclude class="_md-nav-button-text"></span></md-button><md-button ng-if="ctrl.mdNavClick" class="_md-nav-button md-accent"ng-class="ctrl.getNgClassMap()"tabindex="-1"ng-click="ctrl.mdNavClick()"><span ng-transclude class="_md-nav-button-text"></span></md-button></li>',scope:{mdNavClick:"&?",mdNavHref:"@?",mdNavSref:"@?",name:"@"},link:function(n,o,i,r){var a=r[0],d=r[1];e(function(){a.name||(a.name=t.element(o[0].querySelector("._md-nav-button-text")).text().trim());var e=t.element(o[0].querySelector("._md-nav-button"));e.on("click",function(){d.mdSelectedNavItem=a.name,n.$apply()})})}}}function r(e){this._$element=e,this.mdNavClick,this.mdNavHref,this.name,this._selected=!1,this._focused=!1;var t=!!e.attr("md-nav-click"),n=!!e.attr("md-nav-href"),o=!!e.attr("md-nav-sref");if((t?1:0)+(n?1:0)+(o?1:0)>1)throw Error("Must specify exactly one of md-nav-click, md-nav-href, md-nav-sref for nav-item directive")}t.module("material.components.navBar",["material.core"]).controller("MdNavBarController",o).directive("mdNavBar",e).controller("MdNavItemController",r).directive("mdNavItem",i),e.$inject=["$mdAria"],o.$inject=["$element","$scope","$timeout","$mdConstant"],o.prototype._initTabs=function(){this._inkbar=t.element(this._navBarEl.getElementsByTagName("md-nav-ink-bar")[0]);var e=this;this._$timeout(function(){e._updateTabs(e.mdSelectedNavItem,n)}),this._$scope.$watch("ctrl.mdSelectedNavItem",function(t,n){e._$timeout(function(){e._updateTabs(t,n)})})},o.prototype._updateTabs=function(e,t){var n,o=this._getTabs();if(t){var i=this._getTabByName(t);i&&(i.setSelected(!1),n=o.indexOf(i))}if(e){var r=this._getTabByName(e);if(r){r.setSelected(!0);var a=o.indexOf(r),d=this;this._$timeout(function(){d._updateInkBarStyles(r,a,n)})}}},o.prototype._updateInkBarStyles=function(e,t,n){var o=e.getButtonEl(),i=o.offsetLeft;this._inkbar.toggleClass("_md-left",n>t).toggleClass("_md-right",t>n),this._inkbar.css({left:i+"px",width:o.offsetWidth+"px"})},o.prototype._getTabs=function(){var e=Array.prototype.slice.call(this._navBarEl.querySelectorAll(".md-nav-item"));return e.map(function(e){return t.element(e).controller("mdNavItem")})},o.prototype._getTabByName=function(e){return this._findTab(function(t){return t.getName()==e})},o.prototype._getSelectedTab=function(){return this._findTab(function(e){return e.isSelected()})},o.prototype.getFocusedTab=function(){return this._findTab(function(e){return e.hasFocus()})},o.prototype._findTab=function(e){for(var t=this._getTabs(),n=0;n<t.length;n++)if(e(t[n]))return t[n];return null},o.prototype.onFocus=function(){var e=this._getSelectedTab();e&&e.setFocused(!0)},o.prototype.onBlur=function(){var e=this.getFocusedTab();e&&e.setFocused(!1)},o.prototype._moveFocus=function(e,t){e.setFocused(!1),t.setFocused(!0)},o.prototype.onKeydown=function(e){var t=this._$mdConstant.KEY_CODE,n=this._getTabs(),o=this.getFocusedTab();if(o){var i=n.indexOf(o);switch(e.keyCode){case t.UP_ARROW:case t.LEFT_ARROW:i>0&&this._moveFocus(o,n[i-1]);break;case t.DOWN_ARROW:case t.RIGHT_ARROW:i<n.length-1&&this._moveFocus(o,n[i+1]);break;case t.SPACE:case t.ENTER:this._$timeout(function(){o.getButtonEl().click()})}}},i.$inject=["$$rAF"],r.$inject=["$element"],r.prototype.getNgClassMap=function(){return{"md-active":this._selected,"md-primary":this._selected,"md-unselected":!this._selected,"md-focused":this._focused}},r.prototype.getName=function(){return this.name},r.prototype.getButtonEl=function(){return this._$element[0].querySelector("._md-nav-button")},r.prototype.setSelected=function(e){this._selected=e},r.prototype.isSelected=function(){return this._selected},r.prototype.setFocused=function(e){this._focused=e},r.prototype.hasFocus=function(){return this._focused}}(),function(){function e(e,n,o,a){this._defaultConfigOptions={bindToController:!0,clickOutsideToClose:!1,disableParentScroll:!1,escapeToClose:!1,focusOnOpen:!0,fullscreen:!1,hasBackdrop:!1,transformTemplate:t.bind(this,this._wrapTemplate),trapFocus:!1,zIndex:d},this._config={},this._$rootElement=e,this._$rootScope=n,this._$injector=o,this._$window=a,this.animation=r.animation,this.xPosition=i.xPosition,this.yPosition=i.yPosition}function o(e,t){this._$q=t.get("$q"),this._$mdCompiler=t.get("$mdCompiler"),this._$mdConstant=t.get("$mdConstant"),this._$mdUtil=t.get("$mdUtil"),this._$rootScope=t.get("$rootScope"),this._$animate=t.get("$animate"),this._$mdPanel=t.get("$mdPanel"),this._$log=t.get("$log"),this._$window=t.get("$window"),this._$$rAF=t.get("$$rAF"),this.id=e.id,this.isAttached=!1,this._config=e,this._panelContainer,this._panelEl,this._removeListeners=[],this._topFocusTrap,this._bottomFocusTrap,this._backdropRef,this._restoreScroll=null}function i(e){this._$window=e,this._absolute=!1,this._relativeToEl,this._top="",this._bottom="",this._left="",this._right="",this._translateX=[],this._translateY=[],this._positions=[],this._actualPosition}function r(e){this._$mdUtil=e.get("$mdUtil"),this._openFrom,this._closeTo,this._animationClass=""}function a(e){var n=t.isString(e)?document.querySelector(e):e;return t.element(n)}t.module("material.components.panel",["material.core","material.components.backdrop"]).service("$mdPanel",e);var d=80,s="_md-panel-hidden",c=t.element('<div class="_md-panel-focus-trap" tabindex="0"></div>');e.$inject=["$rootElement","$rootScope","$injector","$window"],e.prototype.create=function(e){var n=e||{};this._config={scope:this._$rootScope.$new(!0),attachTo:this._$rootElement},t.extend(this._config,this._defaultConfigOptions,n);var i="panel_"+this._$injector.get("$mdUtil").nextUid(),r=t.extend({id:i},this._config);return new o(r,this._$injector)},e.prototype.open=function(e){var t=this.create(e);return t.open().then(function(){return t})},e.prototype.newPanelPosition=function(){return new i(this._$window)},e.prototype.newPanelAnimation=function(){return new r(this._$injector)},e.prototype._wrapTemplate=function(e){var t=e||"";return'<div class="md-panel-outer-wrapper"> <div class="md-panel" style="left: -9999px;">'+t+"</div></div>"},o.prototype.open=function(){var e=this;return this._$q(function(t,n){var o=e._done(t,e),i=e._simpleBind(e.show,e);e.attach().then(i).then(o)["catch"](n)})},o.prototype.close=function(){var e=this;return this._$q(function(t,n){var o=e._done(t,e),i=e._simpleBind(e.detach,e);e.hide().then(i).then(o)["catch"](n)})},o.prototype.attach=function(){if(this.isAttached&&this._panelEl)return this._$q.when(this);var e=this;return this._$q(function(n,o){var i=e._done(n,e),r=e._config.onDomAdded||t.noop,a=function(t){return e.isAttached=!0,e._addEventListeners(),t};e._$q.all([e._createBackdrop(),e._createPanel().then(a)["catch"](o)]).then(r).then(i)["catch"](o)})},o.prototype.detach=function(){if(!this.isAttached)return this._$q.when(this);var e=this,n=e._config.onDomRemoved||t.noop,o=function(){return e._removeEventListeners(),e._topFocusTrap&&e._topFocusTrap.parentNode&&e._topFocusTrap.parentNode.removeChild(e._topFocusTrap),e._bottomFocusTrap&&e._bottomFocusTrap.parentNode&&e._bottomFocusTrap.parentNode.removeChild(e._bottomFocusTrap),e._panelContainer.remove(),e.isAttached=!1,e._$q.when(e)};return this._restoreScroll&&(this._restoreScroll(),this._restoreScroll=null),this._$q(function(t,i){var r=e._done(t,e);e._$q.all([o(),e._backdropRef?e._backdropRef.detach():!0]).then(n).then(r)["catch"](i)})},o.prototype.destroy=function(){this._config.locals=null},o.prototype.show=function(){if(!this._panelContainer)return this._$q(function(e,t){t("Panel does not exist yet. Call open() or attach().")});if(!this._panelContainer.hasClass(s))return this._$q.when(this);var e=this,n=function(){return e.removeClass(s),e._animateOpen()};return this._$q(function(o,i){var r=e._done(o,e),a=e._config.onOpenComplete||t.noop;e._$q.all([e._backdropRef?e._backdropRef.show():e,n().then(function(){e._focusOnOpen()},i)]).then(a).then(r)["catch"](i)})},o.prototype.hide=function(){if(!this._panelContainer)return this._$q(function(e,t){t("Panel does not exist yet. Call open() or attach().")});if(this._panelContainer.hasClass(s))return this._$q.when(this);var e=this;return this._$q(function(n,o){var i=e._done(n,e),r=e._config.onRemoving||t.noop,d=function(){var t=e._config.origin;t&&a(t).focus()},c=function(){e.addClass(s)};e._$q.all([e._backdropRef?e._backdropRef.hide():e,e._animateClose().then(r).then(c).then(d)["catch"](o)]).then(i,o)})},o.prototype.addClass=function(e){if(!this._panelContainer)throw new Error("Panel does not exist yet. Call open() or attach().");this._panelContainer.hasClass(e)||this._panelContainer.addClass(e)},o.prototype.removeClass=function(e){if(!this._panelContainer)throw new Error("Panel does not exist yet. Call open() or attach().");this._panelContainer.hasClass(e)&&this._panelContainer.removeClass(e)},o.prototype.toggleClass=function(e){if(!this._panelContainer)throw new Error("Panel does not exist yet. Call open() or attach().");this._panelContainer.toggleClass(e)},o.prototype._createPanel=function(){var e=this;return this._$q(function(n,o){e._config.locals||(e._config.locals={}),e._config.locals.mdPanelRef=e,e._$mdCompiler.compile(e._config).then(function(i){e._panelContainer=i.link(e._config.scope),a(e._config.attachTo).append(e._panelContainer),e._config.disableParentScroll&&(e._restoreScroll=e._$mdUtil.disableScrollAround(null,e._panelContainer)),e._panelEl=t.element(e._panelContainer[0].querySelector(".md-panel")),e._config.panelClass&&e._panelEl.addClass(e._config.panelClass),e._$animate.pin&&e._$animate.pin(e._panelContainer,a(e._config.attachTo)),e._configureTrapFocus(),e._addStyles().then(function(){n(e)},o)},o)})},o.prototype._addStyles=function(){var e=this;return this._$q(function(t){e._panelContainer.css("z-index",e._config.zIndex),e._panelEl.css("z-index",e._config.zIndex+1);var n=function(){e._panelEl.css("left",""),e._panelContainer.addClass(s),t(e)};if(e._config.fullscreen)return e._panelEl.addClass("_md-panel-fullscreen"),void n();var o=e._config.position;return o?void e._$rootScope.$$postDigest(function(){e._updatePosition(!0),t(e)}):void n()})},o.prototype._updatePosition=function(e){var t=this._config.position;if(t){t._setPanelPosition(this._panelEl),e&&this._panelContainer.addClass(s),this._panelEl.css("top",t.getTop()),this._panelEl.css("bottom",t.getBottom()),this._panelEl.css("left",t.getLeft()),this._panelEl.css("right",t.getRight());var n=this._$mdConstant.CSS.TRANSFORM;this._panelEl.css(n,t.getTransform())}},o.prototype._focusOnOpen=function(){if(this._config.focusOnOpen){var e=this;this._$rootScope.$$postDigest(function(){var t=e._$mdUtil.findFocusTarget(e._panelEl)||e._panelEl;t.focus()})}},o.prototype._createBackdrop=function(){if(this._config.hasBackdrop){if(!this._backdropRef){var e=this._$mdPanel.newPanelAnimation().openFrom(this._config.attachTo).withAnimation({open:"_md-opaque-enter",close:"_md-opaque-leave"}),t={animation:e,attachTo:this._config.attachTo,focusOnOpen:!1,panelClass:"_md-panel-backdrop",zIndex:this._config.zIndex-1};this._backdropRef=this._$mdPanel.create(t)}if(!this._backdropRef.isAttached)return this._backdropRef.attach()}},o.prototype._addEventListeners=function(){this._configureEscapeToClose(),this._configureClickOutsideToClose(),this._configureScrollListener()},o.prototype._removeEventListeners=function(){this._removeListeners&&this._removeListeners.forEach(function(e){e()}),this._removeListeners=null},o.prototype._configureEscapeToClose=function(){if(this._config.escapeToClose){var e=a(this._config.attachTo),t=this,n=function(e){e.keyCode===t._$mdConstant.KEY_CODE.ESCAPE&&(e.stopPropagation(),e.preventDefault(),t.close())};this._panelContainer.on("keydown",n),e.on("keydown",n),this._removeListeners.push(function(){t._panelContainer.off("keydown",n),e.off("keydown",n)})}},o.prototype._configureClickOutsideToClose=function(){if(this._config.clickOutsideToClose){var e,t=this._panelContainer,n=function(t){e=t.target},o=this,i=function(n){e===t[0]&&n.target===t[0]&&(n.stopPropagation(),n.preventDefault(),o.close())};t.on("mousedown",n),t.on("mouseup",i),this._removeListeners.push(function(){t.off("mousedown",n),t.off("mouseup",i)})}},o.prototype._configureScrollListener=function(){var e=t.bind(this,this._updatePosition),n=this._$$rAF.throttle(e),o=this,i=function(){o._config.disableParentScroll||n()};this._$window.addEventListener("scroll",i,!0),this._removeListeners.push(function(){o._$window.removeEventListener("scroll",i,!0)})},o.prototype._configureTrapFocus=function(){if(this._panelEl.attr("tabIndex","-1"),this._config.trapFocus){var e=this._panelEl;this._topFocusTrap=c.clone()[0],this._bottomFocusTrap=c.clone()[0];var t=function(){e.focus()};this._topFocusTrap.addEventListener("focus",t),this._bottomFocusTrap.addEventListener("focus",t),this._removeListeners.push(this._simpleBind(function(){this._topFocusTrap.removeEventListener("focus",t),this._bottomFocusTrap.removeEventListener("focus",t)},this)),e[0].parentNode.insertBefore(this._topFocusTrap,e[0]),e.after(this._bottomFocusTrap)}},o.prototype._animateOpen=function(){this.addClass("md-panel-is-showing");var e=this._config.animation;if(!e)return this.addClass("_md-panel-shown"),this._$q.when(this);var t=this;return this._$q(function(n){var o=t._done(n,t),i=function(){t._$log.warn("MdPanel Animations failed. Showing panel without animating."),o()};e.animateOpen(t._panelEl).then(o,i)})},o.prototype._animateClose=function(){var e=this._config.animation;if(!e)return this.removeClass("md-panel-is-showing"),this.removeClass("_md-panel-shown"),this._$q.when(this);var t=this;return this._$q(function(n){var o=function(){t.removeClass("md-panel-is-showing"),n(t)},i=function(){t._$log.warn("MdPanel Animations failed. Hiding panel without animating."),o()};e.animateClose(t._panelEl).then(o,i)})},o.prototype._simpleBind=function(e,t){return function(n){return e.apply(t,n)}},o.prototype._done=function(e,t){return function(){e(t)}},i.xPosition={CENTER:"center",ALIGN_START:"align-start",ALIGN_END:"align-end",OFFSET_START:"offset-start",OFFSET_END:"offset-end"},i.yPosition={CENTER:"center",ALIGN_TOPS:"align-tops",ALIGN_BOTTOMS:"align-bottoms",ABOVE:"above",BELOW:"below"},i.prototype.absolute=function(){return this._absolute=!0,this},i.prototype.top=function(e){return this._bottom="",this._top=e||"0",this},i.prototype.bottom=function(e){return this._top="",this._bottom=e||"0",this},i.prototype.left=function(e){return this._right="",this._left=e||"0",this},i.prototype.right=function(e){return this._left="",this._right=e||"0",this},i.prototype.centerHorizontally=function(){return this._left="50%",this._right="",this._translateX=["-50%"],this},i.prototype.centerVertically=function(){return this._top="50%",this._bottom="",this._translateY=["-50%"],this},i.prototype.center=function(){return this.centerHorizontally().centerVertically()},i.prototype.relativeTo=function(e){return this._absolute=!1,this._relativeToEl=a(e),this},i.prototype.addPanelPosition=function(e,t){if(!this._relativeToEl)throw new Error("addPanelPosition can only be used with relative positioning. Set relativeTo first.");return this._validateXPosition(e),this._validateYPosition(t),this._positions.push({x:e,y:t}),this},i.prototype._validateYPosition=function(e){if(null!=e){for(var t,n=Object.keys(i.yPosition),o=[],r=0;t=n[r];r++){var a=i.yPosition[t];if(o.push(a),a===e)return}throw new Error("Panel y position only accepts the following values:\n"+o.join(" | "))}},i.prototype._validateXPosition=function(e){if(null!=e){for(var t,n=Object.keys(i.xPosition),o=[],r=0;t=n[r];r++){var a=i.xPosition[t];if(o.push(a),a===e)return}throw new Error("Panel x Position only accepts the following values:\n"+o.join(" | "))}},i.prototype.withOffsetX=function(e){return this._translateX.push(e),this},i.prototype.withOffsetY=function(e){return this._translateY.push(e),this},i.prototype.getTop=function(){return this._top},i.prototype.getBottom=function(){return this._bottom},i.prototype.getLeft=function(){return this._left},i.prototype.getRight=function(){return this._right},i.prototype.getTransform=function(){var e=this._reduceTranslateValues("translateX",this._translateX),t=this._reduceTranslateValues("translateY",this._translateY);return(e+" "+t).trim()},i.prototype._isOnscreen=function(e){var t=parseInt(this.getLeft()),n=parseInt(this.getTop()),o=t+e[0].offsetWidth,i=n+e[0].offsetHeight;return t>=0&&n>=0&&i<=this._$window.innerHeight&&o<=this._$window.innerWidth},i.prototype.getActualPosition=function(){return this._actualPosition},i.prototype._reduceTranslateValues=function(e,t){return t.map(function(t){return e+"("+t+")"}).join(" ")},i.prototype._setPanelPosition=function(e){if(!this._absolute){if(this._actualPosition)return void this._calculatePanelPosition(e,this._actualPosition);for(var t=0;t<this._positions.length&&(this._actualPosition=this._positions[t],this._calculatePanelPosition(e,this._actualPosition),!this._isOnscreen(e));t++);}},i.prototype._calculatePanelPosition=function(e,t){var n=e[0].getBoundingClientRect(),o=n.width,r=n.height,a=this._relativeToEl[0].getBoundingClientRect(),d=a.left,s=a.right,c=a.width;switch(t.x){case i.xPosition.OFFSET_START:this._left=d-o+"px";break;case i.xPosition.ALIGN_END:this._left=s-o+"px";break;case i.xPosition.CENTER:var l=d+.5*c-.5*o;this._left=l+"px";break;case i.xPosition.ALIGN_START:this._left=d+"px";break;case i.xPosition.OFFSET_END:this._left=s+"px"}var m=a.top,u=a.bottom,h=a.height;switch(t.y){case i.yPosition.ABOVE:this._top=m-r+"px";break;case i.yPosition.ALIGN_BOTTOMS:this._top=u-r+"px";break;case i.yPosition.CENTER:var p=m+.5*h-.5*r;this._top=p+"px";break;case i.yPosition.ALIGN_TOPS:this._top=m+"px";break;case i.yPosition.BELOW:this._top=u+"px"}},r.animation={SLIDE:"md-panel-animate-slide",SCALE:"md-panel-animate-scale",FADE:"md-panel-animate-fade"},r.prototype.openFrom=function(e){return e=e.target?e.target:e,this._openFrom=this._getPanelAnimationTarget(e),this._closeTo||(this._closeTo=this._openFrom),this},r.prototype.closeTo=function(e){return this._closeTo=this._getPanelAnimationTarget(e),this},r.prototype._getPanelAnimationTarget=function(e){return t.isDefined(e.top)||t.isDefined(e.left)?{element:n,bounds:{top:e.top||0,left:e.left||0}}:this._getBoundingClientRect(a(e))},r.prototype.withAnimation=function(e){return this._animationClass=e,this},r.prototype.animateOpen=function(e){var n=this._$mdUtil.dom.animator;this._fixBounds(e);var o={},i=e[0].style.transform||"",a=n.toTransformCss(i),d=n.toTransformCss(i);switch(this._animationClass){case r.animation.SLIDE:e.css("opacity","1"),o={transitionInClass:"_md-panel-animate-enter"};var s=n.calculateSlideToOrigin(e,this._openFrom)||"";a=n.toTransformCss(s+" "+i);break;case r.animation.SCALE:o={transitionInClass:"_md-panel-animate-enter"};var c=n.calculateZoomToOrigin(e,this._openFrom)||"";a=n.toTransformCss(c+" "+i);break;case r.animation.FADE:o={transitionInClass:"_md-panel-animate-enter"};break;default:o=t.isString(this._animationClass)?{transitionInClass:this._animationClass}:{transitionInClass:this._animationClass.open,transitionOutClass:this._animationClass.close}}return n.translate3d(e,a,d,o)},r.prototype.animateClose=function(e){var n=this._$mdUtil.dom.animator,o={},i=e[0].style.transform||"",a=n.toTransformCss(i),d=n.toTransformCss(i);switch(this._animationClass){case r.animation.SLIDE:e.css("opacity","1"),o={transitionInClass:"_md-panel-animate-leave"};var s=n.calculateSlideToOrigin(e,this._closeTo)||"";d=n.toTransformCss(s+" "+i);break;case r.animation.SCALE:o={transitionInClass:"_md-panel-animate-scale-out _md-panel-animate-leave"};var c=n.calculateZoomToOrigin(e,this._closeTo)||"";d=n.toTransformCss(c+" "+i);break;case r.animation.FADE:o={transitionInClass:"_md-panel-animate-fade-out _md-panel-animate-leave"};break;default:o=t.isString(this._animationClass)?{transitionOutClass:this._animationClass}:{transitionInClass:this._animationClass.close,transitionOutClass:this._animationClass.open}}return n.translate3d(e,a,d,o)},r.prototype._fixBounds=function(e){var t=e[0].offsetWidth,n=e[0].offsetHeight;this._openFrom&&null==this._openFrom.bounds.height&&(this._openFrom.bounds.height=n),this._openFrom&&null==this._openFrom.bounds.width&&(this._openFrom.bounds.width=t),this._closeTo&&null==this._closeTo.bounds.height&&(this._closeTo.bounds.height=n), +this._closeTo&&null==this._closeTo.bounds.width&&(this._closeTo.bounds.width=t)},r.prototype._getBoundingClientRect=function(e){return e instanceof t.element?{element:e,bounds:e[0].getBoundingClientRect()}:void 0}}(),function(){function e(e,n,o){function i(e,t,n){return e.attr("aria-valuemin",0),e.attr("aria-valuemax",100),e.attr("role","progressbar"),r}function r(o,i,r){function u(){r.$observe("value",function(e){var t=a(e);i.attr("aria-valuenow",t),p()!=l&&f($,t)}),r.$observe("mdBufferValue",function(e){f(E,a(e))}),r.$observe("disabled",function(e){b=e===!0||e===!1?e:t.isDefined(e),i.toggleClass(m,!!b)}),r.$observe("mdMode",function(e){switch(g&&C.removeClass(g),e){case l:case c:case d:case s:C.addClass(g="_md-mode-"+e);break;default:C.addClass(g="_md-mode-"+s)}})}function h(){if(t.isUndefined(r.mdMode)){var e=t.isDefined(r.value),n=e?d:s;i.attr("md-mode",n),r.mdMode=n}}function p(){var e=(r.mdMode||"").trim();if(e)switch(e){case d:case s:case c:case l:break;default:e=s}return e}function f(e,o){if(!b&&p()){var i=n.supplant("translateX({0}%) scale({1},1)",[(o-100)/2,o/100]),r=v({transform:i});t.element(e).css(r)}}e(i);var g,b=r.hasOwnProperty("disabled"),v=n.dom.animator.toCss,E=t.element(i[0].querySelector("._md-bar1")),$=t.element(i[0].querySelector("._md-bar2")),C=t.element(i[0].querySelector("._md-container"));i.attr("md-mode",p()).toggleClass(m,b),h(),u()}function a(e){return Math.max(0,Math.min(e||0,100))}var d="determinate",s="indeterminate",c="buffer",l="query",m="_md-progress-linear-disabled";return{restrict:"E",template:'<div class="_md-container"><div class="_md-dashed"></div><div class="_md-bar _md-bar1"></div><div class="_md-bar _md-bar2"></div></div>',compile:i}}t.module("material.components.progressLinear",["material.core"]).directive("mdProgressLinear",e),e.$inject=["$mdTheming","$mdUtil","$log"]}(),function(){t.module("material.components.progressCircular",["material.core"])}(),function(){function e(e,n,o,i){function r(r,a,d,s){function c(){a.hasClass("md-focused")||a.addClass("md-focused")}function l(o){var i=o.which||o.keyCode;if(i==n.KEY_CODE.ENTER||o.currentTarget==o.target)switch(i){case n.KEY_CODE.LEFT_ARROW:case n.KEY_CODE.UP_ARROW:o.preventDefault(),m.selectPrevious(),c();break;case n.KEY_CODE.RIGHT_ARROW:case n.KEY_CODE.DOWN_ARROW:o.preventDefault(),m.selectNext(),c();break;case n.KEY_CODE.ENTER:var r=t.element(e.getClosest(a[0],"form"));r.length>0&&r.triggerHandler("submit")}}a.addClass("_md"),o(a);var m=s[0],u=s[1]||e.fakeNgModel();m.init(u),r.mouseActive=!1,a.attr({role:"radiogroup",tabIndex:a.attr("tabindex")||"0"}).on("keydown",l).on("mousedown",function(e){r.mouseActive=!0,i(function(){r.mouseActive=!1},100)}).on("focus",function(){r.mouseActive===!1&&m.$element.addClass("md-focused")}).on("blur",function(){m.$element.removeClass("md-focused")})}function a(e){this._radioButtonRenderFns=[],this.$element=e}function d(){return{init:function(e){this._ngModelCtrl=e,this._ngModelCtrl.$render=t.bind(this,this.render)},add:function(e){this._radioButtonRenderFns.push(e)},remove:function(e){var t=this._radioButtonRenderFns.indexOf(e);-1!==t&&this._radioButtonRenderFns.splice(t,1)},render:function(){this._radioButtonRenderFns.forEach(function(e){e()})},setViewValue:function(e,t){this._ngModelCtrl.$setViewValue(e,t),this.render()},getViewValue:function(){return this._ngModelCtrl.$viewValue},selectNext:function(){return s(this.$element,1)},selectPrevious:function(){return s(this.$element,-1)},setActiveDescendant:function(e){this.$element.attr("aria-activedescendant",e)}}}function s(n,o){var i=e.iterator(n[0].querySelectorAll("md-radio-button"),!0);if(i.count()){var r=function(e){return!t.element(e).attr("disabled")},a=n[0].querySelector("md-radio-button.md-checked"),d=i[0>o?"previous":"next"](a,r)||i.first();t.element(d).triggerHandler("click")}}return a.prototype=d(),{restrict:"E",controller:["$element",a],require:["mdRadioGroup","?ngModel"],link:{pre:r}}}function n(e,t,n){function o(o,r,a,d){function s(e){if(!d)throw"RadioGroupController not found.";d.add(l),a.$observe("value",l),r.on("click",c).on("$destroy",function(){d.remove(l)})}function c(e){r[0].hasAttribute("disabled")||o.$apply(function(){d.setViewValue(a.value,e&&e.type)})}function l(){function e(e){"MD-RADIO-GROUP"!=r.parent()[0].nodeName&&r.parent()[e?"addClass":"removeClass"](i)}var t=d.getViewValue()==a.value;t!==u&&(u=t,r.attr("aria-checked",t),t?(e(!0),r.addClass(i),d.setActiveDescendant(r.attr("id"))):(e(!1),r.removeClass(i)))}function m(n,o){function i(){return a.id||"radio_"+t.nextUid()}o.ariaId=i(),n.attr({id:o.ariaId,role:"radio","aria-checked":"false"}),e.expectWithText(n,"aria-label")}var u;n(r),m(r,o),s()}var i="md-checked";return{restrict:"E",require:"^mdRadioGroup",transclude:!0,template:'<div class="_md-container" md-ink-ripple md-ink-ripple-checkbox><div class="_md-off"></div><div class="_md-on"></div></div><div ng-transclude class="_md-label"></div>',link:o}}t.module("material.components.radioButton",["material.core"]).directive("mdRadioGroup",e).directive("mdRadioButton",n),e.$inject=["$mdUtil","$mdConstant","$mdTheming","$timeout"],n.$inject=["$mdAria","$mdUtil","$mdTheming"]}(),function(){function e(e,o,i,r,a,d){function s(a,s){var c=t.element("<md-select-value><span></span></md-select-value>");if(c.append('<span class="_md-select-icon" aria-hidden="true"></span>'),c.addClass("_md-select-value"),c[0].hasAttribute("id")||c.attr("id","select_value_label_"+o.nextUid()),a.find("md-content").length||a.append(t.element("<md-content>").append(a.contents())),s.mdOnOpen&&(a.find("md-content").prepend(t.element('<div> <md-progress-circular md-mode="indeterminate" ng-if="$$loadingAsyncDone === false" md-diameter="25px"></md-progress-circular></div>')),a.find("md-option").attr("ng-show","$$loadingAsyncDone")),s.name){var l=t.element('<select class="_md-visually-hidden">');l.attr({name:"."+s.name,"ng-model":s.ngModel,"aria-hidden":"true",tabindex:"-1"});var m=a.find("md-option");t.forEach(m,function(e){var n=t.element("<option>"+e.innerHTML+"</option>");e.hasAttribute("ng-value")?n.attr("ng-value",e.getAttribute("ng-value")):e.hasAttribute("value")&&n.attr("value",e.getAttribute("value")),l.append(n)}),a.parent().append(l)}var u=o.parseAttributeBoolean(s.multiple),h=u?"multiple":"",p='<div class="_md-select-menu-container" aria-hidden="true"><md-select-menu {0}>{1}</md-select-menu></div>';return p=o.supplant(p,[h,a.html()]),a.empty().append(c),a.append(p),s.tabindex||s.$set("tabindex",0),function(a,s,c,l){function m(){var e=s.attr("aria-label")||s.attr("placeholder");!e&&y&&y.label&&(e=y.label.text()),$=e,r.expect(s,"aria-label",e)}function h(){N&&(D=D||N.find("md-select-menu").controller("mdSelectMenu"),M.setLabelText(D.selectedLabels()))}function p(){if($){var e=D.selectedLabels({mode:"aria"});s.attr("aria-label",e.length?$+": "+e:$)}}function f(){y&&y.setHasValue(D.selectedLabels().length>0||(s[0].validity||{}).badInput)}function g(){if(N=t.element(s[0].querySelector("._md-select-menu-container")),S=a,c.mdContainerClass){var e=N[0].getAttribute("class")+" "+c.mdContainerClass;N[0].setAttribute("class",e)}D=N.find("md-select-menu").controller("mdSelectMenu"),D.init(_,c.ngModel),s.on("$destroy",function(){N.remove()})}function b(e){var n=[32,13,38,40];if(-1!=n.indexOf(e.keyCode))e.preventDefault(),v(e);else if(e.keyCode<=90&&e.keyCode>=31){e.preventDefault();var o=D.optNodeForKeyboardSearch(e);if(!o)return;var i=t.element(o).controller("mdOption");D.isMultiple||D.deselect(Object.keys(D.selected)[0]),D.select(i.hashKey,i.value),D.refreshViewValue()}}function v(){S.isOpen=!0,s.attr("aria-expanded","true"),e.show({scope:S,preserveScope:!0,skipCompile:!0,element:N,target:s[0],selectCtrl:M,preserveElement:!0,hasBackdrop:!0,loadingAsync:c.mdOnOpen?a.$eval(c.mdOnOpen)||!0:!1})["finally"](function(){S.isOpen=!1,s.focus(),s.attr("aria-expanded","false"),_.$setTouched()})}var E,$,C=!0,y=l[0],M=l[1],_=l[2],A=l[3],T=s.find("md-select-value"),w=t.isDefined(c.readonly),k=o.parseAttributeBoolean(c.mdNoAsterisk);if(y){var x=y.isErrorGetter||function(){return _.$invalid&&_.$touched};if(y.input&&s.find("md-select-header").find("input")[0]!==y.input[0])throw new Error("<md-input-container> can only have *one* child <input>, <textarea> or <select> element!");y.input=s,y.label||r.expect(s,"aria-label",s.attr("placeholder")),a.$watch(x,y.setInvalid)}var N,S,D;if(g(),i(s),c.name&&A){var H=s.parent()[0].querySelector('select[name=".'+c.name+'"]');o.nextTick(function(){var e=t.element(H).controller("ngModel");e&&A.$removeControl(e)})}A&&t.isDefined(c.multiple)&&o.nextTick(function(){var e=_.$modelValue||_.$viewValue;e&&A.$setPristine()});var I=_.$render;_.$render=function(){I(),h(),p(),f()},c.$observe("placeholder",_.$render),y&&y.label&&c.$observe("required",function(e){y.label.toggleClass("md-required",e&&!k)}),M.setLabelText=function(e){if(M.setIsPlaceholder(!e),c.mdSelectedText)e=d(c.mdSelectedText)(a);else{var t=c.placeholder||(y&&y.label?y.label.text():"");e=e||t||""}var n=T.children().eq(0);n.html(e)},M.setIsPlaceholder=function(e){e?(T.addClass("_md-select-placeholder"),y&&y.label&&y.label.addClass("_md-placeholder")):(T.removeClass("_md-select-placeholder"),y&&y.label&&y.label.removeClass("_md-placeholder"))},w||(s.on("focus",function(e){y&&y.element.hasClass("md-input-has-value")&&y.setFocused(!0)}),s.on("blur",function(e){C&&(C=!1,S.isOpen&&e.stopImmediatePropagation()),S.isOpen||(y&&y.setFocused(!1),f())})),M.triggerClose=function(){d(c.mdOnClose)(a)},a.$$postDigest(function(){m(),h(),p()}),a.$watch(function(){return D.selectedLabels()},h);var O;c.$observe("ngMultiple",function(e){O&&O();var t=d(e);O=a.$watch(function(){return t(a)},function(e,t){e===n&&t===n||(e?s.attr("multiple","multiple"):s.removeAttr("multiple"),s.attr("aria-multiselectable",e?"true":"false"),N&&(D.setMultiple(e),I=_.$render,_.$render=function(){I(),h(),p(),f()},_.$render()))})}),c.$observe("disabled",function(e){t.isString(e)&&(e=!0),E!==n&&E===e||(E=e,e?s.attr({"aria-disabled":"true"}).removeAttr("tabindex").off("click",v).off("keydown",b):s.attr({tabindex:c.tabindex,"aria-disabled":"false"}).on("click",v).on("keydown",b))}),c.hasOwnProperty("disabled")||c.hasOwnProperty("ngDisabled")||(s.attr({"aria-disabled":"false"}),s.on("click",v),s.on("keydown",b));var R={role:"listbox","aria-expanded":"false","aria-multiselectable":u&&!c.ngMultiple?"true":"false"};s[0].hasAttribute("id")||(R.id="select_"+o.nextUid());var L="select_container_"+o.nextUid();N.attr("id",L),R["aria-owns"]=L,s.attr(R),a.$on("$destroy",function(){e.destroy()["finally"](function(){y&&(y.setFocused(!1),y.setHasValue(!1),y.input=null),_.$setTouched()})})}}return{restrict:"E",require:["^?mdInputContainer","mdSelect","ngModel","?^form"],compile:s,controller:function(){}}}function o(e,o,i){function r(e,n,r,a){function d(e){13!=e.keyCode&&32!=e.keyCode||s(e)}function s(n){var i=o.getClosest(n.target,"md-option"),r=i&&t.element(i).data("$mdOptionController");if(i&&r){if(i.hasAttribute("disabled"))return n.stopImmediatePropagation(),!1;var a=c.hashGetter(r.value),d=t.isDefined(c.selected[a]);e.$apply(function(){c.isMultiple?d?c.deselect(a):c.select(a,r.value):d||(c.deselect(Object.keys(c.selected)[0]),c.select(a,r.value)),c.refreshViewValue()})}}var c=a[0];n.addClass("_md"),i(n),n.on("click",s),n.on("keypress",d)}function a(i,r,a){function d(){var e=l.ngModel.$modelValue||l.ngModel.$viewValue||[];if(t.isArray(e)){var n=Object.keys(l.selected),o=e.map(l.hashGetter),i=n.filter(function(e){return-1===o.indexOf(e)});i.forEach(l.deselect),o.forEach(function(t,n){l.select(t,e[n])})}}function s(){var e=l.ngModel.$viewValue||l.ngModel.$modelValue;Object.keys(l.selected).forEach(l.deselect),l.select(l.hashGetter(e),e)}var l=this;l.isMultiple=t.isDefined(r.multiple),l.selected={},l.options={},i.$watchCollection(function(){return l.options},function(){l.ngModel.$render()});var m,u;l.setMultiple=function(e){function n(e,n){return t.isArray(e||n||[])}var o=l.ngModel;u=u||o.$isEmpty,l.isMultiple=e,m&&m(),l.isMultiple?(o.$validators["md-multiple"]=n,o.$render=d,i.$watchCollection(l.modelBinding,function(e){n(e)&&d(e),l.ngModel.$setPristine()}),o.$isEmpty=function(e){return!e||0===e.length}):(delete o.$validators["md-multiple"],o.$render=s)};var h,p,f,g="",b=300;l.optNodeForKeyboardSearch=function(e){h&&clearTimeout(h),h=setTimeout(function(){h=n,g="",f=n,p=n},b),g+=String.fromCharCode(e.keyCode);var o=new RegExp("^"+g,"i");p||(p=a.find("md-option"),f=new Array(p.length),t.forEach(p,function(e,t){f[t]=e.textContent.trim()}));for(var i=0;i<f.length;++i)if(o.test(f[i]))return p[i]},l.init=function(n,o){if(l.ngModel=n,l.modelBinding=o,l.ngModel.$isEmpty=function(e){return!l.options[e]},n.$options&&n.$options.trackBy){var r={},a=e(n.$options.trackBy);l.hashGetter=function(e,t){return r.$value=e,a(t||i,r)}}else l.hashGetter=function(e){return t.isObject(e)?"object_"+(e.$$mdSelectId||(e.$$mdSelectId=++c)):e};l.setMultiple(l.isMultiple)},l.selectedLabels=function(e){e=e||{};var t=e.mode||"html",n=o.nodesToArray(a[0].querySelectorAll("md-option[selected]"));if(n.length){var i;return"html"==t?i=function(e){var t=e.innerHTML,n=e.querySelector(".md-ripple-container");return n?t.replace(n.outerHTML,""):t}:"aria"==t&&(i=function(e){return e.hasAttribute("aria-label")?e.getAttribute("aria-label"):e.textContent}),n.map(i).join(", ")}return""},l.select=function(e,t){var n=l.options[e];n&&n.setSelected(!0),l.selected[e]=t},l.deselect=function(e){var t=l.options[e];t&&t.setSelected(!1),delete l.selected[e]},l.addOption=function(e,n){if(t.isDefined(l.options[e]))throw new Error('Duplicate md-option values are not allowed in a select. Duplicate value "'+n.value+'" found.');l.options[e]=n,t.isDefined(l.selected[e])&&(l.select(e,n.value),l.refreshViewValue())},l.removeOption=function(e){delete l.options[e]},l.refreshViewValue=function(){var e,n=[];for(var o in l.selected)(e=l.options[o])?n.push(e.value):n.push(l.selected[o]);var i=l.ngModel.$options&&l.ngModel.$options.trackBy,r=l.isMultiple?n:n[0],a=l.ngModel.$modelValue;(i?t.equals(a,r):a==r)||(l.ngModel.$setViewValue(r),l.ngModel.$render())}}return a.$inject=["$scope","$attrs","$element"],{restrict:"E",require:["mdSelectMenu"],scope:!1,controller:a,link:{pre:r}}}function i(e,n){function o(e,n){return e.append(t.element('<div class="_md-text">').append(e.contents())),e.attr("tabindex",n.tabindex||"0"),i}function i(o,i,r,a){function d(e,t,n){if(!m.hashGetter)return void(n||o.$$postDigest(function(){d(e,t,!0)}));var i=m.hashGetter(t,o),r=m.hashGetter(e,o);c.hashKey=r,c.value=e,m.removeOption(i,c),m.addOption(r,c)}function s(){var e={role:"option","aria-selected":"false"};i[0].hasAttribute("id")||(e.id="select_option_"+n.nextUid()),i.attr(e)}var c=a[0],m=a[1];m.isMultiple&&(i.addClass("_md-checkbox-enabled"),i.prepend(l.clone())),t.isDefined(r.ngValue)?o.$watch(r.ngValue,d):t.isDefined(r.value)?d(r.value):o.$watch(function(){return i.text().trim()},d),r.$observe("disabled",function(e){e?i.attr("tabindex","-1"):i.attr("tabindex","0")}),o.$$postDigest(function(){r.$observe("selected",function(e){t.isDefined(e)&&("string"==typeof e&&(e=!0),e?(m.isMultiple||m.deselect(Object.keys(m.selected)[0]),m.select(c.hashKey,c.value)):m.deselect(c.hashKey),m.refreshViewValue())})}),e.attach(o,i),s(),o.$on("$destroy",function(){m.removeOption(c.hashKey,c)})}function r(e){this.selected=!1,this.setSelected=function(t){t&&!this.selected?e.attr({selected:"selected","aria-selected":"true"}):!t&&this.selected&&(e.removeAttr("selected"),e.attr("aria-selected","false")),this.selected=t}}return r.$inject=["$element"],{restrict:"E",require:["mdOption","^^mdSelectMenu"],controller:r,compile:o}}function r(){function e(e,n){function o(){return e.parent().find("md-select-header").length}function i(){var o=e.find("label");o.length||(o=t.element("<label>"),e.prepend(o)),o.addClass("_md-container-ignore"),n.label&&o.text(n.label)}o()||i()}return{restrict:"E",compile:e}}function a(){return{restrict:"E"}}function d(e){function o(e,o,c,l,m,u,h,p,f){function g(e,t,n){function o(){return h(t,{addClass:"_md-leave"}).start()}function i(){t.removeClass("_md-active"),t.attr("aria-hidden","true"),t[0].style.display="none",v(n),!n.$destroy&&n.restoreFocus&&n.target.focus()}return n=n||{},n.cleanupInteraction(),n.cleanupResizing(),n.hideBackdrop(),n.$destroy===!0?i():o().then(i)}function b(i,r,a){function d(e,t,n){return n.parent.append(t),m(function(e,n){try{h(t,{removeClass:"_md-leave",duration:0}).start().then(s).then(e)}catch(o){n(o)}})}function s(){return m(function(e){if(a.isRemoved)return m.reject(!1);var t=E(i,r,a);t.container.element.css(C.toCss(t.container.styles)),t.dropDown.element.css(C.toCss(t.dropDown.styles)),u(function(){r.addClass("_md-active"),t.dropDown.element.css(C.toCss({transform:""})),b(a.focusedNode),e()})})}function g(e,t,n){return n.disableParentScroll&&!c.getClosest(n.target,"MD-DIALOG")?n.restoreScroll=c.disableScrollAround(n.element,n.parent):n.disableParentScroll=!1,n.hasBackdrop&&(n.backdrop=c.createBackdrop(e,"_md-select-backdrop _md-click-catcher"),p.enter(n.backdrop,f[0].body,null,{duration:0})),function(){n.backdrop&&n.backdrop.remove(),n.disableParentScroll&&n.restoreScroll(),delete n.restoreScroll}}function b(e){e&&!e.hasAttribute("disabled")&&e.focus()}function v(e,n){var o=r.find("md-select-menu");if(!n.target)throw new Error(c.supplant($,[n.target]));t.extend(n,{isRemoved:!1,target:t.element(n.target),parent:t.element(n.parent),selectEl:o,contentEl:r.find("md-content"),optionNodes:o[0].getElementsByTagName("md-option")})}function y(){var e=function(e,t,n){return function(){if(!n.isRemoved){var o=E(e,t,n),i=o.container,r=o.dropDown;i.element.css(C.toCss(i.styles)),r.element.css(C.toCss(r.styles))}}}(i,r,a),n=t.element(l);return n.on("resize",e),n.on("orientationchange",e),function(){n.off("resize",e),n.off("orientationchange",e)}}function M(){a.loadingAsync&&!a.isRemoved&&(i.$$loadingAsyncDone=!1,m.when(a.loadingAsync).then(function(){i.$$loadingAsyncDone=!0,delete a.loadingAsync}).then(function(){u(s)}))}function _(){function t(t){t.preventDefault(),t.stopPropagation(),a.restoreFocus=!1,c.nextTick(e.hide,!0)}function i(t){var n=o.KEY_CODE;switch(t.preventDefault(),t.stopPropagation(),t.keyCode){case n.UP_ARROW:return l();case n.DOWN_ARROW:return s();case n.SPACE:case n.ENTER:var i=c.getClosest(t.target,"md-option");i&&(u.triggerHandler({type:"click",target:i}),t.preventDefault()),m(t);break;case n.TAB:case n.ESCAPE:t.stopPropagation(),t.preventDefault(),a.restoreFocus=!0,c.nextTick(e.hide,!0);break;default:if(t.keyCode>=31&&t.keyCode<=90){var r=u.controller("mdSelectMenu").optNodeForKeyboardSearch(t);a.focusedNode=r||a.focusedNode,r&&r.focus()}}}function d(e){var t,o=c.nodesToArray(a.optionNodes),i=o.indexOf(a.focusedNode);do-1===i?i=0:"next"===e&&i<o.length-1?i++:"prev"===e&&i>0&&i--,t=o[i],t.hasAttribute("disabled")&&(t=n);while(!t&&i<o.length-1&&i>0);t&&t.focus(),a.focusedNode=t}function s(){d("next")}function l(){d("prev")}function m(t){function n(){var e=!1;if(t&&t.currentTarget.children.length>0){var n=t.currentTarget.children[0],o=n.scrollHeight>n.clientHeight;if(o&&n.children.length>0){var i=t.pageX-t.currentTarget.getBoundingClientRect().left;i>n.querySelector("md-option").offsetWidth&&(e=!0)}}return e}if(!(t&&"click"==t.type&&t.currentTarget!=u[0]||n())){var o=c.getClosest(t.target,"md-option");o&&o.hasAttribute&&!o.hasAttribute("disabled")&&(t.preventDefault(),t.stopPropagation(),h.isMultiple||(a.restoreFocus=!0,c.nextTick(function(){e.hide(h.ngModel.$viewValue)},!0)))}}if(!a.isRemoved){var u=a.selectEl,h=u.controller("mdSelectMenu")||{};return r.addClass("_md-clickable"),a.backdrop&&a.backdrop.on("click",t),u.on("keydown",i),u.on("click",m),function(){a.backdrop&&a.backdrop.off("click",t),u.off("keydown",i),u.off("click",m),r.removeClass("_md-clickable"),a.isRemoved=!0}}}return M(),v(i,a),a.hideBackdrop=g(i,r,a),d(i,r,a).then(function(e){return r.attr("aria-hidden","false"),a.alreadyOpen=!0,a.cleanupInteraction=_(),a.cleanupResizing=y(),e},a.hideBackdrop)}function v(e){var t=e.selectCtrl;if(t){var n=e.selectEl.controller("mdSelectMenu");t.setLabelText(n?n.selectedLabels():""),t.triggerClose()}}function E(e,n,o){var m,u=n[0],h=o.target[0].children[0],p=f[0].body,g=o.selectEl[0],b=o.contentEl[0],v=p.getBoundingClientRect(),E=h.getBoundingClientRect(),$=!1,C={left:v.left+s,top:s,bottom:v.height-s,right:v.width-s-(c.floatingScrollbars()?16:0)},y={top:E.top-C.top,left:E.left-C.left,right:C.right-(E.left+E.width),bottom:C.bottom-(E.top+E.height)},M=v.width-2*s,_=g.querySelector("md-option[selected]"),A=g.getElementsByTagName("md-option"),T=g.getElementsByTagName("md-optgroup"),w=d(n,b),k=i(o.loadingAsync);m=k?b.firstElementChild||b:_?_:T.length?T[0]:A.length?A[0]:b.firstElementChild||b,b.offsetWidth>M?b.style["max-width"]=M+"px":b.style.maxWidth=null,$&&(b.style["min-width"]=E.width+"px"),w&&g.classList.add("_md-overflow");var x=m;"MD-OPTGROUP"===(x.tagName||"").toUpperCase()&&(x=A[0]||b.firstElementChild||b,m=x),o.focusedNode=x,u.style.display="block";var N=g.getBoundingClientRect(),S=a(m);if(m){var D=l.getComputedStyle(m);S.paddingLeft=parseInt(D.paddingLeft,10)||0,S.paddingRight=parseInt(D.paddingRight,10)||0}if(w){var H=b.offsetHeight/2;b.scrollTop=S.top+S.height/2-H,y.top<H?b.scrollTop=Math.min(S.top,b.scrollTop+H-y.top):y.bottom<H&&(b.scrollTop=Math.max(S.top+S.height-N.height,b.scrollTop-H+y.bottom))}var I,O,R,L;$?(I=E.left,O=E.top+E.height,R="50% 0",O+N.height>C.bottom&&(O=E.top-N.height,R="50% 100%")):(I=E.left+S.left-S.paddingLeft+2,O=Math.floor(E.top+E.height/2-S.height/2-S.top+b.scrollTop)+2,R=S.left+E.width/2+"px "+(S.top+S.height/2-b.scrollTop)+"px 0px",L=Math.min(E.width+S.paddingLeft+S.paddingRight,M));var P=u.getBoundingClientRect(),F=Math.round(100*Math.min(E.width/N.width,1))/100,B=Math.round(100*Math.min(E.height/N.height,1))/100;return{container:{element:t.element(u),styles:{left:Math.floor(r(C.left,I,C.right-P.width)),top:Math.floor(r(C.top,O,C.bottom-P.height)),"min-width":L}},dropDown:{element:t.element(g),styles:{transformOrigin:R,transform:o.alreadyOpen?"":c.supplant("scale({0},{1})",[F,B])}}}}var $="$mdSelect.show() expected a target element in options.target but got '{0}'!",C=c.dom.animator;return{parent:"body",themable:!0,onShow:b,onRemove:g,hasBackdrop:!0,disableParentScroll:!0}}function i(e){return e&&t.isFunction(e.then)}function r(e,t,n){return Math.max(e,Math.min(t,n))}function a(e){return e?{left:e.offsetLeft,top:e.offsetTop,width:e.offsetWidth,height:e.offsetHeight}:{left:0,top:0,width:0,height:0}}function d(e,t){var n=!1;try{var o=e[0].style.display;e[0].style.display="block",n=t.scrollHeight>t.offsetHeight,e[0].style.display=o}finally{}return n}return o.$inject=["$mdSelect","$mdConstant","$mdUtil","$window","$q","$$rAF","$animateCss","$animate","$document"],e("$mdSelect").setDefaults({methods:["target"],options:o})}var s=8,c=0,l=t.element('<div class="_md-container"><div class="_md-icon"></div></div>');t.module("material.components.select",["material.core","material.components.backdrop"]).directive("mdSelect",e).directive("mdSelectMenu",o).directive("mdOption",i).directive("mdOptgroup",r).directive("mdSelectHeader",a).provider("$mdSelect",d),e.$inject=["$mdSelect","$mdUtil","$mdTheming","$mdAria","$compile","$parse"],o.$inject=["$parse","$mdUtil","$mdTheming"],i.$inject=["$mdButtonInkRipple","$mdUtil"],d.$inject=["$$interimElementProvider"]}(),function(){function n(t,n){return["$mdUtil",function(o){return{restrict:"A",multiElement:!0,link:function(i,r,a){var d=i.$on("$md-resize-enable",function(){d();var s=e.getComputedStyle(r[0]);i.$watch(a[t],function(e){if(!!e===n){o.nextTick(function(){i.$broadcast("$md-resize")});var t={cachedTransitionStyles:s};o.dom.animator.waitTransitionEnd(r,t).then(function(){i.$broadcast("$md-resize")})}})})}}}]}t.module("material.components.showHide",["material.core"]).directive("ngShow",n("ngShow",!0)).directive("ngHide",n("ngHide",!1))}(),function(){function e(e,o,i,r){function a(e,n){var r=function(){return!1},a=function(){return i.when(o.supplant(c,[n||""]))};return t.extend({isLockedOpen:r,isOpen:r,toggle:a,open:a,close:a,then:function(e){return s(n).then(e||t.noop)}},e)}function d(t,i){var a=e.get(t);return a||i?a:(r.error(o.supplant(c,[t||""])),n)}function s(t){return e.when(t)["catch"](r.error)}var c="SideNav '{0}' is not available! Did you use md-component-id='{0}'?",l={find:d,waitFor:s};return function(e,n){if(t.isUndefined(e))return l;var o=n===!0,i=l.find(e,o);return!i&&o?l.waitFor(e):!i&&t.isUndefined(n)?a(l,e):i}}function o(){return{restrict:"A",require:"^mdSidenav",link:function(e,t,n,o){}}}function i(e,o,i,r,a,d,s,c,l,m){function u(d,u,h,p){function f(e,t){d.isLockedOpen=e,e===t?u.toggleClass("_md-locked-open",!!e):a[e?"addClass":"removeClass"](u,"_md-locked-open"),M&&M.toggleClass("_md-locked-open",!!e)}function g(e){var t=o.findFocusTarget(u)||o.findFocusTarget(u,"[md-sidenav-focus]")||u,n=u.parent();n[e?"on":"off"]("keydown",$),M&&M[e?"on":"off"]("click",C);var i=b(n,e);return e&&(A=m[0].activeElement),v(e),T=l.all([e&&M?a.enter(M,n):M?a.leave(M):l.when(!0),a[e?"removeClass":"addClass"](u,"_md-closed")]).then(function(){d.isOpen&&t&&t.focus(),i&&i()})}function b(e,t){var n=u[0],o=e[0].scrollTop;if(t&&o){_={top:n.style.top,bottom:n.style.bottom,height:n.style.height};var i={top:o+"px",bottom:"initial",height:e[0].clientHeight+"px"};u.css(i),M.css(i)}return!t&&_?function(){n.style.top=_.top,n.style.bottom=_.bottom,n.style.height=_.height,M[0].style.top=null,M[0].style.bottom=null,M[0].style.height=null,_=null}:void 0}function v(e){var o=u.parent();e&&!y?(y=o.css("overflow"),o.css("overflow","hidden")):t.isDefined(y)&&(o.css("overflow",y),y=n)}function E(e){return d.isOpen==e?l.when(!0):l(function(t){d.isOpen=e,o.nextTick(function(){T.then(function(e){d.isOpen||(A&&A.focus(),A=null),t(e)})})})}function $(e){var t=e.keyCode===i.KEY_CODE.ESCAPE;return t?C(e):l.when(!0)}function C(e){return e.preventDefault(),p.close()}var y,M,_,A=null,T=l.when(!0),w=s(h.mdIsLockedOpen),k=function(){return w(d.$parent,{$media:function(t){return c.warn("$media is deprecated for is-locked-open. Use $mdMedia instead."),e(t)},$mdMedia:e})};t.isDefined(h.mdDisableBackdrop)||(M=o.createBackdrop(d,"_md-sidenav-backdrop md-opaque ng-enter")),u.addClass("_md"),r(u),M&&r.inherit(M,u),u.on("$destroy",function(){M&&M.remove(),p.destroy()}),d.$on("$destroy",function(){M&&M.remove()}),d.$watch(k,f),d.$watch("isOpen",g),p.$toggleOpen=E}return{restrict:"E",scope:{isOpen:"=?mdIsOpen"},controller:"$mdSidenavController",compile:function(e){return e.addClass("_md-closed"),e.attr("tabIndex","-1"),u}}}function r(e,t,n,o,i){var r=this;r.isOpen=function(){return!!e.isOpen},r.isLockedOpen=function(){return!!e.isLockedOpen},r.open=function(){return r.$toggleOpen(!0)},r.close=function(){return r.$toggleOpen(!1)},r.toggle=function(){return r.$toggleOpen(!e.isOpen)},r.$toggleOpen=function(t){return i.when(e.isOpen=t)},r.destroy=o.register(r,n.mdComponentId)}t.module("material.components.sidenav",["material.core","material.components.backdrop"]).factory("$mdSidenav",e).directive("mdSidenav",i).directive("mdSidenavFocus",o).controller("$mdSidenavController",r),e.$inject=["$mdComponentRegistry","$mdUtil","$q","$log"],i.$inject=["$mdMedia","$mdUtil","$mdConstant","$mdTheming","$animate","$compile","$parse","$log","$q","$document"],r.$inject=["$scope","$element","$attrs","$mdComponentRegistry","$q"]}(),function(){function e(){return{controller:function(){},compile:function(e){var o=e.find("md-slider");if(o){var i=o.attr("md-vertical");return i!==n&&e.attr("md-vertical",""),o.attr("flex")||o.attr("flex",""),function(e,n,o,i){function r(e){n.children().attr("disabled",e),n.find("input").attr("disabled",e)}n.addClass("_md");var a=t.noop;o.disabled?r(!0):o.ngDisabled&&(a=e.$watch(o.ngDisabled,function(e){r(e)})),e.$on("$destroy",function(){a()});var d;i.fitInputWidthToTextLength=function(e){var t=n[0].querySelector("md-input-container");if(t){var o=getComputedStyle(t),i=parseInt(o.minWidth),r=2*parseInt(o.padding);d=d||parseInt(o.maxWidth);var a=Math.max(d,i+r+i/2*e);t.style.maxWidth=a+"px"}}}}}}}function o(e,n,o,i,r,a,d,s,c,l){function m(e,n){var i=t.element(e[0].getElementsByClassName("_md-slider-wrapper")),r=n.tabindex||0;return i.attr("tabindex",r),(n.disabled||n.ngDisabled)&&i.attr("tabindex",-1),i.attr("role","slider"),o.expect(e,"aria-label"),u}function u(o,m,u,h){function p(){y(),x()}function f(e){se=parseFloat(e),m.attr("aria-valuemin",e),p()}function g(e){ce=parseFloat(e),m.attr("aria-valuemax",e),p()}function b(e){le=parseFloat(e)}function v(e){me=N(parseInt(e),0,6)}function E(){m.attr("aria-disabled",!!Y())}function $(){if(ie&&!Y()&&!t.isUndefined(le)){if(0>=le){var e="Slider step value must be greater than zero when in discrete mode";throw c.error(e),new Error(e)}var o=Math.floor((ce-se)/le);ue||(ue=t.element("<canvas>").css("position","absolute"),J.append(ue),he=ue[0].getContext("2d"));var i=M();!i||i.height||i.width||(y(),i=pe),ue[0].width=i.width,ue[0].height=i.height;for(var r,a=0;o>=a;a++){var d=n.getComputedStyle(J[0]);he.fillStyle=d.color||"black",r=Math.floor((oe?i.height:i.width)*(a/o)),he.fillRect(oe?0:r-1,oe?r-1:0,oe?i.width:2,oe?2:i.height)}}}function C(){if(ue&&he){var e=M();he.clearRect(0,0,e.width,e.height)}}function y(){pe=Q[0].getBoundingClientRect()}function M(){return te(),pe}function _(e){if(!Y()){var t;(oe?e.keyCode===r.KEY_CODE.DOWN_ARROW:e.keyCode===r.KEY_CODE.LEFT_ARROW)?t=-le:(oe?e.keyCode===r.KEY_CODE.UP_ARROW:e.keyCode===r.KEY_CODE.RIGHT_ARROW)&&(t=le),t=re?-t:t,t&&((e.metaKey||e.ctrlKey||e.altKey)&&(t*=4),e.preventDefault(),e.stopPropagation(),o.$evalAsync(function(){k(W.$viewValue+t)}))}}function A(){$(),o.mouseActive=!0,ee.removeClass("md-focused"),l(function(){o.mouseActive=!1},100)}function T(){o.mouseActive===!1&&ee.addClass("md-focused")}function w(){ee.removeClass("md-focused"),m.removeClass("_md-active"),C()}function k(e){W.$setViewValue(N(S(e)))}function x(){isNaN(W.$viewValue)&&(W.$viewValue=W.$modelValue),W.$viewValue=N(W.$viewValue);var e=q(W.$viewValue);o.modelValue=W.$viewValue,m.attr("aria-valuenow",W.$viewValue),D(e),G.text(W.$viewValue)}function N(e,n,o){return t.isNumber(e)?(n=t.isNumber(n)?n:se,o=t.isNumber(o)?o:ce,Math.max(n,Math.min(o,e))):void 0}function S(e){if(t.isNumber(e)){var n=Math.round((e-se)/le)*le+se;return n=Math.round(n*Math.pow(10,me))/Math.pow(10,me),V&&V.fitInputWidthToTextLength&&i.debounce(function(){V.fitInputWidthToTextLength(n.toString().length)},100)(),n}}function D(e){e=U(e);var t=100*e+"%",n=re?100*(1-e)+"%":t;X.css(oe?"bottom":"left",t),Z.css(oe?"height":"width",n),m.toggleClass(re?"_md-max":"_md-min",0===e),m.toggleClass(re?"_md-min":"_md-max",1===e)}function H(e){if(!Y()){m.addClass("_md-active"),m[0].focus(),y();var t=z(j(oe?e.pointer.y:e.pointer.x)),n=N(S(t));o.$apply(function(){k(n),D(q(n))})}}function I(e){if(!Y()){m.removeClass("_md-dragging");var t=z(j(oe?e.pointer.y:e.pointer.x)),n=N(S(t));o.$apply(function(){k(n),x()})}}function O(e){Y()||(fe=!0,e.stopPropagation(),m.addClass("_md-dragging"),P(e))}function R(e){fe&&(e.stopPropagation(),P(e))}function L(e){fe&&(e.stopPropagation(),fe=!1)}function P(e){ie?B(oe?e.pointer.y:e.pointer.x):F(oe?e.pointer.y:e.pointer.x)}function F(e){o.$evalAsync(function(){k(z(j(e)))})}function B(e){var t=z(j(e)),n=N(S(t));D(j(e)),G.text(n)}function U(e){return Math.max(0,Math.min(e||0,1))}function j(e){var t=oe?pe.top:pe.left,n=oe?pe.height:pe.width,o=(e-t)/n;return Math.max(0,Math.min(1,oe?1-o:o))}function z(e){var t=re?1-e:e;return se+t*(ce-se)}function q(e){var t=(e-se)/(ce-se);return re?1-t:t}a(m);var W=h[0]||{$setViewValue:function(e){this.$viewValue=e,this.$viewChangeListeners.forEach(function(e){e()})},$parsers:[],$formatters:[],$viewChangeListeners:[]},V=h[1],Y=(t.element(i.getClosest(m,"_md-slider-container",!0)),u.ngDisabled?t.bind(null,s(u.ngDisabled),o.$parent):function(){return m[0].hasAttribute("disabled"); +}),K=t.element(m[0].querySelector("._md-thumb")),G=t.element(m[0].querySelector("._md-thumb-text")),X=K.parent(),Q=t.element(m[0].querySelector("._md-track-container")),Z=t.element(m[0].querySelector("._md-track-fill")),J=t.element(m[0].querySelector("._md-track-ticks")),ee=t.element(m[0].getElementsByClassName("_md-slider-wrapper")),te=(t.element(m[0].getElementsByClassName("_md-slider-content")),i.throttle(y,5e3)),ne=3,oe=t.isDefined(u.mdVertical),ie=t.isDefined(u.mdDiscrete),re=t.isDefined(u.mdInvert);t.isDefined(u.min)?u.$observe("min",f):f(0),t.isDefined(u.max)?u.$observe("max",g):g(100),t.isDefined(u.step)?u.$observe("step",b):b(1),t.isDefined(u.round)?u.$observe("round",v):v(ne);var ae=t.noop;u.ngDisabled&&(ae=o.$parent.$watch(u.ngDisabled,E)),d.register(ee,"drag",{horizontal:!oe}),o.mouseActive=!1,ee.on("keydown",_).on("mousedown",A).on("focus",T).on("blur",w).on("$md.pressdown",H).on("$md.pressup",I).on("$md.dragstart",O).on("$md.drag",R).on("$md.dragend",L),setTimeout(p,0);var de=e.throttle(p);t.element(n).on("resize",de),o.$on("$destroy",function(){t.element(n).off("resize",de)}),W.$render=x,W.$viewChangeListeners.push(x),W.$formatters.push(N),W.$formatters.push(S);var se,ce,le,me,ue,he,pe={};y();var fe=!1}return{scope:{},require:["?ngModel","?^mdSliderContainer"],template:'<div class="_md-slider-wrapper"><div class="_md-slider-content"><div class="_md-track-container"><div class="_md-track"></div><div class="_md-track _md-track-fill"></div><div class="_md-track-ticks"></div></div><div class="_md-thumb-container"><div class="_md-thumb"></div><div class="_md-focus-thumb"></div><div class="_md-focus-ring"></div><div class="_md-sign"><span class="_md-thumb-text"></span></div><div class="_md-disabled-thumb"></div></div></div></div>',compile:m}}t.module("material.components.slider",["material.core"]).directive("mdSlider",o).directive("mdSliderContainer",e),o.$inject=["$$rAF","$window","$mdAria","$mdUtil","$mdConstant","$mdTheming","$mdGesture","$parse","$log","$timeout"]}(),function(){function e(e,o,i,r,a){function d(e){function t(e,t){t.addClass("_md-sticky-clone");var n={element:e,clone:t};return f.items.push(n),r.nextTick(function(){h.prepend(n.clone)}),p(),function(){f.items.forEach(function(t,n){t.element[0]===e[0]&&(f.items.splice(n,1),t.clone.remove())}),p()}}function a(){f.items.forEach(d),f.items=f.items.sort(function(e,t){return e.top<t.top?-1:1});for(var e,t=h.prop("scrollTop"),n=f.items.length-1;n>=0;n--)if(t>f.items[n].top){e=f.items[n];break}l(e)}function d(e){var t=e.element[0];for(e.top=0,e.left=0,e.right=0;t&&t!==h[0];)e.top+=t.offsetTop,e.left+=t.offsetLeft,t.offsetParent&&(e.right+=t.offsetParent.offsetWidth-t.offsetWidth-t.offsetLeft),t=t.offsetParent;e.height=e.element.prop("offsetHeight");var o=r.floatingScrollbars()?"0":n;r.bidi(e.clone,"margin-left",e.left,o),r.bidi(e.clone,"margin-right",o,e.right)}function s(){var e=h.prop("scrollTop"),t=e>(s.prevScrollTop||0);if(s.prevScrollTop=e,0===e)return void l(null);if(t){if(f.next&&f.next.top<=e)return void l(f.next);if(f.current&&f.next&&f.next.top-e<=f.next.height)return void u(f.current,e+(f.next.top-f.next.height-e))}if(!t){if(f.current&&f.prev&&e<f.current.top)return void l(f.prev);if(f.next&&f.current&&e>=f.next.top-f.current.height)return void u(f.current,e+(f.next.top-e-f.current.height))}f.current&&u(f.current,e)}function l(e){if(f.current!==e){f.current&&(u(f.current,null),m(f.current,null)),e&&m(e,"active"),f.current=e;var t=f.items.indexOf(e);f.next=f.items[t+1],f.prev=f.items[t-1],m(f.next,"next"),m(f.prev,"prev")}}function m(e,t){e&&e.state!==t&&(e.state&&(e.clone.attr("sticky-prev-state",e.state),e.element.attr("sticky-prev-state",e.state)),e.clone.attr("sticky-state",t),e.element.attr("sticky-state",t),e.state=t)}function u(e,t){e&&(null===t||t===n?e.translateY&&(e.translateY=null,e.clone.css(o.CSS.TRANSFORM,"")):(e.translateY=t,r.bidi(e.clone,o.CSS.TRANSFORM,"translate3d("+e.left+"px,"+t+"px,0)","translateY("+t+"px)")))}var h=e.$element,p=i.throttle(a);c(h),h.on("$scrollstart",p),h.on("$scroll",s);var f;return f={prev:null,current:null,next:null,items:[],add:t,refreshElements:a}}function s(n){var o,i=t.element("<div>");e[0].body.appendChild(i[0]);for(var r=["sticky","-webkit-sticky"],a=0;a<r.length;++a)if(i.css({position:r[a],top:0,"z-index":2}),i.css("position")==r[a]){o=r[a];break}return i.remove(),o}function c(e){function t(){+r.now()-o>a?(n=!1,e.triggerHandler("$scrollend")):(e.triggerHandler("$scroll"),i.throttle(t))}var n,o,a=200;e.on("scroll touchmove",function(){n||(n=!0,i.throttle(t),e.triggerHandler("$scrollstart")),e.triggerHandler("$scroll"),o=+r.now()})}var l=s();return function(e,t,n){var o=t.controller("mdContent");if(o)if(l)t.css({position:l,top:0,"z-index":2});else{var i=o.$element.data("$$sticky");i||(i=d(o),o.$element.data("$$sticky",i));var r=n||a(t.clone())(e),s=i.add(t,r);e.$on("$destroy",s)}}}t.module("material.components.sticky",["material.core","material.components.content"]).factory("$mdSticky",e),e.$inject=["$document","$mdConstant","$$rAF","$mdUtil","$compile"]}(),function(){function e(e,n,o,i){return{restrict:"E",replace:!0,transclude:!0,template:'<div class="md-subheader _md"> <div class="_md-subheader-inner"> <div class="_md-subheader-content"></div> </div></div>',link:function(n,i,r,a,d){function s(e){return t.element(e[0].querySelector("._md-subheader-content"))}o(i),i.addClass("_md");var c=i[0].outerHTML;d(n,function(e){s(i).append(e)}),i.hasClass("md-no-sticky")||d(n,function(o){var r=t.element('<div class="_md-subheader-wrapper">'+c+"</div>");s(r).append(o),e(n,i,r)})}}}t.module("material.components.subheader",["material.core","material.components.sticky"]).directive("mdSubheader",e),e.$inject=["$mdSticky","$compile","$mdTheming","$mdUtil"]}(),function(){function e(e){function t(e){function t(t,i,r){var a=e(r[n]);i.on(o,function(e){t.$applyAsync(function(){a(t,{$event:e})})})}return{restrict:"A",link:t}}var n="md"+e,o="$md."+e.toLowerCase();return t.$inject=["$parse"],t}t.module("material.components.swipe",["material.core"]).directive("mdSwipeLeft",e("SwipeLeft")).directive("mdSwipeRight",e("SwipeRight")).directive("mdSwipeUp",e("SwipeUp")).directive("mdSwipeDown",e("SwipeDown"))}(),function(){function e(e,n,o,i,r,a){function d(e,d){var c=s.compile(e,d);return e.addClass("_md-dragging"),function(e,d,s,l){function m(t){f&&f(e)||(t.stopPropagation(),d.addClass("_md-dragging"),v={width:g.prop("offsetWidth")})}function u(e){if(v){e.stopPropagation(),e.srcEvent&&e.srcEvent.preventDefault();var t=e.pointer.distanceX/v.width,n=l.$viewValue?1+t:t;n=Math.max(0,Math.min(1,n)),g.css(o.CSS.TRANSFORM,"translate3d("+100*n+"%,0,0)"),v.translate=n}}function h(e){if(v){e.stopPropagation(),d.removeClass("_md-dragging"),g.css(o.CSS.TRANSFORM,"");var t=l.$viewValue?v.translate>.5:v.translate<.5;t&&p(!l.$viewValue),v=null}}function p(t){e.$apply(function(){l.$setViewValue(t),l.$render()})}l=l||n.fakeNgModel();var f=null;null!=s.disabled?f=function(){return!0}:s.ngDisabled&&(f=i(s.ngDisabled));var g=t.element(d[0].querySelector("._md-thumb-container")),b=t.element(d[0].querySelector("._md-container"));r(function(){d.removeClass("_md-dragging")}),c(e,d,s,l),f&&e.$watch(f,function(e){d.attr("tabindex",e?-1:0)}),a.register(b,"drag"),b.on("$md.dragstart",m).on("$md.drag",u).on("$md.dragend",h);var v}}var s=e[0];return{restrict:"E",priority:210,transclude:!0,template:'<div class="_md-container"><div class="_md-bar"></div><div class="_md-thumb-container"><div class="_md-thumb" md-ink-ripple md-ink-ripple-checkbox></div></div></div><div ng-transclude class="_md-label"></div>',require:"?ngModel",compile:d}}t.module("material.components.switch",["material.core","material.components.checkbox"]).directive("mdSwitch",e),e.$inject=["mdCheckboxDirective","$mdUtil","$mdConstant","$parse","$$rAF","$mdGesture"]}(),function(){t.module("material.components.tabs",["material.core","material.components.icon"])}(),function(){function e(e){return{restrict:"E",link:function(t,n){n.addClass("_md"),t.$on("$destroy",function(){e.destroy()})}}}function n(e){function n(e){i=e}function o(e,n,o,r){function a(t,a,d){i=d.textContent||d.content;var l=!r("gt-sm");return a=o.extractElementByName(a,"md-toast",!0),d.element=a,d.onSwipe=function(e,t){var i=e.type.replace("$md.",""),r=i.replace("swipe","");"down"===r&&-1!=d.position.indexOf("top")&&!l||"up"===r&&(-1!=d.position.indexOf("bottom")||l)||("left"!==r&&"right"!==r||!l)&&(a.addClass("_md-"+i),o.nextTick(n.cancel))},d.openClass=s(d.position),d.parent.addClass(d.openClass),o.hasComputedStyle(d.parent,"position","static")&&d.parent.css("position","relative"),a.on(c,d.onSwipe),a.addClass(l?"_md-bottom":d.position.split(" ").map(function(e){return"_md-"+e}).join(" ")),d.parent&&d.parent.addClass("_md-toast-animating"),e.enter(a,d.parent).then(function(){d.parent&&d.parent.removeClass("_md-toast-animating")})}function d(t,n,i){return n.off(c,i.onSwipe),i.parent&&i.parent.addClass("_md-toast-animating"),i.openClass&&i.parent.removeClass(i.openClass),(1==i.$destroy?n.remove():e.leave(n)).then(function(){i.parent&&i.parent.removeClass("_md-toast-animating"),o.hasComputedStyle(i.parent,"position","static")&&i.parent.css("position","")})}function s(e){return r("gt-xs")?"_md-toast-open-"+(e.indexOf("top")>-1?"top":"bottom"):"_md-toast-open-bottom"}var c="$md.swipeleft $md.swiperight $md.swipeup $md.swipedown";return{onShow:a,onRemove:d,position:"bottom left",themable:!0,hideDelay:3e3,autoWrap:!0,transformTemplate:function(e,n){var o=n.autoWrap&&e&&!/md-toast-content/g.test(e);if(o){var i=document.createElement("md-template");i.innerHTML=e;for(var r=0;r<i.children.length;r++)if("MD-TOAST"===i.children[r].nodeName){var a=t.element('<div class="md-toast-content">');a.append(t.element(i.children[r].childNodes)),i.children[r].appendChild(a[0])}return i.outerHTML}return e||""}}}var i,r="ok",a=e("$mdToast").setDefaults({methods:["position","hideDelay","capsule","parent","position"],options:o}).addPreset("simple",{argOption:"textContent",methods:["textContent","content","action","highlightAction","highlightClass","theme","parent"],options:["$mdToast","$mdTheming",function(e,t){return{template:'<md-toast md-theme="{{ toast.theme }}" ng-class="{\'md-capsule\': toast.capsule}"> <div class="md-toast-content"> <span flex class="md-toast-text" role="alert" aria-relevant="all" aria-atomic="true"> {{ toast.content }} </span> <md-button class="md-action" ng-if="toast.action" ng-click="toast.resolve()" ng-class="highlightClasses"> {{ toast.action }} </md-button> </div></md-toast>',controller:["$scope",function(t){var n=this;n.highlightAction&&(t.highlightClasses=["md-highlight",n.highlightClass]),t.$watch(function(){return i},function(){n.content=i}),this.resolve=function(){e.hide(r)}}],theme:t.defaultTheme(),controllerAs:"toast",bindToController:!0}}]}).addMethod("updateTextContent",n).addMethod("updateContent",n);return o.$inject=["$animate","$mdToast","$mdUtil","$mdMedia"],a}t.module("material.components.toast",["material.core","material.components.button"]).directive("mdToast",e).provider("$mdToast",n),e.$inject=["$mdToast"],n.$inject=["$$interimElementProvider"]}(),function(){function e(e,n,o,i,r,a,d,s,c,l){function m(d,m,p){function f(){d.delay=d.delay||u}function g(){var e="center top";switch(d.direction){case"left":e="right center";break;case"right":e="left center";break;case"top":e="center bottom";break;case"bottom":e="center top"}k.css("transform-origin",e)}function b(e){e?M():_()}function v(){if(m[0]&&"MutationObserver"in n){var e=new MutationObserver(function(e){e.forEach(function(e){"md-visible"===e.attributeName&&(d.visibleWatcher||(d.visibleWatcher=d.$watch("visible",b))),"md-direction"===e.attributeName&&A(d.direction)})});e.observe(m[0],{attributes:!0}),p.hasOwnProperty("mdVisible")&&(d.visibleWatcher=d.$watch("visible",b))}else d.visibleWatcher=d.$watch("visible",b),d.$watch("direction",A);var t=function(){d.$destroy()};m.one("$destroy",t),w.one("$destroy",t),d.$on("$destroy",function(){y(!1),m.remove(),e&&e.disconnect()}),m.text().indexOf(l.startSymbol())>-1&&d.$watch(function(){return m.text().trim()},E)}function E(e){if((e||!w.attr("aria-label"))&&!w.text().trim()){var t=e||m.text().trim(),n=l(t)(w.scope());w.attr("aria-label",n)}}function $(){m.detach(),m.attr("role","tooltip")}function C(){function o(){y(!1)}var a=!1;if(w[0]&&"MutationObserver"in n){var s=new MutationObserver(function(e){e.some(function(e){return"disabled"===e.attributeName&&w[0].disabled})&&r.nextTick(function(){y(!1)})});s.observe(w[0],{attributes:!0})}var c=function(){l=document.activeElement===w[0]},l=!1;t.element(n).on("blur",c).on("resize",S),document.addEventListener("scroll",o,!0),d.$on("$destroy",function(){t.element(n).off("blur",c).off("resize",S),w.off("focus mouseenter touchstart",m).off("blur mouseleave touchend touchcancel",u).off("mousedown",h),u(),document.removeEventListener("scroll",o,!0),s&&s.disconnect()});var m=function(e){return"focus"===e.type&&l?void(l=!1):(w.on("blur mouseleave touchend touchcancel",u),void y(!0))},u=function(){var t=d.hasOwnProperty("autohide")?d.autohide:p.hasOwnProperty("mdAutohide");(t||a||i[0].activeElement!==w[0])&&(N&&(e.cancel(N),y.queued=!1,N=null),w.off("blur mouseleave touchend touchcancel",u),w.triggerHandler("blur"),y(!1)),a=!1},h=function(){a=!0};w.on("mousedown",h),w.on("focus mouseenter touchstart",m)}function y(t){y.queued&&y.value===!!t||!y.queued&&d.visible===!!t||(y.value=!!t,y.queued||(t?(y.queued=!0,N=e(function(){d.visible=y.value,y.queued=!1,N=null,d.visibleWatcher||b(d.visible)},d.delay)):r.nextTick(function(){d.visible=!1,d.visibleWatcher||b(!1)})))}function M(){if(m[0].textContent.trim()){if(m.css({top:0,left:0}),x.append(m),r.hasComputedStyle(m,"display","none"))return d.visible=!1,void m.detach();A(),t.forEach([m,k],function(e){s.addClass(e,"_md-show")})}}function _(){var e=[];t.forEach([m,k],function(t){t.parent()&&t.hasClass("_md-show")&&e.push(s.removeClass(t,"_md-show"))}),c.all(e).then(function(){d.visible||m.detach()})}function A(){d.visible&&(g(),T())}function T(){function e(e){var t={left:e.left,top:e.top};return t.left=Math.min(t.left,x.prop("scrollWidth")-n.width-h),t.left=Math.max(t.left,h),t.top=Math.min(t.top,x.prop("scrollHeight")-n.height-h),t.top=Math.max(t.top,h),t}function t(e){return"left"===e?{left:o.left-n.width-h,top:o.top+o.height/2-n.height/2}:"right"===e?{left:o.left+o.width+h,top:o.top+o.height/2-n.height/2}:"top"===e?{left:o.left+o.width/2-n.width/2,top:o.top-n.height-h}:{left:o.left+o.width/2-n.width/2,top:o.top+o.height+h}}var n=r.offsetRect(m,x),o=r.offsetRect(w,x),i=t(d.direction),a=m.prop("offsetParent");d.direction?i=e(i):a&&i.top>a.scrollHeight-n.height-h&&(i=e(t("top"))),m.css({left:i.left+"px",top:i.top+"px"})}a(m);var w=r.getParentWithPointerEvents(m),k=t.element(m[0].getElementsByClassName("_md-content")[0]),x=t.element(document.body),N=null,S=o.throttle(function(){A()});s.pin&&s.pin(m,w),f(),$(),C(),g(),v(),E()}var u=0,h=8;return{restrict:"E",transclude:!0,priority:210,template:'<div class="_md-content _md" ng-transclude></div>',scope:{delay:"=?mdDelay",visible:"=?mdVisible",autohide:"=?mdAutohide",direction:"@?mdDirection"},compile:function(e,t){return t.mdDirection||t.$set("mdDirection","bottom"),m}}}t.module("material.components.tooltip",["material.core"]).directive("mdTooltip",e),e.$inject=["$timeout","$window","$$rAF","$document","$mdUtil","$mdTheming","$rootElement","$animate","$q","$interpolate"]}(),function(){function e(){return{controller:o,template:n,compile:function(e,t){e.addClass("md-virtual-repeat-container").addClass(t.hasOwnProperty("mdOrientHorizontal")?"md-orient-horizontal":"md-orient-vertical")}}}function n(e){return'<div class="md-virtual-repeat-scroller"><div class="md-virtual-repeat-sizer"></div><div class="md-virtual-repeat-offsetter">'+e[0].innerHTML+"</div></div>"}function o(e,n,o,i,r,a,d,s){this.$rootScope=i,this.$scope=a,this.$element=d,this.$attrs=s,this.size=0,this.scrollSize=0,this.scrollOffset=0,this.horizontal=this.$attrs.hasOwnProperty("mdOrientHorizontal"),this.repeater=null,this.autoShrink=this.$attrs.hasOwnProperty("mdAutoShrink"),this.autoShrinkMin=parseInt(this.$attrs.mdAutoShrinkMin,10)||0,this.originalSize=null,this.offsetSize=parseInt(this.$attrs.mdOffsetSize,10)||0,this.oldElementSize=null,this.$attrs.mdTopIndex?(this.bindTopIndex=o(this.$attrs.mdTopIndex),this.topIndex=this.bindTopIndex(this.$scope),t.isDefined(this.topIndex)||(this.topIndex=0,this.bindTopIndex.assign(this.$scope,0)),this.$scope.$watch(this.bindTopIndex,t.bind(this,function(e){e!==this.topIndex&&this.scrollToIndex(e)}))):this.topIndex=0,this.scroller=d[0].getElementsByClassName("md-virtual-repeat-scroller")[0],this.sizer=this.scroller.getElementsByClassName("md-virtual-repeat-sizer")[0],this.offsetter=this.scroller.getElementsByClassName("md-virtual-repeat-offsetter")[0];var c=t.bind(this,this.updateSize);e(t.bind(this,function(){c();var e=n.debounce(c,10,null,!1),o=t.element(r);this.size||e(),o.on("resize",e),a.$on("$destroy",function(){o.off("resize",e)}),a.$emit("$md-resize-enable"),a.$on("$md-resize",c)}))}function i(e){return{controller:r,priority:1e3,require:["mdVirtualRepeat","^^mdVirtualRepeatContainer"],restrict:"A",terminal:!0,transclude:"element",compile:function(t,n){var o=n.mdVirtualRepeat,i=o.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)\s*$/),r=i[1],a=e(i[2]),d=n.mdExtraName&&e(n.mdExtraName);return function(e,t,n,o,i){o[0].link_(o[1],i,r,a,d)}}}}function r(e,n,o,i,r,a,d,s){this.$scope=e,this.$element=n,this.$attrs=o,this.$browser=i,this.$document=r,this.$rootScope=a,this.$$rAF=d,this.onDemand=s.parseAttributeBoolean(o.mdOnDemand),this.browserCheckUrlChange=i.$$checkUrlChange,this.newStartIndex=0,this.newEndIndex=0,this.newVisibleEnd=0,this.startIndex=0,this.endIndex=0,this.itemSize=e.$eval(o.mdItemSize)||null,this.isFirstRender=!0,this.isVirtualRepeatUpdating_=!1,this.itemsLength=0,this.unwatchItemSize_=t.noop,this.blocks={},this.pooledBlocks=[],e.$on("$destroy",t.bind(this,this.cleanupBlocks_))}function a(e){if(!t.isFunction(e.getItemAtIndex)||!t.isFunction(e.getLength))throw Error("When md-on-demand is enabled, the Object passed to md-virtual-repeat must implement functions getItemAtIndex() and getLength() ");this.model=e}t.module("material.components.virtualRepeat",["material.core","material.components.showHide"]).directive("mdVirtualRepeatContainer",e).directive("mdVirtualRepeat",i);var d=1533917,s=3;o.$inject=["$$rAF","$mdUtil","$parse","$rootScope","$window","$scope","$element","$attrs"],o.prototype.register=function(e){this.repeater=e,t.element(this.scroller).on("scroll wheel touchmove touchend",t.bind(this,this.handleScroll_))},o.prototype.isHorizontal=function(){return this.horizontal},o.prototype.getSize=function(){return this.size},o.prototype.setSize_=function(e){var t=this.getDimensionName_();this.size=e,this.$element[0].style[t]=e+"px"},o.prototype.unsetSize_=function(){this.$element[0].style[this.getDimensionName_()]=this.oldElementSize,this.oldElementSize=null},o.prototype.updateSize=function(){this.originalSize||(this.size=this.isHorizontal()?this.$element[0].clientWidth:this.$element[0].clientHeight,this.handleScroll_(),this.repeater&&this.repeater.containerUpdated())},o.prototype.getScrollSize=function(){return this.scrollSize},o.prototype.getDimensionName_=function(){return this.isHorizontal()?"width":"height"},o.prototype.sizeScroller_=function(e){var t=this.getDimensionName_(),n=this.isHorizontal()?"height":"width";if(this.sizer.innerHTML="",d>e)this.sizer.style[t]=e+"px";else{this.sizer.style[t]="auto",this.sizer.style[n]="auto";var o=Math.floor(e/d),i=document.createElement("div");i.style[t]="1533917px",i.style[n]="1px";for(var r=0;o>r;r++)this.sizer.appendChild(i.cloneNode(!1));i.style[t]=e-o*d+"px",this.sizer.appendChild(i)}},o.prototype.autoShrink_=function(e){var t=Math.max(e,this.autoShrinkMin*this.repeater.getItemSize());if(this.autoShrink&&t!==this.size){null===this.oldElementSize&&(this.oldElementSize=this.$element[0].style[this.getDimensionName_()]);var n=this.originalSize||this.size;if(!n||n>t)this.originalSize||(this.originalSize=this.size),this.setSize_(t);else if(null!==this.originalSize){this.unsetSize_();var o=this.originalSize;this.originalSize=null,o||this.updateSize(),this.setSize_(o||this.size)}this.repeater.containerUpdated()}},o.prototype.setScrollSize=function(e){var t=e+this.offsetSize;this.scrollSize!==t&&(this.sizeScroller_(t),this.autoShrink_(t),this.scrollSize=t)},o.prototype.getScrollOffset=function(){return this.scrollOffset},o.prototype.scrollTo=function(e){this.scroller[this.isHorizontal()?"scrollLeft":"scrollTop"]=e,this.handleScroll_()},o.prototype.scrollToIndex=function(e){var t=this.repeater.getItemSize(),n=this.repeater.itemsLength;e>n&&(e=n-1),this.scrollTo(t*e)},o.prototype.resetScroll=function(){this.scrollTo(0)},o.prototype.handleScroll_=function(){var e=t.element(document)[0],n="rtl"!=e.dir&&"rtl"!=e.body.dir;n||this.maxSize||(this.scroller.scrollLeft=this.scrollSize,this.maxSize=this.scroller.scrollLeft);var o=this.isHorizontal()?n?this.scroller.scrollLeft:this.maxSize-this.scroller.scrollLeft:this.scroller.scrollTop;if(!(o===this.scrollOffset||o>this.scrollSize-this.size)){var i=this.repeater.getItemSize();if(i){var r=Math.max(0,Math.floor(o/i)-s),a=(this.isHorizontal()?"translateX(":"translateY(")+(!this.isHorizontal()||n?r*i:-(r*i))+"px)";if(this.scrollOffset=o,this.offsetter.style.webkitTransform=a,this.offsetter.style.transform=a,this.bindTopIndex){var d=Math.floor(o/i);d!==this.topIndex&&d<this.repeater.getItemCount()&&(this.topIndex=d,this.bindTopIndex.assign(this.$scope,d),this.$rootScope.$$phase||this.$scope.$digest())}this.repeater.containerUpdated()}}},i.$inject=["$parse"],r.$inject=["$scope","$element","$attrs","$browser","$document","$rootScope","$$rAF","$mdUtil"],r.Block,r.prototype.link_=function(e,n,o,i,r){this.container=e,this.transclude=n,this.repeatName=o,this.rawRepeatListExpression=i,this.extraName=r,this.sized=!1,this.repeatListExpression=t.bind(this,this.repeatListExpression_),this.container.register(this)},r.prototype.cleanupBlocks_=function(){t.forEach(this.pooledBlocks,function(e){e.element.remove()})},r.prototype.readItemSize_=function(){if(!this.itemSize){this.items=this.repeatListExpression(this.$scope),this.parentNode=this.$element[0].parentNode;var e=this.getBlock_(0);e.element[0].parentNode||this.parentNode.appendChild(e.element[0]),this.itemSize=e.element[0][this.container.isHorizontal()?"offsetWidth":"offsetHeight"]||null,this.blocks[0]=e,this.poolBlock_(0),this.itemSize&&this.containerUpdated()}},r.prototype.repeatListExpression_=function(e){var t=this.rawRepeatListExpression(e);if(this.onDemand&&t){var n=new a(t);return n.$$includeIndexes(this.newStartIndex,this.newVisibleEnd),n}return t},r.prototype.containerUpdated=function(){return this.itemSize?(this.sized||(this.items=this.repeatListExpression(this.$scope)),this.sized||(this.unwatchItemSize_(),this.sized=!0,this.$scope.$watchCollection(this.repeatListExpression,t.bind(this,function(e,t){this.isVirtualRepeatUpdating_||this.virtualRepeatUpdate_(e,t)}))),this.updateIndexes_(),void((this.newStartIndex!==this.startIndex||this.newEndIndex!==this.endIndex||this.container.getScrollOffset()>this.container.getScrollSize())&&(this.items instanceof a&&this.items.$$includeIndexes(this.newStartIndex,this.newEndIndex),this.virtualRepeatUpdate_(this.items,this.items)))):(this.unwatchItemSize_&&this.unwatchItemSize_!==t.noop&&this.unwatchItemSize_(),this.unwatchItemSize_=this.$scope.$watchCollection(this.repeatListExpression,t.bind(this,function(e){e&&e.length&&this.$$rAF(t.bind(this,this.readItemSize_))})),void(this.$rootScope.$$phase||this.$scope.$digest()))},r.prototype.getItemSize=function(){return this.itemSize},r.prototype.getItemCount=function(){return this.itemsLength},r.prototype.virtualRepeatUpdate_=function(e,n){this.isVirtualRepeatUpdating_=!0;var o=e&&e.length||0,i=!1;if(this.items&&o<this.items.length&&0!==this.container.getScrollOffset())return this.items=e,void this.container.resetScroll();if(o!==this.itemsLength&&(i=!0,this.itemsLength=o),this.items=e,(e!==n||i)&&this.updateIndexes_(),this.parentNode=this.$element[0].parentNode,i&&this.container.setScrollSize(o*this.itemSize),this.isFirstRender){this.isFirstRender=!1;var r=this.$attrs.mdStartIndex?this.$scope.$eval(this.$attrs.mdStartIndex):this.container.topIndex;this.container.scrollToIndex(r)}Object.keys(this.blocks).forEach(function(e){var t=parseInt(e,10);(t<this.newStartIndex||t>=this.newEndIndex)&&this.poolBlock_(t)},this),this.$browser.$$checkUrlChange=t.noop;var a,d,s=[],c=[];for(a=this.newStartIndex;a<this.newEndIndex&&null==this.blocks[a];a++)d=this.getBlock_(a),this.updateBlock_(d,a),s.push(d);for(;null!=this.blocks[a];a++)this.updateBlock_(this.blocks[a],a);for(var l=a-1;a<this.newEndIndex;a++)d=this.getBlock_(a),this.updateBlock_(d,a),c.push(d);s.length&&this.parentNode.insertBefore(this.domFragmentFromBlocks_(s),this.$element[0].nextSibling),c.length&&this.parentNode.insertBefore(this.domFragmentFromBlocks_(c),this.blocks[l]&&this.blocks[l].element[0].nextSibling),this.$browser.$$checkUrlChange=this.browserCheckUrlChange,this.startIndex=this.newStartIndex,this.endIndex=this.newEndIndex,this.isVirtualRepeatUpdating_=!1},r.prototype.getBlock_=function(e){if(this.pooledBlocks.length)return this.pooledBlocks.pop();var n;return this.transclude(t.bind(this,function(t,o){n={element:t,"new":!0,scope:o},this.updateScope_(o,e),this.parentNode.appendChild(t[0])})),n},r.prototype.updateBlock_=function(e,t){this.blocks[t]=e,(e["new"]||e.scope.$index!==t||e.scope[this.repeatName]!==this.items[t])&&(e["new"]=!1,this.updateScope_(e.scope,t),this.$rootScope.$$phase||e.scope.$digest())},r.prototype.updateScope_=function(e,t){e.$index=t,e[this.repeatName]=this.items&&this.items[t],this.extraName&&(e[this.extraName(this.$scope)]=this.items[t])},r.prototype.poolBlock_=function(e){this.pooledBlocks.push(this.blocks[e]),this.parentNode.removeChild(this.blocks[e].element[0]),delete this.blocks[e]},r.prototype.domFragmentFromBlocks_=function(e){var t=this.$document[0].createDocumentFragment();return e.forEach(function(e){t.appendChild(e.element[0])}),t},r.prototype.updateIndexes_=function(){var e=this.items?this.items.length:0,t=Math.ceil(this.container.getSize()/this.itemSize);this.newStartIndex=Math.max(0,Math.min(e-t,Math.floor(this.container.getScrollOffset()/this.itemSize))),this.newVisibleEnd=this.newStartIndex+t+s,this.newEndIndex=Math.min(e,this.newVisibleEnd),this.newStartIndex=Math.max(0,this.newStartIndex-s)},a.prototype.$$includeIndexes=function(e,t){for(var n=e;t>n;n++)this.hasOwnProperty(n)||(this[n]=this.model.getItemAtIndex(n));this.length=this.model.getLength()}}(),function(){function e(e,n,o,i,r){var a=t.bind(null,o.supplant,"translate3d(0,{0}px,0)");return{template:"",restrict:"E",link:function(d,s,c){function l(){function i(e){var t=s.parent().find("md-content");!f&&t.length&&l(null,t),e=d.$eval(e),e===!1?g():g=u()}function l(e,t){t&&s.parent()[0]===t.parent()[0]&&(f&&f.off("scroll",$),f=t,g=u())}function m(e){var t=e?e.target.scrollTop:v;C(),b=Math.min(p/E,Math.max(0,b+t-v)),s.css(n.CSS.TRANSFORM,a([-b*E])),f.css(n.CSS.TRANSFORM,a([(p-b)*E])),v=t,o.nextTick(function(){var e=s.hasClass("md-whiteframe-z1");e&&!b?r.removeClass(s,"md-whiteframe-z1"):!e&&b&&r.addClass(s,"md-whiteframe-z1")})}function u(){return f?(f.on("scroll",$),f.attr("scroll-shrink","true"),o.nextTick(h,!1),function(){f.off("scroll",$),f.attr("scroll-shrink","false"),h()}):t.noop}function h(){p=s.prop("offsetHeight");var e=-p*E+"px";f.css({"margin-top":e,"margin-bottom":e}),m()}var p,f,g=t.noop,b=0,v=0,E=c.mdShrinkSpeedFactor||.5,$=e.throttle(m),C=o.debounce(h,5e3);d.$on("$mdContentLoaded",l),c.$observe("mdScrollShrink",i),c.ngShow&&d.$watch(c.ngShow,h),c.ngHide&&d.$watch(c.ngHide,h),d.$on("$destroy",g)}s.addClass("_md"),i(s),t.isDefined(c.mdScrollShrink)&&l()}}}t.module("material.components.toolbar",["material.core","material.components.content"]).directive("mdToolbar",e),e.$inject=["$$rAF","$mdConstant","$mdUtil","$mdTheming","$animate"]}(),function(){function e(e){function t(t,a,d){var s="";d.$observe("mdWhiteframe",function(t){t=parseInt(t,10)||r,t!=n&&(t>i||o>t)&&(e.warn("md-whiteframe attribute value is invalid. It should be a number between "+o+" and "+i,a[0]),t=r);var c=t==n?"":"md-whiteframe-"+t+"dp";d.$updateClass(c,s),s=c})}var n=-1,o=1,i=24,r=4;return{link:t}}t.module("material.components.whiteframe",["material.core"]).directive("mdWhiteframe",e),e.$inject=["$log"]}(),function(){function e(e,o,d,s,c,l,m,u,h,p){function f(){d.initOptionalProperties(e,h,{searchText:null,selectedItem:null}),c(o),E(),d.nextTick(function(){C(),b(),v(),o.on("focus",v)})}function g(){function t(){var e=0,t=o.find("md-input-container");if(t.length){var n=t.find("input");e=t.prop("offsetHeight"),e-=n.prop("offsetTop"),e-=n.prop("offsetHeight"),e+=t.prop("offsetTop")}return e}function n(){var e=he.scrollContainer.getBoundingClientRect(),t={};e.right>m.right-r&&(t.left=c.right-e.width+"px"),he.$.scrollContainer.css(t)}if(!he)return d.nextTick(g,!1,e);var s,c=he.wrap.getBoundingClientRect(),l=he.snap.getBoundingClientRect(),m=he.root.getBoundingClientRect(),u=l.bottom-m.top,p=m.bottom-l.top,f=c.left-m.left,b=c.width,v=t();h.mdFloatingLabel&&(f+=a,b-=2*a),s={left:f+"px",minWidth:b+"px",maxWidth:Math.max(c.right-m.left,m.right-c.left)-r+"px"},u>p&&m.height-c.bottom-r<i?(s.top="auto",s.bottom=p+"px",s.maxHeight=Math.min(i,c.top-m.top-r)+"px"):(s.top=u-v+"px",s.bottom="auto",s.maxHeight=Math.min(i,m.bottom+d.scrollTop()-c.bottom-r)+"px"),he.$.scrollContainer.css(s),d.nextTick(n,!1)}function b(){he.$.root.length&&(c(he.$.scrollContainer),he.$.scrollContainer.detach(),he.$.root.append(he.$.scrollContainer),m.pin&&m.pin(he.$.scrollContainer,u))}function v(){e.autofocus&&he.input.focus()}function E(){var n=parseInt(e.delay,10)||0;h.$observe("disabled",function(e){le.isDisabled=d.parseAttributeBoolean(e,!1)}),h.$observe("required",function(e){le.isRequired=d.parseAttributeBoolean(e,!1)}),h.$observe("readonly",function(e){le.isReadonly=d.parseAttributeBoolean(e,!1)}),e.$watch("searchText",n?d.debounce(O,n):O),e.$watch("selectedItem",x),t.element(l).on("resize",g),e.$on("$destroy",$)}function $(){if(le.hidden||d.enableScrolling(),t.element(l).off("resize",g),he){var e="ul scroller scrollContainer input".split(" ");t.forEach(e,function(e){he.$[e].remove()})}}function C(){he={main:o[0],scrollContainer:o[0].getElementsByClassName("md-virtual-repeat-container")[0],scroller:o[0].getElementsByClassName("md-virtual-repeat-scroller")[0],ul:o.find("ul")[0],input:o.find("input")[0],wrap:o.find("md-autocomplete-wrap")[0],root:document.body},he.li=he.ul.getElementsByTagName("li"),he.snap=y(),he.$=M(he)}function y(){for(var e=o;e.length;e=e.parent())if(t.isDefined(e.attr("md-autocomplete-snap")))return e[0];return he.wrap}function M(e){var n={};for(var o in e)e.hasOwnProperty(o)&&(n[o]=t.element(e[o]));return n}function _(n,o){!n&&o?(g(),he&&d.nextTick(function(){d.disableScrollAround(he.ul),$e=A(t.element(he.wrap))},!1,e)):n&&!o&&d.nextTick(function(){d.enableScrolling(),$e&&($e(),$e=null)},!1,e)}function A(e){function t(e){e.preventDefault()}return e.on("wheel",t),e.on("touchmove",t),function(){e.off("wheel",t),e.off("touchmove",t)}}function T(){fe=!0}function w(){be||he.input.focus(),fe=!1,le.hidden=W()}function k(){he.input.focus()}function x(t,n){t&&U(t).then(function(o){e.searchText=o,D(t,n)}),t!==n&&N()}function N(){t.isFunction(e.itemChange)&&e.itemChange(j(e.selectedItem))}function S(){t.isFunction(e.textChange)&&e.textChange()}function D(e,t){ge.forEach(function(n){n(e,t)})}function H(e){-1==ge.indexOf(e)&&ge.push(e)}function I(e){var t=ge.indexOf(e);-1!=t&&ge.splice(t,1)}function O(t,n){le.index=z(),t!==n&&U(e.selectedItem).then(function(o){t!==o&&(e.selectedItem=null,t!==n&&S(), +Q()?se():(le.matches=[],q(!1),ne()))})}function R(){be=!1,fe||(le.hidden=W())}function L(e){e&&(fe=!1,be=!1),he.input.blur()}function P(n){be=!0,t.isString(e.searchText)||(e.searchText=""),le.hidden=W(),le.hidden||se()}function F(t){switch(t.keyCode){case s.KEY_CODE.DOWN_ARROW:if(le.loading)return;t.stopPropagation(),t.preventDefault(),le.index=Math.min(le.index+1,le.matches.length-1),ie(),ne();break;case s.KEY_CODE.UP_ARROW:if(le.loading)return;t.stopPropagation(),t.preventDefault(),le.index=le.index<0?le.matches.length-1:Math.max(0,le.index-1),ie(),ne();break;case s.KEY_CODE.TAB:if(w(),le.hidden||le.loading||le.index<0||le.matches.length<1)return;J(le.index);break;case s.KEY_CODE.ENTER:if(le.hidden||le.loading||le.index<0||le.matches.length<1)return;if(K())return;t.stopPropagation(),t.preventDefault(),J(le.index);break;case s.KEY_CODE.ESCAPE:t.stopPropagation(),t.preventDefault(),e.searchText&&ee(),L(!0)}}function B(){return t.isNumber(e.minLength)?e.minLength:1}function U(t){function n(t){return t&&e.itemText?e.itemText(j(t)):null}return p.when(n(t)||t)}function j(e){if(!e)return n;var t={};return le.itemName&&(t[le.itemName]=e),t}function z(){return e.autoselect?0:-1}function q(e){le.loading!=e&&(le.loading=e),le.hidden=W()}function W(){return le.loading&&!Y()?!0:K()?!0:be?!V():!0}function V(){return Q()&&Y()||de()}function Y(){return!!le.matches.length}function K(){return!!le.scope.selectedItem}function G(){return le.loading&&!K()}function X(){return U(le.matches[le.index])}function Q(){return(e.searchText||"").length>=B()}function Z(e,t,n){Object.defineProperty(le,e,{get:function(){return n},set:function(e){var o=n;n=e,t(e,o)}})}function J(t){d.nextTick(function(){U(le.matches[t]).then(function(e){var t=he.$.input.controller("ngModel");t.$setViewValue(e),t.$render()})["finally"](function(){e.selectedItem=le.matches[t],q(!1)})},!1)}function ee(t){q(!0),le.index=0,le.matches=[],e.searchText="";var n=document.createEvent("CustomEvent");n.initCustomEvent("input",!0,!0,{value:""}),he.input.dispatchEvent(n),he.input.blur(),e.searchText="",he.input.focus()}function te(n){function o(t){t&&(t=p.when(t),Ee++,q(!0),d.nextTick(function(){t.then(i)["finally"](function(){0===--Ee&&q(!1)})},!0,e))}function i(t){pe[a]=t,(n||"")===(e.searchText||"")&&(le.matches=t,le.hidden=W(),le.loading&&q(!1),e.selectOnMatch&&ce(),ne(),g())}var r=e.$parent.$eval(ue),a=n.toLowerCase(),s=t.isArray(r),c=!!r.then;s?i(r):c&&o(r)}function ne(){X().then(function(e){le.messages=[oe(),e]})}function oe(){if(ve===le.matches.length)return"";switch(ve=le.matches.length,le.matches.length){case 0:return"There are no matches available.";case 1:return"There is 1 match available.";default:return"There are "+le.matches.length+" matches available."}}function ie(){if(he.li[0]){var e=he.li[0].offsetHeight,t=e*le.index,n=t+e,o=he.scroller.clientHeight,i=he.scroller.scrollTop;i>t?ae(t):n>i+o&&ae(n-o)}}function re(){return 0!==Ee}function ae(e){he.$.scrollContainer.controller("mdVirtualRepeatContainer").scrollTo(e)}function de(){var e=(le.scope.searchText||"").length;return le.hasNotFound&&!Y()&&(!le.loading||re())&&e>=B()&&(be||fe)&&!K()}function se(){var t=e.searchText||"",n=t.toLowerCase();!e.noCache&&pe[n]?(le.matches=pe[n],ne(),q(!1)):te(t),le.hidden=W()}function ce(){var t=e.searchText,n=le.matches,o=n[0];1===n.length&&U(o).then(function(n){var o=t==n;e.matchInsensitive&&!o&&(o=t.toLowerCase()==n.toLowerCase()),o&&J(0)})}var le=this,me=e.itemsExpr.split(/ in /i),ue=me[1],he=null,pe={},fe=!1,ge=[],be=!1,ve=0,Ee=0,$e=null;return Z("hidden",_,!0),le.scope=e,le.parent=e.$parent,le.itemName=me[0],le.matches=[],le.loading=!1,le.hidden=!0,le.index=null,le.messages=[],le.id=d.nextUid(),le.isDisabled=null,le.isRequired=null,le.isReadonly=null,le.hasNotFound=!1,le.keydown=F,le.blur=R,le.focus=P,le.clear=ee,le.select=J,le.listEnter=T,le.listLeave=w,le.mouseUp=k,le.getCurrentDisplayValue=X,le.registerSelectedItemWatcher=H,le.unregisterSelectedItemWatcher=I,le.notFoundVisible=de,le.loadingIsVisible=G,f()}t.module("material.components.autocomplete").controller("MdAutocompleteCtrl",e);var o=41,i=5.5*o,r=8,a=2;e.$inject=["$scope","$element","$mdUtil","$mdConstant","$mdTheming","$window","$animate","$rootElement","$attrs","$q"]}(),function(){function e(e){return{controller:"MdAutocompleteCtrl",controllerAs:"$mdAutocompleteCtrl",scope:{inputName:"@mdInputName",inputMinlength:"@mdInputMinlength",inputMaxlength:"@mdInputMaxlength",searchText:"=?mdSearchText",selectedItem:"=?mdSelectedItem",itemsExpr:"@mdItems",itemText:"&mdItemText",placeholder:"@placeholder",noCache:"=?mdNoCache",selectOnMatch:"=?mdSelectOnMatch",matchInsensitive:"=?mdMatchCaseInsensitive",itemChange:"&?mdSelectedItemChange",textChange:"&?mdSearchTextChange",minLength:"=?mdMinLength",delay:"=?mdDelay",autofocus:"=?mdAutofocus",floatingLabel:"@?mdFloatingLabel",autoselect:"=?mdAutoselect",menuClass:"@?mdMenuClass",inputId:"@?mdInputId"},link:function(e,t,n,o){o.hasNotFound=!!t.attr("md-has-not-found")},template:function(t,n){function o(){var e=t.find("md-item-template").detach(),n=e.length?e.html():t.html();return e.length||t.empty(),"<md-autocomplete-parent-scope md-autocomplete-replace>"+n+"</md-autocomplete-parent-scope>"}function i(){var e=t.find("md-not-found").detach(),n=e.length?e.html():"";return n?'<li ng-if="$mdAutocompleteCtrl.notFoundVisible()" md-autocomplete-parent-scope>'+n+"</li>":""}function r(){return n.mdFloatingLabel?' <md-input-container flex ng-if="floatingLabel"> <label>{{floatingLabel}}</label> <input type="search" '+(null!=c?'tabindex="'+c+'"':"")+' id="{{ inputId || \'fl-input-\' + $mdAutocompleteCtrl.id }}" name="{{inputName}}" autocomplete="off" ng-required="$mdAutocompleteCtrl.isRequired" ng-readonly="$mdAutocompleteCtrl.isReadonly" ng-minlength="inputMinlength" ng-maxlength="inputMaxlength" ng-disabled="$mdAutocompleteCtrl.isDisabled" ng-model="$mdAutocompleteCtrl.scope.searchText" ng-keydown="$mdAutocompleteCtrl.keydown($event)" ng-blur="$mdAutocompleteCtrl.blur()" '+(null!=n.mdNoAsterisk?'md-no-asterisk="'+n.mdNoAsterisk+'"':"")+' ng-focus="$mdAutocompleteCtrl.focus($event)" aria-owns="ul-{{$mdAutocompleteCtrl.id}}" '+(null!=n.mdSelectOnFocus?'md-select-on-focus=""':"")+' aria-label="{{floatingLabel}}" aria-autocomplete="list" role="combobox" aria-haspopup="true" aria-activedescendant="" aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/> <div md-autocomplete-parent-scope md-autocomplete-replace>'+s+"</div> </md-input-container>":' <input flex type="search" '+(null!=c?'tabindex="'+c+'"':"")+' id="{{ inputId || \'input-\' + $mdAutocompleteCtrl.id }}" name="{{inputName}}" ng-if="!floatingLabel" autocomplete="off" ng-required="$mdAutocompleteCtrl.isRequired" ng-disabled="$mdAutocompleteCtrl.isDisabled" ng-readonly="$mdAutocompleteCtrl.isReadonly" ng-model="$mdAutocompleteCtrl.scope.searchText" ng-keydown="$mdAutocompleteCtrl.keydown($event)" ng-blur="$mdAutocompleteCtrl.blur()" ng-focus="$mdAutocompleteCtrl.focus($event)" placeholder="{{placeholder}}" aria-owns="ul-{{$mdAutocompleteCtrl.id}}" '+(null!=n.mdSelectOnFocus?'md-select-on-focus=""':"")+' aria-label="{{placeholder}}" aria-autocomplete="list" role="combobox" aria-haspopup="true" aria-activedescendant="" aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/> <button type="button" tabindex="-1" ng-if="$mdAutocompleteCtrl.scope.searchText && !$mdAutocompleteCtrl.isDisabled" ng-click="$mdAutocompleteCtrl.clear($event)"> <md-icon md-svg-src="'+e.mdClose+'"></md-icon> <span class="_md-visually-hidden">Clear</span> </button> '}var a=i(),d=o(),s=t.html(),c=n.tabindex;return a&&t.attr("md-has-not-found",!0),t.attr("tabindex","-1")," <md-autocomplete-wrap layout=\"row\" ng-class=\"{ 'md-whiteframe-z1': !floatingLabel, 'md-menu-showing': !$mdAutocompleteCtrl.hidden }\"> "+r()+' <md-progress-linear class="'+(n.mdFloatingLabel?"md-inline":"")+'" ng-if="$mdAutocompleteCtrl.loadingIsVisible()" md-mode="indeterminate"></md-progress-linear> <md-virtual-repeat-container md-auto-shrink md-auto-shrink-min="1" ng-mouseenter="$mdAutocompleteCtrl.listEnter()" ng-mouseleave="$mdAutocompleteCtrl.listLeave()" ng-mouseup="$mdAutocompleteCtrl.mouseUp()" ng-hide="$mdAutocompleteCtrl.hidden" class="md-autocomplete-suggestions-container md-whiteframe-z1" ng-class="{ \'md-not-found\': $mdAutocompleteCtrl.notFoundVisible() }" role="presentation"> <ul class="md-autocomplete-suggestions" ng-class="::menuClass" id="ul-{{$mdAutocompleteCtrl.id}}"> <li md-virtual-repeat="item in $mdAutocompleteCtrl.matches" ng-class="{ selected: $index === $mdAutocompleteCtrl.index }" ng-click="$mdAutocompleteCtrl.select($index)" md-extra-name="$mdAutocompleteCtrl.itemName"> '+d+" </li>"+a+' </ul> </md-virtual-repeat-container> </md-autocomplete-wrap> <aria-status class="_md-visually-hidden" role="status" aria-live="assertive"> <p ng-repeat="message in $mdAutocompleteCtrl.messages track by $index" ng-if="message">{{message}}</p> </aria-status>'}}}t.module("material.components.autocomplete").directive("mdAutocomplete",e),e.$inject=["$$mdSvgRegistry"]}(),function(){function e(e,t){function n(e,n,o){return function(e,n,i){function r(n,o){s[o]=e[n],e.$watch(n,function(e){t.nextTick(function(){s[o]=e})})}function a(){var t=!1,n=!1;e.$watch(function(){n||t||(t=!0,e.$$postDigest(function(){n||s.$digest(),t=n=!1}))}),s.$watch(function(){n=!0})}var d=e.$mdAutocompleteCtrl,s=d.parent.$new(),c=d.itemName;r("$index","$index"),r("item",c),a(),o(s,function(e){n.after(e)})}}return{restrict:"AE",compile:n,terminal:!0,transclude:"element"}}t.module("material.components.autocomplete").directive("mdAutocompleteParentScope",e),e.$inject=["$compile","$mdUtil"]}(),function(){function e(e,n,o){function i(i,r){var d=null,s=null,c=o.mdHighlightFlags||"",l=e.$watch(function(e){return{term:i(e),unsafeText:r(e)}},function(e,o){null!==d&&e.unsafeText===o.unsafeText||(d=t.element("<div>").text(e.unsafeText).html()),null!==s&&e.term===o.term||(s=a(e.term,c)),n.html(d.replace(s,'<span class="highlight">$&</span>'))},!0);n.on("$destroy",l)}function r(e){return e&&e.replace(/[\\\^\$\*\+\?\.\(\)\|\{}\[\]]/g,"\\$&")}function a(e,t){var n="",o="";return t.indexOf("^")>=0&&(n="^"),t.indexOf("$")>=0&&(o="$"),new RegExp(n+r(e)+o,t.replace(/[\$\^]/g,""))}this.init=i}t.module("material.components.autocomplete").controller("MdHighlightCtrl",e),e.$inject=["$scope","$element","$attrs"]}(),function(){function e(e,t){return{terminal:!0,controller:"MdHighlightCtrl",compile:function(n,o){var i=t(o.mdHighlightText),r=e(n.html());return function(e,t,n,o){o.init(i,r)}}}}t.module("material.components.autocomplete").directive("mdHighlightText",e),e.$inject=["$interpolate","$parse"]}(),function(){function o(e,t,o,i,r){this.$scope=e,this.$element=t,this.$mdConstant=o,this.$timeout=i,this.$mdUtil=r,this.isEditting=!1,this.parentController=n,this.enableChipEdit=!1}t.module("material.components.chips").controller("MdChipCtrl",o),o.$inject=["$scope","$element","$mdConstant","$timeout","$mdUtil"],o.prototype.init=function(e){this.parentController=e,this.enableChipEdit=this.parentController.enableChipEdit,this.enableChipEdit&&(this.$element.on("keydown",this.chipKeyDown.bind(this)),this.$element.on("mousedown",this.chipMouseDown.bind(this)),this.getChipContent().addClass("_md-chip-content-edit-is-enabled"))},o.prototype.getChipContent=function(){var e=this.$element[0].getElementsByClassName("_md-chip-content");return t.element(e[0])},o.prototype.getContentElement=function(){return t.element(this.getChipContent().children()[0])},o.prototype.getChipIndex=function(){return parseInt(this.$element.attr("index"))},o.prototype.goOutOfEditMode=function(){if(this.isEditting){this.isEditting=!1,this.$element.removeClass("_md-chip-editing"),this.getChipContent()[0].contentEditable="false";var e=this.getChipIndex(),t=this.getContentElement().text();t?(this.parentController.updateChipContents(e,this.getContentElement().text()),this.$mdUtil.nextTick(function(){this.parentController.selectedChip===e&&this.parentController.focusChip(e)}.bind(this))):this.parentController.removeChipAndFocusInput(e)}},o.prototype.selectNodeContents=function(t){var n,o;document.body.createTextRange?(n=document.body.createTextRange(),n.moveToElementText(t),n.select()):e.getSelection&&(o=e.getSelection(),n=document.createRange(),n.selectNodeContents(t),o.removeAllRanges(),o.addRange(n))},o.prototype.goInEditMode=function(){this.isEditting=!0,this.$element.addClass("_md-chip-editing"),this.getChipContent()[0].contentEditable="true",this.getChipContent().on("blur",function(){this.goOutOfEditMode()}.bind(this)),this.selectNodeContents(this.getChipContent()[0])},o.prototype.chipKeyDown=function(e){this.isEditting||e.keyCode!==this.$mdConstant.KEY_CODE.ENTER&&e.keyCode!==this.$mdConstant.KEY_CODE.SPACE?this.isEditting&&e.keyCode===this.$mdConstant.KEY_CODE.ENTER&&(e.preventDefault(),this.goOutOfEditMode()):(e.preventDefault(),this.goInEditMode())},o.prototype.chipMouseDown=function(){this.getChipIndex()==this.parentController.selectedChip&&this.enableChipEdit&&!this.isEditting&&this.goInEditMode()}}(),function(){function e(e,o){function i(n,i){return n.append(o.processTemplate(r)),function(n,o,i,r){var a=r.shift(),d=r.shift();e(o),a&&(d.init(a),t.element(o[0].querySelector("._md-chip-content")).on("blur",function(){a.resetSelectedChip(),a.$scope.$applyAsync()}))}}var r=o.processTemplate(n);return{restrict:"E",require:["^?mdChips","mdChip"],compile:i,controller:"MdChipCtrl"}}t.module("material.components.chips").directive("mdChip",e);var n=' <span ng-if="!$mdChipsCtrl.readonly" class="_md-visually-hidden"> {{$mdChipsCtrl.deleteHint}} </span>';e.$inject=["$mdTheming","$mdUtil"]}(),function(){function e(e){function t(t,n,o,i){n.on("click",function(e){t.$apply(function(){i.removeChip(t.$$replacedScope.$index)})}),e(function(){n.attr({tabindex:-1,"aria-hidden":!0}),n.find("button").attr("tabindex","-1")})}return{restrict:"A",require:"^mdChips",scope:!1,link:t}}t.module("material.components.chips").directive("mdChipRemove",e),e.$inject=["$timeout"]}(),function(){function e(e){function t(t,n,o){var i=t.$parent.$mdChipsCtrl,r=i.parent.$new(!1,i.parent);r.$$replacedScope=t,r.$chip=t.$chip,r.$index=t.$index,r.$mdChipsCtrl=i;var a=i.$scope.$eval(o.mdChipTransclude);n.html(a),e(n.contents())(r)}return{restrict:"EA",terminal:!0,link:t,scope:!1}}t.module("material.components.chips").directive("mdChipTransclude",e),e.$inject=["$compile"]}(),function(){function e(e,t,n,o,i,r){this.$timeout=i,this.$mdConstant=t,this.$scope=e,this.parent=e.$parent,this.$log=n,this.$element=o,this.ngModelCtrl=null,this.userInputNgModelCtrl=null,this.userInputElement=null,this.items=[],this.selectedChip=-1,this.hasAutocomplete=!1,this.enableChipEdit=r.parseAttributeBoolean(this.mdEnableChipEdit),this.deleteHint="Press delete to remove this chip.",this.deleteButtonLabel="Remove",this.chipBuffer="",this.useTransformChip=!1,this.useOnAdd=!1,this.useOnRemove=!1,this.useOnSelect=!1}t.module("material.components.chips").controller("MdChipsCtrl",e),e.$inject=["$scope","$mdConstant","$log","$element","$timeout","$mdUtil"],e.prototype.inputKeydown=function(e){var t=this.getChipBuffer();if(!(this.hasAutocomplete&&e.isDefaultPrevented&&e.isDefaultPrevented())){if(e.keyCode===this.$mdConstant.KEY_CODE.BACKSPACE){if(t)return;return e.preventDefault(),e.stopPropagation(),void(this.items.length&&this.selectAndFocusChipSafe(this.items.length-1))}if((!this.separatorKeys||this.separatorKeys.length<1)&&(this.separatorKeys=[this.$mdConstant.KEY_CODE.ENTER]),-1!==this.separatorKeys.indexOf(e.keyCode)){if(this.hasAutocomplete&&this.requireMatch||!t)return;if(e.preventDefault(),this.hasMaxChipsReached())return;this.appendChip(t.trim()),this.resetChipBuffer()}}},e.prototype.updateChipContents=function(e,t){e>=0&&e<this.items.length&&(this.items[e]=t,this.ngModelCtrl.$setDirty())},e.prototype.isEditingChip=function(){return!!this.$element[0].getElementsByClassName("_md-chip-editing").length},e.prototype.chipKeydown=function(e){if(!this.getChipBuffer()&&!this.isEditingChip())switch(e.keyCode){case this.$mdConstant.KEY_CODE.BACKSPACE:case this.$mdConstant.KEY_CODE.DELETE:if(this.selectedChip<0)return;e.preventDefault(),this.removeAndSelectAdjacentChip(this.selectedChip);break;case this.$mdConstant.KEY_CODE.LEFT_ARROW:e.preventDefault(),this.selectedChip<0&&(this.selectedChip=this.items.length),this.items.length&&this.selectAndFocusChipSafe(this.selectedChip-1);break;case this.$mdConstant.KEY_CODE.RIGHT_ARROW:e.preventDefault(),this.selectAndFocusChipSafe(this.selectedChip+1);break;case this.$mdConstant.KEY_CODE.ESCAPE:case this.$mdConstant.KEY_CODE.TAB:if(this.selectedChip<0)return;e.preventDefault(),this.onFocus()}},e.prototype.getPlaceholder=function(){var e=this.items&&this.items.length&&(""==this.secondaryPlaceholder||this.secondaryPlaceholder);return e?this.secondaryPlaceholder:this.placeholder},e.prototype.removeAndSelectAdjacentChip=function(e){var n=this.getAdjacentChipIndex(e);this.removeChip(e),this.$timeout(t.bind(this,function(){this.selectAndFocusChipSafe(n)}))},e.prototype.resetSelectedChip=function(){this.selectedChip=-1},e.prototype.getAdjacentChipIndex=function(e){var t=this.items.length-1;return 0==t?-1:e==t?e-1:e},e.prototype.appendChip=function(e){if(this.useTransformChip&&this.transformChip){var n=this.transformChip({$chip:e});t.isDefined(n)&&(e=n)}if(t.isObject(e)){var o=this.items.some(function(n){return t.equals(e,n)});if(o)return}if(!(null==e||this.items.indexOf(e)+1)){var i=this.items.push(e);this.ngModelCtrl.$setDirty(),this.validateModel(),this.useOnAdd&&this.onAdd&&this.onAdd({$chip:e,$index:i})}},e.prototype.useTransformChipExpression=function(){this.useTransformChip=!0},e.prototype.useOnAddExpression=function(){this.useOnAdd=!0},e.prototype.useOnRemoveExpression=function(){this.useOnRemove=!0},e.prototype.useOnSelectExpression=function(){this.useOnSelect=!0},e.prototype.getChipBuffer=function(){return this.userInputElement?this.userInputNgModelCtrl?this.userInputNgModelCtrl.$viewValue:this.userInputElement[0].value:this.chipBuffer},e.prototype.resetChipBuffer=function(){this.userInputElement?this.userInputNgModelCtrl?(this.userInputNgModelCtrl.$setViewValue(""),this.userInputNgModelCtrl.$render()):this.userInputElement[0].value="":this.chipBuffer=""},e.prototype.hasMaxChipsReached=function(){return t.isString(this.maxChips)&&(this.maxChips=parseInt(this.maxChips,10)||0),this.maxChips>0&&this.items.length>=this.maxChips},e.prototype.validateModel=function(){this.ngModelCtrl.$setValidity("md-max-chips",!this.hasMaxChipsReached())},e.prototype.removeChip=function(e){var t=this.items.splice(e,1);this.ngModelCtrl.$setDirty(),this.validateModel(),t&&t.length&&this.useOnRemove&&this.onRemove&&this.onRemove({$chip:t[0],$index:e})},e.prototype.removeChipAndFocusInput=function(e){this.removeChip(e),this.onFocus()},e.prototype.selectAndFocusChipSafe=function(e){return this.items.length?e===this.items.length?this.onFocus():(e=Math.max(e,0),e=Math.min(e,this.items.length-1),this.selectChip(e),void this.focusChip(e)):(this.selectChip(-1),void this.onFocus())},e.prototype.selectChip=function(e){e>=-1&&e<=this.items.length?(this.selectedChip=e,this.useOnSelect&&this.onSelect&&this.onSelect({$chip:this.items[this.selectedChip]})):this.$log.warn("Selected Chip index out of bounds; ignoring.")},e.prototype.selectAndFocusChip=function(e){this.selectChip(e),-1!=e&&this.focusChip(e)},e.prototype.focusChip=function(e){this.$element[0].querySelector('md-chip[index="'+e+'"] ._md-chip-content').focus()},e.prototype.configureNgModel=function(e){this.ngModelCtrl=e;var t=this;e.$render=function(){t.items=t.ngModelCtrl.$viewValue}},e.prototype.onFocus=function(){var e=this.$element[0].querySelector("input");e&&e.focus(),this.resetSelectedChip()},e.prototype.onInputFocus=function(){this.inputHasFocus=!0,this.resetSelectedChip()},e.prototype.onInputBlur=function(){this.inputHasFocus=!1},e.prototype.configureUserInput=function(e){this.userInputElement=e;var n=e.controller("ngModel");n!=this.ngModelCtrl&&(this.userInputNgModelCtrl=n);var o=this.$scope,i=this,r=function(e,n){o.$evalAsync(t.bind(i,n,e))};e.attr({tabindex:0}).on("keydown",function(e){r(e,i.inputKeydown)}).on("focus",function(e){r(e,i.onInputFocus)}).on("blur",function(e){r(e,i.onInputBlur)})},e.prototype.configureAutocomplete=function(e){e&&(this.hasAutocomplete=!0,e.registerSelectedItemWatcher(t.bind(this,function(e){if(e){if(this.hasMaxChipsReached())return;this.appendChip(e),this.resetChipBuffer()}})),this.$element.find("input").on("focus",t.bind(this,this.onInputFocus)).on("blur",t.bind(this,this.onInputBlur)))},e.prototype.hasFocus=function(){return this.inputHasFocus||this.selectedChip>=0}}(),function(){function e(e,t,a,d,s,c){function l(n,o){function i(e){if(o.ngModel){var t=r[0].querySelector(e);return t&&t.outerHTML}}var r=o.$mdUserTemplate;o.$mdUserTemplate=null;var l=i("md-chips>md-chip-template"),m=t.prefixer().buildList("md-chip-remove").map(function(e){return"md-chips>*["+e+"]"}).join(","),h=i(m)||u.remove,p=l||u["default"],f=i("md-chips>md-autocomplete")||i("md-chips>input")||u.input,g=r.find("md-chip");return r[0].querySelector("md-chip-template>*[md-chip-remove]")&&d.warn("invalid placement of md-chip-remove within md-chip-template."),function(n,i,r,d){t.initOptionalProperties(n,o),e(i);var m=d[0];if(l&&(m.enableChipEdit=!1),m.chipContentsTemplate=p,m.chipRemoveTemplate=h,m.chipInputTemplate=f,m.mdCloseIcon=c.mdClose,i.attr({"aria-hidden":!0,tabindex:-1}).on("focus",function(){m.onFocus()}),o.ngModel&&(m.configureNgModel(i.controller("ngModel")),r.mdTransformChip&&m.useTransformChipExpression(),r.mdOnAppend&&m.useOnAppendExpression(),r.mdOnAdd&&m.useOnAddExpression(),r.mdOnRemove&&m.useOnRemoveExpression(),r.mdOnSelect&&m.useOnSelectExpression(),f!=u.input&&n.$watch("$mdChipsCtrl.readonly",function(e){e||t.nextTick(function(){0===f.indexOf("<md-autocomplete")&&m.configureAutocomplete(i.find("md-autocomplete").controller("mdAutocomplete")),m.configureUserInput(i.find("input"))})}),t.nextTick(function(){var e=i.find("input");e&&e.toggleClass("md-input",!0)})),g.length>0){var b=a(g.clone())(n.$parent);s(function(){i.find("md-chips-wrap").prepend(b)})}}}function m(){return{chips:t.processTemplate(n),input:t.processTemplate(o),"default":t.processTemplate(i),remove:t.processTemplate(r)}}var u=m();return{template:function(e,t){return t.$mdUserTemplate=e.clone(),u.chips},require:["mdChips"],restrict:"E",controller:"MdChipsCtrl",controllerAs:"$mdChipsCtrl",bindToController:!0,compile:l,scope:{readonly:"=readonly",placeholder:"@",mdEnableChipEdit:"@",secondaryPlaceholder:"@",maxChips:"@mdMaxChips",transformChip:"&mdTransformChip",onAppend:"&mdOnAppend",onAdd:"&mdOnAdd",onRemove:"&mdOnRemove",onSelect:"&mdOnSelect",deleteHint:"@",deleteButtonLabel:"@",separatorKeys:"=?mdSeparatorKeys",requireMatch:"=?mdRequireMatch"}}}t.module("material.components.chips").directive("mdChips",e);var n=' <md-chips-wrap ng-keydown="$mdChipsCtrl.chipKeydown($event)" ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus(), \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly}" class="md-chips"> <md-chip ng-repeat="$chip in $mdChipsCtrl.items" index="{{$index}}" ng-class="{\'md-focused\': $mdChipsCtrl.selectedChip == $index, \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly}"> <div class="_md-chip-content" tabindex="-1" aria-hidden="true" ng-click="!$mdChipsCtrl.readonly && $mdChipsCtrl.focusChip($index)" ng-focus="!$mdChipsCtrl.readonly && $mdChipsCtrl.selectChip($index)" md-chip-transclude="$mdChipsCtrl.chipContentsTemplate"></div> <div ng-if="!$mdChipsCtrl.readonly" class="_md-chip-remove-container" md-chip-transclude="$mdChipsCtrl.chipRemoveTemplate"></div> </md-chip> <div class="_md-chip-input-container"> <div ng-if="!$mdChipsCtrl.readonly && $mdChipsCtrl.ngModelCtrl" md-chip-transclude="$mdChipsCtrl.chipInputTemplate"></div> </div> </md-chips-wrap>',o=' <input class="md-input" tabindex="0" placeholder="{{$mdChipsCtrl.getPlaceholder()}}" aria-label="{{$mdChipsCtrl.getPlaceholder()}}" ng-model="$mdChipsCtrl.chipBuffer" ng-focus="$mdChipsCtrl.onInputFocus()" ng-blur="$mdChipsCtrl.onInputBlur()" ng-trim="false" ng-keydown="$mdChipsCtrl.inputKeydown($event)">',i=" <span>{{$chip}}</span>",r=' <button class="_md-chip-remove" ng-if="!$mdChipsCtrl.readonly" ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)" type="button" aria-hidden="true" tabindex="-1"> <md-icon md-svg-src="{{ $mdChipsCtrl.mdCloseIcon }}"></md-icon> <span class="_md-visually-hidden"> {{$mdChipsCtrl.deleteButtonLabel}} </span> </button>';e.$inject=["$mdTheming","$mdUtil","$compile","$log","$timeout","$$mdSvgRegistry"]}(),function(){function e(){this.selectedItem=null,this.searchText=""}t.module("material.components.chips").controller("MdContactChipsCtrl",e),e.prototype.queryContact=function(e){var n=this.contactQuery({$query:e});return this.filterSelected?n.filter(t.bind(this,this.filterSelectedContacts)):n},e.prototype.itemName=function(e){return e[this.contactName]},e.prototype.filterSelectedContacts=function(e){return-1==this.contacts.indexOf(e)}}(),function(){function e(e,t){function o(n,o){return function(n,i,r,a){t.initOptionalProperties(n,o),e(i),i.attr("tabindex","-1")}}return{template:function(e,t){return n},restrict:"E",controller:"MdContactChipsCtrl",controllerAs:"$mdContactChipsCtrl",bindToController:!0,compile:o,scope:{contactQuery:"&mdContacts",placeholder:"@",secondaryPlaceholder:"@",contactName:"@mdContactName",contactImage:"@mdContactImage",contactEmail:"@mdContactEmail",contacts:"=ngModel",requireMatch:"=?mdRequireMatch",highlightFlags:"@?mdHighlightFlags"}}}t.module("material.components.chips").directive("mdContactChips",e);var n=' <md-chips class="md-contact-chips" ng-model="$mdContactChipsCtrl.contacts" md-require-match="$mdContactChipsCtrl.requireMatch" md-autocomplete-snap> <md-autocomplete md-menu-class="md-contact-chips-suggestions" md-selected-item="$mdContactChipsCtrl.selectedItem" md-search-text="$mdContactChipsCtrl.searchText" md-items="item in $mdContactChipsCtrl.queryContact($mdContactChipsCtrl.searchText)" md-item-text="$mdContactChipsCtrl.itemName(item)" md-no-cache="true" md-autoselect placeholder="{{$mdContactChipsCtrl.contacts.length == 0 ? $mdContactChipsCtrl.placeholder : $mdContactChipsCtrl.secondaryPlaceholder}}"> <div class="md-contact-suggestion"> <img ng-src="{{item[$mdContactChipsCtrl.contactImage]}}" alt="{{item[$mdContactChipsCtrl.contactName]}}" ng-if="item[$mdContactChipsCtrl.contactImage]" /> <span class="md-contact-name" md-highlight-text="$mdContactChipsCtrl.searchText" md-highlight-flags="{{$mdContactChipsCtrl.highlightFlags}}"> {{item[$mdContactChipsCtrl.contactName]}} </span> <span class="md-contact-email" >{{item[$mdContactChipsCtrl.contactEmail]}}</span> </div> </md-autocomplete> <md-chip-template> <div class="md-contact-avatar"> <img ng-src="{{$chip[$mdContactChipsCtrl.contactImage]}}" alt="{{$chip[$mdContactChipsCtrl.contactName]}}" ng-if="$chip[$mdContactChipsCtrl.contactImage]" /> </div> <div class="md-contact-name"> {{$chip[$mdContactChipsCtrl.contactName]}} </div> </md-chip-template> </md-chips>';e.$inject=["$mdTheming","$mdUtil"]}(),function(){function e(e,t,n,o){function i(i,a,d){function s(){var e=a.parent();return e.attr("aria-label")||e.text()?!0:!(!e.parent().attr("aria-label")&&!e.parent().text())}function c(){d.mdSvgIcon||d.mdSvgSrc||(d.mdFontIcon&&a.addClass("md-font "+d.mdFontIcon),a.addClass(e.fontSet(d.mdFontSet)))}t(a),c();var l=a[0].getAttribute(d.$attr.mdSvgSrc),m=d.alt||d.mdFontIcon||d.mdSvgIcon||a.text(),u=d.$normalize(d.$attr.mdSvgIcon||d.$attr.mdSvgSrc||"");d["aria-label"]||(""===m||s()?a.text()||n.expect(a,"aria-hidden","true"):(n.expect(a,"aria-label",m),n.expect(a,"role","img"))),u&&d.$observe(u,function(t){r(t)||t!==l||(t=o.trustAsUrl(t)),a.empty(),t&&e(t).then(function(e){a.empty(),a.append(e)})})}function r(e){var t=/^data:image\/svg\+xml[\s*;\w\-\=]*?(base64)?,(.*)$/i;return t.test(e)}return{restrict:"E",link:i}}t.module("material.components.icon").directive("mdIcon",["$mdIcon","$mdTheming","$mdAria","$sce",e])}(),function(){function n(){}function o(e,t){this.url=e,this.viewBoxSize=t||r.defaultViewBoxSize}function i(n,o,i,r,a,d){function s(e){if(e=e||"",t.isString(e)||(e=d.getTrustedUrl(e)),E[e])return i.when(l(E[e]));if($.test(e)||C.test(e))return p(e).then(m(e));-1==e.indexOf(":")&&(e="$default:"+e);var o=n[e]?u:h;return o(e).then(m(e))}function c(e){var o=t.isUndefined(e)||!(e&&e.length);if(o)return n.defaultFontSet;var i=e;return t.forEach(n.fontSets,function(t){t.alias==e&&(i=t.fontSet||i)}),i}function l(e){var n=e.clone(),o="_cache"+a.nextUid();return n.id&&(n.id+=o),t.forEach(n.querySelectorAll("[id]"),function(e){e.id+=o}),n}function m(e){return function(t){return E[e]=f(t)?t:new g(t,n[e]),E[e].clone()}}function u(e){var t=n[e];return p(t.url).then(function(e){return new g(e,t)})}function h(e){function t(t){var n=e.slice(e.lastIndexOf(":")+1),i=t.querySelector("#"+n);return i?new g(i,d):o(e)}function o(e){var t="icon "+e+" not found";return r.warn(t),i.reject(t||e)}var a=e.substring(0,e.lastIndexOf(":"))||"$default",d=n[a];return d?p(d.url).then(t):o(e)}function p(n){function a(n){var o=C.exec(n),r=/base64/i.test(n),a=r?e.atob(o[2]):o[2];return i.when(t.element(a)[0])}function d(e){return i(function(n,i){var a=function(e){var n=t.isString(e)?e:e.message||e.data||e.statusText;r.warn(n),i(e)},d=function(e){var o=t.element("<div>").append(e).find("svg")[0];n(o)};o(e,!0).then(d,a)})}return C.test(n)?a(n):d(n)}function f(e){return t.isDefined(e.element)&&t.isDefined(e.config)}function g(e,n){e&&"svg"!=e.tagName&&(e=t.element('<svg xmlns="http://www.w3.org/2000/svg">').append(e)[0]), +e.getAttribute("xmlns")||e.setAttribute("xmlns","http://www.w3.org/2000/svg"),this.element=e,this.config=n,this.prepare()}function b(){var e=this.config?this.config.viewBoxSize:n.defaultViewBoxSize;t.forEach({fit:"",height:"100%",width:"100%",preserveAspectRatio:"xMidYMid meet",viewBox:this.element.getAttribute("viewBox")||"0 0 "+e+" "+e,focusable:!1},function(e,t){this.element.setAttribute(t,e)},this)}function v(){return this.element.cloneNode(!0)}var E={},$=/[-\w@:%\+.~#?&\/\/=]{2,}\.[a-z]{2,4}\b(\/[-\w@:%\+.~#?&\/\/=]*)?/i,C=/^data:image\/svg\+xml[\s*;\w\-\=]*?(base64)?,(.*)$/i;return g.prototype={clone:v,prepare:b},s.fontSet=c,s}t.module("material.components.icon").constant("$$mdSvgRegistry",{mdTabsArrow:"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwb2x5Z29uIHBvaW50cz0iMTUuNCw3LjQgMTQsNiA4LDEyIDE0LDE4IDE1LjQsMTYuNiAxMC44LDEyICIvPjwvZz48L3N2Zz4=",mdClose:"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xOSA2LjQxbC0xLjQxLTEuNDEtNS41OSA1LjU5LTUuNTktNS41OS0xLjQxIDEuNDEgNS41OSA1LjU5LTUuNTkgNS41OSAxLjQxIDEuNDEgNS41OS01LjU5IDUuNTkgNS41OSAxLjQxLTEuNDEtNS41OS01LjU5eiIvPjwvZz48L3N2Zz4=",mdCancel:"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnPjxwYXRoIGQ9Ik0xMiAyYy01LjUzIDAtMTAgNC40Ny0xMCAxMHM0LjQ3IDEwIDEwIDEwIDEwLTQuNDcgMTAtMTAtNC40Ny0xMC0xMC0xMHptNSAxMy41OWwtMS40MSAxLjQxLTMuNTktMy41OS0zLjU5IDMuNTktMS40MS0xLjQxIDMuNTktMy41OS0zLjU5LTMuNTkgMS40MS0xLjQxIDMuNTkgMy41OSAzLjU5LTMuNTkgMS40MSAxLjQxLTMuNTkgMy41OSAzLjU5IDMuNTl6Ii8+PC9nPjwvc3ZnPg==",mdMenu:"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0zLDZIMjFWOEgzVjZNMywxMUgyMVYxM0gzVjExTTMsMTZIMjFWMThIM1YxNloiIC8+PC9zdmc+",mdToggleArrow:"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiPjxwYXRoIGQ9Ik0yNCAxNmwtMTIgMTIgMi44MyAyLjgzIDkuMTctOS4xNyA5LjE3IDkuMTcgMi44My0yLjgzeiIvPjxwYXRoIGQ9Ik0wIDBoNDh2NDhoLTQ4eiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg==",mdCalendar:"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTkgM2gtMVYxaC0ydjJIOFYxSDZ2Mkg1Yy0xLjExIDAtMS45OS45LTEuOTkgMkwzIDE5YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZINVY4aDE0djExek03IDEwaDV2NUg3eiIvPjwvc3ZnPg=="}).provider("$mdIcon",n);var r={defaultViewBoxSize:24,defaultFontSet:"material-icons",fontSets:[]};n.prototype={icon:function(e,t,n){return-1==e.indexOf(":")&&(e="$default:"+e),r[e]=new o(t,n),this},iconSet:function(e,t,n){return r[e]=new o(t,n),this},defaultIconSet:function(e,t){var n="$default";return r[n]||(r[n]=new o(e,t)),r[n].viewBoxSize=t||r.defaultViewBoxSize,this},defaultViewBoxSize:function(e){return r.defaultViewBoxSize=e,this},fontSet:function(e,t){return r.fontSets.push({alias:e,fontSet:t||e}),this},defaultFontSet:function(e){return r.defaultFontSet=e?e:"",this},defaultIconSize:function(e){return r.defaultIconSize=e,this},$get:["$templateRequest","$q","$log","$templateCache","$mdUtil","$sce",function(e,t,n,o,a,d){return i(r,e,t,n,a,d)}]},i.$inject=["config","$templateRequest","$q","$log","$mdUtil","$sce"]}(),function(){function e(e,o,i,r,a,d,s,c){var l,m,u=a.prefixer(),h=this;this.nestLevel=parseInt(o.mdNestLevel,10)||0,this.init=function(n,o){o=o||{},l=n,m=i[0].querySelector(u.buildSelector(["ng-click","ng-mouseenter"])),m.setAttribute("aria-expanded","false"),this.isInMenuBar=o.isInMenuBar,this.nestedMenus=a.nodesToArray(l[0].querySelectorAll(".md-nested-menu")),l.on("$mdInterimElementRemove",function(){h.isOpen=!1,a.nextTick(function(){h.onIsOpenChanged(h.isOpen)})}),a.nextTick(function(){h.onIsOpenChanged(h.isOpen)});var d="menu_container_"+a.nextUid();l.attr("id",d),t.element(m).attr({"aria-owns":d,"aria-haspopup":"true"}),r.$on("$destroy",this.disableHoverListener),l.on("$destroy",function(){e.destroy()})};var p,f,g=[];this.enableHoverListener=function(){g.push(s.$on("$mdMenuOpen",function(e,t){l[0].contains(t[0])&&(h.currentlyOpenMenu=t.controller("mdMenu"),h.isAlreadyOpening=!1,h.currentlyOpenMenu.registerContainerProxy(h.triggerContainerProxy.bind(h)))})),g.push(s.$on("$mdMenuClose",function(e,t){l[0].contains(t[0])&&(h.currentlyOpenMenu=n)})),f=t.element(a.nodesToArray(l[0].children[0].children)),f.on("mouseenter",h.handleMenuItemHover),f.on("mouseleave",h.handleMenuItemMouseLeave)},this.disableHoverListener=function(){for(;g.length;)g.shift()();f&&f.off("mouseenter",h.handleMenuItemHover),f&&f.off("mouseleave",h.handleMenuItemMouseLeave)},this.handleMenuItemHover=function(e){if(!h.isAlreadyOpening){var n=e.target.querySelector("md-menu")||a.getClosest(e.target,"MD-MENU");p=d(function(){if(n&&(n=t.element(n).controller("mdMenu")),h.currentlyOpenMenu&&h.currentlyOpenMenu!=n){var e=h.nestLevel+1;h.currentlyOpenMenu.close(!0,{closeTo:e}),h.isAlreadyOpening=!!n,n&&n.open()}else n&&!n.isOpen&&n.open&&(h.isAlreadyOpening=!!n,n&&n.open())},n?100:250);var o=e.currentTarget.querySelector(".md-button:not([disabled])");o&&o.focus()}},this.handleMenuItemMouseLeave=function(){p&&(d.cancel(p),p=n)},this.open=function(t){t&&t.stopPropagation(),t&&t.preventDefault(),h.isOpen||(h.enableHoverListener(),h.isOpen=!0,a.nextTick(function(){h.onIsOpenChanged(h.isOpen)}),m=m||(t?t.target:i[0]),m.setAttribute("aria-expanded","true"),r.$emit("$mdMenuOpen",i),e.show({scope:r,mdMenuCtrl:h,nestLevel:h.nestLevel,element:l,target:m,preserveElement:!0,parent:"body"})["finally"](function(){m.setAttribute("aria-expanded","false"),h.disableHoverListener()}))},r.$mdOpenMenu=this.open,this.onIsOpenChanged=function(e){e?(l.attr("aria-hidden","false"),i[0].classList.add("_md-open"),t.forEach(h.nestedMenus,function(e){e.classList.remove("_md-open")})):(l.attr("aria-hidden","true"),i[0].classList.remove("_md-open")),r.$mdMenuIsOpen=h.isOpen},this.focusMenuContainer=function(){var e=l[0].querySelector(u.buildSelector(["md-menu-focus-target","md-autofocus"]));e||(e=l[0].querySelector(".md-button")),e.focus()},this.registerContainerProxy=function(e){this.containerProxy=e},this.triggerContainerProxy=function(e){this.containerProxy&&this.containerProxy(e)},this.destroy=function(){return h.isOpen?e.destroy():c.when(!1)},this.close=function(n,o){if(h.isOpen){h.isOpen=!1,a.nextTick(function(){h.onIsOpenChanged(h.isOpen)});var d=t.extend({},o,{skipFocus:n});if(r.$emit("$mdMenuClose",i,d),e.hide(null,o),!n){var s=h.restoreFocusTo||i.find("button")[0];s instanceof t.element&&(s=s[0]),s&&s.focus()}}},this.positionMode=function(){var e=(o.mdPositionMode||"target").split(" ");return 1==e.length&&e.push(e[0]),{left:e[0],top:e[1]}},this.offsets=function(){var e=(o.mdOffset||"0 0").split(" ").map(parseFloat);if(2==e.length)return{left:e[0],top:e[1]};if(1==e.length)return{top:e[0],left:e[0]};throw Error("Invalid offsets specified. Please follow format <x, y> or <n>")}}t.module("material.components.menu").controller("mdMenuCtrl",e),e.$inject=["$mdMenu","$attrs","$element","$scope","$mdUtil","$timeout","$rootScope","$q"]}(),function(){function e(e){function o(n){n.addClass("md-menu");var o=n.children()[0],a=e.prefixer();if(a.hasAttribute(o,"ng-click")||(o=o.querySelector(a.buildSelector(["ng-click","ng-mouseenter"]))||o),!o||"MD-BUTTON"!=o.nodeName&&"BUTTON"!=o.nodeName||o.hasAttribute("type")||o.setAttribute("type","button"),2!=n.children().length)throw Error(r+"Expected two children elements.");o&&o.setAttribute("aria-haspopup","true");var d=n[0].querySelectorAll("md-menu"),s=parseInt(n[0].getAttribute("md-nest-level"),10)||0;return d&&t.forEach(e.nodesToArray(d),function(e){e.hasAttribute("md-position-mode")||e.setAttribute("md-position-mode","cascade"),e.classList.add("_md-nested-menu"),e.setAttribute("md-nest-level",s+1)}),i}function i(e,o,i,r){var a=r[0],d=r[1]!=n,s=t.element('<div class="_md _md-open-menu-container md-whiteframe-z2"></div>'),c=o.children()[1];o.addClass("_md"),c.hasAttribute("role")||c.setAttribute("role","menu"),s.append(c),o.on("$destroy",function(){s.remove()}),o.append(s),s[0].style.display="none",a.init(s,{isInMenuBar:d})}var r="Invalid HTML for md-menu: ";return{restrict:"E",require:["mdMenu","?^mdMenuBar"],controller:"mdMenuCtrl",scope:!0,compile:o}}t.module("material.components.menu").directive("mdMenu",e),e.$inject=["$mdUtil"]}(),function(){function e(e){function o(e,o,a,d,s,c,l,m,u){function h(n,o,i){return i.nestLevel?t.noop:(i.disableParentScroll&&!e.getClosest(i.target,"MD-DIALOG")?i.restoreScroll=e.disableScrollAround(i.element,i.parent):i.disableParentScroll=!1,i.hasBackdrop&&(i.backdrop=e.createBackdrop(n,"_md-menu-backdrop _md-click-catcher"),u.enter(i.backdrop,d[0].body)),function(){i.backdrop&&i.backdrop.remove(),i.disableParentScroll&&i.restoreScroll()})}function p(e,t,n){function o(){return m(t,{addClass:"_md-leave"}).start()}function i(){t.removeClass("_md-active"),v(t,n),n.alreadyOpen=!1}return n.cleanupInteraction(),n.cleanupResizing(),n.hideBackdrop(),n.$destroy===!0?i():o().then(i)}function f(n,i,r){function d(){return r.parent.append(i),i[0].style.display="",c(function(e){var t=E(i,r);i.removeClass("_md-leave"),m(i,{addClass:"_md-active",from:C.toCss(t),to:C.toCss({transform:""})}).start().then(e)})}function u(){if(!r.target)throw Error("$mdMenu.show() expected a target to animate from in options.target");t.extend(r,{alreadyOpen:!1,isRemoved:!1,target:t.element(r.target),parent:t.element(r.parent),menuContentEl:t.element(i[0].querySelector("md-menu-content"))})}function p(){var e=function(e,t){return l.throttle(function(){if(!r.isRemoved){var n=E(e,t);e.css(C.toCss(n))}})}(i,r);return s.addEventListener("resize",e),s.addEventListener("orientationchange",e),function(){s.removeEventListener("resize",e),s.removeEventListener("orientationchange",e)}}function f(){function t(t){var n;switch(t.keyCode){case a.KEY_CODE.ESCAPE:r.mdMenuCtrl.close(!1,{closeAll:!0}),n=!0;break;case a.KEY_CODE.UP_ARROW:g(t,r.menuContentEl,r,-1)||r.nestLevel||r.mdMenuCtrl.triggerContainerProxy(t),n=!0;break;case a.KEY_CODE.DOWN_ARROW:g(t,r.menuContentEl,r,1)||r.nestLevel||r.mdMenuCtrl.triggerContainerProxy(t),n=!0;break;case a.KEY_CODE.LEFT_ARROW:r.nestLevel?r.mdMenuCtrl.close():r.mdMenuCtrl.triggerContainerProxy(t),n=!0;break;case a.KEY_CODE.RIGHT_ARROW:var o=e.getClosest(t.target,"MD-MENU");o&&o!=r.parent[0]?t.target.click():r.mdMenuCtrl.triggerContainerProxy(t),n=!0}n&&(t.preventDefault(),t.stopImmediatePropagation())}function o(e){e.preventDefault(),e.stopPropagation(),n.$apply(function(){r.mdMenuCtrl.close(!0,{closeAll:!0})})}function d(t){function o(){n.$apply(function(){r.mdMenuCtrl.close(!0,{closeAll:!0})})}function i(e,t){if(!e)return!1;for(var n,o=0;n=t[o];++o)if($.hasAttribute(e,n))return!0;return!1}var a=t.target;do{if(a==r.menuContentEl[0])return;if((i(a,["ng-click","ng-href","ui-sref"])||"BUTTON"==a.nodeName||"MD-BUTTON"==a.nodeName)&&!i(a,["md-prevent-menu-close"])){var d=e.getClosest(a,"MD-MENU");a.hasAttribute("disabled")||d&&d!=r.parent[0]||o();break}}while(a=a.parentNode)}i.addClass("_md-clickable"),r.backdrop&&r.backdrop.on("click",o),r.menuContentEl.on("keydown",t),r.menuContentEl[0].addEventListener("click",d,!0);var s=r.menuContentEl[0].querySelector($.buildSelector(["md-menu-focus-target","md-autofocus"]));if(!s){var c=r.menuContentEl[0].firstElementChild;s=c&&(c.querySelector(".md-button:not([disabled])")||c.firstElementChild)}return s&&s.focus(),function(){i.removeClass("_md-clickable"),r.backdrop&&r.backdrop.off("click",o),r.menuContentEl.off("keydown",t),r.menuContentEl[0].removeEventListener("click",d,!0)}}return u(r),o.inherit(r.menuContentEl,r.target),r.cleanupResizing=p(),r.hideBackdrop=h(n,i,r),d().then(function(e){return r.alreadyOpen=!0,r.cleanupInteraction=f(),e})}function g(t,n,o,i){for(var r,a=e.getClosest(t.target,"MD-MENU-ITEM"),d=e.nodesToArray(n[0].children),s=d.indexOf(a),c=s+i;c>=0&&c<d.length;c+=i){var l=d[c].querySelector(".md-button");if(r=b(l))break}return r}function b(e){return e&&-1!=e.getAttribute("tabindex")?(e.focus(),d[0].activeElement==e):void 0}function v(e,t){t.preserveElement?i(e).style.display="none":i(e).parentNode===i(t.parent)&&i(t.parent).removeChild(i(e))}function E(t,o){function i(e){e.top=Math.max(Math.min(e.top,v.bottom-l.offsetHeight),v.top),e.left=Math.max(Math.min(e.left,v.right-l.offsetWidth),v.left)}function a(){for(var e=0;e<m.children.length;++e)if("none"!=s.getComputedStyle(m.children[e]).display)return m.children[e]}var c,l=t[0],m=t[0].firstElementChild,u=m.getBoundingClientRect(),h=d[0].body,p=h.getBoundingClientRect(),f=s.getComputedStyle(m),g=o.target[0].querySelector($.buildSelector("md-menu-origin"))||o.target[0],b=g.getBoundingClientRect(),v={left:p.left+r,top:Math.max(p.top,0)+r,bottom:Math.max(p.bottom,Math.max(p.top,0)+p.height)-r,right:p.right-r},E={top:0,left:0,right:0,bottom:0},C={top:0,left:0,right:0,bottom:0},y=o.mdMenuCtrl.positionMode();"target"!=y.top&&"target"!=y.left&&"target-right"!=y.left||(c=a(),c&&(c=c.firstElementChild||c,c=c.querySelector($.buildSelector("md-menu-align-target"))||c,E=c.getBoundingClientRect(),C={top:parseFloat(l.style.top||0),left:parseFloat(l.style.left||0)}));var M={},_="top ";switch(y.top){case"target":M.top=C.top+b.top-E.top;break;case"cascade":M.top=b.top-parseFloat(f.paddingTop)-g.style.top;break;case"bottom":M.top=b.top+b.height;break;default:throw new Error('Invalid target mode "'+y.top+'" specified for md-menu on Y axis.')}var A="rtl"==e.bidi();switch(y.left){case"target":M.left=C.left+b.left-E.left,_+=A?"right":"left";break;case"target-left":M.left=b.left,_+="left";break;case"target-right":M.left=b.right-u.width+(u.right-E.right),_+="right";break;case"cascade":var T=A?b.left-u.width<v.left:b.right+u.width<v.right;M.left=T?b.right-g.style.left:b.left-g.style.left-u.width,_+=T?"left":"right";break;case"left":A?(M.left=b.right-u.width,_+="right"):(M.left=b.left,_+="left");break;default:throw new Error('Invalid target mode "'+y.left+'" specified for md-menu on X axis.')}var w=o.mdMenuCtrl.offsets();M.top+=w.top,M.left+=w.left,i(M);var k=Math.round(100*Math.min(b.width/l.offsetWidth,1))/100,x=Math.round(100*Math.min(b.height/l.offsetHeight,1))/100;return{top:Math.round(M.top),left:Math.round(M.left),transform:o.alreadyOpen?n:e.supplant("scale({0},{1})",[k,x]),transformOrigin:_}}var $=e.prefixer(),C=e.dom.animator;return{parent:"body",onShow:f,onRemove:p,hasBackdrop:!0,disableParentScroll:!0,skipCompile:!0,preserveScope:!0,skipHide:!0,themable:!0}}function i(e){return e instanceof t.element&&(e=e[0]),e}var r=8;return o.$inject=["$mdUtil","$mdTheming","$mdConstant","$document","$window","$q","$$rAF","$animateCss","$animate"],e("$mdMenu").setDefaults({methods:["target"],options:o})}t.module("material.components.menu").provider("$mdMenu",e),e.$inject=["$$interimElementProvider"]}(),function(){function e(e,n,i,r,a,d,s,c){this.$element=i,this.$attrs=r,this.$mdConstant=a,this.$mdUtil=s,this.$document=d,this.$scope=e,this.$rootScope=n,this.$timeout=c;var l=this;t.forEach(o,function(e){l[e]=t.bind(l,l[e])})}t.module("material.components.menuBar").controller("MenuBarController",e);var o=["handleKeyDown","handleMenuHover","scheduleOpenHoveredMenu","cancelScheduledOpen"];e.$inject=["$scope","$rootScope","$element","$attrs","$mdConstant","$document","$mdUtil","$timeout"],e.prototype.init=function(){var e=this.$element,t=this.$mdUtil,o=this.$scope,i=this,r=[];e.on("keydown",this.handleKeyDown),this.parentToolbar=t.getClosest(e,"MD-TOOLBAR"),r.push(this.$rootScope.$on("$mdMenuOpen",function(t,n){-1!=i.getMenus().indexOf(n[0])&&(e[0].classList.add("_md-open"),n[0].classList.add("_md-open"),i.currentlyOpenMenu=n.controller("mdMenu"),i.currentlyOpenMenu.registerContainerProxy(i.handleKeyDown),i.enableOpenOnHover())})),r.push(this.$rootScope.$on("$mdMenuClose",function(o,r,a){var d=i.getMenus();if(-1!=d.indexOf(r[0])&&(e[0].classList.remove("_md-open"),r[0].classList.remove("_md-open")),e[0].contains(r[0])){for(var s=r[0];s&&-1==d.indexOf(s);)s=t.getClosest(s,"MD-MENU",!0);s&&(a.skipFocus||s.querySelector("button:not([disabled])").focus(),i.currentlyOpenMenu=n,i.disableOpenOnHover(),i.setKeyboardMode(!0))}})),o.$on("$destroy",function(){for(;r.length;)r.shift()()}),this.setKeyboardMode(!0)},e.prototype.setKeyboardMode=function(e){e?this.$element[0].classList.add("_md-keyboard-mode"):this.$element[0].classList.remove("_md-keyboard-mode")},e.prototype.enableOpenOnHover=function(){if(!this.openOnHoverEnabled){this.openOnHoverEnabled=!0;var e;(e=this.parentToolbar)&&(e.dataset.mdRestoreStyle=e.getAttribute("style"),e.style.position="relative",e.style.zIndex=100),t.element(this.getMenus()).on("mouseenter",this.handleMenuHover)}},e.prototype.handleMenuHover=function(e){this.setKeyboardMode(!1),this.openOnHoverEnabled&&this.scheduleOpenHoveredMenu(e)},e.prototype.disableOpenOnHover=function(){if(this.openOnHoverEnabled){this.openOnHoverEnabled=!1;var e;(e=this.parentToolbar)&&(e.style.cssText=e.dataset.mdRestoreStyle||""),t.element(this.getMenus()).off("mouseenter",this.handleMenuHover)}},e.prototype.scheduleOpenHoveredMenu=function(e){var n=t.element(e.currentTarget),o=n.controller("mdMenu");this.setKeyboardMode(!1),this.scheduleOpenMenu(o)},e.prototype.scheduleOpenMenu=function(e){var t=this,o=this.$timeout;e!=t.currentlyOpenMenu&&(o.cancel(t.pendingMenuOpen),t.pendingMenuOpen=o(function(){t.pendingMenuOpen=n,t.currentlyOpenMenu&&t.currentlyOpenMenu.close(!0,{closeAll:!0}),e.open()},200,!1))},e.prototype.handleKeyDown=function(e){var n=this.$mdConstant.KEY_CODE,o=this.currentlyOpenMenu,i=o&&o.isOpen;this.setKeyboardMode(!0);var r,a,d;switch(e.keyCode){case n.DOWN_ARROW:o?o.focusMenuContainer():this.openFocusedMenu(),r=!0;break;case n.UP_ARROW:o&&o.close(),r=!0;break;case n.LEFT_ARROW:a=this.focusMenu(-1),i&&(d=t.element(a).controller("mdMenu"),this.scheduleOpenMenu(d)),r=!0;break;case n.RIGHT_ARROW:a=this.focusMenu(1),i&&(d=t.element(a).controller("mdMenu"),this.scheduleOpenMenu(d)),r=!0}r&&(e&&e.preventDefault&&e.preventDefault(),e&&e.stopImmediatePropagation&&e.stopImmediatePropagation())},e.prototype.focusMenu=function(e){var t=this.getMenus(),n=this.getFocusedMenuIndex();-1==n&&(n=this.getOpenMenuIndex());var o=!1;return-1==n?(n=0,o=!0):(0>e&&n>0||e>0&&n<t.length-e)&&(n+=e,o=!0),o?(t[n].querySelector("button").focus(),t[n]):void 0},e.prototype.openFocusedMenu=function(){var e=this.getFocusedMenu();e&&t.element(e).controller("mdMenu").open()},e.prototype.getMenus=function(){var e=this.$element;return this.$mdUtil.nodesToArray(e[0].children).filter(function(e){return"MD-MENU"==e.nodeName})},e.prototype.getFocusedMenu=function(){return this.getMenus()[this.getFocusedMenuIndex()]},e.prototype.getFocusedMenuIndex=function(){var e=this.$mdUtil,t=e.getClosest(this.$document[0].activeElement,"MD-MENU");if(!t)return-1;var n=this.getMenus().indexOf(t);return n},e.prototype.getOpenMenuIndex=function(){for(var e=this.getMenus(),t=0;t<e.length;++t)if(e[t].classList.contains("_md-open"))return t;return-1}}(),function(){function e(e,n){return{restrict:"E",require:"mdMenuBar",controller:"MenuBarController",compile:function(o,i){return i.ariaRole||o[0].setAttribute("role","menubar"),t.forEach(o[0].children,function(n){if("MD-MENU"==n.nodeName){n.hasAttribute("md-position-mode")||(n.setAttribute("md-position-mode","left bottom"),n.querySelector("button,a").setAttribute("role","menuitem"));var o=e.nodesToArray(n.querySelectorAll("md-menu-content"));t.forEach(o,function(e){e.classList.add("_md-menu-bar-menu"),e.classList.add("md-dense"),e.hasAttribute("width")||e.setAttribute("width",5)})}}),function(e,t,o,i){t.addClass("_md"),n(e,t),i.init()}}}}t.module("material.components.menuBar").directive("mdMenuBar",e),e.$inject=["$mdUtil","$mdTheming"]}(),function(){function e(){return{restrict:"E",compile:function(e,t){t.role||e[0].setAttribute("role","separator")}}}t.module("material.components.menuBar").directive("mdMenuDivider",e)}(),function(){function e(e,t,n){this.$element=t,this.$attrs=n,this.$scope=e}t.module("material.components.menuBar").controller("MenuItemController",e),e.$inject=["$scope","$element","$attrs"],e.prototype.init=function(e){var t=this.$element,n=this.$attrs;this.ngModel=e,"checkbox"!=n.type&&"radio"!=n.type||(this.mode=n.type,this.iconEl=t[0].children[0],this.buttonEl=t[0].children[1],e&&this.initClickListeners())},e.prototype.clearNgAria=function(){var e=this.$element[0],n=["role","tabindex","aria-invalid","aria-checked"];t.forEach(n,function(t){e.removeAttribute(t)})},e.prototype.initClickListeners=function(){function e(){if("radio"==d){var e=a.ngValue?r.$eval(a.ngValue):a.value;return i.$modelValue==e}return i.$modelValue}function n(e){e?c.off("click",l):c.on("click",l)}var o=this,i=this.ngModel,r=this.$scope,a=this.$attrs,d=(this.$element,this.mode);this.handleClick=t.bind(this,this.handleClick);var s=this.iconEl,c=t.element(this.buttonEl),l=this.handleClick;a.$observe("disabled",n),n(a.disabled),i.$render=function(){o.clearNgAria(),e()?(s.style.display="",c.attr("aria-checked","true")):(s.style.display="none",c.attr("aria-checked","false"))},r.$$postDigest(i.$render)},e.prototype.handleClick=function(e){var t,n=this.mode,o=this.ngModel,i=this.$attrs;"checkbox"==n?t=!o.$modelValue:"radio"==n&&(t=i.ngValue?this.$scope.$eval(i.ngValue):i.value),o.$setViewValue(t),o.$render()}}(),function(){function e(e){return{require:["mdMenuItem","?ngModel"],priority:210,compile:function(n,o){function i(e,o,i){i=i||n,i instanceof t.element&&(i=i[0]),i.hasAttribute(e)||i.setAttribute(e,o)}function r(e){if(n[0].hasAttribute(e)){var t=n[0].getAttribute(e);s[0].setAttribute(e,t),n[0].removeAttribute(e)}}function a(){return!!e.getClosest(n,"md-menu-bar",!0)}if(!a()||"checkbox"!=o.type&&"radio"!=o.type)i("role","menuitem",n[0].querySelector("md-button, button, a"));else{var d=n[0].textContent,s=t.element('<md-button type="button"></md-button>');s.html(d),s.attr("tabindex","0"),n.html(""),n.append(t.element('<md-icon md-svg-icon="check"></md-icon>')),n.append(s),n[0].classList.add("md-indent"),i("role","checkbox"==o.type?"menuitemcheckbox":"menuitemradio",s),t.forEach(["ng-disabled"],r)}return function(e,t,n,o){var i=o[0],r=o[1];i.init(r)}},controller:"MenuItemController"}}t.module("material.components.menuBar").directive("mdMenuItem",e),e.$inject=["$mdUtil"]}(),function(){function e(e,n,o,i,r,a){function d(a,d,c){function f(t,o,r,d,c){var l=++S,p=i.now(),f=o-t,g=m(a.mdDiameter),b=g-u(g),v=r||n.easeFn,E=d||n.duration;o===t?w.attr("d",s(o,g,b,c)):M=h(function $(n){var o=e.Math.max(0,e.Math.min((n||i.now())-p,E));w.attr("d",s(v(o,t,f,E),g,b,c)),l===S&&E>o&&(M=h($))})}function $(){f(k,x,n.easeFnIndeterminate,n.durationIndeterminate,N),N=(N+x)%100;var e=k;k=-x,x=-e}function C(){_||(_=r($,n.durationIndeterminate+50,0,!1),$(),d.addClass(E).removeAttr("aria-valuenow"))}function y(){_&&(r.cancel(_),_=null,d.removeClass(E))}var M,_,A=d[0],T=t.element(A.querySelector("svg")),w=t.element(A.querySelector("path")),k=n.startIndeterminate,x=n.endIndeterminate,N=0,S=0;o(d),d.toggleClass(v,c.hasOwnProperty("disabled")),a.mdMode===b&&C(),a.$on("$destroy",function(){y(),M&&p(M)}),a.$watchGroup(["value","mdMode",function(){var e=A.disabled;return e===!0||e===!1?e:t.isDefined(d.attr("disabled"))}],function(e,t){var n=e[1],o=e[2],i=t[2];if(o!==i&&d.toggleClass(v,!!o),o)y();else if(n!==g&&n!==b&&(n=b,c.$set("mdMode",n)),n===b)C();else{var r=l(e[0]);y(),d.attr("aria-valuenow",r),f(l(t[0]),r)}}),a.$watch("mdDiameter",function(e){var t=m(e),n=u(t),o=t/2+"px",i={width:t+"px",height:t+"px"};T[0].setAttribute("viewBox","0 0 "+t+" "+t),T.css(i).css("transform-origin",o+" "+o+" "+o),d.css(i),w.css("stroke-width",n+"px")})}function s(e,t,n,o){var i,r=3.5999,a=o||0,d=t/2,s=n/2,l=a*r,m=e*r,u=c(d,s,l),h=c(d,s,m+l),p=0>m?0:1;return i=0>m?m>=-180?0:1:180>=m?0:1,"M"+u+"A"+s+","+s+" 0 "+i+","+p+" "+h}function c(t,n,o){var i=(o-90)*f;return t+n*e.Math.cos(i)+","+(t+n*e.Math.sin(i))}function l(t){return e.Math.max(0,e.Math.min(t||0,100))}function m(e){var t=n.progressSize;if(e){var o=parseFloat(e);return e.lastIndexOf("%")===e.length-1&&(o=o/100*t),o}return t}function u(e){return n.strokeWidth/100*e}var h=e.requestAnimationFrame||t.noop,p=e.cancelAnimationFrame||t.noop,f=e.Math.PI/180,g="determinate",b="indeterminate",v="_md-progress-circular-disabled",E="_md-mode-indeterminate";return{restrict:"E",scope:{value:"@",mdDiameter:"@",mdMode:"@"},template:'<svg xmlns="http://www.w3.org/2000/svg"><path fill="none"/></svg>',compile:function(e,n){if(e.attr({"aria-valuemin":0,"aria-valuemax":100,role:"progressbar"}),t.isUndefined(n.mdMode)){var o=t.isDefined(n.value),i=o?g:b;n.$set("mdMode",i)}else n.$set("mdMode",n.mdMode.trim());return d}}}t.module("material.components.progressCircular").directive("mdProgressCircular",e),e.$inject=["$window","$mdProgressCircular","$mdTheming","$mdUtil","$interval","$log"]}(),function(){function e(){function e(e,t,n,o){return n*e/o+t}function n(e,t,n,o){var i=(e/=o)*e,r=i*e;return t+n*(6*r*i+-15*i*i+10*r)}var o={progressSize:50,strokeWidth:10,duration:100,easeFn:e,durationIndeterminate:500,startIndeterminate:3,endIndeterminate:80,easeFnIndeterminate:n,easingPresets:{linearEase:e,materialEase:n}};return{configure:function(e){return o=t.extend(o,e||{})},$get:function(){return o}}}t.module("material.components.progressCircular").provider("$mdProgressCircular",e)}(),function(){function e(){function e(e,o,i,r){if(r){var a=r.getTabElementIndex(o),d=n(o,"md-tab-body").remove(),s=n(o,"md-tab-label").remove(),c=r.insertTab({scope:e,parent:e.$parent,index:a,element:o,template:d.html(),label:s.html()},a);e.select=e.select||t.noop,e.deselect=e.deselect||t.noop,e.$watch("active",function(e){e&&r.select(c.getIndex(),!0)}),e.$watch("disabled",function(){r.refreshIndex()}),e.$watch(function(){return r.getTabElementIndex(o)},function(e){c.index=e,r.updateTabOrder()}),e.$on("$destroy",function(){r.removeTab(c)})}}function n(e,n){for(var o=e[0].children,i=0,r=o.length;r>i;i++){var a=o[i];if(a.tagName===n.toUpperCase())return t.element(a)}return t.element()}return{require:"^?mdTabs",terminal:!0,compile:function(o,i){var r=n(o,"md-tab-label"),a=n(o,"md-tab-body");if(0==r.length&&(r=t.element("<md-tab-label></md-tab-label>"),i.label?r.text(i.label):r.append(o.contents()),0==a.length)){var d=o.contents().detach();a=t.element("<md-tab-body></md-tab-body>"),a.append(d)}return o.append(r),a.html()&&o.append(a),e},scope:{active:"=?mdActive",disabled:"=?ngDisabled",select:"&?mdOnSelect",deselect:"&?mdOnDeselect"}}}t.module("material.components.tabs").directive("mdTab",e)}(),function(){function e(){return{require:"^?mdTabs",link:function(e,t,n,o){o&&o.attachRipple(e,t)}}}t.module("material.components.tabs").directive("mdTabItem",e)}(),function(){function e(){return{terminal:!0}}t.module("material.components.tabs").directive("mdTabLabel",e)}(),function(){function e(e){return{restrict:"A",compile:function(t,n){var o=e(n.mdTabScroll,null,!0);return function(e,t){t.on("mousewheel",function(t){e.$apply(function(){o(e,{$event:t})})})}}}}t.module("material.components.tabs").directive("mdTabScroll",e),e.$inject=["$parse"]}(),function(){function e(e,o,i,r,a,d,s,c,l,m){function u(){le.selectedIndex=le.selectedIndex||0,h(),f(),p(),m(o),d.nextTick(function(){ue=F(),re(),te(),ae(),le.tabs[le.selectedIndex]&&le.tabs[le.selectedIndex].scope.select(),fe=!0,Y()})}function h(){var e=c.$mdTabsTemplate,n=t.element(o[0].querySelector("md-tab-data"));n.html(e),l(n.contents())(le.parent),delete c.$mdTabsTemplate}function p(){t.element(i).on("resize",I),e.$on("$destroy",v)}function f(){e.$watch("$mdTabsCtrl.selectedIndex",T)}function g(e,t){var n=c.$normalize("md-"+e);t&&V(e,t),c.$observe(n,function(t){le[e]=t})}function b(e,t){function n(t){le[e]="false"!==t}var o=c.$normalize("md-"+e);t&&V(e,t),c.hasOwnProperty(o)&&n(c[o]),c.$observe(o,n)}function v(){pe=!0,t.element(i).off("resize",I)}function E(e){var n=F();t.element(n.wrapper).toggleClass("md-stretch-tabs",j()),ae()}function $(e){le.shouldCenterTabs=z()}function C(e,n){if(e!==n){var o=F();t.forEach(o.tabs,function(t){t.style.maxWidth=e+"px"}),d.nextTick(le.updateInkBarStyles)}}function y(e,t){e!==t&&(le.maxTabWidth=Q(),le.shouldCenterTabs=z(),d.nextTick(function(){le.maxTabWidth=Q(),te(le.selectedIndex)}))}function M(e){o[e?"removeClass":"addClass"]("md-no-tab-content")}function _(n){var o=F(),i=le.shouldCenterTabs?"":"-"+n+"px";t.element(o.paging).css(r.CSS.TRANSFORM,"translate3d("+i+", 0, 0)"),e.$broadcast("$mdTabsPaginationChanged")}function A(e,t){e!==t&&F().tabs[e]&&(te(),ee())}function T(t,n){t!==n&&(le.selectedIndex=W(t),le.lastSelectedIndex=n,le.updateInkBarStyles(),re(),te(t),e.$broadcast("$mdTabsChanged"),le.tabs[n]&&le.tabs[n].scope.deselect(),le.tabs[t]&&le.tabs[t].scope.select())}function w(e){var t=o[0].getElementsByTagName("md-tab");return Array.prototype.indexOf.call(t,e[0])}function k(){k.watcher||(k.watcher=e.$watch(function(){d.nextTick(function(){k.watcher&&o.prop("offsetParent")&&(k.watcher(),k.watcher=null,I())},!1)}))}function x(e){switch(e.keyCode){case r.KEY_CODE.LEFT_ARROW:e.preventDefault(),J(-1,!0);break;case r.KEY_CODE.RIGHT_ARROW:e.preventDefault(),J(1,!0);break;case r.KEY_CODE.SPACE:case r.KEY_CODE.ENTER:e.preventDefault(),me||N(le.focusIndex)}le.lastClick=!1}function N(e,t){me||(le.focusIndex=le.selectedIndex=e),le.lastClick=!0,t&&le.noSelectClick||d.nextTick(function(){le.tabs[e].element.triggerHandler("click")},!1)}function S(e){le.shouldPaginate&&(e.preventDefault(),le.offsetLeft=se(le.offsetLeft-e.wheelDelta))}function D(){var e,t,n=F(),o=n.canvas.clientWidth,i=o+le.offsetLeft;for(e=0;e<n.tabs.length&&(t=n.tabs[e],!(t.offsetLeft+t.offsetWidth>i));e++);le.offsetLeft=se(t.offsetLeft)}function H(){var e,t,n=F();for(e=0;e<n.tabs.length&&(t=n.tabs[e],!(t.offsetLeft+t.offsetWidth>=le.offsetLeft));e++);le.offsetLeft=se(t.offsetLeft+t.offsetWidth-n.canvas.clientWidth)}function I(){le.lastSelectedIndex=le.selectedIndex,le.offsetLeft=se(le.offsetLeft),d.nextTick(function(){le.updateInkBarStyles(),Y()})}function O(e){t.element(F().inkBar).toggleClass("ng-hide",e)}function R(e){o.toggleClass("md-dynamic-height",e)}function L(e){if(!pe){var t=le.selectedIndex,n=le.tabs.splice(e.getIndex(),1)[0];ie(),le.selectedIndex===t&&(n.scope.deselect(),le.tabs[le.selectedIndex]&&le.tabs[le.selectedIndex].scope.select()),d.nextTick(function(){Y(),le.offsetLeft=se(le.offsetLeft)})}}function P(e,n){var o=fe,i={getIndex:function(){return le.tabs.indexOf(r)},isActive:function(){return this.getIndex()===le.selectedIndex},isLeft:function(){return this.getIndex()<le.selectedIndex},isRight:function(){return this.getIndex()>le.selectedIndex},shouldRender:function(){return!le.noDisconnect||this.isActive()},hasFocus:function(){return!le.lastClick&&le.hasFocus&&this.getIndex()===le.focusIndex},id:d.nextUid()},r=t.extend(i,e);return t.isDefined(n)?le.tabs.splice(n,0,r):le.tabs.push(r),ne(),oe(),d.nextTick(function(){Y(),o&&le.autoselect&&d.nextTick(function(){d.nextTick(function(){N(le.tabs.indexOf(r))})})}),r}function F(){var e={},t=o[0];return e.wrapper=t.querySelector("md-tabs-wrapper"),e.canvas=e.wrapper.querySelector("md-tabs-canvas"),e.paging=e.canvas.querySelector("md-pagination-wrapper"),e.inkBar=e.paging.querySelector("md-ink-bar"),e.contents=t.querySelectorAll("md-tabs-content-wrapper > md-tab-content"),e.tabs=e.paging.querySelectorAll("md-tab-item"),e.dummies=e.canvas.querySelectorAll("md-dummy-tab"),e}function B(){return le.offsetLeft>0}function U(){var e=F(),t=e.tabs[e.tabs.length-1];return t&&t.offsetLeft+t.offsetWidth>e.canvas.clientWidth+le.offsetLeft}function j(){switch(le.stretchTabs){case"always":return!0;case"never":return!1;default:return!le.shouldPaginate&&i.matchMedia("(max-width: 600px)").matches}}function z(){return le.centerTabs&&!le.shouldPaginate}function q(){if(le.noPagination||!fe)return!1;var e=o.prop("clientWidth");return t.forEach(F().dummies,function(t){e-=t.offsetWidth}),0>e}function W(e){ +if(-1===e)return-1;var t,n,o=Math.max(le.tabs.length-e,e);for(t=0;o>=t;t++){if(n=le.tabs[e+t],n&&n.scope.disabled!==!0)return n.getIndex();if(n=le.tabs[e-t],n&&n.scope.disabled!==!0)return n.getIndex()}return e}function V(e,t,n){Object.defineProperty(le,e,{get:function(){return n},set:function(e){var o=n;n=e,t&&t(e,o)}})}function Y(){K(),le.maxTabWidth=Q(),le.shouldPaginate=q()}function K(){var e=F();j()?t.element(e.paging).css("width",""):t.element(e.paging).css("width",G()+"px")}function G(){return X(F().dummies)}function X(e){var n=0;return t.forEach(e,function(e){n+=Math.max(e.offsetWidth,e.getBoundingClientRect().width)}),Math.ceil(n)}function Q(){return o.prop("clientWidth")}function Z(){var e=le.tabs[le.selectedIndex],t=le.tabs[le.focusIndex];le.tabs=le.tabs.sort(function(e,t){return e.index-t.index}),le.selectedIndex=le.tabs.indexOf(e),le.focusIndex=le.tabs.indexOf(t)}function J(e,t){var n,o=t?"focusIndex":"selectedIndex",i=le[o];for(n=i+e;le.tabs[n]&&le.tabs[n].scope.disabled;n+=e);le.tabs[n]&&(le[o]=n)}function ee(){F().dummies[le.focusIndex].focus()}function te(e){var t=F();if(null==e&&(e=le.focusIndex),t.tabs[e]&&!le.shouldCenterTabs){var n=t.tabs[e],o=n.offsetLeft,i=n.offsetWidth+o;le.offsetLeft=Math.max(le.offsetLeft,se(i-t.canvas.clientWidth+64)),le.offsetLeft=Math.min(le.offsetLeft,se(o))}}function ne(){he.forEach(function(e){d.nextTick(e)}),he=[]}function oe(){var e=!1;t.forEach(le.tabs,function(t){t.template&&(e=!0)}),le.hasContent=e}function ie(){le.selectedIndex=W(le.selectedIndex),le.focusIndex=W(le.focusIndex)}function re(){if(!le.dynamicHeight)return o.css("height","");if(!le.tabs.length)return he.push(re);var e=F(),t=e.contents[le.selectedIndex],i=t?t.offsetHeight:0,r=e.wrapper.offsetHeight,a=i+r,c=o.prop("clientHeight");if(c!==a){"bottom"===o.attr("md-align-tabs")&&(c-=r,a-=r,o.attr("md-border-bottom")!==n&&++c),me=!0;var l={height:c+"px"},m={height:a+"px"};o.css(l),s(o,{from:l,to:m,easing:"cubic-bezier(0.35, 0, 0.25, 1)",duration:.5}).start().done(function(){o.css({transition:"none",height:""}),d.nextTick(function(){o.css("transition","")}),me=!1})}}function ae(){var e=F();if(!e.tabs[le.selectedIndex])return void t.element(e.inkBar).css({left:"auto",right:"auto"});if(!le.tabs.length)return he.push(le.updateInkBarStyles);if(!o.prop("offsetParent"))return k();var n=le.selectedIndex,i=e.paging.offsetWidth,r=e.tabs[n],a=r.offsetLeft,s=i-a-r.offsetWidth;if(le.shouldCenterTabs){var c=X(e.tabs);i>c&&d.nextTick(ae,!1)}de(),t.element(e.inkBar).css({left:a+"px",right:s+"px"})}function de(){var e=F(),n=le.selectedIndex,o=le.lastSelectedIndex,i=t.element(e.inkBar);t.isNumber(o)&&i.toggleClass("md-left",o>n).toggleClass("md-right",n>o)}function se(e){var t=F();if(!t.tabs.length||!le.shouldPaginate)return 0;var n=t.tabs[t.tabs.length-1],o=n.offsetLeft+n.offsetWidth;return e=Math.max(0,e),e=Math.min(o-t.canvas.clientWidth,e)}function ce(e,n){var o=F(),i={colorElement:t.element(o.inkBar)};a.attach(e,n,i)}var le=this,me=!1,ue=F(),he=[],pe=!1,fe=!1;g("stretchTabs",E),V("focusIndex",A,le.selectedIndex||0),V("offsetLeft",_,0),V("hasContent",M,!1),V("maxTabWidth",C,Q()),V("shouldPaginate",y,!1),b("noInkBar",O),b("dynamicHeight",R),b("noPagination"),b("swipeContent"),b("noDisconnect"),b("autoselect"),b("noSelectClick"),b("centerTabs",$,!1),b("enableDisconnect"),le.scope=e,le.parent=e.$parent,le.tabs=[],le.lastSelectedIndex=null,le.hasFocus=!1,le.lastClick=!0,le.shouldCenterTabs=z(),le.updatePagination=d.debounce(Y,100),le.redirectFocus=ee,le.attachRipple=ce,le.insertTab=P,le.removeTab=L,le.select=N,le.scroll=S,le.nextPage=D,le.previousPage=H,le.keydown=x,le.canPageForward=U,le.canPageBack=B,le.refreshIndex=ie,le.incrementIndex=J,le.getTabElementIndex=w,le.updateInkBarStyles=d.debounce(ae,100),le.updateTabOrder=d.debounce(Z,100),u()}t.module("material.components.tabs").controller("MdTabsController",e),e.$inject=["$scope","$element","$window","$mdConstant","$mdTabInkRipple","$mdUtil","$animateCss","$attrs","$compile","$mdTheming"]}(),function(){function e(e){return{scope:{selectedIndex:"=?mdSelected"},template:function(t,n){return n.$mdTabsTemplate=t.html(),'<md-tabs-wrapper> <md-tab-data></md-tab-data> <md-prev-button tabindex="-1" role="button" aria-label="Previous Page" aria-disabled="{{!$mdTabsCtrl.canPageBack()}}" ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageBack() }" ng-if="$mdTabsCtrl.shouldPaginate" ng-click="$mdTabsCtrl.previousPage()"> <md-icon md-svg-src="'+e.mdTabsArrow+'"></md-icon> </md-prev-button> <md-next-button tabindex="-1" role="button" aria-label="Next Page" aria-disabled="{{!$mdTabsCtrl.canPageForward()}}" ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageForward() }" ng-if="$mdTabsCtrl.shouldPaginate" ng-click="$mdTabsCtrl.nextPage()"> <md-icon md-svg-src="'+e.mdTabsArrow+'"></md-icon> </md-next-button> <md-tabs-canvas tabindex="{{ $mdTabsCtrl.hasFocus ? -1 : 0 }}" aria-activedescendant="tab-item-{{$mdTabsCtrl.tabs[$mdTabsCtrl.focusIndex].id}}" ng-focus="$mdTabsCtrl.redirectFocus()" ng-class="{ \'md-paginated\': $mdTabsCtrl.shouldPaginate, \'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs }" ng-keydown="$mdTabsCtrl.keydown($event)" role="tablist"> <md-pagination-wrapper ng-class="{ \'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs }" md-tab-scroll="$mdTabsCtrl.scroll($event)"> <md-tab-item tabindex="-1" class="md-tab" ng-repeat="tab in $mdTabsCtrl.tabs" role="tab" aria-controls="tab-content-{{::tab.id}}" aria-selected="{{tab.isActive()}}" aria-disabled="{{tab.scope.disabled || \'false\'}}" ng-click="$mdTabsCtrl.select(tab.getIndex())" ng-class="{ \'md-active\': tab.isActive(), \'md-focused\': tab.hasFocus(), \'md-disabled\': tab.scope.disabled }" ng-disabled="tab.scope.disabled" md-swipe-left="$mdTabsCtrl.nextPage()" md-swipe-right="$mdTabsCtrl.previousPage()" md-tabs-template="::tab.label" md-scope="::tab.parent"></md-tab-item> <md-ink-bar></md-ink-bar> </md-pagination-wrapper> <md-tabs-dummy-wrapper class="_md-visually-hidden md-dummy-wrapper"> <md-dummy-tab class="md-tab" tabindex="-1" id="tab-item-{{::tab.id}}" role="tab" aria-controls="tab-content-{{::tab.id}}" aria-selected="{{tab.isActive()}}" aria-disabled="{{tab.scope.disabled || \'false\'}}" ng-focus="$mdTabsCtrl.hasFocus = true" ng-blur="$mdTabsCtrl.hasFocus = false" ng-repeat="tab in $mdTabsCtrl.tabs" md-tabs-template="::tab.label" md-scope="::tab.parent"></md-dummy-tab> </md-tabs-dummy-wrapper> </md-tabs-canvas> </md-tabs-wrapper> <md-tabs-content-wrapper ng-show="$mdTabsCtrl.hasContent && $mdTabsCtrl.selectedIndex >= 0" class="_md"> <md-tab-content id="tab-content-{{::tab.id}}" class="_md" role="tabpanel" aria-labelledby="tab-item-{{::tab.id}}" md-swipe-left="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(1)" md-swipe-right="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(-1)" ng-if="$mdTabsCtrl.hasContent" ng-repeat="(index, tab) in $mdTabsCtrl.tabs" ng-class="{ \'md-no-transition\': $mdTabsCtrl.lastSelectedIndex == null, \'md-active\': tab.isActive(), \'md-left\': tab.isLeft(), \'md-right\': tab.isRight(), \'md-no-scroll\': $mdTabsCtrl.dynamicHeight }"> <div md-tabs-template="::tab.template" md-connected-if="tab.isActive()" md-scope="::tab.parent" ng-if="$mdTabsCtrl.enableDisconnect || tab.shouldRender()"></div> </md-tab-content> </md-tabs-content-wrapper>'},controller:"MdTabsController",controllerAs:"$mdTabsCtrl",bindToController:!0}}t.module("material.components.tabs").directive("mdTabs",e),e.$inject=["$$mdSvgRegistry"]}(),function(){function e(e){return{require:"^?mdTabs",link:function(e,t,n,o){if(o){var i=new MutationObserver(function(e){o.updatePagination(),o.updateInkBarStyles()}),r={childList:!0,subtree:!0};i.observe(t[0],r),e.$on("$destroy",function(){i&&i.disconnect()})}}}}t.module("material.components.tabs").directive("mdTabsDummyWrapper",e),e.$inject=["$mdUtil"]}(),function(){function e(e,t){function n(n,o,i,r){function a(){n.$watch("connected",function(e){e===!1?d():s()}),n.$on("$destroy",s)}function d(){r.enableDisconnect&&t.disconnectScope(c)}function s(){r.enableDisconnect&&t.reconnectScope(c)}if(r){var c=r.enableDisconnect?n.compileScope.$new():n.compileScope;return o.html(n.template),e(o.contents())(c),t.nextTick(a)}}return{restrict:"A",link:n,scope:{template:"=mdTabsTemplate",connected:"=?mdConnectedIf",compileScope:"=mdScope"},require:"^?mdTabs"}}t.module("material.components.tabs").directive("mdTabsTemplate",e),e.$inject=["$compile","$mdUtil"]}(),function(){t.module("material.core").constant("$MD_THEME_CSS","/* Only used with Theme processes */html.md-THEME_NAME-theme, body.md-THEME_NAME-theme { color: '{{foreground-1}}'; background-color: '{{background-color}}'; }md-autocomplete.md-THEME_NAME-theme { background: '{{background-A100}}'; } md-autocomplete.md-THEME_NAME-theme[disabled]:not([md-floating-label]) { background: '{{background-100}}'; } md-autocomplete.md-THEME_NAME-theme button md-icon path { fill: '{{background-600}}'; } md-autocomplete.md-THEME_NAME-theme button:after { background: '{{background-600-0.3}}'; }.md-autocomplete-suggestions-container.md-THEME_NAME-theme { background: '{{background-A100}}'; } .md-autocomplete-suggestions-container.md-THEME_NAME-theme li { color: '{{background-900}}'; } .md-autocomplete-suggestions-container.md-THEME_NAME-theme li .highlight { color: '{{background-600}}'; } .md-autocomplete-suggestions-container.md-THEME_NAME-theme li:hover, .md-autocomplete-suggestions-container.md-THEME_NAME-theme li.selected { background: '{{background-200}}'; }md-backdrop { background-color: '{{background-900-0.0}}'; } md-backdrop.md-opaque.md-THEME_NAME-theme { background-color: '{{background-900-1.0}}'; }.md-button.md-THEME_NAME-theme:not([disabled]):hover { background-color: '{{background-500-0.2}}'; }.md-button.md-THEME_NAME-theme:not([disabled]).md-focused { background-color: '{{background-500-0.2}}'; }.md-button.md-THEME_NAME-theme:not([disabled]).md-icon-button:hover { background-color: transparent; }.md-button.md-THEME_NAME-theme.md-fab { background-color: '{{accent-color}}'; color: '{{accent-contrast}}'; } .md-button.md-THEME_NAME-theme.md-fab md-icon { color: '{{accent-contrast}}'; } .md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover { background-color: '{{accent-A700}}'; } .md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused { background-color: '{{accent-A700}}'; }.md-button.md-THEME_NAME-theme.md-primary { color: '{{primary-color}}'; } .md-button.md-THEME_NAME-theme.md-primary.md-raised, .md-button.md-THEME_NAME-theme.md-primary.md-fab { color: '{{primary-contrast}}'; background-color: '{{primary-color}}'; } .md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]) md-icon { color: '{{primary-contrast}}'; } .md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]):hover { background-color: '{{primary-600}}'; } .md-button.md-THEME_NAME-theme.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-primary.md-fab:not([disabled]).md-focused { background-color: '{{primary-600}}'; } .md-button.md-THEME_NAME-theme.md-primary:not([disabled]) md-icon { color: '{{primary-color}}'; }.md-button.md-THEME_NAME-theme.md-fab { background-color: '{{accent-color}}'; color: '{{accent-contrast}}'; } .md-button.md-THEME_NAME-theme.md-fab:not([disabled]) .md-icon { color: '{{accent-contrast}}'; } .md-button.md-THEME_NAME-theme.md-fab:not([disabled]):hover { background-color: '{{accent-A700}}'; } .md-button.md-THEME_NAME-theme.md-fab:not([disabled]).md-focused { background-color: '{{accent-A700}}'; }.md-button.md-THEME_NAME-theme.md-raised { color: '{{background-900}}'; background-color: '{{background-50}}'; } .md-button.md-THEME_NAME-theme.md-raised:not([disabled]) md-icon { color: '{{background-900}}'; } .md-button.md-THEME_NAME-theme.md-raised:not([disabled]):hover { background-color: '{{background-50}}'; } .md-button.md-THEME_NAME-theme.md-raised:not([disabled]).md-focused { background-color: '{{background-200}}'; }.md-button.md-THEME_NAME-theme.md-warn { color: '{{warn-color}}'; } .md-button.md-THEME_NAME-theme.md-warn.md-raised, .md-button.md-THEME_NAME-theme.md-warn.md-fab { color: '{{warn-contrast}}'; background-color: '{{warn-color}}'; } .md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]) md-icon { color: '{{warn-contrast}}'; } .md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]):hover { background-color: '{{warn-600}}'; } .md-button.md-THEME_NAME-theme.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-warn.md-fab:not([disabled]).md-focused { background-color: '{{warn-600}}'; } .md-button.md-THEME_NAME-theme.md-warn:not([disabled]) md-icon { color: '{{warn-color}}'; }.md-button.md-THEME_NAME-theme.md-accent { color: '{{accent-color}}'; } .md-button.md-THEME_NAME-theme.md-accent.md-raised, .md-button.md-THEME_NAME-theme.md-accent.md-fab { color: '{{accent-contrast}}'; background-color: '{{accent-color}}'; } .md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]) md-icon, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]) md-icon { color: '{{accent-contrast}}'; } .md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]):hover, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]):hover { background-color: '{{accent-A700}}'; } .md-button.md-THEME_NAME-theme.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-THEME_NAME-theme.md-accent.md-fab:not([disabled]).md-focused { background-color: '{{accent-A700}}'; } .md-button.md-THEME_NAME-theme.md-accent:not([disabled]) md-icon { color: '{{accent-color}}'; }.md-button.md-THEME_NAME-theme[disabled], .md-button.md-THEME_NAME-theme.md-raised[disabled], .md-button.md-THEME_NAME-theme.md-fab[disabled], .md-button.md-THEME_NAME-theme.md-accent[disabled], .md-button.md-THEME_NAME-theme.md-warn[disabled] { color: '{{foreground-3}}'; cursor: default; } .md-button.md-THEME_NAME-theme[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-raised[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-fab[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-accent[disabled] md-icon, .md-button.md-THEME_NAME-theme.md-warn[disabled] md-icon { color: '{{foreground-3}}'; }.md-button.md-THEME_NAME-theme.md-raised[disabled], .md-button.md-THEME_NAME-theme.md-fab[disabled] { background-color: '{{foreground-4}}'; }.md-button.md-THEME_NAME-theme[disabled] { background-color: transparent; }._md a.md-THEME_NAME-theme:not(.md-button).md-primary { color: '{{primary-color}}'; } ._md a.md-THEME_NAME-theme:not(.md-button).md-primary:hover { color: '{{primary-700}}'; }._md a.md-THEME_NAME-theme:not(.md-button).md-accent { color: '{{accent-color}}'; } ._md a.md-THEME_NAME-theme:not(.md-button).md-accent:hover { color: '{{accent-700}}'; }._md a.md-THEME_NAME-theme:not(.md-button).md-accent { color: '{{accent-color}}'; } ._md a.md-THEME_NAME-theme:not(.md-button).md-accent:hover { color: '{{accent-A700}}'; }._md a.md-THEME_NAME-theme:not(.md-button).md-warn { color: '{{warn-color}}'; } ._md a.md-THEME_NAME-theme:not(.md-button).md-warn:hover { color: '{{warn-700}}'; }md-bottom-sheet.md-THEME_NAME-theme { background-color: '{{background-50}}'; border-top-color: '{{background-300}}'; } md-bottom-sheet.md-THEME_NAME-theme.md-list md-list-item { color: '{{foreground-1}}'; } md-bottom-sheet.md-THEME_NAME-theme .md-subheader { background-color: '{{background-50}}'; } md-bottom-sheet.md-THEME_NAME-theme .md-subheader { color: '{{foreground-1}}'; }md-checkbox.md-THEME_NAME-theme .md-ripple { color: '{{accent-A700}}'; }md-checkbox.md-THEME_NAME-theme.md-checked .md-ripple { color: '{{background-600}}'; }md-checkbox.md-THEME_NAME-theme.md-checked.md-focused ._md-container:before { background-color: '{{accent-color-0.26}}'; }md-checkbox.md-THEME_NAME-theme .md-ink-ripple { color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme.md-checked .md-ink-ripple { color: '{{accent-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme ._md-icon { border-color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme.md-checked ._md-icon { background-color: '{{accent-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme.md-checked ._md-icon:after { border-color: '{{accent-contrast-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-ripple { color: '{{primary-600}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ripple { color: '{{background-600}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-ink-ripple { color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple { color: '{{primary-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary ._md-icon { border-color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked ._md-icon { background-color: '{{primary-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked.md-focused ._md-container:before { background-color: '{{primary-color-0.26}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked ._md-icon:after { border-color: '{{primary-contrast-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-primary .md-indeterminate[disabled] ._md-container { color: '{{foreground-3}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn .md-ripple { color: '{{warn-600}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn .md-ink-ripple { color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple { color: '{{warn-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn ._md-icon { border-color: '{{foreground-2}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked ._md-icon { background-color: '{{warn-color-0.87}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) ._md-container:before { background-color: '{{warn-color-0.26}}'; }md-checkbox.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked ._md-icon:after { border-color: '{{background-200}}'; }md-checkbox.md-THEME_NAME-theme[disabled] ._md-icon { border-color: '{{foreground-3}}'; }md-checkbox.md-THEME_NAME-theme[disabled].md-checked ._md-icon { background-color: '{{foreground-3}}'; }md-checkbox.md-THEME_NAME-theme[disabled].md-checked ._md-icon:after { border-color: '{{background-200}}'; }md-checkbox.md-THEME_NAME-theme[disabled] ._md-icon:after { border-color: '{{foreground-3}}'; }md-checkbox.md-THEME_NAME-theme[disabled] ._md-label { color: '{{foreground-3}}'; }md-chips.md-THEME_NAME-theme .md-chips { box-shadow: 0 1px '{{foreground-4}}'; } md-chips.md-THEME_NAME-theme .md-chips.md-focused { box-shadow: 0 2px '{{primary-color}}'; } md-chips.md-THEME_NAME-theme .md-chips ._md-chip-input-container input { color: '{{foreground-1}}'; } md-chips.md-THEME_NAME-theme .md-chips ._md-chip-input-container input::-webkit-input-placeholder { color: '{{foreground-3}}'; } md-chips.md-THEME_NAME-theme .md-chips ._md-chip-input-container input:-moz-placeholder { color: '{{foreground-3}}'; } md-chips.md-THEME_NAME-theme .md-chips ._md-chip-input-container input::-moz-placeholder { color: '{{foreground-3}}'; } md-chips.md-THEME_NAME-theme .md-chips ._md-chip-input-container input:-ms-input-placeholder { color: '{{foreground-3}}'; } md-chips.md-THEME_NAME-theme .md-chips ._md-chip-input-container input::-webkit-input-placeholder { color: '{{foreground-3}}'; }md-chips.md-THEME_NAME-theme md-chip { background: '{{background-300}}'; color: '{{background-800}}'; } md-chips.md-THEME_NAME-theme md-chip md-icon { color: '{{background-700}}'; } md-chips.md-THEME_NAME-theme md-chip.md-focused { background: '{{primary-color}}'; color: '{{primary-contrast}}'; } md-chips.md-THEME_NAME-theme md-chip.md-focused md-icon { color: '{{primary-contrast}}'; } md-chips.md-THEME_NAME-theme md-chip._md-chip-editing { background: transparent; color: '{{background-800}}'; }md-chips.md-THEME_NAME-theme md-chip-remove .md-button md-icon path { fill: '{{background-500}}'; }.md-contact-suggestion span.md-contact-email { color: '{{background-400}}'; }md-card.md-THEME_NAME-theme { color: '{{foreground-1}}'; background-color: '{{background-hue-1}}'; border-radius: 2px; } md-card.md-THEME_NAME-theme .md-card-image { border-radius: 2px 2px 0 0; } md-card.md-THEME_NAME-theme md-card-header md-card-avatar md-icon { color: '{{background-color}}'; background-color: '{{foreground-3}}'; } md-card.md-THEME_NAME-theme md-card-header md-card-header-text .md-subhead { color: '{{foreground-2}}'; } md-card.md-THEME_NAME-theme md-card-title md-card-title-text:not(:only-child) .md-subhead { color: '{{foreground-2}}'; }md-content.md-THEME_NAME-theme { color: '{{foreground-1}}'; background-color: '{{background-default}}'; }/** Theme styles for mdCalendar. */.md-calendar.md-THEME_NAME-theme { background: '{{background-A100}}'; color: '{{background-A200-0.87}}'; } .md-calendar.md-THEME_NAME-theme tr:last-child td { border-bottom-color: '{{background-200}}'; }.md-THEME_NAME-theme .md-calendar-day-header { background: '{{background-300}}'; color: '{{background-A200-0.87}}'; }.md-THEME_NAME-theme .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator { border: 1px solid '{{primary-500}}'; }.md-THEME_NAME-theme .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled { color: '{{primary-500-0.6}}'; }.md-calendar-date.md-focus .md-THEME_NAME-theme .md-calendar-date-selection-indicator, .md-THEME_NAME-theme .md-calendar-date-selection-indicator:hover { background: '{{background-300}}'; }.md-THEME_NAME-theme .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-THEME_NAME-theme .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator { background: '{{primary-500}}'; color: '{{primary-500-contrast}}'; border-color: transparent; }.md-THEME_NAME-theme .md-calendar-date-disabled,.md-THEME_NAME-theme .md-calendar-month-label-disabled { color: '{{background-A200-0.435}}'; }/** Theme styles for mdDatepicker. */.md-THEME_NAME-theme .md-datepicker-input { color: '{{foreground-1}}'; } .md-THEME_NAME-theme .md-datepicker-input::-webkit-input-placeholder { color: '{{foreground-3}}'; } .md-THEME_NAME-theme .md-datepicker-input:-moz-placeholder { color: '{{foreground-3}}'; } .md-THEME_NAME-theme .md-datepicker-input::-moz-placeholder { color: '{{foreground-3}}'; } .md-THEME_NAME-theme .md-datepicker-input:-ms-input-placeholder { color: '{{foreground-3}}'; } .md-THEME_NAME-theme .md-datepicker-input::-webkit-input-placeholder { color: '{{foreground-3}}'; }.md-THEME_NAME-theme .md-datepicker-input-container { border-bottom-color: '{{foreground-4}}'; } .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-focused { border-bottom-color: '{{primary-color}}'; } .md-THEME_NAME-theme .md-datepicker-input-container.md-datepicker-invalid { border-bottom-color: '{{warn-A700}}'; }.md-THEME_NAME-theme .md-datepicker-calendar-pane { border-color: '{{background-hue-1}}'; }.md-THEME_NAME-theme .md-datepicker-triangle-button .md-datepicker-expand-triangle { border-top-color: '{{foreground-3}}'; }.md-THEME_NAME-theme .md-datepicker-triangle-button:hover .md-datepicker-expand-triangle { border-top-color: '{{foreground-2}}'; }.md-THEME_NAME-theme .md-datepicker-open .md-datepicker-calendar-icon { fill: '{{primary-500}}'; }.md-THEME_NAME-theme .md-datepicker-open .md-datepicker-input-container,.md-THEME_NAME-theme .md-datepicker-input-mask-opaque { background: '{{background-hue-1}}'; }.md-THEME_NAME-theme .md-datepicker-calendar { background: '{{background-A100}}'; }md-dialog.md-THEME_NAME-theme { border-radius: 4px; background-color: '{{background-hue-1}}'; } md-dialog.md-THEME_NAME-theme.md-content-overflow .md-actions, md-dialog.md-THEME_NAME-theme.md-content-overflow md-dialog-actions { border-top-color: '{{foreground-4}}'; }md-divider.md-THEME_NAME-theme { border-top-color: '{{foreground-4}}'; }.layout-row > md-divider.md-THEME_NAME-theme,.layout-xs-row > md-divider.md-THEME_NAME-theme, .layout-gt-xs-row > md-divider.md-THEME_NAME-theme,.layout-sm-row > md-divider.md-THEME_NAME-theme, .layout-gt-sm-row > md-divider.md-THEME_NAME-theme,.layout-md-row > md-divider.md-THEME_NAME-theme, .layout-gt-md-row > md-divider.md-THEME_NAME-theme,.layout-lg-row > md-divider.md-THEME_NAME-theme, .layout-gt-lg-row > md-divider.md-THEME_NAME-theme,.layout-xl-row > md-divider.md-THEME_NAME-theme { border-right-color: '{{foreground-4}}'; }md-icon.md-THEME_NAME-theme { color: '{{foreground-2}}'; } md-icon.md-THEME_NAME-theme.md-primary { color: '{{primary-color}}'; } md-icon.md-THEME_NAME-theme.md-accent { color: '{{accent-color}}'; } md-icon.md-THEME_NAME-theme.md-warn { color: '{{warn-color}}'; }md-input-container.md-THEME_NAME-theme .md-input { color: '{{foreground-1}}'; border-color: '{{foreground-4}}'; } md-input-container.md-THEME_NAME-theme .md-input::-webkit-input-placeholder { color: '{{foreground-3}}'; } md-input-container.md-THEME_NAME-theme .md-input:-moz-placeholder { color: '{{foreground-3}}'; } md-input-container.md-THEME_NAME-theme .md-input::-moz-placeholder { color: '{{foreground-3}}'; } md-input-container.md-THEME_NAME-theme .md-input:-ms-input-placeholder { color: '{{foreground-3}}'; } md-input-container.md-THEME_NAME-theme .md-input::-webkit-input-placeholder { color: '{{foreground-3}}'; }md-input-container.md-THEME_NAME-theme > md-icon { color: '{{foreground-1}}'; }md-input-container.md-THEME_NAME-theme label,md-input-container.md-THEME_NAME-theme ._md-placeholder { color: '{{foreground-3}}'; }md-input-container.md-THEME_NAME-theme label.md-required:after { color: '{{warn-A700}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-focused):not(.md-input-invalid) label.md-required:after { color: '{{foreground-2}}'; }md-input-container.md-THEME_NAME-theme .md-input-messages-animation, md-input-container.md-THEME_NAME-theme .md-input-message-animation { color: '{{warn-A700}}'; } md-input-container.md-THEME_NAME-theme .md-input-messages-animation .md-char-counter, md-input-container.md-THEME_NAME-theme .md-input-message-animation .md-char-counter { color: '{{foreground-1}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-has-value label { color: '{{foreground-2}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused .md-input, md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-resized .md-input { border-color: '{{primary-color}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused label { color: '{{primary-color}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused md-icon { color: '{{primary-color}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent .md-input { border-color: '{{accent-color}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-accent label { color: '{{accent-color}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn .md-input { border-color: '{{warn-A700}}'; }md-input-container.md-THEME_NAME-theme:not(.md-input-invalid).md-input-focused.md-warn label { color: '{{warn-A700}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input { border-color: '{{warn-A700}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid label { color: '{{warn-A700}}'; }md-input-container.md-THEME_NAME-theme.md-input-invalid .md-input-message-animation, md-input-container.md-THEME_NAME-theme.md-input-invalid .md-char-counter { color: '{{warn-A700}}'; }md-input-container.md-THEME_NAME-theme .md-input[disabled],[disabled] md-input-container.md-THEME_NAME-theme .md-input { border-bottom-color: transparent; color: '{{foreground-3}}'; background-image: linear-gradient(to right, \"{{foreground-3}}\" 0%, \"{{foreground-3}}\" 33%, transparent 0%); background-image: -ms-linear-gradient(left, transparent 0%, \"{{foreground-3}}\" 100%); }md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h3, md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text h4,md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h3,md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text h4 { color: '{{foreground-1}}'; }md-list.md-THEME_NAME-theme md-list-item.md-2-line .md-list-item-text p,md-list.md-THEME_NAME-theme md-list-item.md-3-line .md-list-item-text p { color: '{{foreground-2}}'; }md-list.md-THEME_NAME-theme ._md-proxy-focus.md-focused div._md-no-style { background-color: '{{background-100}}'; }md-list.md-THEME_NAME-theme md-list-item .md-avatar-icon { background-color: '{{foreground-3}}'; color: '{{background-color}}'; }md-list.md-THEME_NAME-theme md-list-item > md-icon { color: '{{foreground-2}}'; } md-list.md-THEME_NAME-theme md-list-item > md-icon.md-highlight { color: '{{primary-color}}'; } md-list.md-THEME_NAME-theme md-list-item > md-icon.md-highlight.md-accent { color: '{{accent-color}}'; }md-menu-content.md-THEME_NAME-theme { background-color: '{{background-A100}}'; } md-menu-content.md-THEME_NAME-theme md-menu-item { color: '{{background-A200-0.87}}'; } md-menu-content.md-THEME_NAME-theme md-menu-item md-icon { color: '{{background-A200-0.54}}'; } md-menu-content.md-THEME_NAME-theme md-menu-item .md-button[disabled] { color: '{{background-A200-0.25}}'; } md-menu-content.md-THEME_NAME-theme md-menu-item .md-button[disabled] md-icon { color: '{{background-A200-0.25}}'; } md-menu-content.md-THEME_NAME-theme md-menu-divider { background-color: '{{background-A200-0.11}}'; }md-menu-bar.md-THEME_NAME-theme > button.md-button { color: '{{foreground-2}}'; border-radius: 2px; }md-menu-bar.md-THEME_NAME-theme md-menu._md-open > button, md-menu-bar.md-THEME_NAME-theme md-menu > button:focus { outline: none; background: '{{background-200}}'; }md-menu-bar.md-THEME_NAME-theme._md-open:not(._md-keyboard-mode) md-menu:hover > button { background-color: '{{ background-500-0.2}}'; }md-menu-bar.md-THEME_NAME-theme:not(._md-keyboard-mode):not(._md-open) md-menu button:hover,md-menu-bar.md-THEME_NAME-theme:not(._md-keyboard-mode):not(._md-open) md-menu button:focus { background: transparent; }md-menu-content.md-THEME_NAME-theme .md-menu > .md-button:after { color: '{{background-A200-0.54}}'; }md-menu-content.md-THEME_NAME-theme .md-menu._md-open > .md-button { background-color: '{{ background-500-0.2}}'; }md-toolbar.md-THEME_NAME-theme.md-menu-toolbar { background-color: '{{background-A100}}'; color: '{{background-A200}}'; } md-toolbar.md-THEME_NAME-theme.md-menu-toolbar md-toolbar-filler { background-color: '{{primary-color}}'; color: '{{background-A100-0.87}}'; } md-toolbar.md-THEME_NAME-theme.md-menu-toolbar md-toolbar-filler md-icon { color: '{{background-A100-0.87}}'; }md-nav-bar.md-THEME_NAME-theme .md-nav-bar { background-color: transparent; border-color: '{{foreground-4}}'; }md-nav-bar.md-THEME_NAME-theme .md-button._md-nav-button.md-unselected { color: '{{foreground-2}}'; }md-nav-bar.md-THEME_NAME-theme md-nav-ink-bar { color: '{{accent-color}}'; background: '{{accent-color}}'; }.md-panel { background-color: '{{background-900-0.0}}'; } .md-panel._md-panel-backdrop.md-THEME_NAME-theme { background-color: '{{background-900-1.0}}'; }md-progress-linear.md-THEME_NAME-theme ._md-container { background-color: '{{primary-100}}'; }md-progress-linear.md-THEME_NAME-theme ._md-bar { background-color: '{{primary-color}}'; }md-progress-linear.md-THEME_NAME-theme.md-warn ._md-container { background-color: '{{warn-100}}'; }md-progress-linear.md-THEME_NAME-theme.md-warn ._md-bar { background-color: '{{warn-color}}'; }md-progress-linear.md-THEME_NAME-theme.md-accent ._md-container { background-color: '{{accent-A100}}'; }md-progress-linear.md-THEME_NAME-theme.md-accent ._md-bar { background-color: '{{accent-color}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn ._md-bar1 { background-color: '{{warn-100}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-warn ._md-dashed:before { background: radial-gradient(\"{{warn-100}}\" 0%, \"{{warn-100}}\" 16%, transparent 42%); }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent ._md-bar1 { background-color: '{{accent-A100}}'; }md-progress-linear.md-THEME_NAME-theme[md-mode=buffer].md-accent ._md-dashed:before { background: radial-gradient(\"{{accent-A100}}\" 0%, \"{{accent-A100}}\" 16%, transparent 42%); }md-progress-circular.md-THEME_NAME-theme path { stroke: '{{primary-color}}'; }md-progress-circular.md-THEME_NAME-theme.md-warn path { stroke: '{{warn-color}}'; }md-progress-circular.md-THEME_NAME-theme.md-accent path { stroke: '{{accent-color}}'; }md-radio-button.md-THEME_NAME-theme ._md-off { border-color: '{{foreground-2}}'; }md-radio-button.md-THEME_NAME-theme ._md-on { background-color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme.md-checked ._md-off { border-color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme.md-checked .md-ink-ripple { color: '{{accent-color-0.87}}'; }md-radio-button.md-THEME_NAME-theme ._md-container .md-ripple { color: '{{accent-A700}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary ._md-on, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary ._md-on,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary ._md-on,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary ._md-on { background-color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked ._md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked ._md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked ._md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked ._md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked ._md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked ._md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked ._md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked ._md-off { border-color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary.md-checked .md-ink-ripple { color: '{{primary-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-primary ._md-container .md-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-primary ._md-container .md-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-primary ._md-container .md-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-primary ._md-container .md-ripple { color: '{{primary-600}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn ._md-on, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn ._md-on,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn ._md-on,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn ._md-on { background-color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked ._md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked ._md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked ._md-off, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked ._md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked ._md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked ._md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked ._md-off,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked ._md-off { border-color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn.md-checked .md-ink-ripple { color: '{{warn-color-0.87}}'; }md-radio-group.md-THEME_NAME-theme:not([disabled]) .md-warn ._md-container .md-ripple, md-radio-group.md-THEME_NAME-theme:not([disabled]).md-warn ._md-container .md-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]) .md-warn ._md-container .md-ripple,md-radio-button.md-THEME_NAME-theme:not([disabled]).md-warn ._md-container .md-ripple { color: '{{warn-600}}'; }md-radio-group.md-THEME_NAME-theme[disabled],md-radio-button.md-THEME_NAME-theme[disabled] { color: '{{foreground-3}}'; } md-radio-group.md-THEME_NAME-theme[disabled] ._md-container ._md-off, md-radio-button.md-THEME_NAME-theme[disabled] ._md-container ._md-off { border-color: '{{foreground-3}}'; } md-radio-group.md-THEME_NAME-theme[disabled] ._md-container ._md-on, md-radio-button.md-THEME_NAME-theme[disabled] ._md-container ._md-on { border-color: '{{foreground-3}}'; }md-radio-group.md-THEME_NAME-theme .md-checked .md-ink-ripple { color: '{{accent-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-primary .md-checked:not([disabled]) .md-ink-ripple, md-radio-group.md-THEME_NAME-theme .md-checked:not([disabled]).md-primary .md-ink-ripple { color: '{{primary-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme .md-checked.md-primary .md-ink-ripple { color: '{{warn-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked ._md-container:before { background-color: '{{accent-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty).md-primary .md-checked ._md-container:before,md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-primary ._md-container:before { background-color: '{{primary-color-0.26}}'; }md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty).md-warn .md-checked ._md-container:before,md-radio-group.md-THEME_NAME-theme.md-focused:not(:empty) .md-checked.md-warn ._md-container:before { background-color: '{{warn-color-0.26}}'; }md-select.md-THEME_NAME-theme[disabled] ._md-select-value { border-bottom-color: transparent; background-image: linear-gradient(to right, \"{{foreground-3}}\" 0%, \"{{foreground-3}}\" 33%, transparent 0%); background-image: -ms-linear-gradient(left, transparent 0%, \"{{foreground-3}}\" 100%); }md-select.md-THEME_NAME-theme ._md-select-value { border-bottom-color: '{{foreground-4}}'; } md-select.md-THEME_NAME-theme ._md-select-value._md-select-placeholder { color: '{{foreground-3}}'; }md-select.md-THEME_NAME-theme.ng-invalid.ng-dirty ._md-select-value { color: '{{warn-A700}}' !important; border-bottom-color: '{{warn-A700}}' !important; }md-select.md-THEME_NAME-theme:not([disabled]):focus ._md-select-value { border-bottom-color: '{{primary-color}}'; color: '{{ foreground-1 }}'; } md-select.md-THEME_NAME-theme:not([disabled]):focus ._md-select-value._md-select-placeholder { color: '{{ foreground-1 }}'; }md-select.md-THEME_NAME-theme:not([disabled]):focus.md-accent ._md-select-value { border-bottom-color: '{{accent-color}}'; }md-select.md-THEME_NAME-theme:not([disabled]):focus.md-warn ._md-select-value { border-bottom-color: '{{warn-color}}'; }md-select.md-THEME_NAME-theme[disabled] ._md-select-value { color: '{{foreground-3}}'; } md-select.md-THEME_NAME-theme[disabled] ._md-select-value._md-select-placeholder { color: '{{foreground-3}}'; }md-select-menu.md-THEME_NAME-theme md-content { background: '{{background-A100}}'; } md-select-menu.md-THEME_NAME-theme md-content md-optgroup { color: '{{background-600-0.87}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option { color: '{{background-900-0.87}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option[disabled] ._md-text { color: '{{background-400-0.87}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option:not([disabled]):focus, md-select-menu.md-THEME_NAME-theme md-content md-option:not([disabled]):hover { background: '{{background-200}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option[selected] { color: '{{primary-500}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option[selected]:focus { color: '{{primary-600}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option[selected].md-accent { color: '{{accent-color}}'; } md-select-menu.md-THEME_NAME-theme md-content md-option[selected].md-accent:focus { color: '{{accent-A700}}'; }._md-checkbox-enabled.md-THEME_NAME-theme .md-ripple { color: '{{primary-600}}'; }._md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-ripple { color: '{{background-600}}'; }._md-checkbox-enabled.md-THEME_NAME-theme .md-ink-ripple { color: '{{foreground-2}}'; }._md-checkbox-enabled.md-THEME_NAME-theme[selected] .md-ink-ripple { color: '{{primary-color-0.87}}'; }._md-checkbox-enabled.md-THEME_NAME-theme ._md-icon { border-color: '{{foreground-2}}'; }._md-checkbox-enabled.md-THEME_NAME-theme[selected] ._md-icon { background-color: '{{primary-color-0.87}}'; }._md-checkbox-enabled.md-THEME_NAME-theme[selected].md-focused ._md-container:before { background-color: '{{primary-color-0.26}}'; }._md-checkbox-enabled.md-THEME_NAME-theme[selected] ._md-icon:after { border-color: '{{primary-contrast-0.87}}'; }._md-checkbox-enabled.md-THEME_NAME-theme .md-indeterminate[disabled] ._md-container { color: '{{foreground-3}}'; }._md-checkbox-enabled.md-THEME_NAME-theme md-option ._md-text { color: '{{background-900-0.87}}'; }md-sidenav.md-THEME_NAME-theme, md-sidenav.md-THEME_NAME-theme md-content { background-color: '{{background-hue-1}}'; }md-slider.md-THEME_NAME-theme ._md-track { background-color: '{{foreground-3}}'; }md-slider.md-THEME_NAME-theme ._md-track-ticks { color: '{{background-contrast}}'; }md-slider.md-THEME_NAME-theme ._md-focus-ring { background-color: '{{accent-A200-0.2}}'; }md-slider.md-THEME_NAME-theme ._md-disabled-thumb { border-color: '{{background-color}}'; background-color: '{{background-color}}'; }md-slider.md-THEME_NAME-theme._md-min ._md-thumb:after { background-color: '{{background-color}}'; border-color: '{{foreground-3}}'; }md-slider.md-THEME_NAME-theme._md-min ._md-focus-ring { background-color: '{{foreground-3-0.38}}'; }md-slider.md-THEME_NAME-theme._md-min[md-discrete] ._md-thumb:after { background-color: '{{background-contrast}}'; border-color: transparent; }md-slider.md-THEME_NAME-theme._md-min[md-discrete] ._md-sign { background-color: '{{background-400}}'; } md-slider.md-THEME_NAME-theme._md-min[md-discrete] ._md-sign:after { border-top-color: '{{background-400}}'; }md-slider.md-THEME_NAME-theme._md-min[md-discrete][md-vertical] ._md-sign:after { border-top-color: transparent; border-left-color: '{{background-400}}'; }md-slider.md-THEME_NAME-theme ._md-track._md-track-fill { background-color: '{{accent-color}}'; }md-slider.md-THEME_NAME-theme ._md-thumb:after { border-color: '{{accent-color}}'; background-color: '{{accent-color}}'; }md-slider.md-THEME_NAME-theme ._md-sign { background-color: '{{accent-color}}'; } md-slider.md-THEME_NAME-theme ._md-sign:after { border-top-color: '{{accent-color}}'; }md-slider.md-THEME_NAME-theme[md-vertical] ._md-sign:after { border-top-color: transparent; border-left-color: '{{accent-color}}'; }md-slider.md-THEME_NAME-theme ._md-thumb-text { color: '{{accent-contrast}}'; }md-slider.md-THEME_NAME-theme.md-warn ._md-focus-ring { background-color: '{{warn-200-0.38}}'; }md-slider.md-THEME_NAME-theme.md-warn ._md-track._md-track-fill { background-color: '{{warn-color}}'; }md-slider.md-THEME_NAME-theme.md-warn ._md-thumb:after { border-color: '{{warn-color}}'; background-color: '{{warn-color}}'; }md-slider.md-THEME_NAME-theme.md-warn ._md-sign { background-color: '{{warn-color}}'; } md-slider.md-THEME_NAME-theme.md-warn ._md-sign:after { border-top-color: '{{warn-color}}'; }md-slider.md-THEME_NAME-theme.md-warn[md-vertical] ._md-sign:after { border-top-color: transparent; border-left-color: '{{warn-color}}'; }md-slider.md-THEME_NAME-theme.md-warn ._md-thumb-text { color: '{{warn-contrast}}'; }md-slider.md-THEME_NAME-theme.md-primary ._md-focus-ring { background-color: '{{primary-200-0.38}}'; }md-slider.md-THEME_NAME-theme.md-primary ._md-track._md-track-fill { background-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme.md-primary ._md-thumb:after { border-color: '{{primary-color}}'; background-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme.md-primary ._md-sign { background-color: '{{primary-color}}'; } md-slider.md-THEME_NAME-theme.md-primary ._md-sign:after { border-top-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme.md-primary[md-vertical] ._md-sign:after { border-top-color: transparent; border-left-color: '{{primary-color}}'; }md-slider.md-THEME_NAME-theme.md-primary ._md-thumb-text { color: '{{primary-contrast}}'; }md-slider.md-THEME_NAME-theme[disabled] ._md-thumb:after { border-color: transparent; }md-slider.md-THEME_NAME-theme[disabled]:not(._md-min) ._md-thumb:after, md-slider.md-THEME_NAME-theme[disabled][md-discrete] ._md-thumb:after { background-color: '{{foreground-3}}'; border-color: transparent; }md-slider.md-THEME_NAME-theme[disabled][readonly] ._md-sign { background-color: '{{background-400}}'; } md-slider.md-THEME_NAME-theme[disabled][readonly] ._md-sign:after { border-top-color: '{{background-400}}'; }md-slider.md-THEME_NAME-theme[disabled][readonly][md-vertical] ._md-sign:after { border-top-color: transparent; border-left-color: '{{background-400}}'; }md-slider.md-THEME_NAME-theme[disabled][readonly] ._md-disabled-thumb { border-color: transparent; background-color: transparent; }md-slider-container[disabled] > *:first-child:not(md-slider),md-slider-container[disabled] > *:last-child:not(md-slider) { color: '{{foreground-3}}'; }.md-subheader.md-THEME_NAME-theme { color: '{{ foreground-2-0.23 }}'; background-color: '{{background-default}}'; } .md-subheader.md-THEME_NAME-theme.md-primary { color: '{{primary-color}}'; } .md-subheader.md-THEME_NAME-theme.md-accent { color: '{{accent-color}}'; } .md-subheader.md-THEME_NAME-theme.md-warn { color: '{{warn-color}}'; }md-switch.md-THEME_NAME-theme .md-ink-ripple { color: '{{background-500}}'; }md-switch.md-THEME_NAME-theme ._md-thumb { background-color: '{{background-50}}'; }md-switch.md-THEME_NAME-theme ._md-bar { background-color: '{{background-500}}'; }md-switch.md-THEME_NAME-theme.md-checked .md-ink-ripple { color: '{{accent-color}}'; }md-switch.md-THEME_NAME-theme.md-checked ._md-thumb { background-color: '{{accent-color}}'; }md-switch.md-THEME_NAME-theme.md-checked ._md-bar { background-color: '{{accent-color-0.5}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-focused ._md-thumb:before { background-color: '{{accent-color-0.26}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-primary .md-ink-ripple { color: '{{primary-color}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-primary ._md-thumb { background-color: '{{primary-color}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-primary ._md-bar { background-color: '{{primary-color-0.5}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-primary.md-focused ._md-thumb:before { background-color: '{{primary-color-0.26}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-warn .md-ink-ripple { color: '{{warn-color}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-warn ._md-thumb { background-color: '{{warn-color}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-warn ._md-bar { background-color: '{{warn-color-0.5}}'; }md-switch.md-THEME_NAME-theme.md-checked.md-warn.md-focused ._md-thumb:before { background-color: '{{warn-color-0.26}}'; }md-switch.md-THEME_NAME-theme[disabled] ._md-thumb { background-color: '{{background-400}}'; }md-switch.md-THEME_NAME-theme[disabled] ._md-bar { background-color: '{{foreground-4}}'; }md-tabs.md-THEME_NAME-theme md-tabs-wrapper { background-color: transparent; border-color: '{{foreground-4}}'; }md-tabs.md-THEME_NAME-theme .md-paginator md-icon { color: '{{primary-color}}'; }md-tabs.md-THEME_NAME-theme md-ink-bar { color: '{{accent-color}}'; background: '{{accent-color}}'; }md-tabs.md-THEME_NAME-theme .md-tab { color: '{{foreground-2}}'; } md-tabs.md-THEME_NAME-theme .md-tab[disabled], md-tabs.md-THEME_NAME-theme .md-tab[disabled] md-icon { color: '{{foreground-3}}'; } md-tabs.md-THEME_NAME-theme .md-tab.md-active, md-tabs.md-THEME_NAME-theme .md-tab.md-active md-icon, md-tabs.md-THEME_NAME-theme .md-tab.md-focused, md-tabs.md-THEME_NAME-theme .md-tab.md-focused md-icon { color: '{{primary-color}}'; } md-tabs.md-THEME_NAME-theme .md-tab.md-focused { background: '{{primary-color-0.1}}'; } md-tabs.md-THEME_NAME-theme .md-tab .md-ripple-container { color: '{{accent-A100}}'; }md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper { background-color: '{{accent-color}}'; } md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{accent-A100}}'; } md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{accent-contrast}}'; } md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused { background: '{{accent-contrast-0.1}}'; } md-tabs.md-THEME_NAME-theme.md-accent > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-ink-bar { color: '{{primary-600-1}}'; background: '{{primary-600-1}}'; }md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper { background-color: '{{primary-color}}'; } md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{primary-100}}'; } md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{primary-contrast}}'; } md-tabs.md-THEME_NAME-theme.md-primary > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused { background: '{{primary-contrast-0.1}}'; }md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper { background-color: '{{warn-color}}'; } md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{warn-100}}'; } md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{warn-contrast}}'; } md-tabs.md-THEME_NAME-theme.md-warn > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused { background: '{{warn-contrast-0.1}}'; }md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper { background-color: '{{primary-color}}'; } md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{primary-100}}'; } md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{primary-contrast}}'; } md-toolbar > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused { background: '{{primary-contrast-0.1}}'; }md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper { background-color: '{{accent-color}}'; } md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{accent-A100}}'; } md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{accent-contrast}}'; } md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused { background: '{{accent-contrast-0.1}}'; } md-toolbar.md-accent > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-ink-bar { color: '{{primary-600-1}}'; background: '{{primary-600-1}}'; }md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper { background-color: '{{warn-color}}'; } md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]) { color: '{{warn-100}}'; } md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active, md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused md-icon { color: '{{warn-contrast}}'; } md-toolbar.md-warn > md-tabs.md-THEME_NAME-theme > md-tabs-wrapper > md-tabs-canvas > md-pagination-wrapper > md-tab-item:not([disabled]).md-focused { background: '{{warn-contrast-0.1}}'; }md-toast.md-THEME_NAME-theme .md-toast-content { background-color: #323232; color: '{{background-50}}'; } md-toast.md-THEME_NAME-theme .md-toast-content .md-button { color: '{{background-50}}'; } md-toast.md-THEME_NAME-theme .md-toast-content .md-button.md-highlight { color: '{{accent-color}}'; } md-toast.md-THEME_NAME-theme .md-toast-content .md-button.md-highlight.md-primary { color: '{{primary-color}}'; } md-toast.md-THEME_NAME-theme .md-toast-content .md-button.md-highlight.md-warn { color: '{{warn-color}}'; }md-tooltip.md-THEME_NAME-theme { color: '{{background-A100}}'; } md-tooltip.md-THEME_NAME-theme ._md-content { background-color: '{{foreground-2}}'; }md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar) { background-color: '{{primary-color}}'; color: '{{primary-contrast}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar) md-icon { color: '{{primary-contrast}}'; fill: '{{primary-contrast}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar) .md-button[disabled] md-icon { color: '{{primary-contrast-0.26}}'; fill: '{{primary-contrast-0.26}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent { background-color: '{{accent-color}}'; color: '{{accent-contrast}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent .md-ink-ripple { color: '{{accent-contrast}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent md-icon { color: '{{accent-contrast}}'; fill: '{{accent-contrast}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon { color: '{{accent-contrast-0.26}}'; fill: '{{accent-contrast-0.26}}'; } md-toolbar.md-THEME_NAME-theme:not(.md-menu-toolbar).md-warn { background-color: '{{warn-color}}'; color: '{{warn-contrast}}'; }"); +}()}(window,window.angular),window.ngMaterial={version:{full:"1.1.0-rc.5"}}; \ No newline at end of file diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.js deleted file mode 100644 index 34a93c1c41a..00000000000 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.js +++ /dev/null @@ -1,28904 +0,0 @@ -/** - * @license AngularJS v1.4.7 - * (c) 2010-2015 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, document, undefined) {'use strict'; - -/** - * @description - * - * This object provides a utility for producing rich Error messages within - * Angular. It can be called as follows: - * - * var exampleMinErr = minErr('example'); - * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); - * - * The above creates an instance of minErr in the example namespace. The - * resulting error will have a namespaced error code of example.one. The - * resulting error will replace {0} with the value of foo, and {1} with the - * value of bar. The object is not restricted in the number of arguments it can - * take. - * - * If fewer arguments are specified than necessary for interpolation, the extra - * interpolation markers will be preserved in the final string. - * - * Since data will be parsed statically during a build step, some restrictions - * are applied with respect to how minErr instances are created and called. - * Instances should have names of the form namespaceMinErr for a minErr created - * using minErr('namespace') . Error codes, namespaces and template strings - * should all be static strings, not variables or general expressions. - * - * @param {string} module The namespace to use for the new minErr instance. - * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning - * error from returned function, for cases when a particular type of error is useful. - * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance - */ - -function minErr(module, ErrorConstructor) { - ErrorConstructor = ErrorConstructor || Error; - return function() { - var SKIP_INDEXES = 2; - - var templateArgs = arguments, - code = templateArgs[0], - message = '[' + (module ? module + ':' : '') + code + '] ', - template = templateArgs[1], - paramPrefix, i; - - message += template.replace(/\{\d+\}/g, function(match) { - var index = +match.slice(1, -1), - shiftedIndex = index + SKIP_INDEXES; - - if (shiftedIndex < templateArgs.length) { - return toDebugString(templateArgs[shiftedIndex]); - } - - return match; - }); - - message += '\nhttp://errors.angularjs.org/1.4.7/' + - (module ? module + '/' : '') + code; - - for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { - message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + - encodeURIComponent(toDebugString(templateArgs[i])); - } - - return new ErrorConstructor(message); - }; -} - -/* We need to tell jshint what variables are being exported */ -/* global angular: true, - msie: true, - jqLite: true, - jQuery: true, - slice: true, - splice: true, - push: true, - toString: true, - ngMinErr: true, - angularModule: true, - uid: true, - REGEX_STRING_REGEXP: true, - VALIDITY_STATE_PROPERTY: true, - - lowercase: true, - uppercase: true, - manualLowercase: true, - manualUppercase: true, - nodeName_: true, - isArrayLike: true, - forEach: true, - forEachSorted: true, - reverseParams: true, - nextUid: true, - setHashKey: true, - extend: true, - toInt: true, - inherit: true, - merge: true, - noop: true, - identity: true, - valueFn: true, - isUndefined: true, - isDefined: true, - isObject: true, - isBlankObject: true, - isString: true, - isNumber: true, - isDate: true, - isArray: true, - isFunction: true, - isRegExp: true, - isWindow: true, - isScope: true, - isFile: true, - isFormData: true, - isBlob: true, - isBoolean: true, - isPromiseLike: true, - trim: true, - escapeForRegexp: true, - isElement: true, - makeMap: true, - includes: true, - arrayRemove: true, - copy: true, - shallowCopy: true, - equals: true, - csp: true, - jq: true, - concat: true, - sliceArgs: true, - bind: true, - toJsonReplacer: true, - toJson: true, - fromJson: true, - convertTimezoneToLocal: true, - timezoneToOffset: true, - startingTag: true, - tryDecodeURIComponent: true, - parseKeyValue: true, - toKeyValue: true, - encodeUriSegment: true, - encodeUriQuery: true, - angularInit: true, - bootstrap: true, - getTestability: true, - snake_case: true, - bindJQuery: true, - assertArg: true, - assertArgFn: true, - assertNotHasOwnProperty: true, - getter: true, - getBlockNodes: true, - hasOwnProperty: true, - createMap: true, - - NODE_TYPE_ELEMENT: true, - NODE_TYPE_ATTRIBUTE: true, - NODE_TYPE_TEXT: true, - NODE_TYPE_COMMENT: true, - NODE_TYPE_DOCUMENT: true, - NODE_TYPE_DOCUMENT_FRAGMENT: true, -*/ - -//////////////////////////////////// - -/** - * @ngdoc module - * @name ng - * @module ng - * @description - * - * # ng (core module) - * The ng module is loaded by default when an AngularJS application is started. The module itself - * contains the essential components for an AngularJS application to function. The table below - * lists a high level breakdown of each of the services/factories, filters, directives and testing - * components available within this core module. - * - * <div doc-module-components="ng"></div> - */ - -var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; - -// The name of a form control's ValidityState property. -// This is used so that it's possible for internal tests to create mock ValidityStates. -var VALIDITY_STATE_PROPERTY = 'validity'; - -/** - * @ngdoc function - * @name angular.lowercase - * @module ng - * @kind function - * - * @description Converts the specified string to lowercase. - * @param {string} string String to be converted to lowercase. - * @returns {string} Lowercased string. - */ -var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; -var hasOwnProperty = Object.prototype.hasOwnProperty; - -/** - * @ngdoc function - * @name angular.uppercase - * @module ng - * @kind function - * - * @description Converts the specified string to uppercase. - * @param {string} string String to be converted to uppercase. - * @returns {string} Uppercased string. - */ -var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; - - -var manualLowercase = function(s) { - /* jshint bitwise: false */ - return isString(s) - ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) - : s; -}; -var manualUppercase = function(s) { - /* jshint bitwise: false */ - return isString(s) - ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) - : s; -}; - - -// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish -// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods -// with correct but slower alternatives. -if ('i' !== 'I'.toLowerCase()) { - lowercase = manualLowercase; - uppercase = manualUppercase; -} - - -var - msie, // holds major version number for IE, or NaN if UA is not IE. - jqLite, // delay binding since jQuery could be loaded after us. - jQuery, // delay binding - slice = [].slice, - splice = [].splice, - push = [].push, - toString = Object.prototype.toString, - getPrototypeOf = Object.getPrototypeOf, - ngMinErr = minErr('ng'), - - /** @name angular */ - angular = window.angular || (window.angular = {}), - angularModule, - uid = 0; - -/** - * documentMode is an IE-only property - * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx - */ -msie = document.documentMode; - - -/** - * @private - * @param {*} obj - * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, - * String ...) - */ -function isArrayLike(obj) { - if (obj == null || isWindow(obj)) { - return false; - } - - // Support: iOS 8.2 (not reproducible in simulator) - // "length" in obj used to prevent JIT error (gh-11508) - var length = "length" in Object(obj) && obj.length; - - if (obj.nodeType === NODE_TYPE_ELEMENT && length) { - return true; - } - - return isString(obj) || isArray(obj) || length === 0 || - typeof length === 'number' && length > 0 && (length - 1) in obj; -} - -/** - * @ngdoc function - * @name angular.forEach - * @module ng - * @kind function - * - * @description - * Invokes the `iterator` function once for each item in `obj` collection, which can be either an - * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` - * is the value of an object property or an array element, `key` is the object property key or - * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. - * - * It is worth noting that `.forEach` does not iterate over inherited properties because it filters - * using the `hasOwnProperty` method. - * - * Unlike ES262's - * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), - * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just - * return the value provided. - * - ```js - var values = {name: 'misko', gender: 'male'}; - var log = []; - angular.forEach(values, function(value, key) { - this.push(key + ': ' + value); - }, log); - expect(log).toEqual(['name: misko', 'gender: male']); - ``` - * - * @param {Object|Array} obj Object to iterate over. - * @param {Function} iterator Iterator function. - * @param {Object=} context Object to become context (`this`) for the iterator function. - * @returns {Object|Array} Reference to `obj`. - */ - -function forEach(obj, iterator, context) { - var key, length; - if (obj) { - if (isFunction(obj)) { - for (key in obj) { - // Need to check if hasOwnProperty exists, - // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function - if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { - iterator.call(context, obj[key], key, obj); - } - } - } else if (isArray(obj) || isArrayLike(obj)) { - var isPrimitive = typeof obj !== 'object'; - for (key = 0, length = obj.length; key < length; key++) { - if (isPrimitive || key in obj) { - iterator.call(context, obj[key], key, obj); - } - } - } else if (obj.forEach && obj.forEach !== forEach) { - obj.forEach(iterator, context, obj); - } else if (isBlankObject(obj)) { - // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty - for (key in obj) { - iterator.call(context, obj[key], key, obj); - } - } else if (typeof obj.hasOwnProperty === 'function') { - // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed - for (key in obj) { - if (obj.hasOwnProperty(key)) { - iterator.call(context, obj[key], key, obj); - } - } - } else { - // Slow path for objects which do not have a method `hasOwnProperty` - for (key in obj) { - if (hasOwnProperty.call(obj, key)) { - iterator.call(context, obj[key], key, obj); - } - } - } - } - return obj; -} - -function forEachSorted(obj, iterator, context) { - var keys = Object.keys(obj).sort(); - for (var i = 0; i < keys.length; i++) { - iterator.call(context, obj[keys[i]], keys[i]); - } - return keys; -} - - -/** - * when using forEach the params are value, key, but it is often useful to have key, value. - * @param {function(string, *)} iteratorFn - * @returns {function(*, string)} - */ -function reverseParams(iteratorFn) { - return function(value, key) { iteratorFn(key, value); }; -} - -/** - * A consistent way of creating unique IDs in angular. - * - * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before - * we hit number precision issues in JavaScript. - * - * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M - * - * @returns {number} an unique alpha-numeric string - */ -function nextUid() { - return ++uid; -} - - -/** - * Set or clear the hashkey for an object. - * @param obj object - * @param h the hashkey (!truthy to delete the hashkey) - */ -function setHashKey(obj, h) { - if (h) { - obj.$$hashKey = h; - } else { - delete obj.$$hashKey; - } -} - - -function baseExtend(dst, objs, deep) { - var h = dst.$$hashKey; - - for (var i = 0, ii = objs.length; i < ii; ++i) { - var obj = objs[i]; - if (!isObject(obj) && !isFunction(obj)) continue; - var keys = Object.keys(obj); - for (var j = 0, jj = keys.length; j < jj; j++) { - var key = keys[j]; - var src = obj[key]; - - if (deep && isObject(src)) { - if (isDate(src)) { - dst[key] = new Date(src.valueOf()); - } else if (isRegExp(src)) { - dst[key] = new RegExp(src); - } else { - if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; - baseExtend(dst[key], [src], true); - } - } else { - dst[key] = src; - } - } - } - - setHashKey(dst, h); - return dst; -} - -/** - * @ngdoc function - * @name angular.extend - * @module ng - * @kind function - * - * @description - * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) - * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so - * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. - * - * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use - * {@link angular.merge} for this. - * - * @param {Object} dst Destination object. - * @param {...Object} src Source object(s). - * @returns {Object} Reference to `dst`. - */ -function extend(dst) { - return baseExtend(dst, slice.call(arguments, 1), false); -} - - -/** -* @ngdoc function -* @name angular.merge -* @module ng -* @kind function -* -* @description -* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) -* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so -* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. -* -* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source -* objects, performing a deep copy. -* -* @param {Object} dst Destination object. -* @param {...Object} src Source object(s). -* @returns {Object} Reference to `dst`. -*/ -function merge(dst) { - return baseExtend(dst, slice.call(arguments, 1), true); -} - - - -function toInt(str) { - return parseInt(str, 10); -} - - -function inherit(parent, extra) { - return extend(Object.create(parent), extra); -} - -/** - * @ngdoc function - * @name angular.noop - * @module ng - * @kind function - * - * @description - * A function that performs no operations. This function can be useful when writing code in the - * functional style. - ```js - function foo(callback) { - var result = calculateResult(); - (callback || angular.noop)(result); - } - ``` - */ -function noop() {} -noop.$inject = []; - - -/** - * @ngdoc function - * @name angular.identity - * @module ng - * @kind function - * - * @description - * A function that returns its first argument. This function is useful when writing code in the - * functional style. - * - ```js - function transformer(transformationFn, value) { - return (transformationFn || angular.identity)(value); - }; - ``` - * @param {*} value to be returned. - * @returns {*} the value passed in. - */ -function identity($) {return $;} -identity.$inject = []; - - -function valueFn(value) {return function() {return value;};} - -function hasCustomToString(obj) { - return isFunction(obj.toString) && obj.toString !== Object.prototype.toString; -} - - -/** - * @ngdoc function - * @name angular.isUndefined - * @module ng - * @kind function - * - * @description - * Determines if a reference is undefined. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is undefined. - */ -function isUndefined(value) {return typeof value === 'undefined';} - - -/** - * @ngdoc function - * @name angular.isDefined - * @module ng - * @kind function - * - * @description - * Determines if a reference is defined. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is defined. - */ -function isDefined(value) {return typeof value !== 'undefined';} - - -/** - * @ngdoc function - * @name angular.isObject - * @module ng - * @kind function - * - * @description - * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not - * considered to be objects. Note that JavaScript arrays are objects. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is an `Object` but not `null`. - */ -function isObject(value) { - // http://jsperf.com/isobject4 - return value !== null && typeof value === 'object'; -} - - -/** - * Determine if a value is an object with a null prototype - * - * @returns {boolean} True if `value` is an `Object` with a null prototype - */ -function isBlankObject(value) { - return value !== null && typeof value === 'object' && !getPrototypeOf(value); -} - - -/** - * @ngdoc function - * @name angular.isString - * @module ng - * @kind function - * - * @description - * Determines if a reference is a `String`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `String`. - */ -function isString(value) {return typeof value === 'string';} - - -/** - * @ngdoc function - * @name angular.isNumber - * @module ng - * @kind function - * - * @description - * Determines if a reference is a `Number`. - * - * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. - * - * If you wish to exclude these then you can use the native - * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) - * method. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Number`. - */ -function isNumber(value) {return typeof value === 'number';} - - -/** - * @ngdoc function - * @name angular.isDate - * @module ng - * @kind function - * - * @description - * Determines if a value is a date. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Date`. - */ -function isDate(value) { - return toString.call(value) === '[object Date]'; -} - - -/** - * @ngdoc function - * @name angular.isArray - * @module ng - * @kind function - * - * @description - * Determines if a reference is an `Array`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is an `Array`. - */ -var isArray = Array.isArray; - -/** - * @ngdoc function - * @name angular.isFunction - * @module ng - * @kind function - * - * @description - * Determines if a reference is a `Function`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Function`. - */ -function isFunction(value) {return typeof value === 'function';} - - -/** - * Determines if a value is a regular expression object. - * - * @private - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `RegExp`. - */ -function isRegExp(value) { - return toString.call(value) === '[object RegExp]'; -} - - -/** - * Checks if `obj` is a window object. - * - * @private - * @param {*} obj Object to check - * @returns {boolean} True if `obj` is a window obj. - */ -function isWindow(obj) { - return obj && obj.window === obj; -} - - -function isScope(obj) { - return obj && obj.$evalAsync && obj.$watch; -} - - -function isFile(obj) { - return toString.call(obj) === '[object File]'; -} - - -function isFormData(obj) { - return toString.call(obj) === '[object FormData]'; -} - - -function isBlob(obj) { - return toString.call(obj) === '[object Blob]'; -} - - -function isBoolean(value) { - return typeof value === 'boolean'; -} - - -function isPromiseLike(obj) { - return obj && isFunction(obj.then); -} - - -var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/; -function isTypedArray(value) { - return TYPED_ARRAY_REGEXP.test(toString.call(value)); -} - - -var trim = function(value) { - return isString(value) ? value.trim() : value; -}; - -// Copied from: -// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 -// Prereq: s is a string. -var escapeForRegexp = function(s) { - return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). - replace(/\x08/g, '\\x08'); -}; - - -/** - * @ngdoc function - * @name angular.isElement - * @module ng - * @kind function - * - * @description - * Determines if a reference is a DOM element (or wrapped jQuery element). - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). - */ -function isElement(node) { - return !!(node && - (node.nodeName // we are a direct element - || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API -} - -/** - * @param str 'key1,key2,...' - * @returns {object} in the form of {key1:true, key2:true, ...} - */ -function makeMap(str) { - var obj = {}, items = str.split(","), i; - for (i = 0; i < items.length; i++) { - obj[items[i]] = true; - } - return obj; -} - - -function nodeName_(element) { - return lowercase(element.nodeName || (element[0] && element[0].nodeName)); -} - -function includes(array, obj) { - return Array.prototype.indexOf.call(array, obj) != -1; -} - -function arrayRemove(array, value) { - var index = array.indexOf(value); - if (index >= 0) { - array.splice(index, 1); - } - return index; -} - -/** - * @ngdoc function - * @name angular.copy - * @module ng - * @kind function - * - * @description - * Creates a deep copy of `source`, which should be an object or an array. - * - * * If no destination is supplied, a copy of the object or array is created. - * * If a destination is provided, all of its elements (for arrays) or properties (for objects) - * are deleted and then all elements/properties from the source are copied to it. - * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. - * * If `source` is identical to 'destination' an exception will be thrown. - * - * @param {*} source The source that will be used to make a copy. - * Can be any type, including primitives, `null`, and `undefined`. - * @param {(Object|Array)=} destination Destination into which the source is copied. If - * provided, must be of the same type as `source`. - * @returns {*} The copy or updated `destination`, if `destination` was specified. - * - * @example - <example module="copyExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <form novalidate class="simple-form"> - Name: <input type="text" ng-model="user.name" /><br /> - E-mail: <input type="email" ng-model="user.email" /><br /> - Gender: <input type="radio" ng-model="user.gender" value="male" />male - <input type="radio" ng-model="user.gender" value="female" />female<br /> - <button ng-click="reset()">RESET</button> - <button ng-click="update(user)">SAVE</button> - </form> - <pre>form = {{user | json}}</pre> - <pre>master = {{master | json}}</pre> - </div> - - <script> - angular.module('copyExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.master= {}; - - $scope.update = function(user) { - // Example with 1 argument - $scope.master= angular.copy(user); - }; - - $scope.reset = function() { - // Example with 2 arguments - angular.copy($scope.master, $scope.user); - }; - - $scope.reset(); - }]); - </script> - </file> - </example> - */ -function copy(source, destination, stackSource, stackDest) { - if (isWindow(source) || isScope(source)) { - throw ngMinErr('cpws', - "Can't copy! Making copies of Window or Scope instances is not supported."); - } - if (isTypedArray(destination)) { - throw ngMinErr('cpta', - "Can't copy! TypedArray destination cannot be mutated."); - } - - if (!destination) { - destination = source; - if (isObject(source)) { - var index; - if (stackSource && (index = stackSource.indexOf(source)) !== -1) { - return stackDest[index]; - } - - // TypedArray, Date and RegExp have specific copy functionality and must be - // pushed onto the stack before returning. - // Array and other objects create the base object and recurse to copy child - // objects. The array/object will be pushed onto the stack when recursed. - if (isArray(source)) { - return copy(source, [], stackSource, stackDest); - } else if (isTypedArray(source)) { - destination = new source.constructor(source); - } else if (isDate(source)) { - destination = new Date(source.getTime()); - } else if (isRegExp(source)) { - destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); - destination.lastIndex = source.lastIndex; - } else if (isFunction(source.cloneNode)) { - destination = source.cloneNode(true); - } else { - var emptyObject = Object.create(getPrototypeOf(source)); - return copy(source, emptyObject, stackSource, stackDest); - } - - if (stackDest) { - stackSource.push(source); - stackDest.push(destination); - } - } - } else { - if (source === destination) throw ngMinErr('cpi', - "Can't copy! Source and destination are identical."); - - stackSource = stackSource || []; - stackDest = stackDest || []; - - if (isObject(source)) { - stackSource.push(source); - stackDest.push(destination); - } - - var result, key; - if (isArray(source)) { - destination.length = 0; - for (var i = 0; i < source.length; i++) { - destination.push(copy(source[i], null, stackSource, stackDest)); - } - } else { - var h = destination.$$hashKey; - if (isArray(destination)) { - destination.length = 0; - } else { - forEach(destination, function(value, key) { - delete destination[key]; - }); - } - if (isBlankObject(source)) { - // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty - for (key in source) { - destination[key] = copy(source[key], null, stackSource, stackDest); - } - } else if (source && typeof source.hasOwnProperty === 'function') { - // Slow path, which must rely on hasOwnProperty - for (key in source) { - if (source.hasOwnProperty(key)) { - destination[key] = copy(source[key], null, stackSource, stackDest); - } - } - } else { - // Slowest path --- hasOwnProperty can't be called as a method - for (key in source) { - if (hasOwnProperty.call(source, key)) { - destination[key] = copy(source[key], null, stackSource, stackDest); - } - } - } - setHashKey(destination,h); - } - } - return destination; -} - -/** - * Creates a shallow copy of an object, an array or a primitive. - * - * Assumes that there are no proto properties for objects. - */ -function shallowCopy(src, dst) { - if (isArray(src)) { - dst = dst || []; - - for (var i = 0, ii = src.length; i < ii; i++) { - dst[i] = src[i]; - } - } else if (isObject(src)) { - dst = dst || {}; - - for (var key in src) { - if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { - dst[key] = src[key]; - } - } - } - - return dst || src; -} - - -/** - * @ngdoc function - * @name angular.equals - * @module ng - * @kind function - * - * @description - * Determines if two objects or two values are equivalent. Supports value types, regular - * expressions, arrays and objects. - * - * Two objects or values are considered equivalent if at least one of the following is true: - * - * * Both objects or values pass `===` comparison. - * * Both objects or values are of the same type and all of their properties are equal by - * comparing them with `angular.equals`. - * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) - * * Both values represent the same regular expression (In JavaScript, - * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual - * representation matches). - * - * During a property comparison, properties of `function` type and properties with names - * that begin with `$` are ignored. - * - * Scope and DOMWindow objects are being compared only by identify (`===`). - * - * @param {*} o1 Object or value to compare. - * @param {*} o2 Object or value to compare. - * @returns {boolean} True if arguments are equal. - */ -function equals(o1, o2) { - if (o1 === o2) return true; - if (o1 === null || o2 === null) return false; - if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN - var t1 = typeof o1, t2 = typeof o2, length, key, keySet; - if (t1 == t2) { - if (t1 == 'object') { - if (isArray(o1)) { - if (!isArray(o2)) return false; - if ((length = o1.length) == o2.length) { - for (key = 0; key < length; key++) { - if (!equals(o1[key], o2[key])) return false; - } - return true; - } - } else if (isDate(o1)) { - if (!isDate(o2)) return false; - return equals(o1.getTime(), o2.getTime()); - } else if (isRegExp(o1)) { - return isRegExp(o2) ? o1.toString() == o2.toString() : false; - } else { - if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || - isArray(o2) || isDate(o2) || isRegExp(o2)) return false; - keySet = createMap(); - for (key in o1) { - if (key.charAt(0) === '$' || isFunction(o1[key])) continue; - if (!equals(o1[key], o2[key])) return false; - keySet[key] = true; - } - for (key in o2) { - if (!(key in keySet) && - key.charAt(0) !== '$' && - isDefined(o2[key]) && - !isFunction(o2[key])) return false; - } - return true; - } - } - } - return false; -} - -var csp = function() { - if (!isDefined(csp.rules)) { - - - var ngCspElement = (document.querySelector('[ng-csp]') || - document.querySelector('[data-ng-csp]')); - - if (ngCspElement) { - var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || - ngCspElement.getAttribute('data-ng-csp'); - csp.rules = { - noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), - noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) - }; - } else { - csp.rules = { - noUnsafeEval: noUnsafeEval(), - noInlineStyle: false - }; - } - } - - return csp.rules; - - function noUnsafeEval() { - try { - /* jshint -W031, -W054 */ - new Function(''); - /* jshint +W031, +W054 */ - return false; - } catch (e) { - return true; - } - } -}; - -/** - * @ngdoc directive - * @module ng - * @name ngJq - * - * @element ANY - * @param {string=} ngJq the name of the library available under `window` - * to be used for angular.element - * @description - * Use this directive to force the angular.element library. This should be - * used to force either jqLite by leaving ng-jq blank or setting the name of - * the jquery variable under window (eg. jQuery). - * - * Since angular looks for this directive when it is loaded (doesn't wait for the - * DOMContentLoaded event), it must be placed on an element that comes before the script - * which loads angular. Also, only the first instance of `ng-jq` will be used and all - * others ignored. - * - * @example - * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. - ```html - <!doctype html> - <html ng-app ng-jq> - ... - ... - </html> - ``` - * @example - * This example shows how to use a jQuery based library of a different name. - * The library name must be available at the top most 'window'. - ```html - <!doctype html> - <html ng-app ng-jq="jQueryLib"> - ... - ... - </html> - ``` - */ -var jq = function() { - if (isDefined(jq.name_)) return jq.name_; - var el; - var i, ii = ngAttrPrefixes.length, prefix, name; - for (i = 0; i < ii; ++i) { - prefix = ngAttrPrefixes[i]; - if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) { - name = el.getAttribute(prefix + 'jq'); - break; - } - } - - return (jq.name_ = name); -}; - -function concat(array1, array2, index) { - return array1.concat(slice.call(array2, index)); -} - -function sliceArgs(args, startIndex) { - return slice.call(args, startIndex || 0); -} - - -/* jshint -W101 */ -/** - * @ngdoc function - * @name angular.bind - * @module ng - * @kind function - * - * @description - * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for - * `fn`). You can supply optional `args` that are prebound to the function. This feature is also - * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as - * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). - * - * @param {Object} self Context which `fn` should be evaluated in. - * @param {function()} fn Function to be bound. - * @param {...*} args Optional arguments to be prebound to the `fn` function call. - * @returns {function()} Function that wraps the `fn` with all the specified bindings. - */ -/* jshint +W101 */ -function bind(self, fn) { - var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; - if (isFunction(fn) && !(fn instanceof RegExp)) { - return curryArgs.length - ? function() { - return arguments.length - ? fn.apply(self, concat(curryArgs, arguments, 0)) - : fn.apply(self, curryArgs); - } - : function() { - return arguments.length - ? fn.apply(self, arguments) - : fn.call(self); - }; - } else { - // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) - return fn; - } -} - - -function toJsonReplacer(key, value) { - var val = value; - - if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { - val = undefined; - } else if (isWindow(value)) { - val = '$WINDOW'; - } else if (value && document === value) { - val = '$DOCUMENT'; - } else if (isScope(value)) { - val = '$SCOPE'; - } - - return val; -} - - -/** - * @ngdoc function - * @name angular.toJson - * @module ng - * @kind function - * - * @description - * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be - * stripped since angular uses this notation internally. - * - * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. - * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. - * If set to an integer, the JSON output will contain that many spaces per indentation. - * @returns {string|undefined} JSON-ified string representing `obj`. - */ -function toJson(obj, pretty) { - if (typeof obj === 'undefined') return undefined; - if (!isNumber(pretty)) { - pretty = pretty ? 2 : null; - } - return JSON.stringify(obj, toJsonReplacer, pretty); -} - - -/** - * @ngdoc function - * @name angular.fromJson - * @module ng - * @kind function - * - * @description - * Deserializes a JSON string. - * - * @param {string} json JSON string to deserialize. - * @returns {Object|Array|string|number} Deserialized JSON string. - */ -function fromJson(json) { - return isString(json) - ? JSON.parse(json) - : json; -} - - -function timezoneToOffset(timezone, fallback) { - var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; - return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; -} - - -function addDateMinutes(date, minutes) { - date = new Date(date.getTime()); - date.setMinutes(date.getMinutes() + minutes); - return date; -} - - -function convertTimezoneToLocal(date, timezone, reverse) { - reverse = reverse ? -1 : 1; - var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); - return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset())); -} - - -/** - * @returns {string} Returns the string representation of the element. - */ -function startingTag(element) { - element = jqLite(element).clone(); - try { - // turns out IE does not let you set .html() on elements which - // are not allowed to have children. So we just ignore it. - element.empty(); - } catch (e) {} - var elemHtml = jqLite('<div>').append(element).html(); - try { - return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : - elemHtml. - match(/^(<[^>]+>)/)[1]. - replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); - } catch (e) { - return lowercase(elemHtml); - } - -} - - -///////////////////////////////////////////////// - -/** - * Tries to decode the URI component without throwing an exception. - * - * @private - * @param str value potential URI component to check. - * @returns {boolean} True if `value` can be decoded - * with the decodeURIComponent function. - */ -function tryDecodeURIComponent(value) { - try { - return decodeURIComponent(value); - } catch (e) { - // Ignore any invalid uri component - } -} - - -/** - * Parses an escaped url query string into key-value pairs. - * @returns {Object.<string,boolean|Array>} - */ -function parseKeyValue(/**string*/keyValue) { - var obj = {}; - forEach((keyValue || "").split('&'), function(keyValue) { - var splitPoint, key, val; - if (keyValue) { - key = keyValue = keyValue.replace(/\+/g,'%20'); - splitPoint = keyValue.indexOf('='); - if (splitPoint !== -1) { - key = keyValue.substring(0, splitPoint); - val = keyValue.substring(splitPoint + 1); - } - key = tryDecodeURIComponent(key); - if (isDefined(key)) { - val = isDefined(val) ? tryDecodeURIComponent(val) : true; - if (!hasOwnProperty.call(obj, key)) { - obj[key] = val; - } else if (isArray(obj[key])) { - obj[key].push(val); - } else { - obj[key] = [obj[key],val]; - } - } - } - }); - return obj; -} - -function toKeyValue(obj) { - var parts = []; - forEach(obj, function(value, key) { - if (isArray(value)) { - forEach(value, function(arrayValue) { - parts.push(encodeUriQuery(key, true) + - (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); - }); - } else { - parts.push(encodeUriQuery(key, true) + - (value === true ? '' : '=' + encodeUriQuery(value, true))); - } - }); - return parts.length ? parts.join('&') : ''; -} - - -/** - * We need our custom method because encodeURIComponent is too aggressive and doesn't follow - * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path - * segments: - * segment = *pchar - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * pct-encoded = "%" HEXDIG HEXDIG - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ -function encodeUriSegment(val) { - return encodeUriQuery(val, true). - replace(/%26/gi, '&'). - replace(/%3D/gi, '='). - replace(/%2B/gi, '+'); -} - - -/** - * This method is intended for encoding *key* or *value* parts of query component. We need a custom - * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be - * encoded per http://tools.ietf.org/html/rfc3986: - * query = *( pchar / "/" / "?" ) - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * pct-encoded = "%" HEXDIG HEXDIG - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ -function encodeUriQuery(val, pctEncodeSpaces) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace(/%3B/gi, ';'). - replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); -} - -var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; - -function getNgAttribute(element, ngAttr) { - var attr, i, ii = ngAttrPrefixes.length; - for (i = 0; i < ii; ++i) { - attr = ngAttrPrefixes[i] + ngAttr; - if (isString(attr = element.getAttribute(attr))) { - return attr; - } - } - return null; -} - -/** - * @ngdoc directive - * @name ngApp - * @module ng - * - * @element ANY - * @param {angular.Module} ngApp an optional application - * {@link angular.module module} name to load. - * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be - * created in "strict-di" mode. This means that the application will fail to invoke functions which - * do not use explicit function annotation (and are thus unsuitable for minification), as described - * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in - * tracking down the root of these bugs. - * - * @description - * - * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive - * designates the **root element** of the application and is typically placed near the root element - * of the page - e.g. on the `<body>` or `<html>` tags. - * - * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` - * found in the document will be used to define the root element to auto-bootstrap as an - * application. To run multiple applications in an HTML document you must manually bootstrap them using - * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. - * - * You can specify an **AngularJS module** to be used as the root module for the application. This - * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It - * should contain the application code needed or have dependencies on other modules that will - * contain the code. See {@link angular.module} for more information. - * - * In the example below if the `ngApp` directive were not placed on the `html` element then the - * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` - * would not be resolved to `3`. - * - * `ngApp` is the easiest, and most common way to bootstrap an application. - * - <example module="ngAppDemo"> - <file name="index.html"> - <div ng-controller="ngAppDemoController"> - I can add: {{a}} + {{b}} = {{ a+b }} - </div> - </file> - <file name="script.js"> - angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { - $scope.a = 1; - $scope.b = 2; - }); - </file> - </example> - * - * Using `ngStrictDi`, you would see something like this: - * - <example ng-app-included="true"> - <file name="index.html"> - <div ng-app="ngAppStrictDemo" ng-strict-di> - <div ng-controller="GoodController1"> - I can add: {{a}} + {{b}} = {{ a+b }} - - <p>This renders because the controller does not fail to - instantiate, by using explicit annotation style (see - script.js for details) - </p> - </div> - - <div ng-controller="GoodController2"> - Name: <input ng-model="name"><br /> - Hello, {{name}}! - - <p>This renders because the controller does not fail to - instantiate, by using explicit annotation style - (see script.js for details) - </p> - </div> - - <div ng-controller="BadController"> - I can add: {{a}} + {{b}} = {{ a+b }} - - <p>The controller could not be instantiated, due to relying - on automatic function annotations (which are disabled in - strict mode). As such, the content of this section is not - interpolated, and there should be an error in your web console. - </p> - </div> - </div> - </file> - <file name="script.js"> - angular.module('ngAppStrictDemo', []) - // BadController will fail to instantiate, due to relying on automatic function annotation, - // rather than an explicit annotation - .controller('BadController', function($scope) { - $scope.a = 1; - $scope.b = 2; - }) - // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, - // due to using explicit annotations using the array style and $inject property, respectively. - .controller('GoodController1', ['$scope', function($scope) { - $scope.a = 1; - $scope.b = 2; - }]) - .controller('GoodController2', GoodController2); - function GoodController2($scope) { - $scope.name = "World"; - } - GoodController2.$inject = ['$scope']; - </file> - <file name="style.css"> - div[ng-controller] { - margin-bottom: 1em; - -webkit-border-radius: 4px; - border-radius: 4px; - border: 1px solid; - padding: .5em; - } - div[ng-controller^=Good] { - border-color: #d6e9c6; - background-color: #dff0d8; - color: #3c763d; - } - div[ng-controller^=Bad] { - border-color: #ebccd1; - background-color: #f2dede; - color: #a94442; - margin-bottom: 0; - } - </file> - </example> - */ -function angularInit(element, bootstrap) { - var appElement, - module, - config = {}; - - // The element `element` has priority over any other element - forEach(ngAttrPrefixes, function(prefix) { - var name = prefix + 'app'; - - if (!appElement && element.hasAttribute && element.hasAttribute(name)) { - appElement = element; - module = element.getAttribute(name); - } - }); - forEach(ngAttrPrefixes, function(prefix) { - var name = prefix + 'app'; - var candidate; - - if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { - appElement = candidate; - module = candidate.getAttribute(name); - } - }); - if (appElement) { - config.strictDi = getNgAttribute(appElement, "strict-di") !== null; - bootstrap(appElement, module ? [module] : [], config); - } -} - -/** - * @ngdoc function - * @name angular.bootstrap - * @module ng - * @description - * Use this function to manually start up angular application. - * - * See: {@link guide/bootstrap Bootstrap} - * - * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. - * They must use {@link ng.directive:ngApp ngApp}. - * - * Angular will detect if it has been loaded into the browser more than once and only allow the - * first loaded script to be bootstrapped and will report a warning to the browser console for - * each of the subsequent scripts. This prevents strange results in applications, where otherwise - * multiple instances of Angular try to work on the DOM. - * - * ```html - * <!doctype html> - * <html> - * <body> - * <div ng-controller="WelcomeController"> - * {{greeting}} - * </div> - * - * <script src="angular.js"></script> - * <script> - * var app = angular.module('demo', []) - * .controller('WelcomeController', function($scope) { - * $scope.greeting = 'Welcome!'; - * }); - * angular.bootstrap(document, ['demo']); - * </script> - * </body> - * </html> - * ``` - * - * @param {DOMElement} element DOM element which is the root of angular application. - * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. - * Each item in the array should be the name of a predefined module or a (DI annotated) - * function that will be invoked by the injector as a `config` block. - * See: {@link angular.module modules} - * @param {Object=} config an object for defining configuration options for the application. The - * following keys are supported: - * - * * `strictDi` - disable automatic function annotation for the application. This is meant to - * assist in finding bugs which break minified code. Defaults to `false`. - * - * @returns {auto.$injector} Returns the newly created injector for this app. - */ -function bootstrap(element, modules, config) { - if (!isObject(config)) config = {}; - var defaultConfig = { - strictDi: false - }; - config = extend(defaultConfig, config); - var doBootstrap = function() { - element = jqLite(element); - - if (element.injector()) { - var tag = (element[0] === document) ? 'document' : startingTag(element); - //Encode angle brackets to prevent input from being sanitized to empty string #8683 - throw ngMinErr( - 'btstrpd', - "App Already Bootstrapped with this Element '{0}'", - tag.replace(/</,'<').replace(/>/,'>')); - } - - modules = modules || []; - modules.unshift(['$provide', function($provide) { - $provide.value('$rootElement', element); - }]); - - if (config.debugInfoEnabled) { - // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. - modules.push(['$compileProvider', function($compileProvider) { - $compileProvider.debugInfoEnabled(true); - }]); - } - - modules.unshift('ng'); - var injector = createInjector(modules, config.strictDi); - injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', - function bootstrapApply(scope, element, compile, injector) { - scope.$apply(function() { - element.data('$injector', injector); - compile(element)(scope); - }); - }] - ); - return injector; - }; - - var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; - var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; - - if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { - config.debugInfoEnabled = true; - window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); - } - - if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { - return doBootstrap(); - } - - window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); - angular.resumeBootstrap = function(extraModules) { - forEach(extraModules, function(module) { - modules.push(module); - }); - return doBootstrap(); - }; - - if (isFunction(angular.resumeDeferredBootstrap)) { - angular.resumeDeferredBootstrap(); - } -} - -/** - * @ngdoc function - * @name angular.reloadWithDebugInfo - * @module ng - * @description - * Use this function to reload the current application with debug information turned on. - * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. - * - * See {@link ng.$compileProvider#debugInfoEnabled} for more. - */ -function reloadWithDebugInfo() { - window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; - window.location.reload(); -} - -/** - * @name angular.getTestability - * @module ng - * @description - * Get the testability service for the instance of Angular on the given - * element. - * @param {DOMElement} element DOM element which is the root of angular application. - */ -function getTestability(rootElement) { - var injector = angular.element(rootElement).injector(); - if (!injector) { - throw ngMinErr('test', - 'no injector found for element argument to getTestability'); - } - return injector.get('$$testability'); -} - -var SNAKE_CASE_REGEXP = /[A-Z]/g; -function snake_case(name, separator) { - separator = separator || '_'; - return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { - return (pos ? separator : '') + letter.toLowerCase(); - }); -} - -var bindJQueryFired = false; -var skipDestroyOnNextJQueryCleanData; -function bindJQuery() { - var originalCleanData; - - if (bindJQueryFired) { - return; - } - - // bind to jQuery if present; - var jqName = jq(); - jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) - !jqName ? undefined : // use jqLite - window[jqName]; // use jQuery specified by `ngJq` - - // Use jQuery if it exists with proper functionality, otherwise default to us. - // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. - // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older - // versions. It will not work for sure with jQuery <1.7, though. - if (jQuery && jQuery.fn.on) { - jqLite = jQuery; - extend(jQuery.fn, { - scope: JQLitePrototype.scope, - isolateScope: JQLitePrototype.isolateScope, - controller: JQLitePrototype.controller, - injector: JQLitePrototype.injector, - inheritedData: JQLitePrototype.inheritedData - }); - - // All nodes removed from the DOM via various jQuery APIs like .remove() - // are passed through jQuery.cleanData. Monkey-patch this method to fire - // the $destroy event on all removed nodes. - originalCleanData = jQuery.cleanData; - jQuery.cleanData = function(elems) { - var events; - if (!skipDestroyOnNextJQueryCleanData) { - for (var i = 0, elem; (elem = elems[i]) != null; i++) { - events = jQuery._data(elem, "events"); - if (events && events.$destroy) { - jQuery(elem).triggerHandler('$destroy'); - } - } - } else { - skipDestroyOnNextJQueryCleanData = false; - } - originalCleanData(elems); - }; - } else { - jqLite = JQLite; - } - - angular.element = jqLite; - - // Prevent double-proxying. - bindJQueryFired = true; -} - -/** - * throw error if the argument is falsy. - */ -function assertArg(arg, name, reason) { - if (!arg) { - throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); - } - return arg; -} - -function assertArgFn(arg, name, acceptArrayAnnotation) { - if (acceptArrayAnnotation && isArray(arg)) { - arg = arg[arg.length - 1]; - } - - assertArg(isFunction(arg), name, 'not a function, got ' + - (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); - return arg; -} - -/** - * throw error if the name given is hasOwnProperty - * @param {String} name the name to test - * @param {String} context the context in which the name is used, such as module or directive - */ -function assertNotHasOwnProperty(name, context) { - if (name === 'hasOwnProperty') { - throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); - } -} - -/** - * Return the value accessible from the object by path. Any undefined traversals are ignored - * @param {Object} obj starting object - * @param {String} path path to traverse - * @param {boolean} [bindFnToScope=true] - * @returns {Object} value as accessible by path - */ -//TODO(misko): this function needs to be removed -function getter(obj, path, bindFnToScope) { - if (!path) return obj; - var keys = path.split('.'); - var key; - var lastInstance = obj; - var len = keys.length; - - for (var i = 0; i < len; i++) { - key = keys[i]; - if (obj) { - obj = (lastInstance = obj)[key]; - } - } - if (!bindFnToScope && isFunction(obj)) { - return bind(lastInstance, obj); - } - return obj; -} - -/** - * Return the DOM siblings between the first and last node in the given array. - * @param {Array} array like object - * @returns {Array} the inputted object or a jqLite collection containing the nodes - */ -function getBlockNodes(nodes) { - // TODO(perf): update `nodes` instead of creating a new object? - var node = nodes[0]; - var endNode = nodes[nodes.length - 1]; - var blockNodes; - - for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { - if (blockNodes || nodes[i] !== node) { - if (!blockNodes) { - blockNodes = jqLite(slice.call(nodes, 0, i)); - } - blockNodes.push(node); - } - } - - return blockNodes || nodes; -} - - -/** - * Creates a new object without a prototype. This object is useful for lookup without having to - * guard against prototypically inherited properties via hasOwnProperty. - * - * Related micro-benchmarks: - * - http://jsperf.com/object-create2 - * - http://jsperf.com/proto-map-lookup/2 - * - http://jsperf.com/for-in-vs-object-keys2 - * - * @returns {Object} - */ -function createMap() { - return Object.create(null); -} - -var NODE_TYPE_ELEMENT = 1; -var NODE_TYPE_ATTRIBUTE = 2; -var NODE_TYPE_TEXT = 3; -var NODE_TYPE_COMMENT = 8; -var NODE_TYPE_DOCUMENT = 9; -var NODE_TYPE_DOCUMENT_FRAGMENT = 11; - -/** - * @ngdoc type - * @name angular.Module - * @module ng - * @description - * - * Interface for configuring angular {@link angular.module modules}. - */ - -function setupModuleLoader(window) { - - var $injectorMinErr = minErr('$injector'); - var ngMinErr = minErr('ng'); - - function ensure(obj, name, factory) { - return obj[name] || (obj[name] = factory()); - } - - var angular = ensure(window, 'angular', Object); - - // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap - angular.$$minErr = angular.$$minErr || minErr; - - return ensure(angular, 'module', function() { - /** @type {Object.<string, angular.Module>} */ - var modules = {}; - - /** - * @ngdoc function - * @name angular.module - * @module ng - * @description - * - * The `angular.module` is a global place for creating, registering and retrieving Angular - * modules. - * All modules (angular core or 3rd party) that should be available to an application must be - * registered using this mechanism. - * - * Passing one argument retrieves an existing {@link angular.Module}, - * whereas passing more than one argument creates a new {@link angular.Module} - * - * - * # Module - * - * A module is a collection of services, directives, controllers, filters, and configuration information. - * `angular.module` is used to configure the {@link auto.$injector $injector}. - * - * ```js - * // Create a new module - * var myModule = angular.module('myModule', []); - * - * // register a new service - * myModule.value('appName', 'MyCoolApp'); - * - * // configure existing services inside initialization blocks. - * myModule.config(['$locationProvider', function($locationProvider) { - * // Configure existing providers - * $locationProvider.hashPrefix('!'); - * }]); - * ``` - * - * Then you can create an injector and load your modules like this: - * - * ```js - * var injector = angular.injector(['ng', 'myModule']) - * ``` - * - * However it's more likely that you'll just use - * {@link ng.directive:ngApp ngApp} or - * {@link angular.bootstrap} to simplify this process for you. - * - * @param {!string} name The name of the module to create or retrieve. - * @param {!Array.<string>=} requires If specified then new module is being created. If - * unspecified then the module is being retrieved for further configuration. - * @param {Function=} configFn Optional configuration function for the module. Same as - * {@link angular.Module#config Module#config()}. - * @returns {module} new module with the {@link angular.Module} api. - */ - return function module(name, requires, configFn) { - var assertNotHasOwnProperty = function(name, context) { - if (name === 'hasOwnProperty') { - throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); - } - }; - - assertNotHasOwnProperty(name, 'module'); - if (requires && modules.hasOwnProperty(name)) { - modules[name] = null; - } - return ensure(modules, name, function() { - if (!requires) { - throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + - "the module name or forgot to load it. If registering a module ensure that you " + - "specify the dependencies as the second argument.", name); - } - - /** @type {!Array.<Array.<*>>} */ - var invokeQueue = []; - - /** @type {!Array.<Function>} */ - var configBlocks = []; - - /** @type {!Array.<Function>} */ - var runBlocks = []; - - var config = invokeLater('$injector', 'invoke', 'push', configBlocks); - - /** @type {angular.Module} */ - var moduleInstance = { - // Private state - _invokeQueue: invokeQueue, - _configBlocks: configBlocks, - _runBlocks: runBlocks, - - /** - * @ngdoc property - * @name angular.Module#requires - * @module ng - * - * @description - * Holds the list of modules which the injector will load before the current module is - * loaded. - */ - requires: requires, - - /** - * @ngdoc property - * @name angular.Module#name - * @module ng - * - * @description - * Name of the module. - */ - name: name, - - - /** - * @ngdoc method - * @name angular.Module#provider - * @module ng - * @param {string} name service name - * @param {Function} providerType Construction function for creating new instance of the - * service. - * @description - * See {@link auto.$provide#provider $provide.provider()}. - */ - provider: invokeLaterAndSetModuleName('$provide', 'provider'), - - /** - * @ngdoc method - * @name angular.Module#factory - * @module ng - * @param {string} name service name - * @param {Function} providerFunction Function for creating new instance of the service. - * @description - * See {@link auto.$provide#factory $provide.factory()}. - */ - factory: invokeLaterAndSetModuleName('$provide', 'factory'), - - /** - * @ngdoc method - * @name angular.Module#service - * @module ng - * @param {string} name service name - * @param {Function} constructor A constructor function that will be instantiated. - * @description - * See {@link auto.$provide#service $provide.service()}. - */ - service: invokeLaterAndSetModuleName('$provide', 'service'), - - /** - * @ngdoc method - * @name angular.Module#value - * @module ng - * @param {string} name service name - * @param {*} object Service instance object. - * @description - * See {@link auto.$provide#value $provide.value()}. - */ - value: invokeLater('$provide', 'value'), - - /** - * @ngdoc method - * @name angular.Module#constant - * @module ng - * @param {string} name constant name - * @param {*} object Constant value. - * @description - * Because the constant are fixed, they get applied before other provide methods. - * See {@link auto.$provide#constant $provide.constant()}. - */ - constant: invokeLater('$provide', 'constant', 'unshift'), - - /** - * @ngdoc method - * @name angular.Module#decorator - * @module ng - * @param {string} The name of the service to decorate. - * @param {Function} This function will be invoked when the service needs to be - * instantiated and should return the decorated service instance. - * @description - * See {@link auto.$provide#decorator $provide.decorator()}. - */ - decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), - - /** - * @ngdoc method - * @name angular.Module#animation - * @module ng - * @param {string} name animation name - * @param {Function} animationFactory Factory function for creating new instance of an - * animation. - * @description - * - * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. - * - * - * Defines an animation hook that can be later used with - * {@link $animate $animate} service and directives that use this service. - * - * ```js - * module.animation('.animation-name', function($inject1, $inject2) { - * return { - * eventName : function(element, done) { - * //code to run the animation - * //once complete, then run done() - * return function cancellationFunction(element) { - * //code to cancel the animation - * } - * } - * } - * }) - * ``` - * - * See {@link ng.$animateProvider#register $animateProvider.register()} and - * {@link ngAnimate ngAnimate module} for more information. - */ - animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), - - /** - * @ngdoc method - * @name angular.Module#filter - * @module ng - * @param {string} name Filter name - this must be a valid angular expression identifier - * @param {Function} filterFactory Factory function for creating new instance of filter. - * @description - * See {@link ng.$filterProvider#register $filterProvider.register()}. - * - * <div class="alert alert-warning"> - * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. - * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace - * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores - * (`myapp_subsection_filterx`). - * </div> - */ - filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), - - /** - * @ngdoc method - * @name angular.Module#controller - * @module ng - * @param {string|Object} name Controller name, or an object map of controllers where the - * keys are the names and the values are the constructors. - * @param {Function} constructor Controller constructor function. - * @description - * See {@link ng.$controllerProvider#register $controllerProvider.register()}. - */ - controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), - - /** - * @ngdoc method - * @name angular.Module#directive - * @module ng - * @param {string|Object} name Directive name, or an object map of directives where the - * keys are the names and the values are the factories. - * @param {Function} directiveFactory Factory function for creating new instance of - * directives. - * @description - * See {@link ng.$compileProvider#directive $compileProvider.directive()}. - */ - directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), - - /** - * @ngdoc method - * @name angular.Module#config - * @module ng - * @param {Function} configFn Execute this function on module load. Useful for service - * configuration. - * @description - * Use this method to register work which needs to be performed on module loading. - * For more about how to configure services, see - * {@link providers#provider-recipe Provider Recipe}. - */ - config: config, - - /** - * @ngdoc method - * @name angular.Module#run - * @module ng - * @param {Function} initializationFn Execute this function after injector creation. - * Useful for application initialization. - * @description - * Use this method to register work which should be performed when the injector is done - * loading all modules. - */ - run: function(block) { - runBlocks.push(block); - return this; - } - }; - - if (configFn) { - config(configFn); - } - - return moduleInstance; - - /** - * @param {string} provider - * @param {string} method - * @param {String=} insertMethod - * @returns {angular.Module} - */ - function invokeLater(provider, method, insertMethod, queue) { - if (!queue) queue = invokeQueue; - return function() { - queue[insertMethod || 'push']([provider, method, arguments]); - return moduleInstance; - }; - } - - /** - * @param {string} provider - * @param {string} method - * @returns {angular.Module} - */ - function invokeLaterAndSetModuleName(provider, method) { - return function(recipeName, factoryFunction) { - if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; - invokeQueue.push([provider, method, arguments]); - return moduleInstance; - }; - } - }); - }; - }); - -} - -/* global: toDebugString: true */ - -function serializeObject(obj) { - var seen = []; - - return JSON.stringify(obj, function(key, val) { - val = toJsonReplacer(key, val); - if (isObject(val)) { - - if (seen.indexOf(val) >= 0) return '...'; - - seen.push(val); - } - return val; - }); -} - -function toDebugString(obj) { - if (typeof obj === 'function') { - return obj.toString().replace(/ \{[\s\S]*$/, ''); - } else if (isUndefined(obj)) { - return 'undefined'; - } else if (typeof obj !== 'string') { - return serializeObject(obj); - } - return obj; -} - -/* global angularModule: true, - version: true, - - $CompileProvider, - - htmlAnchorDirective, - inputDirective, - inputDirective, - formDirective, - scriptDirective, - selectDirective, - styleDirective, - optionDirective, - ngBindDirective, - ngBindHtmlDirective, - ngBindTemplateDirective, - ngClassDirective, - ngClassEvenDirective, - ngClassOddDirective, - ngCloakDirective, - ngControllerDirective, - ngFormDirective, - ngHideDirective, - ngIfDirective, - ngIncludeDirective, - ngIncludeFillContentDirective, - ngInitDirective, - ngNonBindableDirective, - ngPluralizeDirective, - ngRepeatDirective, - ngShowDirective, - ngStyleDirective, - ngSwitchDirective, - ngSwitchWhenDirective, - ngSwitchDefaultDirective, - ngOptionsDirective, - ngTranscludeDirective, - ngModelDirective, - ngListDirective, - ngChangeDirective, - patternDirective, - patternDirective, - requiredDirective, - requiredDirective, - minlengthDirective, - minlengthDirective, - maxlengthDirective, - maxlengthDirective, - ngValueDirective, - ngModelOptionsDirective, - ngAttributeAliasDirectives, - ngEventDirectives, - - $AnchorScrollProvider, - $AnimateProvider, - $CoreAnimateCssProvider, - $$CoreAnimateQueueProvider, - $$CoreAnimateRunnerProvider, - $BrowserProvider, - $CacheFactoryProvider, - $ControllerProvider, - $DocumentProvider, - $ExceptionHandlerProvider, - $FilterProvider, - $$ForceReflowProvider, - $InterpolateProvider, - $IntervalProvider, - $$HashMapProvider, - $HttpProvider, - $HttpParamSerializerProvider, - $HttpParamSerializerJQLikeProvider, - $HttpBackendProvider, - $xhrFactoryProvider, - $LocationProvider, - $LogProvider, - $ParseProvider, - $RootScopeProvider, - $QProvider, - $$QProvider, - $$SanitizeUriProvider, - $SceProvider, - $SceDelegateProvider, - $SnifferProvider, - $TemplateCacheProvider, - $TemplateRequestProvider, - $$TestabilityProvider, - $TimeoutProvider, - $$RAFProvider, - $WindowProvider, - $$jqLiteProvider, - $$CookieReaderProvider -*/ - - -/** - * @ngdoc object - * @name angular.version - * @module ng - * @description - * An object that contains information about the current AngularJS version. - * - * This object has the following properties: - * - * - `full` – `{string}` – Full version string, such as "0.9.18". - * - `major` – `{number}` – Major version number, such as "0". - * - `minor` – `{number}` – Minor version number, such as "9". - * - `dot` – `{number}` – Dot version number, such as "18". - * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". - */ -var version = { - full: '1.4.7', // all of these placeholder strings will be replaced by grunt's - major: 1, // package task - minor: 4, - dot: 7, - codeName: 'dark-luminescence' -}; - - -function publishExternalAPI(angular) { - extend(angular, { - 'bootstrap': bootstrap, - 'copy': copy, - 'extend': extend, - 'merge': merge, - 'equals': equals, - 'element': jqLite, - 'forEach': forEach, - 'injector': createInjector, - 'noop': noop, - 'bind': bind, - 'toJson': toJson, - 'fromJson': fromJson, - 'identity': identity, - 'isUndefined': isUndefined, - 'isDefined': isDefined, - 'isString': isString, - 'isFunction': isFunction, - 'isObject': isObject, - 'isNumber': isNumber, - 'isElement': isElement, - 'isArray': isArray, - 'version': version, - 'isDate': isDate, - 'lowercase': lowercase, - 'uppercase': uppercase, - 'callbacks': {counter: 0}, - 'getTestability': getTestability, - '$$minErr': minErr, - '$$csp': csp, - 'reloadWithDebugInfo': reloadWithDebugInfo - }); - - angularModule = setupModuleLoader(window); - - angularModule('ng', ['ngLocale'], ['$provide', - function ngModule($provide) { - // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. - $provide.provider({ - $$sanitizeUri: $$SanitizeUriProvider - }); - $provide.provider('$compile', $CompileProvider). - directive({ - a: htmlAnchorDirective, - input: inputDirective, - textarea: inputDirective, - form: formDirective, - script: scriptDirective, - select: selectDirective, - style: styleDirective, - option: optionDirective, - ngBind: ngBindDirective, - ngBindHtml: ngBindHtmlDirective, - ngBindTemplate: ngBindTemplateDirective, - ngClass: ngClassDirective, - ngClassEven: ngClassEvenDirective, - ngClassOdd: ngClassOddDirective, - ngCloak: ngCloakDirective, - ngController: ngControllerDirective, - ngForm: ngFormDirective, - ngHide: ngHideDirective, - ngIf: ngIfDirective, - ngInclude: ngIncludeDirective, - ngInit: ngInitDirective, - ngNonBindable: ngNonBindableDirective, - ngPluralize: ngPluralizeDirective, - ngRepeat: ngRepeatDirective, - ngShow: ngShowDirective, - ngStyle: ngStyleDirective, - ngSwitch: ngSwitchDirective, - ngSwitchWhen: ngSwitchWhenDirective, - ngSwitchDefault: ngSwitchDefaultDirective, - ngOptions: ngOptionsDirective, - ngTransclude: ngTranscludeDirective, - ngModel: ngModelDirective, - ngList: ngListDirective, - ngChange: ngChangeDirective, - pattern: patternDirective, - ngPattern: patternDirective, - required: requiredDirective, - ngRequired: requiredDirective, - minlength: minlengthDirective, - ngMinlength: minlengthDirective, - maxlength: maxlengthDirective, - ngMaxlength: maxlengthDirective, - ngValue: ngValueDirective, - ngModelOptions: ngModelOptionsDirective - }). - directive({ - ngInclude: ngIncludeFillContentDirective - }). - directive(ngAttributeAliasDirectives). - directive(ngEventDirectives); - $provide.provider({ - $anchorScroll: $AnchorScrollProvider, - $animate: $AnimateProvider, - $animateCss: $CoreAnimateCssProvider, - $$animateQueue: $$CoreAnimateQueueProvider, - $$AnimateRunner: $$CoreAnimateRunnerProvider, - $browser: $BrowserProvider, - $cacheFactory: $CacheFactoryProvider, - $controller: $ControllerProvider, - $document: $DocumentProvider, - $exceptionHandler: $ExceptionHandlerProvider, - $filter: $FilterProvider, - $$forceReflow: $$ForceReflowProvider, - $interpolate: $InterpolateProvider, - $interval: $IntervalProvider, - $http: $HttpProvider, - $httpParamSerializer: $HttpParamSerializerProvider, - $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, - $httpBackend: $HttpBackendProvider, - $xhrFactory: $xhrFactoryProvider, - $location: $LocationProvider, - $log: $LogProvider, - $parse: $ParseProvider, - $rootScope: $RootScopeProvider, - $q: $QProvider, - $$q: $$QProvider, - $sce: $SceProvider, - $sceDelegate: $SceDelegateProvider, - $sniffer: $SnifferProvider, - $templateCache: $TemplateCacheProvider, - $templateRequest: $TemplateRequestProvider, - $$testability: $$TestabilityProvider, - $timeout: $TimeoutProvider, - $window: $WindowProvider, - $$rAF: $$RAFProvider, - $$jqLite: $$jqLiteProvider, - $$HashMap: $$HashMapProvider, - $$cookieReader: $$CookieReaderProvider - }); - } - ]); -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Any commits to this file should be reviewed with security in mind. * - * Changes to this file can potentially create security vulnerabilities. * - * An approval from 2 Core members with history of modifying * - * this file is required. * - * * - * Does the change somehow allow for arbitrary javascript to be executed? * - * Or allows for someone to change the prototype of built-in objects? * - * Or gives undesired access to variables likes document or window? * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -/* global JQLitePrototype: true, - addEventListenerFn: true, - removeEventListenerFn: true, - BOOLEAN_ATTR: true, - ALIASED_ATTR: true, -*/ - -////////////////////////////////// -//JQLite -////////////////////////////////// - -/** - * @ngdoc function - * @name angular.element - * @module ng - * @kind function - * - * @description - * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. - * - * If jQuery is available, `angular.element` is an alias for the - * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` - * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." - * - * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows - * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most - * commonly needed functionality with the goal of having a very small footprint.</div> - * - * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. - * - * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or - * jqLite; they are never raw DOM references.</div> - * - * ## Angular's jqLite - * jqLite provides only the following jQuery methods: - * - * - [`addClass()`](http://api.jquery.com/addClass/) - * - [`after()`](http://api.jquery.com/after/) - * - [`append()`](http://api.jquery.com/append/) - * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters - * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData - * - [`children()`](http://api.jquery.com/children/) - Does not support selectors - * - [`clone()`](http://api.jquery.com/clone/) - * - [`contents()`](http://api.jquery.com/contents/) - * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'. - * - [`data()`](http://api.jquery.com/data/) - * - [`detach()`](http://api.jquery.com/detach/) - * - [`empty()`](http://api.jquery.com/empty/) - * - [`eq()`](http://api.jquery.com/eq/) - * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name - * - [`hasClass()`](http://api.jquery.com/hasClass/) - * - [`html()`](http://api.jquery.com/html/) - * - [`next()`](http://api.jquery.com/next/) - Does not support selectors - * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData - * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter - * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors - * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors - * - [`prepend()`](http://api.jquery.com/prepend/) - * - [`prop()`](http://api.jquery.com/prop/) - * - [`ready()`](http://api.jquery.com/ready/) - * - [`remove()`](http://api.jquery.com/remove/) - * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - * - [`removeClass()`](http://api.jquery.com/removeClass/) - * - [`removeData()`](http://api.jquery.com/removeData/) - * - [`replaceWith()`](http://api.jquery.com/replaceWith/) - * - [`text()`](http://api.jquery.com/text/) - * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. - * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter - * - [`val()`](http://api.jquery.com/val/) - * - [`wrap()`](http://api.jquery.com/wrap/) - * - * ## jQuery/jqLite Extras - * Angular also provides the following additional methods and events to both jQuery and jqLite: - * - * ### Events - * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event - * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM - * element before it is removed. - * - * ### Methods - * - `controller(name)` - retrieves the controller of the current element or its parent. By default - * retrieves controller associated with the `ngController` directive. If `name` is provided as - * camelCase directive name, then the controller for this directive will be retrieved (e.g. - * `'ngModel'`). - * - `injector()` - retrieves the injector of the current element or its parent. - * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current - * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to - * be enabled. - * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the - * current element. This getter should be used only on elements that contain a directive which starts a new isolate - * scope. Calling `scope()` on this element always returns the original non-isolate scope. - * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. - * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top - * parent element is reached. - * - * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. - * @returns {Object} jQuery object. - */ - -JQLite.expando = 'ng339'; - -var jqCache = JQLite.cache = {}, - jqId = 1, - addEventListenerFn = function(element, type, fn) { - element.addEventListener(type, fn, false); - }, - removeEventListenerFn = function(element, type, fn) { - element.removeEventListener(type, fn, false); - }; - -/* - * !!! This is an undocumented "private" function !!! - */ -JQLite._data = function(node) { - //jQuery always returns an object on cache miss - return this.cache[node[this.expando]] || {}; -}; - -function jqNextId() { return ++jqId; } - - -var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; -var MOZ_HACK_REGEXP = /^moz([A-Z])/; -var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; -var jqLiteMinErr = minErr('jqLite'); - -/** - * Converts snake_case to camelCase. - * Also there is special case for Moz prefix starting with upper case letter. - * @param name Name to normalize - */ -function camelCase(name) { - return name. - replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { - return offset ? letter.toUpperCase() : letter; - }). - replace(MOZ_HACK_REGEXP, 'Moz$1'); -} - -var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; -var HTML_REGEXP = /<|&#?\w+;/; -var TAG_NAME_REGEXP = /<([\w:-]+)/; -var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; - -var wrapMap = { - 'option': [1, '<select multiple="multiple">', '</select>'], - - 'thead': [1, '<table>', '</table>'], - 'col': [2, '<table><colgroup>', '</colgroup></table>'], - 'tr': [2, '<table><tbody>', '</tbody></table>'], - 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], - '_default': [0, "", ""] -}; - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - - -function jqLiteIsTextNode(html) { - return !HTML_REGEXP.test(html); -} - -function jqLiteAcceptsData(node) { - // The window object can accept data but has no nodeType - // Otherwise we are only interested in elements (1) and documents (9) - var nodeType = node.nodeType; - return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; -} - -function jqLiteHasData(node) { - for (var key in jqCache[node.ng339]) { - return true; - } - return false; -} - -function jqLiteBuildFragment(html, context) { - var tmp, tag, wrap, - fragment = context.createDocumentFragment(), - nodes = [], i; - - if (jqLiteIsTextNode(html)) { - // Convert non-html into a text node - nodes.push(context.createTextNode(html)); - } else { - // Convert html into DOM nodes - tmp = tmp || fragment.appendChild(context.createElement("div")); - tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); - wrap = wrapMap[tag] || wrapMap._default; - tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; - - // Descend through wrappers to the right content - i = wrap[0]; - while (i--) { - tmp = tmp.lastChild; - } - - nodes = concat(nodes, tmp.childNodes); - - tmp = fragment.firstChild; - tmp.textContent = ""; - } - - // Remove wrapper from fragment - fragment.textContent = ""; - fragment.innerHTML = ""; // Clear inner HTML - forEach(nodes, function(node) { - fragment.appendChild(node); - }); - - return fragment; -} - -function jqLiteParseHTML(html, context) { - context = context || document; - var parsed; - - if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { - return [context.createElement(parsed[1])]; - } - - if ((parsed = jqLiteBuildFragment(html, context))) { - return parsed.childNodes; - } - - return []; -} - -///////////////////////////////////////////// -function JQLite(element) { - if (element instanceof JQLite) { - return element; - } - - var argIsString; - - if (isString(element)) { - element = trim(element); - argIsString = true; - } - if (!(this instanceof JQLite)) { - if (argIsString && element.charAt(0) != '<') { - throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); - } - return new JQLite(element); - } - - if (argIsString) { - jqLiteAddNodes(this, jqLiteParseHTML(element)); - } else { - jqLiteAddNodes(this, element); - } -} - -function jqLiteClone(element) { - return element.cloneNode(true); -} - -function jqLiteDealoc(element, onlyDescendants) { - if (!onlyDescendants) jqLiteRemoveData(element); - - if (element.querySelectorAll) { - var descendants = element.querySelectorAll('*'); - for (var i = 0, l = descendants.length; i < l; i++) { - jqLiteRemoveData(descendants[i]); - } - } -} - -function jqLiteOff(element, type, fn, unsupported) { - if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); - - var expandoStore = jqLiteExpandoStore(element); - var events = expandoStore && expandoStore.events; - var handle = expandoStore && expandoStore.handle; - - if (!handle) return; //no listeners registered - - if (!type) { - for (type in events) { - if (type !== '$destroy') { - removeEventListenerFn(element, type, handle); - } - delete events[type]; - } - } else { - forEach(type.split(' '), function(type) { - if (isDefined(fn)) { - var listenerFns = events[type]; - arrayRemove(listenerFns || [], fn); - if (listenerFns && listenerFns.length > 0) { - return; - } - } - - removeEventListenerFn(element, type, handle); - delete events[type]; - }); - } -} - -function jqLiteRemoveData(element, name) { - var expandoId = element.ng339; - var expandoStore = expandoId && jqCache[expandoId]; - - if (expandoStore) { - if (name) { - delete expandoStore.data[name]; - return; - } - - if (expandoStore.handle) { - if (expandoStore.events.$destroy) { - expandoStore.handle({}, '$destroy'); - } - jqLiteOff(element); - } - delete jqCache[expandoId]; - element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it - } -} - - -function jqLiteExpandoStore(element, createIfNecessary) { - var expandoId = element.ng339, - expandoStore = expandoId && jqCache[expandoId]; - - if (createIfNecessary && !expandoStore) { - element.ng339 = expandoId = jqNextId(); - expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; - } - - return expandoStore; -} - - -function jqLiteData(element, key, value) { - if (jqLiteAcceptsData(element)) { - - var isSimpleSetter = isDefined(value); - var isSimpleGetter = !isSimpleSetter && key && !isObject(key); - var massGetter = !key; - var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); - var data = expandoStore && expandoStore.data; - - if (isSimpleSetter) { // data('key', value) - data[key] = value; - } else { - if (massGetter) { // data() - return data; - } else { - if (isSimpleGetter) { // data('key') - // don't force creation of expandoStore if it doesn't exist yet - return data && data[key]; - } else { // mass-setter: data({key1: val1, key2: val2}) - extend(data, key); - } - } - } - } -} - -function jqLiteHasClass(element, selector) { - if (!element.getAttribute) return false; - return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). - indexOf(" " + selector + " ") > -1); -} - -function jqLiteRemoveClass(element, cssClasses) { - if (cssClasses && element.setAttribute) { - forEach(cssClasses.split(' '), function(cssClass) { - element.setAttribute('class', trim( - (" " + (element.getAttribute('class') || '') + " ") - .replace(/[\n\t]/g, " ") - .replace(" " + trim(cssClass) + " ", " ")) - ); - }); - } -} - -function jqLiteAddClass(element, cssClasses) { - if (cssClasses && element.setAttribute) { - var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') - .replace(/[\n\t]/g, " "); - - forEach(cssClasses.split(' '), function(cssClass) { - cssClass = trim(cssClass); - if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { - existingClasses += cssClass + ' '; - } - }); - - element.setAttribute('class', trim(existingClasses)); - } -} - - -function jqLiteAddNodes(root, elements) { - // THIS CODE IS VERY HOT. Don't make changes without benchmarking. - - if (elements) { - - // if a Node (the most common case) - if (elements.nodeType) { - root[root.length++] = elements; - } else { - var length = elements.length; - - // if an Array or NodeList and not a Window - if (typeof length === 'number' && elements.window !== elements) { - if (length) { - for (var i = 0; i < length; i++) { - root[root.length++] = elements[i]; - } - } - } else { - root[root.length++] = elements; - } - } - } -} - - -function jqLiteController(element, name) { - return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); -} - -function jqLiteInheritedData(element, name, value) { - // if element is the document object work with the html element instead - // this makes $(document).scope() possible - if (element.nodeType == NODE_TYPE_DOCUMENT) { - element = element.documentElement; - } - var names = isArray(name) ? name : [name]; - - while (element) { - for (var i = 0, ii = names.length; i < ii; i++) { - if (isDefined(value = jqLite.data(element, names[i]))) return value; - } - - // If dealing with a document fragment node with a host element, and no parent, use the host - // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM - // to lookup parent controllers. - element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); - } -} - -function jqLiteEmpty(element) { - jqLiteDealoc(element, true); - while (element.firstChild) { - element.removeChild(element.firstChild); - } -} - -function jqLiteRemove(element, keepData) { - if (!keepData) jqLiteDealoc(element); - var parent = element.parentNode; - if (parent) parent.removeChild(element); -} - - -function jqLiteDocumentLoaded(action, win) { - win = win || window; - if (win.document.readyState === 'complete') { - // Force the action to be run async for consistent behaviour - // from the action's point of view - // i.e. it will definitely not be in a $apply - win.setTimeout(action); - } else { - // No need to unbind this handler as load is only ever called once - jqLite(win).on('load', action); - } -} - -////////////////////////////////////////// -// Functions which are declared directly. -////////////////////////////////////////// -var JQLitePrototype = JQLite.prototype = { - ready: function(fn) { - var fired = false; - - function trigger() { - if (fired) return; - fired = true; - fn(); - } - - // check if document is already loaded - if (document.readyState === 'complete') { - setTimeout(trigger); - } else { - this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 - // we can not use jqLite since we are not done loading and jQuery could be loaded later. - // jshint -W064 - JQLite(window).on('load', trigger); // fallback to window.onload for others - // jshint +W064 - } - }, - toString: function() { - var value = []; - forEach(this, function(e) { value.push('' + e);}); - return '[' + value.join(', ') + ']'; - }, - - eq: function(index) { - return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); - }, - - length: 0, - push: push, - sort: [].sort, - splice: [].splice -}; - -////////////////////////////////////////// -// Functions iterating getter/setters. -// these functions return self on setter and -// value on get. -////////////////////////////////////////// -var BOOLEAN_ATTR = {}; -forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { - BOOLEAN_ATTR[lowercase(value)] = value; -}); -var BOOLEAN_ELEMENTS = {}; -forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { - BOOLEAN_ELEMENTS[value] = true; -}); -var ALIASED_ATTR = { - 'ngMinlength': 'minlength', - 'ngMaxlength': 'maxlength', - 'ngMin': 'min', - 'ngMax': 'max', - 'ngPattern': 'pattern' -}; - -function getBooleanAttrName(element, name) { - // check dom last since we will most likely fail on name - var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; - - // booleanAttr is here twice to minimize DOM access - return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; -} - -function getAliasedAttrName(name) { - return ALIASED_ATTR[name]; -} - -forEach({ - data: jqLiteData, - removeData: jqLiteRemoveData, - hasData: jqLiteHasData -}, function(fn, name) { - JQLite[name] = fn; -}); - -forEach({ - data: jqLiteData, - inheritedData: jqLiteInheritedData, - - scope: function(element) { - // Can't use jqLiteData here directly so we stay compatible with jQuery! - return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); - }, - - isolateScope: function(element) { - // Can't use jqLiteData here directly so we stay compatible with jQuery! - return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); - }, - - controller: jqLiteController, - - injector: function(element) { - return jqLiteInheritedData(element, '$injector'); - }, - - removeAttr: function(element, name) { - element.removeAttribute(name); - }, - - hasClass: jqLiteHasClass, - - css: function(element, name, value) { - name = camelCase(name); - - if (isDefined(value)) { - element.style[name] = value; - } else { - return element.style[name]; - } - }, - - attr: function(element, name, value) { - var nodeType = element.nodeType; - if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { - return; - } - var lowercasedName = lowercase(name); - if (BOOLEAN_ATTR[lowercasedName]) { - if (isDefined(value)) { - if (!!value) { - element[name] = true; - element.setAttribute(name, lowercasedName); - } else { - element[name] = false; - element.removeAttribute(lowercasedName); - } - } else { - return (element[name] || - (element.attributes.getNamedItem(name) || noop).specified) - ? lowercasedName - : undefined; - } - } else if (isDefined(value)) { - element.setAttribute(name, value); - } else if (element.getAttribute) { - // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code - // some elements (e.g. Document) don't have get attribute, so return undefined - var ret = element.getAttribute(name, 2); - // normalize non-existing attributes to undefined (as jQuery) - return ret === null ? undefined : ret; - } - }, - - prop: function(element, name, value) { - if (isDefined(value)) { - element[name] = value; - } else { - return element[name]; - } - }, - - text: (function() { - getText.$dv = ''; - return getText; - - function getText(element, value) { - if (isUndefined(value)) { - var nodeType = element.nodeType; - return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; - } - element.textContent = value; - } - })(), - - val: function(element, value) { - if (isUndefined(value)) { - if (element.multiple && nodeName_(element) === 'select') { - var result = []; - forEach(element.options, function(option) { - if (option.selected) { - result.push(option.value || option.text); - } - }); - return result.length === 0 ? null : result; - } - return element.value; - } - element.value = value; - }, - - html: function(element, value) { - if (isUndefined(value)) { - return element.innerHTML; - } - jqLiteDealoc(element, true); - element.innerHTML = value; - }, - - empty: jqLiteEmpty -}, function(fn, name) { - /** - * Properties: writes return selection, reads return first value - */ - JQLite.prototype[name] = function(arg1, arg2) { - var i, key; - var nodeCount = this.length; - - // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it - // in a way that survives minification. - // jqLiteEmpty takes no arguments but is a setter. - if (fn !== jqLiteEmpty && - (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { - if (isObject(arg1)) { - - // we are a write, but the object properties are the key/values - for (i = 0; i < nodeCount; i++) { - if (fn === jqLiteData) { - // data() takes the whole object in jQuery - fn(this[i], arg1); - } else { - for (key in arg1) { - fn(this[i], key, arg1[key]); - } - } - } - // return self for chaining - return this; - } else { - // we are a read, so read the first child. - // TODO: do we still need this? - var value = fn.$dv; - // Only if we have $dv do we iterate over all, otherwise it is just the first element. - var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; - for (var j = 0; j < jj; j++) { - var nodeValue = fn(this[j], arg1, arg2); - value = value ? value + nodeValue : nodeValue; - } - return value; - } - } else { - // we are a write, so apply to all children - for (i = 0; i < nodeCount; i++) { - fn(this[i], arg1, arg2); - } - // return self for chaining - return this; - } - }; -}); - -function createEventHandler(element, events) { - var eventHandler = function(event, type) { - // jQuery specific api - event.isDefaultPrevented = function() { - return event.defaultPrevented; - }; - - var eventFns = events[type || event.type]; - var eventFnsLength = eventFns ? eventFns.length : 0; - - if (!eventFnsLength) return; - - if (isUndefined(event.immediatePropagationStopped)) { - var originalStopImmediatePropagation = event.stopImmediatePropagation; - event.stopImmediatePropagation = function() { - event.immediatePropagationStopped = true; - - if (event.stopPropagation) { - event.stopPropagation(); - } - - if (originalStopImmediatePropagation) { - originalStopImmediatePropagation.call(event); - } - }; - } - - event.isImmediatePropagationStopped = function() { - return event.immediatePropagationStopped === true; - }; - - // Copy event handlers in case event handlers array is modified during execution. - if ((eventFnsLength > 1)) { - eventFns = shallowCopy(eventFns); - } - - for (var i = 0; i < eventFnsLength; i++) { - if (!event.isImmediatePropagationStopped()) { - eventFns[i].call(element, event); - } - } - }; - - // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all - // events on `element` - eventHandler.elem = element; - return eventHandler; -} - -////////////////////////////////////////// -// Functions iterating traversal. -// These functions chain results into a single -// selector. -////////////////////////////////////////// -forEach({ - removeData: jqLiteRemoveData, - - on: function jqLiteOn(element, type, fn, unsupported) { - if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); - - // Do not add event handlers to non-elements because they will not be cleaned up. - if (!jqLiteAcceptsData(element)) { - return; - } - - var expandoStore = jqLiteExpandoStore(element, true); - var events = expandoStore.events; - var handle = expandoStore.handle; - - if (!handle) { - handle = expandoStore.handle = createEventHandler(element, events); - } - - // http://jsperf.com/string-indexof-vs-split - var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; - var i = types.length; - - while (i--) { - type = types[i]; - var eventFns = events[type]; - - if (!eventFns) { - events[type] = []; - - if (type === 'mouseenter' || type === 'mouseleave') { - // Refer to jQuery's implementation of mouseenter & mouseleave - // Read about mouseenter and mouseleave: - // http://www.quirksmode.org/js/events_mouse.html#link8 - - jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) { - var target = this, related = event.relatedTarget; - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if (!related || (related !== target && !target.contains(related))) { - handle(event, type); - } - }); - - } else { - if (type !== '$destroy') { - addEventListenerFn(element, type, handle); - } - } - eventFns = events[type]; - } - eventFns.push(fn); - } - }, - - off: jqLiteOff, - - one: function(element, type, fn) { - element = jqLite(element); - - //add the listener twice so that when it is called - //you can remove the original function and still be - //able to call element.off(ev, fn) normally - element.on(type, function onFn() { - element.off(type, fn); - element.off(type, onFn); - }); - element.on(type, fn); - }, - - replaceWith: function(element, replaceNode) { - var index, parent = element.parentNode; - jqLiteDealoc(element); - forEach(new JQLite(replaceNode), function(node) { - if (index) { - parent.insertBefore(node, index.nextSibling); - } else { - parent.replaceChild(node, element); - } - index = node; - }); - }, - - children: function(element) { - var children = []; - forEach(element.childNodes, function(element) { - if (element.nodeType === NODE_TYPE_ELEMENT) { - children.push(element); - } - }); - return children; - }, - - contents: function(element) { - return element.contentDocument || element.childNodes || []; - }, - - append: function(element, node) { - var nodeType = element.nodeType; - if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; - - node = new JQLite(node); - - for (var i = 0, ii = node.length; i < ii; i++) { - var child = node[i]; - element.appendChild(child); - } - }, - - prepend: function(element, node) { - if (element.nodeType === NODE_TYPE_ELEMENT) { - var index = element.firstChild; - forEach(new JQLite(node), function(child) { - element.insertBefore(child, index); - }); - } - }, - - wrap: function(element, wrapNode) { - wrapNode = jqLite(wrapNode).eq(0).clone()[0]; - var parent = element.parentNode; - if (parent) { - parent.replaceChild(wrapNode, element); - } - wrapNode.appendChild(element); - }, - - remove: jqLiteRemove, - - detach: function(element) { - jqLiteRemove(element, true); - }, - - after: function(element, newElement) { - var index = element, parent = element.parentNode; - newElement = new JQLite(newElement); - - for (var i = 0, ii = newElement.length; i < ii; i++) { - var node = newElement[i]; - parent.insertBefore(node, index.nextSibling); - index = node; - } - }, - - addClass: jqLiteAddClass, - removeClass: jqLiteRemoveClass, - - toggleClass: function(element, selector, condition) { - if (selector) { - forEach(selector.split(' '), function(className) { - var classCondition = condition; - if (isUndefined(classCondition)) { - classCondition = !jqLiteHasClass(element, className); - } - (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); - }); - } - }, - - parent: function(element) { - var parent = element.parentNode; - return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; - }, - - next: function(element) { - return element.nextElementSibling; - }, - - find: function(element, selector) { - if (element.getElementsByTagName) { - return element.getElementsByTagName(selector); - } else { - return []; - } - }, - - clone: jqLiteClone, - - triggerHandler: function(element, event, extraParameters) { - - var dummyEvent, eventFnsCopy, handlerArgs; - var eventName = event.type || event; - var expandoStore = jqLiteExpandoStore(element); - var events = expandoStore && expandoStore.events; - var eventFns = events && events[eventName]; - - if (eventFns) { - // Create a dummy event to pass to the handlers - dummyEvent = { - preventDefault: function() { this.defaultPrevented = true; }, - isDefaultPrevented: function() { return this.defaultPrevented === true; }, - stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, - isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, - stopPropagation: noop, - type: eventName, - target: element - }; - - // If a custom event was provided then extend our dummy event with it - if (event.type) { - dummyEvent = extend(dummyEvent, event); - } - - // Copy event handlers in case event handlers array is modified during execution. - eventFnsCopy = shallowCopy(eventFns); - handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; - - forEach(eventFnsCopy, function(fn) { - if (!dummyEvent.isImmediatePropagationStopped()) { - fn.apply(element, handlerArgs); - } - }); - } - } -}, function(fn, name) { - /** - * chaining functions - */ - JQLite.prototype[name] = function(arg1, arg2, arg3) { - var value; - - for (var i = 0, ii = this.length; i < ii; i++) { - if (isUndefined(value)) { - value = fn(this[i], arg1, arg2, arg3); - if (isDefined(value)) { - // any function which returns a value needs to be wrapped - value = jqLite(value); - } - } else { - jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); - } - } - return isDefined(value) ? value : this; - }; - - // bind legacy bind/unbind to on/off - JQLite.prototype.bind = JQLite.prototype.on; - JQLite.prototype.unbind = JQLite.prototype.off; -}); - - -// Provider for private $$jqLite service -function $$jqLiteProvider() { - this.$get = function $$jqLite() { - return extend(JQLite, { - hasClass: function(node, classes) { - if (node.attr) node = node[0]; - return jqLiteHasClass(node, classes); - }, - addClass: function(node, classes) { - if (node.attr) node = node[0]; - return jqLiteAddClass(node, classes); - }, - removeClass: function(node, classes) { - if (node.attr) node = node[0]; - return jqLiteRemoveClass(node, classes); - } - }); - }; -} - -/** - * Computes a hash of an 'obj'. - * Hash of a: - * string is string - * number is number as string - * object is either result of calling $$hashKey function on the object or uniquely generated id, - * that is also assigned to the $$hashKey property of the object. - * - * @param obj - * @returns {string} hash string such that the same input will have the same hash string. - * The resulting string key is in 'type:hashKey' format. - */ -function hashKey(obj, nextUidFn) { - var key = obj && obj.$$hashKey; - - if (key) { - if (typeof key === 'function') { - key = obj.$$hashKey(); - } - return key; - } - - var objType = typeof obj; - if (objType == 'function' || (objType == 'object' && obj !== null)) { - key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); - } else { - key = objType + ':' + obj; - } - - return key; -} - -/** - * HashMap which can use objects as keys - */ -function HashMap(array, isolatedUid) { - if (isolatedUid) { - var uid = 0; - this.nextUid = function() { - return ++uid; - }; - } - forEach(array, this.put, this); -} -HashMap.prototype = { - /** - * Store key value pair - * @param key key to store can be any type - * @param value value to store can be any type - */ - put: function(key, value) { - this[hashKey(key, this.nextUid)] = value; - }, - - /** - * @param key - * @returns {Object} the value for the key - */ - get: function(key) { - return this[hashKey(key, this.nextUid)]; - }, - - /** - * Remove the key/value pair - * @param key - */ - remove: function(key) { - var value = this[key = hashKey(key, this.nextUid)]; - delete this[key]; - return value; - } -}; - -var $$HashMapProvider = [function() { - this.$get = [function() { - return HashMap; - }]; -}]; - -/** - * @ngdoc function - * @module ng - * @name angular.injector - * @kind function - * - * @description - * Creates an injector object that can be used for retrieving services as well as for - * dependency injection (see {@link guide/di dependency injection}). - * - * @param {Array.<string|Function>} modules A list of module functions or their aliases. See - * {@link angular.module}. The `ng` module must be explicitly added. - * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which - * disallows argument name annotation inference. - * @returns {injector} Injector object. See {@link auto.$injector $injector}. - * - * @example - * Typical usage - * ```js - * // create an injector - * var $injector = angular.injector(['ng']); - * - * // use the injector to kick off your application - * // use the type inference to auto inject arguments, or use implicit injection - * $injector.invoke(function($rootScope, $compile, $document) { - * $compile($document)($rootScope); - * $rootScope.$digest(); - * }); - * ``` - * - * Sometimes you want to get access to the injector of a currently running Angular app - * from outside Angular. Perhaps, you want to inject and compile some markup after the - * application has been bootstrapped. You can do this using the extra `injector()` added - * to JQuery/jqLite elements. See {@link angular.element}. - * - * *This is fairly rare but could be the case if a third party library is injecting the - * markup.* - * - * In the following example a new block of HTML containing a `ng-controller` - * directive is added to the end of the document body by JQuery. We then compile and link - * it into the current AngularJS scope. - * - * ```js - * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); - * $(document.body).append($div); - * - * angular.element(document).injector().invoke(function($compile) { - * var scope = angular.element($div).scope(); - * $compile($div)(scope); - * }); - * ``` - */ - - -/** - * @ngdoc module - * @name auto - * @description - * - * Implicit module which gets automatically added to each {@link auto.$injector $injector}. - */ - -var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m; -var FN_ARG_SPLIT = /,/; -var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; -var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; -var $injectorMinErr = minErr('$injector'); - -function anonFn(fn) { - // For anonymous functions, showing at the very least the function signature can help in - // debugging. - var fnText = fn.toString().replace(STRIP_COMMENTS, ''), - args = fnText.match(FN_ARGS); - if (args) { - return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; - } - return 'fn'; -} - -function annotate(fn, strictDi, name) { - var $inject, - fnText, - argDecl, - last; - - if (typeof fn === 'function') { - if (!($inject = fn.$inject)) { - $inject = []; - if (fn.length) { - if (strictDi) { - if (!isString(name) || !name) { - name = fn.name || anonFn(fn); - } - throw $injectorMinErr('strictdi', - '{0} is not using explicit annotation and cannot be invoked in strict mode', name); - } - fnText = fn.toString().replace(STRIP_COMMENTS, ''); - argDecl = fnText.match(FN_ARGS); - forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { - arg.replace(FN_ARG, function(all, underscore, name) { - $inject.push(name); - }); - }); - } - fn.$inject = $inject; - } - } else if (isArray(fn)) { - last = fn.length - 1; - assertArgFn(fn[last], 'fn'); - $inject = fn.slice(0, last); - } else { - assertArgFn(fn, 'fn', true); - } - return $inject; -} - -/////////////////////////////////////// - -/** - * @ngdoc service - * @name $injector - * - * @description - * - * `$injector` is used to retrieve object instances as defined by - * {@link auto.$provide provider}, instantiate types, invoke methods, - * and load modules. - * - * The following always holds true: - * - * ```js - * var $injector = angular.injector(); - * expect($injector.get('$injector')).toBe($injector); - * expect($injector.invoke(function($injector) { - * return $injector; - * })).toBe($injector); - * ``` - * - * # Injection Function Annotation - * - * JavaScript does not have annotations, and annotations are needed for dependency injection. The - * following are all valid ways of annotating function with injection arguments and are equivalent. - * - * ```js - * // inferred (only works if code not minified/obfuscated) - * $injector.invoke(function(serviceA){}); - * - * // annotated - * function explicit(serviceA) {}; - * explicit.$inject = ['serviceA']; - * $injector.invoke(explicit); - * - * // inline - * $injector.invoke(['serviceA', function(serviceA){}]); - * ``` - * - * ## Inference - * - * In JavaScript calling `toString()` on a function returns the function definition. The definition - * can then be parsed and the function arguments can be extracted. This method of discovering - * annotations is disallowed when the injector is in strict mode. - * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the - * argument names. - * - * ## `$inject` Annotation - * By adding an `$inject` property onto a function the injection parameters can be specified. - * - * ## Inline - * As an array of injection names, where the last item in the array is the function to call. - */ - -/** - * @ngdoc method - * @name $injector#get - * - * @description - * Return an instance of the service. - * - * @param {string} name The name of the instance to retrieve. - * @param {string=} caller An optional string to provide the origin of the function call for error messages. - * @return {*} The instance. - */ - -/** - * @ngdoc method - * @name $injector#invoke - * - * @description - * Invoke the method and supply the method arguments from the `$injector`. - * - * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are - * injected according to the {@link guide/di $inject Annotation} rules. - * @param {Object=} self The `this` for the invoked method. - * @param {Object=} locals Optional object. If preset then any argument names are read from this - * object first, before the `$injector` is consulted. - * @returns {*} the value returned by the invoked `fn` function. - */ - -/** - * @ngdoc method - * @name $injector#has - * - * @description - * Allows the user to query if the particular service exists. - * - * @param {string} name Name of the service to query. - * @returns {boolean} `true` if injector has given service. - */ - -/** - * @ngdoc method - * @name $injector#instantiate - * @description - * Create a new instance of JS type. The method takes a constructor function, invokes the new - * operator, and supplies all of the arguments to the constructor function as specified by the - * constructor annotation. - * - * @param {Function} Type Annotated constructor function. - * @param {Object=} locals Optional object. If preset then any argument names are read from this - * object first, before the `$injector` is consulted. - * @returns {Object} new instance of `Type`. - */ - -/** - * @ngdoc method - * @name $injector#annotate - * - * @description - * Returns an array of service names which the function is requesting for injection. This API is - * used by the injector to determine which services need to be injected into the function when the - * function is invoked. There are three ways in which the function can be annotated with the needed - * dependencies. - * - * # Argument names - * - * The simplest form is to extract the dependencies from the arguments of the function. This is done - * by converting the function into a string using `toString()` method and extracting the argument - * names. - * ```js - * // Given - * function MyController($scope, $route) { - * // ... - * } - * - * // Then - * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); - * ``` - * - * You can disallow this method by using strict injection mode. - * - * This method does not work with code minification / obfuscation. For this reason the following - * annotation strategies are supported. - * - * # The `$inject` property - * - * If a function has an `$inject` property and its value is an array of strings, then the strings - * represent names of services to be injected into the function. - * ```js - * // Given - * var MyController = function(obfuscatedScope, obfuscatedRoute) { - * // ... - * } - * // Define function dependencies - * MyController['$inject'] = ['$scope', '$route']; - * - * // Then - * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); - * ``` - * - * # The array notation - * - * It is often desirable to inline Injected functions and that's when setting the `$inject` property - * is very inconvenient. In these situations using the array notation to specify the dependencies in - * a way that survives minification is a better choice: - * - * ```js - * // We wish to write this (not minification / obfuscation safe) - * injector.invoke(function($compile, $rootScope) { - * // ... - * }); - * - * // We are forced to write break inlining - * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { - * // ... - * }; - * tmpFn.$inject = ['$compile', '$rootScope']; - * injector.invoke(tmpFn); - * - * // To better support inline function the inline annotation is supported - * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { - * // ... - * }]); - * - * // Therefore - * expect(injector.annotate( - * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) - * ).toEqual(['$compile', '$rootScope']); - * ``` - * - * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to - * be retrieved as described above. - * - * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. - * - * @returns {Array.<string>} The names of the services which the function requires. - */ - - - - -/** - * @ngdoc service - * @name $provide - * - * @description - * - * The {@link auto.$provide $provide} service has a number of methods for registering components - * with the {@link auto.$injector $injector}. Many of these functions are also exposed on - * {@link angular.Module}. - * - * An Angular **service** is a singleton object created by a **service factory**. These **service - * factories** are functions which, in turn, are created by a **service provider**. - * The **service providers** are constructor functions. When instantiated they must contain a - * property called `$get`, which holds the **service factory** function. - * - * When you request a service, the {@link auto.$injector $injector} is responsible for finding the - * correct **service provider**, instantiating it and then calling its `$get` **service factory** - * function to get the instance of the **service**. - * - * Often services have no configuration options and there is no need to add methods to the service - * provider. The provider will be no more than a constructor function with a `$get` property. For - * these cases the {@link auto.$provide $provide} service has additional helper methods to register - * services without specifying a provider. - * - * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the - * {@link auto.$injector $injector} - * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by - * providers and services. - * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by - * services, not providers. - * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, - * that will be wrapped in a **service provider** object, whose `$get` property will contain the - * given factory function. - * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` - * that will be wrapped in a **service provider** object, whose `$get` property will instantiate - * a new object using the given constructor function. - * - * See the individual methods for more information and examples. - */ - -/** - * @ngdoc method - * @name $provide#provider - * @description - * - * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions - * are constructor functions, whose instances are responsible for "providing" a factory for a - * service. - * - * Service provider names start with the name of the service they provide followed by `Provider`. - * For example, the {@link ng.$log $log} service has a provider called - * {@link ng.$logProvider $logProvider}. - * - * Service provider objects can have additional methods which allow configuration of the provider - * and its service. Importantly, you can configure what kind of service is created by the `$get` - * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a - * method {@link ng.$logProvider#debugEnabled debugEnabled} - * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the - * console or not. - * - * @param {string} name The name of the instance. NOTE: the provider will be available under `name + - 'Provider'` key. - * @param {(Object|function())} provider If the provider is: - * - * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using - * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. - * - `Constructor`: a new instance of the provider will be created using - * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. - * - * @returns {Object} registered provider instance - - * @example - * - * The following example shows how to create a simple event tracking service and register it using - * {@link auto.$provide#provider $provide.provider()}. - * - * ```js - * // Define the eventTracker provider - * function EventTrackerProvider() { - * var trackingUrl = '/track'; - * - * // A provider method for configuring where the tracked events should been saved - * this.setTrackingUrl = function(url) { - * trackingUrl = url; - * }; - * - * // The service factory function - * this.$get = ['$http', function($http) { - * var trackedEvents = {}; - * return { - * // Call this to track an event - * event: function(event) { - * var count = trackedEvents[event] || 0; - * count += 1; - * trackedEvents[event] = count; - * return count; - * }, - * // Call this to save the tracked events to the trackingUrl - * save: function() { - * $http.post(trackingUrl, trackedEvents); - * } - * }; - * }]; - * } - * - * describe('eventTracker', function() { - * var postSpy; - * - * beforeEach(module(function($provide) { - * // Register the eventTracker provider - * $provide.provider('eventTracker', EventTrackerProvider); - * })); - * - * beforeEach(module(function(eventTrackerProvider) { - * // Configure eventTracker provider - * eventTrackerProvider.setTrackingUrl('/custom-track'); - * })); - * - * it('tracks events', inject(function(eventTracker) { - * expect(eventTracker.event('login')).toEqual(1); - * expect(eventTracker.event('login')).toEqual(2); - * })); - * - * it('saves to the tracking url', inject(function(eventTracker, $http) { - * postSpy = spyOn($http, 'post'); - * eventTracker.event('login'); - * eventTracker.save(); - * expect(postSpy).toHaveBeenCalled(); - * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); - * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); - * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); - * })); - * }); - * ``` - */ - -/** - * @ngdoc method - * @name $provide#factory - * @description - * - * Register a **service factory**, which will be called to return the service instance. - * This is short for registering a service where its provider consists of only a `$get` property, - * which is the given service factory function. - * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to - * configure your service in a provider. - * - * @param {string} name The name of the instance. - * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. - * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. - * @returns {Object} registered provider instance - * - * @example - * Here is an example of registering a service - * ```js - * $provide.factory('ping', ['$http', function($http) { - * return function ping() { - * return $http.send('/ping'); - * }; - * }]); - * ``` - * You would then inject and use this service like this: - * ```js - * someModule.controller('Ctrl', ['ping', function(ping) { - * ping(); - * }]); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#service - * @description - * - * Register a **service constructor**, which will be invoked with `new` to create the service - * instance. - * This is short for registering a service where its provider's `$get` property is the service - * constructor function that will be used to instantiate the service instance. - * - * You should use {@link auto.$provide#service $provide.service(class)} if you define your service - * as a type/class. - * - * @param {string} name The name of the instance. - * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) - * that will be instantiated. - * @returns {Object} registered provider instance - * - * @example - * Here is an example of registering a service using - * {@link auto.$provide#service $provide.service(class)}. - * ```js - * var Ping = function($http) { - * this.$http = $http; - * }; - * - * Ping.$inject = ['$http']; - * - * Ping.prototype.send = function() { - * return this.$http.get('/ping'); - * }; - * $provide.service('ping', Ping); - * ``` - * You would then inject and use this service like this: - * ```js - * someModule.controller('Ctrl', ['ping', function(ping) { - * ping.send(); - * }]); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#value - * @description - * - * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a - * number, an array, an object or a function. This is short for registering a service where its - * provider's `$get` property is a factory function that takes no arguments and returns the **value - * service**. - * - * Value services are similar to constant services, except that they cannot be injected into a - * module configuration function (see {@link angular.Module#config}) but they can be overridden by - * an Angular - * {@link auto.$provide#decorator decorator}. - * - * @param {string} name The name of the instance. - * @param {*} value The value. - * @returns {Object} registered provider instance - * - * @example - * Here are some examples of creating value services. - * ```js - * $provide.value('ADMIN_USER', 'admin'); - * - * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); - * - * $provide.value('halfOf', function(value) { - * return value / 2; - * }); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#constant - * @description - * - * Register a **constant service**, such as a string, a number, an array, an object or a function, - * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be - * injected into a module configuration function (see {@link angular.Module#config}) and it cannot - * be overridden by an Angular {@link auto.$provide#decorator decorator}. - * - * @param {string} name The name of the constant. - * @param {*} value The constant value. - * @returns {Object} registered instance - * - * @example - * Here a some examples of creating constants: - * ```js - * $provide.constant('SHARD_HEIGHT', 306); - * - * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); - * - * $provide.constant('double', function(value) { - * return value * 2; - * }); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#decorator - * @description - * - * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator - * intercepts the creation of a service, allowing it to override or modify the behaviour of the - * service. The object returned by the decorator may be the original service, or a new service - * object which replaces or wraps and delegates to the original service. - * - * @param {string} name The name of the service to decorate. - * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be - * instantiated and should return the decorated service instance. The function is called using - * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. - * Local injection arguments: - * - * * `$delegate` - The original service instance, which can be monkey patched, configured, - * decorated or delegated to. - * - * @example - * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting - * calls to {@link ng.$log#error $log.warn()}. - * ```js - * $provide.decorator('$log', ['$delegate', function($delegate) { - * $delegate.warn = $delegate.error; - * return $delegate; - * }]); - * ``` - */ - - -function createInjector(modulesToLoad, strictDi) { - strictDi = (strictDi === true); - var INSTANTIATING = {}, - providerSuffix = 'Provider', - path = [], - loadedModules = new HashMap([], true), - providerCache = { - $provide: { - provider: supportObject(provider), - factory: supportObject(factory), - service: supportObject(service), - value: supportObject(value), - constant: supportObject(constant), - decorator: decorator - } - }, - providerInjector = (providerCache.$injector = - createInternalInjector(providerCache, function(serviceName, caller) { - if (angular.isString(caller)) { - path.push(caller); - } - throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); - })), - instanceCache = {}, - instanceInjector = (instanceCache.$injector = - createInternalInjector(instanceCache, function(serviceName, caller) { - var provider = providerInjector.get(serviceName + providerSuffix, caller); - return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); - })); - - - forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); }); - - return instanceInjector; - - //////////////////////////////////// - // $provider - //////////////////////////////////// - - function supportObject(delegate) { - return function(key, value) { - if (isObject(key)) { - forEach(key, reverseParams(delegate)); - } else { - return delegate(key, value); - } - }; - } - - function provider(name, provider_) { - assertNotHasOwnProperty(name, 'service'); - if (isFunction(provider_) || isArray(provider_)) { - provider_ = providerInjector.instantiate(provider_); - } - if (!provider_.$get) { - throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); - } - return providerCache[name + providerSuffix] = provider_; - } - - function enforceReturnValue(name, factory) { - return function enforcedReturnValue() { - var result = instanceInjector.invoke(factory, this); - if (isUndefined(result)) { - throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); - } - return result; - }; - } - - function factory(name, factoryFn, enforce) { - return provider(name, { - $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn - }); - } - - function service(name, constructor) { - return factory(name, ['$injector', function($injector) { - return $injector.instantiate(constructor); - }]); - } - - function value(name, val) { return factory(name, valueFn(val), false); } - - function constant(name, value) { - assertNotHasOwnProperty(name, 'constant'); - providerCache[name] = value; - instanceCache[name] = value; - } - - function decorator(serviceName, decorFn) { - var origProvider = providerInjector.get(serviceName + providerSuffix), - orig$get = origProvider.$get; - - origProvider.$get = function() { - var origInstance = instanceInjector.invoke(orig$get, origProvider); - return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); - }; - } - - //////////////////////////////////// - // Module Loading - //////////////////////////////////// - function loadModules(modulesToLoad) { - assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); - var runBlocks = [], moduleFn; - forEach(modulesToLoad, function(module) { - if (loadedModules.get(module)) return; - loadedModules.put(module, true); - - function runInvokeQueue(queue) { - var i, ii; - for (i = 0, ii = queue.length; i < ii; i++) { - var invokeArgs = queue[i], - provider = providerInjector.get(invokeArgs[0]); - - provider[invokeArgs[1]].apply(provider, invokeArgs[2]); - } - } - - try { - if (isString(module)) { - moduleFn = angularModule(module); - runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); - runInvokeQueue(moduleFn._invokeQueue); - runInvokeQueue(moduleFn._configBlocks); - } else if (isFunction(module)) { - runBlocks.push(providerInjector.invoke(module)); - } else if (isArray(module)) { - runBlocks.push(providerInjector.invoke(module)); - } else { - assertArgFn(module, 'module'); - } - } catch (e) { - if (isArray(module)) { - module = module[module.length - 1]; - } - if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { - // Safari & FF's stack traces don't contain error.message content - // unlike those of Chrome and IE - // So if stack doesn't contain message, we create a new string that contains both. - // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. - /* jshint -W022 */ - e = e.message + '\n' + e.stack; - } - throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", - module, e.stack || e.message || e); - } - }); - return runBlocks; - } - - //////////////////////////////////// - // internal Injector - //////////////////////////////////// - - function createInternalInjector(cache, factory) { - - function getService(serviceName, caller) { - if (cache.hasOwnProperty(serviceName)) { - if (cache[serviceName] === INSTANTIATING) { - throw $injectorMinErr('cdep', 'Circular dependency found: {0}', - serviceName + ' <- ' + path.join(' <- ')); - } - return cache[serviceName]; - } else { - try { - path.unshift(serviceName); - cache[serviceName] = INSTANTIATING; - return cache[serviceName] = factory(serviceName, caller); - } catch (err) { - if (cache[serviceName] === INSTANTIATING) { - delete cache[serviceName]; - } - throw err; - } finally { - path.shift(); - } - } - } - - function invoke(fn, self, locals, serviceName) { - if (typeof locals === 'string') { - serviceName = locals; - locals = null; - } - - var args = [], - $inject = createInjector.$$annotate(fn, strictDi, serviceName), - length, i, - key; - - for (i = 0, length = $inject.length; i < length; i++) { - key = $inject[i]; - if (typeof key !== 'string') { - throw $injectorMinErr('itkn', - 'Incorrect injection token! Expected service name as string, got {0}', key); - } - args.push( - locals && locals.hasOwnProperty(key) - ? locals[key] - : getService(key, serviceName) - ); - } - if (isArray(fn)) { - fn = fn[length]; - } - - // http://jsperf.com/angularjs-invoke-apply-vs-switch - // #5388 - return fn.apply(self, args); - } - - function instantiate(Type, locals, serviceName) { - // Check if Type is annotated and use just the given function at n-1 as parameter - // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); - // Object creation: http://jsperf.com/create-constructor/2 - var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null); - var returnedValue = invoke(Type, instance, locals, serviceName); - - return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; - } - - return { - invoke: invoke, - instantiate: instantiate, - get: getService, - annotate: createInjector.$$annotate, - has: function(name) { - return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); - } - }; - } -} - -createInjector.$$annotate = annotate; - -/** - * @ngdoc provider - * @name $anchorScrollProvider - * - * @description - * Use `$anchorScrollProvider` to disable automatic scrolling whenever - * {@link ng.$location#hash $location.hash()} changes. - */ -function $AnchorScrollProvider() { - - var autoScrollingEnabled = true; - - /** - * @ngdoc method - * @name $anchorScrollProvider#disableAutoScrolling - * - * @description - * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to - * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> - * Use this method to disable automatic scrolling. - * - * If automatic scrolling is disabled, one must explicitly call - * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the - * current hash. - */ - this.disableAutoScrolling = function() { - autoScrollingEnabled = false; - }; - - /** - * @ngdoc service - * @name $anchorScroll - * @kind function - * @requires $window - * @requires $location - * @requires $rootScope - * - * @description - * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the - * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified - * in the - * [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). - * - * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to - * match any anchor whenever it changes. This can be disabled by calling - * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. - * - * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a - * vertical scroll-offset (either fixed or dynamic). - * - * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of - * {@link ng.$location#hash $location.hash()} will be used. - * - * @property {(number|function|jqLite)} yOffset - * If set, specifies a vertical scroll-offset. This is often useful when there are fixed - * positioned elements at the top of the page, such as navbars, headers etc. - * - * `yOffset` can be specified in various ways: - * - **number**: A fixed number of pixels to be used as offset.<br /><br /> - * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return - * a number representing the offset (in pixels).<br /><br /> - * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from - * the top of the page to the element's bottom will be used as offset.<br /> - * **Note**: The element will be taken into account only as long as its `position` is set to - * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust - * their height and/or positioning according to the viewport's size. - * - * <br /> - * <div class="alert alert-warning"> - * In order for `yOffset` to work properly, scrolling should take place on the document's root and - * not some child element. - * </div> - * - * @example - <example module="anchorScrollExample"> - <file name="index.html"> - <div id="scrollArea" ng-controller="ScrollController"> - <a ng-click="gotoBottom()">Go to bottom</a> - <a id="bottom"></a> You're at the bottom! - </div> - </file> - <file name="script.js"> - angular.module('anchorScrollExample', []) - .controller('ScrollController', ['$scope', '$location', '$anchorScroll', - function ($scope, $location, $anchorScroll) { - $scope.gotoBottom = function() { - // set the location.hash to the id of - // the element you wish to scroll to. - $location.hash('bottom'); - - // call $anchorScroll() - $anchorScroll(); - }; - }]); - </file> - <file name="style.css"> - #scrollArea { - height: 280px; - overflow: auto; - } - - #bottom { - display: block; - margin-top: 2000px; - } - </file> - </example> - * - * <hr /> - * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). - * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. - * - * @example - <example module="anchorScrollOffsetExample"> - <file name="index.html"> - <div class="fixed-header" ng-controller="headerCtrl"> - <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> - Go to anchor {{x}} - </a> - </div> - <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> - Anchor {{x}} of 5 - </div> - </file> - <file name="script.js"> - angular.module('anchorScrollOffsetExample', []) - .run(['$anchorScroll', function($anchorScroll) { - $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels - }]) - .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', - function ($anchorScroll, $location, $scope) { - $scope.gotoAnchor = function(x) { - var newHash = 'anchor' + x; - if ($location.hash() !== newHash) { - // set the $location.hash to `newHash` and - // $anchorScroll will automatically scroll to it - $location.hash('anchor' + x); - } else { - // call $anchorScroll() explicitly, - // since $location.hash hasn't changed - $anchorScroll(); - } - }; - } - ]); - </file> - <file name="style.css"> - body { - padding-top: 50px; - } - - .anchor { - border: 2px dashed DarkOrchid; - padding: 10px 10px 200px 10px; - } - - .fixed-header { - background-color: rgba(0, 0, 0, 0.2); - height: 50px; - position: fixed; - top: 0; left: 0; right: 0; - } - - .fixed-header > a { - display: inline-block; - margin: 5px 15px; - } - </file> - </example> - */ - this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { - var document = $window.document; - - // Helper function to get first anchor from a NodeList - // (using `Array#some()` instead of `angular#forEach()` since it's more performant - // and working in all supported browsers.) - function getFirstAnchor(list) { - var result = null; - Array.prototype.some.call(list, function(element) { - if (nodeName_(element) === 'a') { - result = element; - return true; - } - }); - return result; - } - - function getYOffset() { - - var offset = scroll.yOffset; - - if (isFunction(offset)) { - offset = offset(); - } else if (isElement(offset)) { - var elem = offset[0]; - var style = $window.getComputedStyle(elem); - if (style.position !== 'fixed') { - offset = 0; - } else { - offset = elem.getBoundingClientRect().bottom; - } - } else if (!isNumber(offset)) { - offset = 0; - } - - return offset; - } - - function scrollTo(elem) { - if (elem) { - elem.scrollIntoView(); - - var offset = getYOffset(); - - if (offset) { - // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. - // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the - // top of the viewport. - // - // IF the number of pixels from the top of `elem` to the end of the page's content is less - // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some - // way down the page. - // - // This is often the case for elements near the bottom of the page. - // - // In such cases we do not need to scroll the whole `offset` up, just the difference between - // the top of the element and the offset, which is enough to align the top of `elem` at the - // desired position. - var elemTop = elem.getBoundingClientRect().top; - $window.scrollBy(0, elemTop - offset); - } - } else { - $window.scrollTo(0, 0); - } - } - - function scroll(hash) { - hash = isString(hash) ? hash : $location.hash(); - var elm; - - // empty hash, scroll to the top of the page - if (!hash) scrollTo(null); - - // element with given id - else if ((elm = document.getElementById(hash))) scrollTo(elm); - - // first anchor with given name :-D - else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); - - // no element and hash == 'top', scroll to the top of the page - else if (hash === 'top') scrollTo(null); - } - - // does not scroll when user clicks on anchor link that is currently on - // (no url change, no $location.hash() change), browser native does scroll - if (autoScrollingEnabled) { - $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, - function autoScrollWatchAction(newVal, oldVal) { - // skip the initial scroll if $location.hash is empty - if (newVal === oldVal && newVal === '') return; - - jqLiteDocumentLoaded(function() { - $rootScope.$evalAsync(scroll); - }); - }); - } - - return scroll; - }]; -} - -var $animateMinErr = minErr('$animate'); -var ELEMENT_NODE = 1; -var NG_ANIMATE_CLASSNAME = 'ng-animate'; - -function mergeClasses(a,b) { - if (!a && !b) return ''; - if (!a) return b; - if (!b) return a; - if (isArray(a)) a = a.join(' '); - if (isArray(b)) b = b.join(' '); - return a + ' ' + b; -} - -function extractElementNode(element) { - for (var i = 0; i < element.length; i++) { - var elm = element[i]; - if (elm.nodeType === ELEMENT_NODE) { - return elm; - } - } -} - -function splitClasses(classes) { - if (isString(classes)) { - classes = classes.split(' '); - } - - // Use createMap() to prevent class assumptions involving property names in - // Object.prototype - var obj = createMap(); - forEach(classes, function(klass) { - // sometimes the split leaves empty string values - // incase extra spaces were applied to the options - if (klass.length) { - obj[klass] = true; - } - }); - return obj; -} - -// if any other type of options value besides an Object value is -// passed into the $animate.method() animation then this helper code -// will be run which will ignore it. While this patch is not the -// greatest solution to this, a lot of existing plugins depend on -// $animate to either call the callback (< 1.2) or return a promise -// that can be changed. This helper function ensures that the options -// are wiped clean incase a callback function is provided. -function prepareAnimateOptions(options) { - return isObject(options) - ? options - : {}; -} - -var $$CoreAnimateRunnerProvider = function() { - this.$get = ['$q', '$$rAF', function($q, $$rAF) { - function AnimateRunner() {} - AnimateRunner.all = noop; - AnimateRunner.chain = noop; - AnimateRunner.prototype = { - end: noop, - cancel: noop, - resume: noop, - pause: noop, - complete: noop, - then: function(pass, fail) { - return $q(function(resolve) { - $$rAF(function() { - resolve(); - }); - }).then(pass, fail); - } - }; - return AnimateRunner; - }]; -}; - -// this is prefixed with Core since it conflicts with -// the animateQueueProvider defined in ngAnimate/animateQueue.js -var $$CoreAnimateQueueProvider = function() { - var postDigestQueue = new HashMap(); - var postDigestElements = []; - - this.$get = ['$$AnimateRunner', '$rootScope', - function($$AnimateRunner, $rootScope) { - return { - enabled: noop, - on: noop, - off: noop, - pin: noop, - - push: function(element, event, options, domOperation) { - domOperation && domOperation(); - - options = options || {}; - options.from && element.css(options.from); - options.to && element.css(options.to); - - if (options.addClass || options.removeClass) { - addRemoveClassesPostDigest(element, options.addClass, options.removeClass); - } - - return new $$AnimateRunner(); // jshint ignore:line - } - }; - - - function updateData(data, classes, value) { - var changed = false; - if (classes) { - classes = isString(classes) ? classes.split(' ') : - isArray(classes) ? classes : []; - forEach(classes, function(className) { - if (className) { - changed = true; - data[className] = value; - } - }); - } - return changed; - } - - function handleCSSClassChanges() { - forEach(postDigestElements, function(element) { - var data = postDigestQueue.get(element); - if (data) { - var existing = splitClasses(element.attr('class')); - var toAdd = ''; - var toRemove = ''; - forEach(data, function(status, className) { - var hasClass = !!existing[className]; - if (status !== hasClass) { - if (status) { - toAdd += (toAdd.length ? ' ' : '') + className; - } else { - toRemove += (toRemove.length ? ' ' : '') + className; - } - } - }); - - forEach(element, function(elm) { - toAdd && jqLiteAddClass(elm, toAdd); - toRemove && jqLiteRemoveClass(elm, toRemove); - }); - postDigestQueue.remove(element); - } - }); - postDigestElements.length = 0; - } - - - function addRemoveClassesPostDigest(element, add, remove) { - var data = postDigestQueue.get(element) || {}; - - var classesAdded = updateData(data, add, true); - var classesRemoved = updateData(data, remove, false); - - if (classesAdded || classesRemoved) { - - postDigestQueue.put(element, data); - postDigestElements.push(element); - - if (postDigestElements.length === 1) { - $rootScope.$$postDigest(handleCSSClassChanges); - } - } - } - }]; -}; - -/** - * @ngdoc provider - * @name $animateProvider - * - * @description - * Default implementation of $animate that doesn't perform any animations, instead just - * synchronously performs DOM updates and resolves the returned runner promise. - * - * In order to enable animations the `ngAnimate` module has to be loaded. - * - * To see the functional implementation check out `src/ngAnimate/animate.js`. - */ -var $AnimateProvider = ['$provide', function($provide) { - var provider = this; - - this.$$registeredAnimations = Object.create(null); - - /** - * @ngdoc method - * @name $animateProvider#register - * - * @description - * Registers a new injectable animation factory function. The factory function produces the - * animation object which contains callback functions for each event that is expected to be - * animated. - * - * * `eventFn`: `function(element, ... , doneFunction, options)` - * The element to animate, the `doneFunction` and the options fed into the animation. Depending - * on the type of animation additional arguments will be injected into the animation function. The - * list below explains the function signatures for the different animation methods: - * - * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) - * - addClass: function(element, addedClasses, doneFunction, options) - * - removeClass: function(element, removedClasses, doneFunction, options) - * - enter, leave, move: function(element, doneFunction, options) - * - animate: function(element, fromStyles, toStyles, doneFunction, options) - * - * Make sure to trigger the `doneFunction` once the animation is fully complete. - * - * ```js - * return { - * //enter, leave, move signature - * eventFn : function(element, done, options) { - * //code to run the animation - * //once complete, then run done() - * return function endFunction(wasCancelled) { - * //code to cancel the animation - * } - * } - * } - * ``` - * - * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). - * @param {Function} factory The factory function that will be executed to return the animation - * object. - */ - this.register = function(name, factory) { - if (name && name.charAt(0) !== '.') { - throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); - } - - var key = name + '-animation'; - provider.$$registeredAnimations[name.substr(1)] = key; - $provide.factory(key, factory); - }; - - /** - * @ngdoc method - * @name $animateProvider#classNameFilter - * - * @description - * Sets and/or returns the CSS class regular expression that is checked when performing - * an animation. Upon bootstrap the classNameFilter value is not set at all and will - * therefore enable $animate to attempt to perform an animation on any element that is triggered. - * When setting the `classNameFilter` value, animations will only be performed on elements - * that successfully match the filter expression. This in turn can boost performance - * for low-powered devices as well as applications containing a lot of structural operations. - * @param {RegExp=} expression The className expression which will be checked against all animations - * @return {RegExp} The current CSS className expression value. If null then there is no expression value - */ - this.classNameFilter = function(expression) { - if (arguments.length === 1) { - this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; - if (this.$$classNameFilter) { - var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)"); - if (reservedRegex.test(this.$$classNameFilter.toString())) { - throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); - - } - } - } - return this.$$classNameFilter; - }; - - this.$get = ['$$animateQueue', function($$animateQueue) { - function domInsert(element, parentElement, afterElement) { - // if for some reason the previous element was removed - // from the dom sometime before this code runs then let's - // just stick to using the parent element as the anchor - if (afterElement) { - var afterNode = extractElementNode(afterElement); - if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { - afterElement = null; - } - } - afterElement ? afterElement.after(element) : parentElement.prepend(element); - } - - /** - * @ngdoc service - * @name $animate - * @description The $animate service exposes a series of DOM utility methods that provide support - * for animation hooks. The default behavior is the application of DOM operations, however, - * when an animation is detected (and animations are enabled), $animate will do the heavy lifting - * to ensure that animation runs with the triggered DOM operation. - * - * By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't - * included and only when it is active then the animation hooks that `$animate` triggers will be - * functional. Once active then all structural `ng-` directives will trigger animations as they perform - * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, - * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. - * - * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. - * - * To learn more about enabling animation support, click here to visit the - * {@link ngAnimate ngAnimate module page}. - */ - return { - // we don't call it directly since non-existant arguments may - // be interpreted as null within the sub enabled function - - /** - * - * @ngdoc method - * @name $animate#on - * @kind function - * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) - * has fired on the given element or among any of its children. Once the listener is fired, the provided callback - * is fired with the following params: - * - * ```js - * $animate.on('enter', container, - * function callback(element, phase) { - * // cool we detected an enter animation within the container - * } - * ); - * ``` - * - * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) - * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself - * as well as among its children - * @param {Function} callback the callback function that will be fired when the listener is triggered - * - * The arguments present in the callback function are: - * * `element` - The captured DOM element that the animation was fired on. - * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). - */ - on: $$animateQueue.on, - - /** - * - * @ngdoc method - * @name $animate#off - * @kind function - * @description Deregisters an event listener based on the event which has been associated with the provided element. This method - * can be used in three different ways depending on the arguments: - * - * ```js - * // remove all the animation event listeners listening for `enter` - * $animate.off('enter'); - * - * // remove all the animation event listeners listening for `enter` on the given element and its children - * $animate.off('enter', container); - * - * // remove the event listener function provided by `listenerFn` that is set - * // to listen for `enter` on the given `element` as well as its children - * $animate.off('enter', container, callback); - * ``` - * - * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...) - * @param {DOMElement=} container the container element the event listener was placed on - * @param {Function=} callback the callback function that was registered as the listener - */ - off: $$animateQueue.off, - - /** - * @ngdoc method - * @name $animate#pin - * @kind function - * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists - * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the - * element despite being outside the realm of the application or within another application. Say for example if the application - * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated - * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind - * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. - * - * Note that this feature is only active when the `ngAnimate` module is used. - * - * @param {DOMElement} element the external element that will be pinned - * @param {DOMElement} parentElement the host parent element that will be associated with the external element - */ - pin: $$animateQueue.pin, - - /** - * - * @ngdoc method - * @name $animate#enabled - * @kind function - * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This - * function can be called in four ways: - * - * ```js - * // returns true or false - * $animate.enabled(); - * - * // changes the enabled state for all animations - * $animate.enabled(false); - * $animate.enabled(true); - * - * // returns true or false if animations are enabled for an element - * $animate.enabled(element); - * - * // changes the enabled state for an element and its children - * $animate.enabled(element, true); - * $animate.enabled(element, false); - * ``` - * - * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state - * @param {boolean=} enabled whether or not the animations will be enabled for the element - * - * @return {boolean} whether or not animations are enabled - */ - enabled: $$animateQueue.enabled, - - /** - * @ngdoc method - * @name $animate#cancel - * @kind function - * @description Cancels the provided animation. - * - * @param {Promise} animationPromise The animation promise that is returned when an animation is started. - */ - cancel: function(runner) { - runner.end && runner.end(); - }, - - /** - * - * @ngdoc method - * @name $animate#enter - * @kind function - * @description Inserts the element into the DOM either after the `after` element (if provided) or - * as the first child within the `parent` element and then triggers an animation. - * A promise is returned that will be resolved during the next digest once the animation - * has completed. - * - * @param {DOMElement} element the element which will be inserted into the DOM - * @param {DOMElement} parent the parent element which will append the element as - * a child (so long as the after element is not present) - * @param {DOMElement=} after the sibling element after which the element will be appended - * @param {object=} options an optional collection of options/styles that will be applied to the element - * - * @return {Promise} the animation callback promise - */ - enter: function(element, parent, after, options) { - parent = parent && jqLite(parent); - after = after && jqLite(after); - parent = parent || after.parent(); - domInsert(element, parent, after); - return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); - }, - - /** - * - * @ngdoc method - * @name $animate#move - * @kind function - * @description Inserts (moves) the element into its new position in the DOM either after - * the `after` element (if provided) or as the first child within the `parent` element - * and then triggers an animation. A promise is returned that will be resolved - * during the next digest once the animation has completed. - * - * @param {DOMElement} element the element which will be moved into the new DOM position - * @param {DOMElement} parent the parent element which will append the element as - * a child (so long as the after element is not present) - * @param {DOMElement=} after the sibling element after which the element will be appended - * @param {object=} options an optional collection of options/styles that will be applied to the element - * - * @return {Promise} the animation callback promise - */ - move: function(element, parent, after, options) { - parent = parent && jqLite(parent); - after = after && jqLite(after); - parent = parent || after.parent(); - domInsert(element, parent, after); - return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); - }, - - /** - * @ngdoc method - * @name $animate#leave - * @kind function - * @description Triggers an animation and then removes the element from the DOM. - * When the function is called a promise is returned that will be resolved during the next - * digest once the animation has completed. - * - * @param {DOMElement} element the element which will be removed from the DOM - * @param {object=} options an optional collection of options/styles that will be applied to the element - * - * @return {Promise} the animation callback promise - */ - leave: function(element, options) { - return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { - element.remove(); - }); - }, - - /** - * @ngdoc method - * @name $animate#addClass - * @kind function - * - * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon - * execution, the addClass operation will only be handled after the next digest and it will not trigger an - * animation if element already contains the CSS class or if the class is removed at a later step. - * Note that class-based animations are treated differently compared to structural animations - * (like enter, move and leave) since the CSS classes may be added/removed at different points - * depending if CSS or JavaScript animations are used. - * - * @param {DOMElement} element the element which the CSS classes will be applied to - * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) - * @param {object=} options an optional collection of options/styles that will be applied to the element - * - * @return {Promise} the animation callback promise - */ - addClass: function(element, className, options) { - options = prepareAnimateOptions(options); - options.addClass = mergeClasses(options.addclass, className); - return $$animateQueue.push(element, 'addClass', options); - }, - - /** - * @ngdoc method - * @name $animate#removeClass - * @kind function - * - * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon - * execution, the removeClass operation will only be handled after the next digest and it will not trigger an - * animation if element does not contain the CSS class or if the class is added at a later step. - * Note that class-based animations are treated differently compared to structural animations - * (like enter, move and leave) since the CSS classes may be added/removed at different points - * depending if CSS or JavaScript animations are used. - * - * @param {DOMElement} element the element which the CSS classes will be applied to - * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) - * @param {object=} options an optional collection of options/styles that will be applied to the element - * - * @return {Promise} the animation callback promise - */ - removeClass: function(element, className, options) { - options = prepareAnimateOptions(options); - options.removeClass = mergeClasses(options.removeClass, className); - return $$animateQueue.push(element, 'removeClass', options); - }, - - /** - * @ngdoc method - * @name $animate#setClass - * @kind function - * - * @description Performs both the addition and removal of a CSS classes on an element and (during the process) - * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and - * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has - * passed. Note that class-based animations are treated differently compared to structural animations - * (like enter, move and leave) since the CSS classes may be added/removed at different points - * depending if CSS or JavaScript animations are used. - * - * @param {DOMElement} element the element which the CSS classes will be applied to - * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) - * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) - * @param {object=} options an optional collection of options/styles that will be applied to the element - * - * @return {Promise} the animation callback promise - */ - setClass: function(element, add, remove, options) { - options = prepareAnimateOptions(options); - options.addClass = mergeClasses(options.addClass, add); - options.removeClass = mergeClasses(options.removeClass, remove); - return $$animateQueue.push(element, 'setClass', options); - }, - - /** - * @ngdoc method - * @name $animate#animate - * @kind function - * - * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. - * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take - * on the provided styles. For example, if a transition animation is set for the given className then the provided from and - * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles - * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter). - * - * @param {DOMElement} element the element which the CSS styles will be applied to - * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. - * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. - * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If - * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. - * (Note that if no animation is detected then this value will not be appplied to the element.) - * @param {object=} options an optional collection of options/styles that will be applied to the element - * - * @return {Promise} the animation callback promise - */ - animate: function(element, from, to, className, options) { - options = prepareAnimateOptions(options); - options.from = options.from ? extend(options.from, from) : from; - options.to = options.to ? extend(options.to, to) : to; - - className = className || 'ng-inline-animate'; - options.tempClasses = mergeClasses(options.tempClasses, className); - return $$animateQueue.push(element, 'animate', options); - } - }; - }]; -}]; - -/** - * @ngdoc service - * @name $animateCss - * @kind object - * - * @description - * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, - * then the `$animateCss` service will actually perform animations. - * - * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. - */ -var $CoreAnimateCssProvider = function() { - this.$get = ['$$rAF', '$q', function($$rAF, $q) { - - var RAFPromise = function() {}; - RAFPromise.prototype = { - done: function(cancel) { - this.defer && this.defer[cancel === true ? 'reject' : 'resolve'](); - }, - end: function() { - this.done(); - }, - cancel: function() { - this.done(true); - }, - getPromise: function() { - if (!this.defer) { - this.defer = $q.defer(); - } - return this.defer.promise; - }, - then: function(f1,f2) { - return this.getPromise().then(f1,f2); - }, - 'catch': function(f1) { - return this.getPromise()['catch'](f1); - }, - 'finally': function(f1) { - return this.getPromise()['finally'](f1); - } - }; - - return function(element, options) { - // there is no point in applying the styles since - // there is no animation that goes on at all in - // this version of $animateCss. - if (options.cleanupStyles) { - options.from = options.to = null; - } - - if (options.from) { - element.css(options.from); - options.from = null; - } - - var closed, runner = new RAFPromise(); - return { - start: run, - end: run - }; - - function run() { - $$rAF(function() { - close(); - if (!closed) { - runner.done(); - } - closed = true; - }); - return runner; - } - - function close() { - if (options.addClass) { - element.addClass(options.addClass); - options.addClass = null; - } - if (options.removeClass) { - element.removeClass(options.removeClass); - options.removeClass = null; - } - if (options.to) { - element.css(options.to); - options.to = null; - } - } - }; - }]; -}; - -/* global stripHash: true */ - -/** - * ! This is a private undocumented service ! - * - * @name $browser - * @requires $log - * @description - * This object has two goals: - * - * - hide all the global state in the browser caused by the window object - * - abstract away all the browser specific features and inconsistencies - * - * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` - * service, which can be used for convenient testing of the application without the interaction with - * the real browser apis. - */ -/** - * @param {object} window The global window object. - * @param {object} document jQuery wrapped document. - * @param {object} $log window.console or an object with the same interface. - * @param {object} $sniffer $sniffer service - */ -function Browser(window, document, $log, $sniffer) { - var self = this, - rawDocument = document[0], - location = window.location, - history = window.history, - setTimeout = window.setTimeout, - clearTimeout = window.clearTimeout, - pendingDeferIds = {}; - - self.isMock = false; - - var outstandingRequestCount = 0; - var outstandingRequestCallbacks = []; - - // TODO(vojta): remove this temporary api - self.$$completeOutstandingRequest = completeOutstandingRequest; - self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; - - /** - * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` - * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. - */ - function completeOutstandingRequest(fn) { - try { - fn.apply(null, sliceArgs(arguments, 1)); - } finally { - outstandingRequestCount--; - if (outstandingRequestCount === 0) { - while (outstandingRequestCallbacks.length) { - try { - outstandingRequestCallbacks.pop()(); - } catch (e) { - $log.error(e); - } - } - } - } - } - - function getHash(url) { - var index = url.indexOf('#'); - return index === -1 ? '' : url.substr(index); - } - - /** - * @private - * Note: this method is used only by scenario runner - * TODO(vojta): prefix this method with $$ ? - * @param {function()} callback Function that will be called when no outstanding request - */ - self.notifyWhenNoOutstandingRequests = function(callback) { - if (outstandingRequestCount === 0) { - callback(); - } else { - outstandingRequestCallbacks.push(callback); - } - }; - - ////////////////////////////////////////////////////////////// - // URL API - ////////////////////////////////////////////////////////////// - - var cachedState, lastHistoryState, - lastBrowserUrl = location.href, - baseElement = document.find('base'), - pendingLocation = null; - - cacheState(); - lastHistoryState = cachedState; - - /** - * @name $browser#url - * - * @description - * GETTER: - * Without any argument, this method just returns current value of location.href. - * - * SETTER: - * With at least one argument, this method sets url to new value. - * If html5 history api supported, pushState/replaceState is used, otherwise - * location.href/location.replace is used. - * Returns its own instance to allow chaining - * - * NOTE: this api is intended for use only by the $location service. Please use the - * {@link ng.$location $location service} to change url. - * - * @param {string} url New url (when used as setter) - * @param {boolean=} replace Should new url replace current history record? - * @param {object=} state object to use with pushState/replaceState - */ - self.url = function(url, replace, state) { - // In modern browsers `history.state` is `null` by default; treating it separately - // from `undefined` would cause `$browser.url('/foo')` to change `history.state` - // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. - if (isUndefined(state)) { - state = null; - } - - // Android Browser BFCache causes location, history reference to become stale. - if (location !== window.location) location = window.location; - if (history !== window.history) history = window.history; - - // setter - if (url) { - var sameState = lastHistoryState === state; - - // Don't change anything if previous and current URLs and states match. This also prevents - // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. - // See https://github.com/angular/angular.js/commit/ffb2701 - if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { - return self; - } - var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); - lastBrowserUrl = url; - lastHistoryState = state; - // Don't use history API if only the hash changed - // due to a bug in IE10/IE11 which leads - // to not firing a `hashchange` nor `popstate` event - // in some cases (see #9143). - if ($sniffer.history && (!sameBase || !sameState)) { - history[replace ? 'replaceState' : 'pushState'](state, '', url); - cacheState(); - // Do the assignment again so that those two variables are referentially identical. - lastHistoryState = cachedState; - } else { - if (!sameBase || pendingLocation) { - pendingLocation = url; - } - if (replace) { - location.replace(url); - } else if (!sameBase) { - location.href = url; - } else { - location.hash = getHash(url); - } - if (location.href !== url) { - pendingLocation = url; - } - } - return self; - // getter - } else { - // - pendingLocation is needed as browsers don't allow to read out - // the new location.href if a reload happened or if there is a bug like in iOS 9 (see - // https://openradar.appspot.com/22186109). - // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 - return pendingLocation || location.href.replace(/%27/g,"'"); - } - }; - - /** - * @name $browser#state - * - * @description - * This method is a getter. - * - * Return history.state or null if history.state is undefined. - * - * @returns {object} state - */ - self.state = function() { - return cachedState; - }; - - var urlChangeListeners = [], - urlChangeInit = false; - - function cacheStateAndFireUrlChange() { - pendingLocation = null; - cacheState(); - fireUrlChange(); - } - - function getCurrentState() { - try { - return history.state; - } catch (e) { - // MSIE can reportedly throw when there is no state (UNCONFIRMED). - } - } - - // This variable should be used *only* inside the cacheState function. - var lastCachedState = null; - function cacheState() { - // This should be the only place in $browser where `history.state` is read. - cachedState = getCurrentState(); - cachedState = isUndefined(cachedState) ? null : cachedState; - - // Prevent callbacks fo fire twice if both hashchange & popstate were fired. - if (equals(cachedState, lastCachedState)) { - cachedState = lastCachedState; - } - lastCachedState = cachedState; - } - - function fireUrlChange() { - if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { - return; - } - - lastBrowserUrl = self.url(); - lastHistoryState = cachedState; - forEach(urlChangeListeners, function(listener) { - listener(self.url(), cachedState); - }); - } - - /** - * @name $browser#onUrlChange - * - * @description - * Register callback function that will be called, when url changes. - * - * It's only called when the url is changed from outside of angular: - * - user types different url into address bar - * - user clicks on history (forward/back) button - * - user clicks on a link - * - * It's not called when url is changed by $browser.url() method - * - * The listener gets called with new url as parameter. - * - * NOTE: this api is intended for use only by the $location service. Please use the - * {@link ng.$location $location service} to monitor url changes in angular apps. - * - * @param {function(string)} listener Listener function to be called when url changes. - * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. - */ - self.onUrlChange = function(callback) { - // TODO(vojta): refactor to use node's syntax for events - if (!urlChangeInit) { - // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) - // don't fire popstate when user change the address bar and don't fire hashchange when url - // changed by push/replaceState - - // html5 history api - popstate event - if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); - // hashchange event - jqLite(window).on('hashchange', cacheStateAndFireUrlChange); - - urlChangeInit = true; - } - - urlChangeListeners.push(callback); - return callback; - }; - - /** - * @private - * Remove popstate and hashchange handler from window. - * - * NOTE: this api is intended for use only by $rootScope. - */ - self.$$applicationDestroyed = function() { - jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); - }; - - /** - * Checks whether the url has changed outside of Angular. - * Needs to be exported to be able to check for changes that have been done in sync, - * as hashchange/popstate events fire in async. - */ - self.$$checkUrlChange = fireUrlChange; - - ////////////////////////////////////////////////////////////// - // Misc API - ////////////////////////////////////////////////////////////// - - /** - * @name $browser#baseHref - * - * @description - * Returns current <base href> - * (always relative - without domain) - * - * @returns {string} The current base href - */ - self.baseHref = function() { - var href = baseElement.attr('href'); - return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; - }; - - /** - * @name $browser#defer - * @param {function()} fn A function, who's execution should be deferred. - * @param {number=} [delay=0] of milliseconds to defer the function execution. - * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. - * - * @description - * Executes a fn asynchronously via `setTimeout(fn, delay)`. - * - * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using - * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed - * via `$browser.defer.flush()`. - * - */ - self.defer = function(fn, delay) { - var timeoutId; - outstandingRequestCount++; - timeoutId = setTimeout(function() { - delete pendingDeferIds[timeoutId]; - completeOutstandingRequest(fn); - }, delay || 0); - pendingDeferIds[timeoutId] = true; - return timeoutId; - }; - - - /** - * @name $browser#defer.cancel - * - * @description - * Cancels a deferred task identified with `deferId`. - * - * @param {*} deferId Token returned by the `$browser.defer` function. - * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully - * canceled. - */ - self.defer.cancel = function(deferId) { - if (pendingDeferIds[deferId]) { - delete pendingDeferIds[deferId]; - clearTimeout(deferId); - completeOutstandingRequest(noop); - return true; - } - return false; - }; - -} - -function $BrowserProvider() { - this.$get = ['$window', '$log', '$sniffer', '$document', - function($window, $log, $sniffer, $document) { - return new Browser($window, $document, $log, $sniffer); - }]; -} - -/** - * @ngdoc service - * @name $cacheFactory - * - * @description - * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to - * them. - * - * ```js - * - * var cache = $cacheFactory('cacheId'); - * expect($cacheFactory.get('cacheId')).toBe(cache); - * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); - * - * cache.put("key", "value"); - * cache.put("another key", "another value"); - * - * // We've specified no options on creation - * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); - * - * ``` - * - * - * @param {string} cacheId Name or id of the newly created cache. - * @param {object=} options Options object that specifies the cache behavior. Properties: - * - * - `{number=}` `capacity` — turns the cache into LRU cache. - * - * @returns {object} Newly created cache object with the following set of methods: - * - * - `{object}` `info()` — Returns id, size, and options of cache. - * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns - * it. - * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. - * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. - * - `{void}` `removeAll()` — Removes all cached values. - * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. - * - * @example - <example module="cacheExampleApp"> - <file name="index.html"> - <div ng-controller="CacheController"> - <input ng-model="newCacheKey" placeholder="Key"> - <input ng-model="newCacheValue" placeholder="Value"> - <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> - - <p ng-if="keys.length">Cached Values</p> - <div ng-repeat="key in keys"> - <span ng-bind="key"></span> - <span>: </span> - <b ng-bind="cache.get(key)"></b> - </div> - - <p>Cache Info</p> - <div ng-repeat="(key, value) in cache.info()"> - <span ng-bind="key"></span> - <span>: </span> - <b ng-bind="value"></b> - </div> - </div> - </file> - <file name="script.js"> - angular.module('cacheExampleApp', []). - controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { - $scope.keys = []; - $scope.cache = $cacheFactory('cacheId'); - $scope.put = function(key, value) { - if (angular.isUndefined($scope.cache.get(key))) { - $scope.keys.push(key); - } - $scope.cache.put(key, angular.isUndefined(value) ? null : value); - }; - }]); - </file> - <file name="style.css"> - p { - margin: 10px 0 3px; - } - </file> - </example> - */ -function $CacheFactoryProvider() { - - this.$get = function() { - var caches = {}; - - function cacheFactory(cacheId, options) { - if (cacheId in caches) { - throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); - } - - var size = 0, - stats = extend({}, options, {id: cacheId}), - data = {}, - capacity = (options && options.capacity) || Number.MAX_VALUE, - lruHash = {}, - freshEnd = null, - staleEnd = null; - - /** - * @ngdoc type - * @name $cacheFactory.Cache - * - * @description - * A cache object used to store and retrieve data, primarily used by - * {@link $http $http} and the {@link ng.directive:script script} directive to cache - * templates and other data. - * - * ```js - * angular.module('superCache') - * .factory('superCache', ['$cacheFactory', function($cacheFactory) { - * return $cacheFactory('super-cache'); - * }]); - * ``` - * - * Example test: - * - * ```js - * it('should behave like a cache', inject(function(superCache) { - * superCache.put('key', 'value'); - * superCache.put('another key', 'another value'); - * - * expect(superCache.info()).toEqual({ - * id: 'super-cache', - * size: 2 - * }); - * - * superCache.remove('another key'); - * expect(superCache.get('another key')).toBeUndefined(); - * - * superCache.removeAll(); - * expect(superCache.info()).toEqual({ - * id: 'super-cache', - * size: 0 - * }); - * })); - * ``` - */ - return caches[cacheId] = { - - /** - * @ngdoc method - * @name $cacheFactory.Cache#put - * @kind function - * - * @description - * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be - * retrieved later, and incrementing the size of the cache if the key was not already - * present in the cache. If behaving like an LRU cache, it will also remove stale - * entries from the set. - * - * It will not insert undefined values into the cache. - * - * @param {string} key the key under which the cached data is stored. - * @param {*} value the value to store alongside the key. If it is undefined, the key - * will not be stored. - * @returns {*} the value stored. - */ - put: function(key, value) { - if (isUndefined(value)) return; - if (capacity < Number.MAX_VALUE) { - var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); - - refresh(lruEntry); - } - - if (!(key in data)) size++; - data[key] = value; - - if (size > capacity) { - this.remove(staleEnd.key); - } - - return value; - }, - - /** - * @ngdoc method - * @name $cacheFactory.Cache#get - * @kind function - * - * @description - * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. - * - * @param {string} key the key of the data to be retrieved - * @returns {*} the value stored. - */ - get: function(key) { - if (capacity < Number.MAX_VALUE) { - var lruEntry = lruHash[key]; - - if (!lruEntry) return; - - refresh(lruEntry); - } - - return data[key]; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#remove - * @kind function - * - * @description - * Removes an entry from the {@link $cacheFactory.Cache Cache} object. - * - * @param {string} key the key of the entry to be removed - */ - remove: function(key) { - if (capacity < Number.MAX_VALUE) { - var lruEntry = lruHash[key]; - - if (!lruEntry) return; - - if (lruEntry == freshEnd) freshEnd = lruEntry.p; - if (lruEntry == staleEnd) staleEnd = lruEntry.n; - link(lruEntry.n,lruEntry.p); - - delete lruHash[key]; - } - - delete data[key]; - size--; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#removeAll - * @kind function - * - * @description - * Clears the cache object of any entries. - */ - removeAll: function() { - data = {}; - size = 0; - lruHash = {}; - freshEnd = staleEnd = null; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#destroy - * @kind function - * - * @description - * Destroys the {@link $cacheFactory.Cache Cache} object entirely, - * removing it from the {@link $cacheFactory $cacheFactory} set. - */ - destroy: function() { - data = null; - stats = null; - lruHash = null; - delete caches[cacheId]; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#info - * @kind function - * - * @description - * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. - * - * @returns {object} an object with the following properties: - * <ul> - * <li>**id**: the id of the cache instance</li> - * <li>**size**: the number of entries kept in the cache instance</li> - * <li>**...**: any additional properties from the options object when creating the - * cache.</li> - * </ul> - */ - info: function() { - return extend({}, stats, {size: size}); - } - }; - - - /** - * makes the `entry` the freshEnd of the LRU linked list - */ - function refresh(entry) { - if (entry != freshEnd) { - if (!staleEnd) { - staleEnd = entry; - } else if (staleEnd == entry) { - staleEnd = entry.n; - } - - link(entry.n, entry.p); - link(entry, freshEnd); - freshEnd = entry; - freshEnd.n = null; - } - } - - - /** - * bidirectionally links two entries of the LRU linked list - */ - function link(nextEntry, prevEntry) { - if (nextEntry != prevEntry) { - if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify - if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify - } - } - } - - - /** - * @ngdoc method - * @name $cacheFactory#info - * - * @description - * Get information about all the caches that have been created - * - * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` - */ - cacheFactory.info = function() { - var info = {}; - forEach(caches, function(cache, cacheId) { - info[cacheId] = cache.info(); - }); - return info; - }; - - - /** - * @ngdoc method - * @name $cacheFactory#get - * - * @description - * Get access to a cache object by the `cacheId` used when it was created. - * - * @param {string} cacheId Name or id of a cache to access. - * @returns {object} Cache object identified by the cacheId or undefined if no such cache. - */ - cacheFactory.get = function(cacheId) { - return caches[cacheId]; - }; - - - return cacheFactory; - }; -} - -/** - * @ngdoc service - * @name $templateCache - * - * @description - * The first time a template is used, it is loaded in the template cache for quick retrieval. You - * can load templates directly into the cache in a `script` tag, or by consuming the - * `$templateCache` service directly. - * - * Adding via the `script` tag: - * - * ```html - * <script type="text/ng-template" id="templateId.html"> - * <p>This is the content of the template</p> - * </script> - * ``` - * - * **Note:** the `script` tag containing the template does not need to be included in the `head` of - * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, - * element with ng-app attribute), otherwise the template will be ignored. - * - * Adding via the `$templateCache` service: - * - * ```js - * var myApp = angular.module('myApp', []); - * myApp.run(function($templateCache) { - * $templateCache.put('templateId.html', 'This is the content of the template'); - * }); - * ``` - * - * To retrieve the template later, simply use it in your HTML: - * ```html - * <div ng-include=" 'templateId.html' "></div> - * ``` - * - * or get it via Javascript: - * ```js - * $templateCache.get('templateId.html') - * ``` - * - * See {@link ng.$cacheFactory $cacheFactory}. - * - */ -function $TemplateCacheProvider() { - this.$get = ['$cacheFactory', function($cacheFactory) { - return $cacheFactory('templates'); - }]; -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Any commits to this file should be reviewed with security in mind. * - * Changes to this file can potentially create security vulnerabilities. * - * An approval from 2 Core members with history of modifying * - * this file is required. * - * * - * Does the change somehow allow for arbitrary javascript to be executed? * - * Or allows for someone to change the prototype of built-in objects? * - * Or gives undesired access to variables likes document or window? * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! - * - * DOM-related variables: - * - * - "node" - DOM Node - * - "element" - DOM Element or Node - * - "$node" or "$element" - jqLite-wrapped node or element - * - * - * Compiler related stuff: - * - * - "linkFn" - linking fn of a single directive - * - "nodeLinkFn" - function that aggregates all linking fns for a particular node - * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node - * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) - */ - - -/** - * @ngdoc service - * @name $compile - * @kind function - * - * @description - * Compiles an HTML string or DOM into a template and produces a template function, which - * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. - * - * The compilation is a process of walking the DOM tree and matching DOM elements to - * {@link ng.$compileProvider#directive directives}. - * - * <div class="alert alert-warning"> - * **Note:** This document is an in-depth reference of all directive options. - * For a gentle introduction to directives with examples of common use cases, - * see the {@link guide/directive directive guide}. - * </div> - * - * ## Comprehensive Directive API - * - * There are many different options for a directive. - * - * The difference resides in the return value of the factory function. - * You can either return a "Directive Definition Object" (see below) that defines the directive properties, - * or just the `postLink` function (all other properties will have the default values). - * - * <div class="alert alert-success"> - * **Best Practice:** It's recommended to use the "directive definition object" form. - * </div> - * - * Here's an example directive declared with a Directive Definition Object: - * - * ```js - * var myModule = angular.module(...); - * - * myModule.directive('directiveName', function factory(injectables) { - * var directiveDefinitionObject = { - * priority: 0, - * template: '<div></div>', // or // function(tElement, tAttrs) { ... }, - * // or - * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, - * transclude: false, - * restrict: 'A', - * templateNamespace: 'html', - * scope: false, - * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, - * controllerAs: 'stringIdentifier', - * bindToController: false, - * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], - * compile: function compile(tElement, tAttrs, transclude) { - * return { - * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, - * post: function postLink(scope, iElement, iAttrs, controller) { ... } - * } - * // or - * // return function postLink( ... ) { ... } - * }, - * // or - * // link: { - * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, - * // post: function postLink(scope, iElement, iAttrs, controller) { ... } - * // } - * // or - * // link: function postLink( ... ) { ... } - * }; - * return directiveDefinitionObject; - * }); - * ``` - * - * <div class="alert alert-warning"> - * **Note:** Any unspecified options will use the default value. You can see the default values below. - * </div> - * - * Therefore the above can be simplified as: - * - * ```js - * var myModule = angular.module(...); - * - * myModule.directive('directiveName', function factory(injectables) { - * var directiveDefinitionObject = { - * link: function postLink(scope, iElement, iAttrs) { ... } - * }; - * return directiveDefinitionObject; - * // or - * // return function postLink(scope, iElement, iAttrs) { ... } - * }); - * ``` - * - * - * - * ### Directive Definition Object - * - * The directive definition object provides instructions to the {@link ng.$compile - * compiler}. The attributes are: - * - * #### `multiElement` - * When this property is set to true, the HTML compiler will collect DOM nodes between - * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them - * together as the directive elements. It is recommended that this feature be used on directives - * which are not strictly behavioural (such as {@link ngClick}), and which - * do not manipulate or replace child nodes (such as {@link ngInclude}). - * - * #### `priority` - * When there are multiple directives defined on a single DOM element, sometimes it - * is necessary to specify the order in which the directives are applied. The `priority` is used - * to sort the directives before their `compile` functions get called. Priority is defined as a - * number. Directives with greater numerical `priority` are compiled first. Pre-link functions - * are also run in priority order, but post-link functions are run in reverse order. The order - * of directives with the same priority is undefined. The default priority is `0`. - * - * #### `terminal` - * If set to true then the current `priority` will be the last set of directives - * which will execute (any directives at the current priority will still execute - * as the order of execution on same `priority` is undefined). Note that expressions - * and other directives used in the directive's template will also be excluded from execution. - * - * #### `scope` - * The scope property can be `true`, an object or a falsy value: - * - * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope. - * - * * **`true`:** A new child scope that prototypically inherits from its parent will be created for - * the directive's element. If multiple directives on the same element request a new scope, - * only one new scope is created. The new scope rule does not apply for the root of the template - * since the root of the template always gets a new scope. - * - * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The - * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent - * scope. This is useful when creating reusable components, which should not accidentally read or modify - * data in the parent scope. - * - * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the - * directive's element. These local properties are useful for aliasing values for templates. The keys in - * the object hash map to the name of the property on the isolate scope; the values define how the property - * is bound to the parent scope, via matching attributes on the directive's element: - * - * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is - * always a string since DOM attributes are strings. If no `attr` name is specified then the - * attribute name is assumed to be the same as the local name. - * Given `<widget my-attr="hello {{name}}">` and widget definition - * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect - * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the - * `localName` property on the widget scope. The `name` is read from the parent scope (not - * component scope). - * - * * `=` or `=attr` - set up bi-directional binding between a local scope property and the - * parent scope property of name defined via the value of the `attr` attribute. If no `attr` - * name is specified then the attribute name is assumed to be the same as the local name. - * Given `<widget my-attr="parentModel">` and widget definition of - * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the - * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected - * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent - * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You - * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If - * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use - * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). - * - * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. - * If no `attr` name is specified then the attribute name is assumed to be the same as the - * local name. Given `<widget my-attr="count = count + value">` and widget definition of - * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to - * a function wrapper for the `count = count + value` expression. Often it's desirable to - * pass data from the isolated scope via an expression to the parent scope, this can be - * done by passing a map of local variable names and values into the expression wrapper fn. - * For example, if the expression is `increment(amount)` then we can specify the amount value - * by calling the `localFn` as `localFn({amount: 22})`. - * - * In general it's possible to apply more than one directive to one element, but there might be limitations - * depending on the type of scope required by the directives. The following points will help explain these limitations. - * For simplicity only two directives are taken into account, but it is also applicable for several directives: - * - * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope - * * **child scope** + **no scope** => Both directives will share one single child scope - * * **child scope** + **child scope** => Both directives will share one single child scope - * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use - * its parent's scope - * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot - * be applied to the same element. - * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives - * cannot be applied to the same element. - * - * - * #### `bindToController` - * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will - * allow a component to have its properties bound to the controller, rather than to scope. When the controller - * is instantiated, the initial values of the isolate scope bindings are already available. - * - * #### `controller` - * Controller constructor function. The controller is instantiated before the - * pre-linking phase and can be accessed by other directives (see - * `require` attribute). This allows the directives to communicate with each other and augment - * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: - * - * * `$scope` - Current scope associated with the element - * * `$element` - Current element - * * `$attrs` - Current attributes object for the element - * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: - * `function([scope], cloneLinkingFn, futureParentElement)`. - * * `scope`: optional argument to override the scope. - * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content. - * * `futureParentElement`: - * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. - * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. - * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) - * and when the `cloneLinkinFn` is passed, - * as those elements need to created and cloned in a special way when they are defined outside their - * usual containers (e.g. like `<svg>`). - * * See also the `directive.templateNamespace` property. - * - * - * #### `require` - * Require another directive and inject its controller as the fourth argument to the linking function. The - * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the - * injected argument will be an array in corresponding order. If no such directive can be - * found, or if the directive does not have a controller, then an error is raised (unless no link function - * is specified, in which case error checking is skipped). The name can be prefixed with: - * - * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. - * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. - * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. - * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. - * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass - * `null` to the `link` fn if not found. - * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass - * `null` to the `link` fn if not found. - * - * - * #### `controllerAs` - * Identifier name for a reference to the controller in the directive's scope. - * This allows the controller to be referenced from the directive template. This is especially - * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible - * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the - * `controllerAs` reference might overwrite a property that already exists on the parent scope. - * - * - * #### `restrict` - * String of subset of `EACM` which restricts the directive to a specific directive - * declaration style. If omitted, the defaults (elements and attributes) are used. - * - * * `E` - Element name (default): `<my-directive></my-directive>` - * * `A` - Attribute (default): `<div my-directive="exp"></div>` - * * `C` - Class: `<div class="my-directive: exp;"></div>` - * * `M` - Comment: `<!-- directive: my-directive exp -->` - * - * - * #### `templateNamespace` - * String representing the document type used by the markup in the template. - * AngularJS needs this information as those elements need to be created and cloned - * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. - * - * * `html` - All root nodes in the template are HTML. Root nodes may also be - * top-level elements such as `<svg>` or `<math>`. - * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). - * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). - * - * If no `templateNamespace` is specified, then the namespace is considered to be `html`. - * - * #### `template` - * HTML markup that may: - * * Replace the contents of the directive's element (default). - * * Replace the directive's element itself (if `replace` is true - DEPRECATED). - * * Wrap the contents of the directive's element (if `transclude` is true). - * - * Value may be: - * - * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. - * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` - * function api below) and returns a string value. - * - * - * #### `templateUrl` - * This is similar to `template` but the template is loaded from the specified URL, asynchronously. - * - * Because template loading is asynchronous the compiler will suspend compilation of directives on that element - * for later when the template has been resolved. In the meantime it will continue to compile and link - * sibling and parent elements as though this element had not contained any directives. - * - * The compiler does not suspend the entire compilation to wait for templates to be loaded because this - * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the - * case when only one deeply nested directive has `templateUrl`. - * - * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} - * - * You can specify `templateUrl` as a string representing the URL or as a function which takes two - * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns - * a string value representing the url. In either case, the template URL is passed through {@link - * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. - * - * - * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) - * specify what the template should replace. Defaults to `false`. - * - * * `true` - the template will replace the directive's element. - * * `false` - the template will replace the contents of the directive's element. - * - * The replacement process migrates all of the attributes / classes from the old element to the new - * one. See the {@link guide/directive#template-expanding-directive - * Directives Guide} for an example. - * - * There are very few scenarios where element replacement is required for the application function, - * the main one being reusable custom components that are used within SVG contexts - * (because SVG doesn't work with custom elements in the DOM tree). - * - * #### `transclude` - * Extract the contents of the element where the directive appears and make it available to the directive. - * The contents are compiled and provided to the directive as a **transclusion function**. See the - * {@link $compile#transclusion Transclusion} section below. - * - * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the - * directive's element or the entire element: - * - * * `true` - transclude the content (i.e. the child nodes) of the directive's element. - * * `'element'` - transclude the whole of the directive's element including any directives on this - * element that defined at a lower priority than this directive. When used, the `template` - * property is ignored. - * - * - * #### `compile` - * - * ```js - * function compile(tElement, tAttrs, transclude) { ... } - * ``` - * - * The compile function deals with transforming the template DOM. Since most directives do not do - * template transformation, it is not used often. The compile function takes the following arguments: - * - * * `tElement` - template element - The element where the directive has been declared. It is - * safe to do template transformation on the element and child elements only. - * - * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared - * between all directive compile functions. - * - * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` - * - * <div class="alert alert-warning"> - * **Note:** The template instance and the link instance may be different objects if the template has - * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that - * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration - * should be done in a linking function rather than in a compile function. - * </div> - - * <div class="alert alert-warning"> - * **Note:** The compile function cannot handle directives that recursively use themselves in their - * own templates or compile functions. Compiling these directives results in an infinite loop and a - * stack overflow errors. - * - * This can be avoided by manually using $compile in the postLink function to imperatively compile - * a directive's template instead of relying on automatic template compilation via `template` or - * `templateUrl` declaration or manual compilation inside the compile function. - * </div> - * - * <div class="alert alert-danger"> - * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it - * e.g. does not know about the right outer scope. Please use the transclude function that is passed - * to the link function instead. - * </div> - - * A compile function can have a return value which can be either a function or an object. - * - * * returning a (post-link) function - is equivalent to registering the linking function via the - * `link` property of the config object when the compile function is empty. - * - * * returning an object with function(s) registered via `pre` and `post` properties - allows you to - * control when a linking function should be called during the linking phase. See info about - * pre-linking and post-linking functions below. - * - * - * #### `link` - * This property is used only if the `compile` property is not defined. - * - * ```js - * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } - * ``` - * - * The link function is responsible for registering DOM listeners as well as updating the DOM. It is - * executed after the template has been cloned. This is where most of the directive logic will be - * put. - * - * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the - * directive for registering {@link ng.$rootScope.Scope#$watch watches}. - * - * * `iElement` - instance element - The element where the directive is to be used. It is safe to - * manipulate the children of the element only in `postLink` function since the children have - * already been linked. - * - * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared - * between all directive linking functions. - * - * * `controller` - the directive's required controller instance(s) - Instances are shared - * among all directives, which allows the directives to use the controllers as a communication - * channel. The exact value depends on the directive's `require` property: - * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one - * * `string`: the controller instance - * * `array`: array of controller instances - * - * If a required controller cannot be found, and it is optional, the instance is `null`, - * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. - * - * Note that you can also require the directive's own controller - it will be made available like - * any other controller. - * - * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. - * This is the same as the `$transclude` - * parameter of directive controllers, see there for details. - * `function([scope], cloneLinkingFn, futureParentElement)`. - * - * #### Pre-linking function - * - * Executed before the child elements are linked. Not safe to do DOM transformation since the - * compiler linking function will fail to locate the correct elements for linking. - * - * #### Post-linking function - * - * Executed after the child elements are linked. - * - * Note that child elements that contain `templateUrl` directives will not have been compiled - * and linked since they are waiting for their template to load asynchronously and their own - * compilation and linking has been suspended until that occurs. - * - * It is safe to do DOM transformation in the post-linking function on elements that are not waiting - * for their async templates to be resolved. - * - * - * ### Transclusion - * - * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and - * copying them to another part of the DOM, while maintaining their connection to the original AngularJS - * scope from where they were taken. - * - * Transclusion is used (often with {@link ngTransclude}) to insert the - * original contents of a directive's element into a specified place in the template of the directive. - * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded - * content has access to the properties on the scope from which it was taken, even if the directive - * has isolated scope. - * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. - * - * This makes it possible for the widget to have private state for its template, while the transcluded - * content has access to its originating scope. - * - * <div class="alert alert-warning"> - * **Note:** When testing an element transclude directive you must not place the directive at the root of the - * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives - * Testing Transclusion Directives}. - * </div> - * - * #### Transclusion Functions - * - * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion - * function** to the directive's `link` function and `controller`. This transclusion function is a special - * **linking function** that will return the compiled contents linked to a new transclusion scope. - * - * <div class="alert alert-info"> - * If you are just using {@link ngTransclude} then you don't need to worry about this function, since - * ngTransclude will deal with it for us. - * </div> - * - * If you want to manually control the insertion and removal of the transcluded content in your directive - * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery - * object that contains the compiled DOM, which is linked to the correct transclusion scope. - * - * When you call a transclusion function you can pass in a **clone attach function**. This function accepts - * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded - * content and the `scope` is the newly created transclusion scope, to which the clone is bound. - * - * <div class="alert alert-info"> - * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function - * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. - * </div> - * - * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone - * attach function**: - * - * ```js - * var transcludedContent, transclusionScope; - * - * $transclude(function(clone, scope) { - * element.append(clone); - * transcludedContent = clone; - * transclusionScope = scope; - * }); - * ``` - * - * Later, if you want to remove the transcluded content from your DOM then you should also destroy the - * associated transclusion scope: - * - * ```js - * transcludedContent.remove(); - * transclusionScope.$destroy(); - * ``` - * - * <div class="alert alert-info"> - * **Best Practice**: if you intend to add and remove transcluded content manually in your directive - * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), - * then you are also responsible for calling `$destroy` on the transclusion scope. - * </div> - * - * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} - * automatically destroy their transluded clones as necessary so you do not need to worry about this if - * you are simply using {@link ngTransclude} to inject the transclusion into your directive. - * - * - * #### Transclusion Scopes - * - * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion - * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed - * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it - * was taken. - * - * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look - * like this: - * - * ```html - * <div ng-app> - * <div isolate> - * <div transclusion> - * </div> - * </div> - * </div> - * ``` - * - * The `$parent` scope hierarchy will look like this: - * - * ``` - * - $rootScope - * - isolate - * - transclusion - * ``` - * - * but the scopes will inherit prototypically from different scopes to their `$parent`. - * - * ``` - * - $rootScope - * - transclusion - * - isolate - * ``` - * - * - * ### Attributes - * - * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the - * `link()` or `compile()` functions. It has a variety of uses. - * - * accessing *Normalized attribute names:* - * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. - * the attributes object allows for normalized access to - * the attributes. - * - * * *Directive inter-communication:* All directives share the same instance of the attributes - * object which allows the directives to use the attributes object as inter directive - * communication. - * - * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object - * allowing other directives to read the interpolated value. - * - * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes - * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also - * the only way to easily get the actual value because during the linking phase the interpolation - * hasn't been evaluated yet and so the value is at this time set to `undefined`. - * - * ```js - * function linkingFn(scope, elm, attrs, ctrl) { - * // get the attribute value - * console.log(attrs.ngModel); - * - * // change the attribute - * attrs.$set('ngModel', 'new value'); - * - * // observe changes to interpolated attribute - * attrs.$observe('ngModel', function(value) { - * console.log('ngModel has changed value to ' + value); - * }); - * } - * ``` - * - * ## Example - * - * <div class="alert alert-warning"> - * **Note**: Typically directives are registered with `module.directive`. The example below is - * to illustrate how `$compile` works. - * </div> - * - <example module="compileExample"> - <file name="index.html"> - <script> - angular.module('compileExample', [], function($compileProvider) { - // configure new 'compile' directive by passing a directive - // factory function. The factory function injects the '$compile' - $compileProvider.directive('compile', function($compile) { - // directive factory creates a link function - return function(scope, element, attrs) { - scope.$watch( - function(scope) { - // watch the 'compile' expression for changes - return scope.$eval(attrs.compile); - }, - function(value) { - // when the 'compile' expression changes - // assign it into the current DOM - element.html(value); - - // compile the new DOM and link it to the current - // scope. - // NOTE: we only compile .childNodes so that - // we don't get into infinite loop compiling ourselves - $compile(element.contents())(scope); - } - ); - }; - }); - }) - .controller('GreeterController', ['$scope', function($scope) { - $scope.name = 'Angular'; - $scope.html = 'Hello {{name}}'; - }]); - </script> - <div ng-controller="GreeterController"> - <input ng-model="name"> <br/> - <textarea ng-model="html"></textarea> <br/> - <div compile="html"></div> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should auto compile', function() { - var textarea = $('textarea'); - var output = $('div[compile]'); - // The initial state reads 'Hello Angular'. - expect(output.getText()).toBe('Hello Angular'); - textarea.clear(); - textarea.sendKeys('{{name}}!'); - expect(output.getText()).toBe('Angular!'); - }); - </file> - </example> - - * - * - * @param {string|DOMElement} element Element or HTML string to compile into a template function. - * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. - * - * <div class="alert alert-danger"> - * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it - * e.g. will not use the right outer scope. Please pass the transclude function as a - * `parentBoundTranscludeFn` to the link function instead. - * </div> - * - * @param {number} maxPriority only apply directives lower than given priority (Only effects the - * root element(s), not their children) - * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template - * (a DOM element/tree) to a scope. Where: - * - * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. - * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the - * `template` and call the `cloneAttachFn` function allowing the caller to attach the - * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is - * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: - * - * * `clonedElement` - is a clone of the original `element` passed into the compiler. - * * `scope` - is the current scope with which the linking function is working with. - * - * * `options` - An optional object hash with linking options. If `options` is provided, then the following - * keys may be used to control linking behavior: - * - * * `parentBoundTranscludeFn` - the transclude function made available to - * directives; if given, it will be passed through to the link functions of - * directives found in `element` during compilation. - * * `transcludeControllers` - an object hash with keys that map controller names - * to controller instances; if given, it will make the controllers - * available to directives. - * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add - * the cloned elements; only needed for transcludes that are allowed to contain non html - * elements (e.g. SVG elements). See also the directive.controller property. - * - * Calling the linking function returns the element of the template. It is either the original - * element passed in, or the clone of the element if the `cloneAttachFn` is provided. - * - * After linking the view is not updated until after a call to $digest which typically is done by - * Angular automatically. - * - * If you need access to the bound view, there are two ways to do it: - * - * - If you are not asking the linking function to clone the template, create the DOM element(s) - * before you send them to the compiler and keep this reference around. - * ```js - * var element = $compile('<p>{{total}}</p>')(scope); - * ``` - * - * - if on the other hand, you need the element to be cloned, the view reference from the original - * example would not point to the clone, but rather to the original template that was cloned. In - * this case, you can access the clone via the cloneAttachFn: - * ```js - * var templateElement = angular.element('<p>{{total}}</p>'), - * scope = ....; - * - * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { - * //attach the clone to DOM document at the right place - * }); - * - * //now we have reference to the cloned DOM via `clonedElement` - * ``` - * - * - * For information on how the compiler works, see the - * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. - */ - -var $compileMinErr = minErr('$compile'); - -/** - * @ngdoc provider - * @name $compileProvider - * - * @description - */ -$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; -function $CompileProvider($provide, $$sanitizeUriProvider) { - var hasDirectives = {}, - Suffix = 'Directive', - COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, - CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, - ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), - REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; - - // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes - // The assumption is that future DOM event attribute names will begin with - // 'on' and be composed of only English letters. - var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; - - function parseIsolateBindings(scope, directiveName, isController) { - var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; - - var bindings = {}; - - forEach(scope, function(definition, scopeName) { - var match = definition.match(LOCAL_REGEXP); - - if (!match) { - throw $compileMinErr('iscp', - "Invalid {3} for directive '{0}'." + - " Definition: {... {1}: '{2}' ...}", - directiveName, scopeName, definition, - (isController ? "controller bindings definition" : - "isolate scope definition")); - } - - bindings[scopeName] = { - mode: match[1][0], - collection: match[2] === '*', - optional: match[3] === '?', - attrName: match[4] || scopeName - }; - }); - - return bindings; - } - - function parseDirectiveBindings(directive, directiveName) { - var bindings = { - isolateScope: null, - bindToController: null - }; - if (isObject(directive.scope)) { - if (directive.bindToController === true) { - bindings.bindToController = parseIsolateBindings(directive.scope, - directiveName, true); - bindings.isolateScope = {}; - } else { - bindings.isolateScope = parseIsolateBindings(directive.scope, - directiveName, false); - } - } - if (isObject(directive.bindToController)) { - bindings.bindToController = - parseIsolateBindings(directive.bindToController, directiveName, true); - } - if (isObject(bindings.bindToController)) { - var controller = directive.controller; - var controllerAs = directive.controllerAs; - if (!controller) { - // There is no controller, there may or may not be a controllerAs property - throw $compileMinErr('noctrl', - "Cannot bind to controller without directive '{0}'s controller.", - directiveName); - } else if (!identifierForController(controller, controllerAs)) { - // There is a controller, but no identifier or controllerAs property - throw $compileMinErr('noident', - "Cannot bind to controller without identifier for directive '{0}'.", - directiveName); - } - } - return bindings; - } - - function assertValidDirectiveName(name) { - var letter = name.charAt(0); - if (!letter || letter !== lowercase(letter)) { - throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name); - } - if (name !== name.trim()) { - throw $compileMinErr('baddir', - "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces", - name); - } - } - - /** - * @ngdoc method - * @name $compileProvider#directive - * @kind function - * - * @description - * Register a new directive with the compiler. - * - * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which - * will match as <code>ng-bind</code>), or an object map of directives where the keys are the - * names and the values are the factories. - * @param {Function|Array} directiveFactory An injectable directive factory function. See - * {@link guide/directive} for more info. - * @returns {ng.$compileProvider} Self for chaining. - */ - this.directive = function registerDirective(name, directiveFactory) { - assertNotHasOwnProperty(name, 'directive'); - if (isString(name)) { - assertValidDirectiveName(name); - assertArg(directiveFactory, 'directiveFactory'); - if (!hasDirectives.hasOwnProperty(name)) { - hasDirectives[name] = []; - $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', - function($injector, $exceptionHandler) { - var directives = []; - forEach(hasDirectives[name], function(directiveFactory, index) { - try { - var directive = $injector.invoke(directiveFactory); - if (isFunction(directive)) { - directive = { compile: valueFn(directive) }; - } else if (!directive.compile && directive.link) { - directive.compile = valueFn(directive.link); - } - directive.priority = directive.priority || 0; - directive.index = index; - directive.name = directive.name || name; - directive.require = directive.require || (directive.controller && directive.name); - directive.restrict = directive.restrict || 'EA'; - var bindings = directive.$$bindings = - parseDirectiveBindings(directive, directive.name); - if (isObject(bindings.isolateScope)) { - directive.$$isolateBindings = bindings.isolateScope; - } - directive.$$moduleName = directiveFactory.$$moduleName; - directives.push(directive); - } catch (e) { - $exceptionHandler(e); - } - }); - return directives; - }]); - } - hasDirectives[name].push(directiveFactory); - } else { - forEach(name, reverseParams(registerDirective)); - } - return this; - }; - - - /** - * @ngdoc method - * @name $compileProvider#aHrefSanitizationWhitelist - * @kind function - * - * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during a[href] sanitization. - * - * The sanitization is a security measure aimed at preventing XSS attacks via html links. - * - * Any url about to be assigned to a[href] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.aHrefSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); - return this; - } else { - return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); - } - }; - - - /** - * @ngdoc method - * @name $compileProvider#imgSrcSanitizationWhitelist - * @kind function - * - * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during img[src] sanitization. - * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. - * - * Any url about to be assigned to img[src] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.imgSrcSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); - return this; - } else { - return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); - } - }; - - /** - * @ngdoc method - * @name $compileProvider#debugInfoEnabled - * - * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the - * current debugInfoEnabled state - * @returns {*} current value if used as getter or itself (chaining) if used as setter - * - * @kind function - * - * @description - * Call this method to enable/disable various debug runtime information in the compiler such as adding - * binding information and a reference to the current scope on to DOM elements. - * If enabled, the compiler will add the following to DOM elements that have been bound to the scope - * * `ng-binding` CSS class - * * `$binding` data property containing an array of the binding expressions - * - * You may want to disable this in production for a significant performance boost. See - * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. - * - * The default value is true. - */ - var debugInfoEnabled = true; - this.debugInfoEnabled = function(enabled) { - if (isDefined(enabled)) { - debugInfoEnabled = enabled; - return this; - } - return debugInfoEnabled; - }; - - this.$get = [ - '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', - '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', - function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, - $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { - - var Attributes = function(element, attributesToCopy) { - if (attributesToCopy) { - var keys = Object.keys(attributesToCopy); - var i, l, key; - - for (i = 0, l = keys.length; i < l; i++) { - key = keys[i]; - this[key] = attributesToCopy[key]; - } - } else { - this.$attr = {}; - } - - this.$$element = element; - }; - - Attributes.prototype = { - /** - * @ngdoc method - * @name $compile.directive.Attributes#$normalize - * @kind function - * - * @description - * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or - * `data-`) to its normalized, camelCase form. - * - * Also there is special case for Moz prefix starting with upper case letter. - * - * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} - * - * @param {string} name Name to normalize - */ - $normalize: directiveNormalize, - - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$addClass - * @kind function - * - * @description - * Adds the CSS class value specified by the classVal parameter to the element. If animations - * are enabled then an animation will be triggered for the class addition. - * - * @param {string} classVal The className value that will be added to the element - */ - $addClass: function(classVal) { - if (classVal && classVal.length > 0) { - $animate.addClass(this.$$element, classVal); - } - }, - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$removeClass - * @kind function - * - * @description - * Removes the CSS class value specified by the classVal parameter from the element. If - * animations are enabled then an animation will be triggered for the class removal. - * - * @param {string} classVal The className value that will be removed from the element - */ - $removeClass: function(classVal) { - if (classVal && classVal.length > 0) { - $animate.removeClass(this.$$element, classVal); - } - }, - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$updateClass - * @kind function - * - * @description - * Adds and removes the appropriate CSS class values to the element based on the difference - * between the new and old CSS class values (specified as newClasses and oldClasses). - * - * @param {string} newClasses The current CSS className value - * @param {string} oldClasses The former CSS className value - */ - $updateClass: function(newClasses, oldClasses) { - var toAdd = tokenDifference(newClasses, oldClasses); - if (toAdd && toAdd.length) { - $animate.addClass(this.$$element, toAdd); - } - - var toRemove = tokenDifference(oldClasses, newClasses); - if (toRemove && toRemove.length) { - $animate.removeClass(this.$$element, toRemove); - } - }, - - /** - * Set a normalized attribute on the element in a way such that all directives - * can share the attribute. This function properly handles boolean attributes. - * @param {string} key Normalized key. (ie ngAttribute) - * @param {string|boolean} value The value to set. If `null` attribute will be deleted. - * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. - * Defaults to true. - * @param {string=} attrName Optional none normalized name. Defaults to key. - */ - $set: function(key, value, writeAttr, attrName) { - // TODO: decide whether or not to throw an error if "class" - //is set through this function since it may cause $updateClass to - //become unstable. - - var node = this.$$element[0], - booleanKey = getBooleanAttrName(node, key), - aliasedKey = getAliasedAttrName(key), - observer = key, - nodeName; - - if (booleanKey) { - this.$$element.prop(key, value); - attrName = booleanKey; - } else if (aliasedKey) { - this[aliasedKey] = value; - observer = aliasedKey; - } - - this[key] = value; - - // translate normalized key to actual key - if (attrName) { - this.$attr[key] = attrName; - } else { - attrName = this.$attr[key]; - if (!attrName) { - this.$attr[key] = attrName = snake_case(key, '-'); - } - } - - nodeName = nodeName_(this.$$element); - - if ((nodeName === 'a' && key === 'href') || - (nodeName === 'img' && key === 'src')) { - // sanitize a[href] and img[src] values - this[key] = value = $$sanitizeUri(value, key === 'src'); - } else if (nodeName === 'img' && key === 'srcset') { - // sanitize img[srcset] values - var result = ""; - - // first check if there are spaces because it's not the same pattern - var trimmedSrcset = trim(value); - // ( 999x ,| 999w ,| ,|, ) - var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; - var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; - - // split srcset into tuple of uri and descriptor except for the last item - var rawUris = trimmedSrcset.split(pattern); - - // for each tuples - var nbrUrisWith2parts = Math.floor(rawUris.length / 2); - for (var i = 0; i < nbrUrisWith2parts; i++) { - var innerIdx = i * 2; - // sanitize the uri - result += $$sanitizeUri(trim(rawUris[innerIdx]), true); - // add the descriptor - result += (" " + trim(rawUris[innerIdx + 1])); - } - - // split the last item into uri and descriptor - var lastTuple = trim(rawUris[i * 2]).split(/\s/); - - // sanitize the last uri - result += $$sanitizeUri(trim(lastTuple[0]), true); - - // and add the last descriptor if any - if (lastTuple.length === 2) { - result += (" " + trim(lastTuple[1])); - } - this[key] = value = result; - } - - if (writeAttr !== false) { - if (value === null || isUndefined(value)) { - this.$$element.removeAttr(attrName); - } else { - this.$$element.attr(attrName, value); - } - } - - // fire observers - var $$observers = this.$$observers; - $$observers && forEach($$observers[observer], function(fn) { - try { - fn(value); - } catch (e) { - $exceptionHandler(e); - } - }); - }, - - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$observe - * @kind function - * - * @description - * Observes an interpolated attribute. - * - * The observer function will be invoked once during the next `$digest` following - * compilation. The observer is then invoked whenever the interpolated value - * changes. - * - * @param {string} key Normalized key. (ie ngAttribute) . - * @param {function(interpolatedValue)} fn Function that will be called whenever - the interpolated value of the attribute changes. - * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info. - * @returns {function()} Returns a deregistration function for this observer. - */ - $observe: function(key, fn) { - var attrs = this, - $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), - listeners = ($$observers[key] || ($$observers[key] = [])); - - listeners.push(fn); - $rootScope.$evalAsync(function() { - if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { - // no one registered attribute interpolation function, so lets call it manually - fn(attrs[key]); - } - }); - - return function() { - arrayRemove(listeners, fn); - }; - } - }; - - - function safeAddClass($element, className) { - try { - $element.addClass(className); - } catch (e) { - // ignore, since it means that we are trying to set class on - // SVG element, where class name is read-only. - } - } - - - var startSymbol = $interpolate.startSymbol(), - endSymbol = $interpolate.endSymbol(), - denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') - ? identity - : function denormalizeTemplate(template) { - return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); - }, - NG_ATTR_BINDING = /^ngAttr[A-Z]/; - - compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { - var bindings = $element.data('$binding') || []; - - if (isArray(binding)) { - bindings = bindings.concat(binding); - } else { - bindings.push(binding); - } - - $element.data('$binding', bindings); - } : noop; - - compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { - safeAddClass($element, 'ng-binding'); - } : noop; - - compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { - var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; - $element.data(dataName, scope); - } : noop; - - compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { - safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); - } : noop; - - return compile; - - //================================ - - function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, - previousCompileContext) { - if (!($compileNodes instanceof jqLite)) { - // jquery always rewraps, whereas we need to preserve the original selector so that we can - // modify it. - $compileNodes = jqLite($compileNodes); - } - // We can not compile top level text elements since text nodes can be merged and we will - // not be able to attach scope data to them, so we will wrap them in <span> - forEach($compileNodes, function(node, index) { - if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { - $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0]; - } - }); - var compositeLinkFn = - compileNodes($compileNodes, transcludeFn, $compileNodes, - maxPriority, ignoreDirective, previousCompileContext); - compile.$$addScopeClass($compileNodes); - var namespace = null; - return function publicLinkFn(scope, cloneConnectFn, options) { - assertArg(scope, 'scope'); - - options = options || {}; - var parentBoundTranscludeFn = options.parentBoundTranscludeFn, - transcludeControllers = options.transcludeControllers, - futureParentElement = options.futureParentElement; - - // When `parentBoundTranscludeFn` is passed, it is a - // `controllersBoundTransclude` function (it was previously passed - // as `transclude` to directive.link) so we must unwrap it to get - // its `boundTranscludeFn` - if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { - parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; - } - - if (!namespace) { - namespace = detectNamespaceForChildElements(futureParentElement); - } - var $linkNode; - if (namespace !== 'html') { - // When using a directive with replace:true and templateUrl the $compileNodes - // (or a child element inside of them) - // might change, so we need to recreate the namespace adapted compileNodes - // for call to the link function. - // Note: This will already clone the nodes... - $linkNode = jqLite( - wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) - ); - } else if (cloneConnectFn) { - // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart - // and sometimes changes the structure of the DOM. - $linkNode = JQLitePrototype.clone.call($compileNodes); - } else { - $linkNode = $compileNodes; - } - - if (transcludeControllers) { - for (var controllerName in transcludeControllers) { - $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); - } - } - - compile.$$addScopeInfo($linkNode, scope); - - if (cloneConnectFn) cloneConnectFn($linkNode, scope); - if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); - return $linkNode; - }; - } - - function detectNamespaceForChildElements(parentElement) { - // TODO: Make this detect MathML as well... - var node = parentElement && parentElement[0]; - if (!node) { - return 'html'; - } else { - return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; - } - } - - /** - * Compile function matches each node in nodeList against the directives. Once all directives - * for a particular node are collected their compile functions are executed. The compile - * functions return values - the linking functions - are combined into a composite linking - * function, which is the a linking function for the node. - * - * @param {NodeList} nodeList an array of nodes or NodeList to compile - * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the - * scope argument is auto-generated to the new child of the transcluded parent scope. - * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then - * the rootElement must be set the jqLite collection of the compile root. This is - * needed so that the jqLite collection items can be replaced with widgets. - * @param {number=} maxPriority Max directive priority. - * @returns {Function} A composite linking function of all of the matched directives or null. - */ - function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, - previousCompileContext) { - var linkFns = [], - attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; - - for (var i = 0; i < nodeList.length; i++) { - attrs = new Attributes(); - - // we must always refer to nodeList[i] since the nodes can be replaced underneath us. - directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, - ignoreDirective); - - nodeLinkFn = (directives.length) - ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, - null, [], [], previousCompileContext) - : null; - - if (nodeLinkFn && nodeLinkFn.scope) { - compile.$$addScopeClass(attrs.$$element); - } - - childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || - !(childNodes = nodeList[i].childNodes) || - !childNodes.length) - ? null - : compileNodes(childNodes, - nodeLinkFn ? ( - (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) - && nodeLinkFn.transclude) : transcludeFn); - - if (nodeLinkFn || childLinkFn) { - linkFns.push(i, nodeLinkFn, childLinkFn); - linkFnFound = true; - nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; - } - - //use the previous context only for the first element in the virtual group - previousCompileContext = null; - } - - // return a linking function if we have found anything, null otherwise - return linkFnFound ? compositeLinkFn : null; - - function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { - var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; - var stableNodeList; - - - if (nodeLinkFnFound) { - // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our - // offsets don't get screwed up - var nodeListLength = nodeList.length; - stableNodeList = new Array(nodeListLength); - - // create a sparse array by only copying the elements which have a linkFn - for (i = 0; i < linkFns.length; i+=3) { - idx = linkFns[i]; - stableNodeList[idx] = nodeList[idx]; - } - } else { - stableNodeList = nodeList; - } - - for (i = 0, ii = linkFns.length; i < ii;) { - node = stableNodeList[linkFns[i++]]; - nodeLinkFn = linkFns[i++]; - childLinkFn = linkFns[i++]; - - if (nodeLinkFn) { - if (nodeLinkFn.scope) { - childScope = scope.$new(); - compile.$$addScopeInfo(jqLite(node), childScope); - var destroyBindings = nodeLinkFn.$$destroyBindings; - if (destroyBindings) { - nodeLinkFn.$$destroyBindings = null; - childScope.$on('$destroyed', destroyBindings); - } - } else { - childScope = scope; - } - - if (nodeLinkFn.transcludeOnThisElement) { - childBoundTranscludeFn = createBoundTranscludeFn( - scope, nodeLinkFn.transclude, parentBoundTranscludeFn); - - } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { - childBoundTranscludeFn = parentBoundTranscludeFn; - - } else if (!parentBoundTranscludeFn && transcludeFn) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); - - } else { - childBoundTranscludeFn = null; - } - - nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn, - nodeLinkFn); - - } else if (childLinkFn) { - childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); - } - } - } - } - - function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { - - var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { - - if (!transcludedScope) { - transcludedScope = scope.$new(false, containingScope); - transcludedScope.$$transcluded = true; - } - - return transcludeFn(transcludedScope, cloneFn, { - parentBoundTranscludeFn: previousBoundTranscludeFn, - transcludeControllers: controllers, - futureParentElement: futureParentElement - }); - }; - - return boundTranscludeFn; - } - - /** - * Looks for directives on the given node and adds them to the directive collection which is - * sorted. - * - * @param node Node to search. - * @param directives An array to which the directives are added to. This array is sorted before - * the function returns. - * @param attrs The shared attrs object which is used to populate the normalized attributes. - * @param {number=} maxPriority Max directive priority. - */ - function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { - var nodeType = node.nodeType, - attrsMap = attrs.$attr, - match, - className; - - switch (nodeType) { - case NODE_TYPE_ELEMENT: /* Element */ - // use the node name: <directive> - addDirective(directives, - directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); - - // iterate over the attributes - for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, - j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { - var attrStartName = false; - var attrEndName = false; - - attr = nAttrs[j]; - name = attr.name; - value = trim(attr.value); - - // support ngAttr attribute binding - ngAttrName = directiveNormalize(name); - if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { - name = name.replace(PREFIX_REGEXP, '') - .substr(8).replace(/_(.)/g, function(match, letter) { - return letter.toUpperCase(); - }); - } - - var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); - if (directiveIsMultiElement(directiveNName)) { - if (ngAttrName === directiveNName + 'Start') { - attrStartName = name; - attrEndName = name.substr(0, name.length - 5) + 'end'; - name = name.substr(0, name.length - 6); - } - } - - nName = directiveNormalize(name.toLowerCase()); - attrsMap[nName] = name; - if (isNgAttr || !attrs.hasOwnProperty(nName)) { - attrs[nName] = value; - if (getBooleanAttrName(node, nName)) { - attrs[nName] = true; // presence means true - } - } - addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); - addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, - attrEndName); - } - - // use class as directive - className = node.className; - if (isObject(className)) { - // Maybe SVGAnimatedString - className = className.animVal; - } - if (isString(className) && className !== '') { - while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { - nName = directiveNormalize(match[2]); - if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { - attrs[nName] = trim(match[3]); - } - className = className.substr(match.index + match[0].length); - } - } - break; - case NODE_TYPE_TEXT: /* Text Node */ - if (msie === 11) { - // Workaround for #11781 - while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { - node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; - node.parentNode.removeChild(node.nextSibling); - } - } - addTextInterpolateDirective(directives, node.nodeValue); - break; - case NODE_TYPE_COMMENT: /* Comment */ - try { - match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); - if (match) { - nName = directiveNormalize(match[1]); - if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { - attrs[nName] = trim(match[2]); - } - } - } catch (e) { - // turns out that under some circumstances IE9 throws errors when one attempts to read - // comment's node value. - // Just ignore it and continue. (Can't seem to reproduce in test case.) - } - break; - } - - directives.sort(byPriority); - return directives; - } - - /** - * Given a node with an directive-start it collects all of the siblings until it finds - * directive-end. - * @param node - * @param attrStart - * @param attrEnd - * @returns {*} - */ - function groupScan(node, attrStart, attrEnd) { - var nodes = []; - var depth = 0; - if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { - do { - if (!node) { - throw $compileMinErr('uterdir', - "Unterminated attribute, found '{0}' but no matching '{1}' found.", - attrStart, attrEnd); - } - if (node.nodeType == NODE_TYPE_ELEMENT) { - if (node.hasAttribute(attrStart)) depth++; - if (node.hasAttribute(attrEnd)) depth--; - } - nodes.push(node); - node = node.nextSibling; - } while (depth > 0); - } else { - nodes.push(node); - } - - return jqLite(nodes); - } - - /** - * Wrapper for linking function which converts normal linking function into a grouped - * linking function. - * @param linkFn - * @param attrStart - * @param attrEnd - * @returns {Function} - */ - function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { - return function(scope, element, attrs, controllers, transcludeFn) { - element = groupScan(element[0], attrStart, attrEnd); - return linkFn(scope, element, attrs, controllers, transcludeFn); - }; - } - - /** - * Once the directives have been collected, their compile functions are executed. This method - * is responsible for inlining directive templates as well as terminating the application - * of the directives if the terminal directive has been reached. - * - * @param {Array} directives Array of collected directives to execute their compile function. - * this needs to be pre-sorted by priority order. - * @param {Node} compileNode The raw DOM node to apply the compile functions to - * @param {Object} templateAttrs The shared attribute function - * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the - * scope argument is auto-generated to the new - * child of the transcluded parent scope. - * @param {JQLite} jqCollection If we are working on the root of the compile tree then this - * argument has the root jqLite array so that we can replace nodes - * on it. - * @param {Object=} originalReplaceDirective An optional directive that will be ignored when - * compiling the transclusion. - * @param {Array.<Function>} preLinkFns - * @param {Array.<Function>} postLinkFns - * @param {Object} previousCompileContext Context used for previous compilation of the current - * node - * @returns {Function} linkFn - */ - function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, - jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, - previousCompileContext) { - previousCompileContext = previousCompileContext || {}; - - var terminalPriority = -Number.MAX_VALUE, - newScopeDirective = previousCompileContext.newScopeDirective, - controllerDirectives = previousCompileContext.controllerDirectives, - newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, - templateDirective = previousCompileContext.templateDirective, - nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, - hasTranscludeDirective = false, - hasTemplate = false, - hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, - $compileNode = templateAttrs.$$element = jqLite(compileNode), - directive, - directiveName, - $template, - replaceDirective = originalReplaceDirective, - childTranscludeFn = transcludeFn, - linkFn, - directiveValue; - - // executes all directives on the current element - for (var i = 0, ii = directives.length; i < ii; i++) { - directive = directives[i]; - var attrStart = directive.$$start; - var attrEnd = directive.$$end; - - // collect multiblock sections - if (attrStart) { - $compileNode = groupScan(compileNode, attrStart, attrEnd); - } - $template = undefined; - - if (terminalPriority > directive.priority) { - break; // prevent further processing of directives - } - - if (directiveValue = directive.scope) { - - // skip the check for directives with async templates, we'll check the derived sync - // directive when the template arrives - if (!directive.templateUrl) { - if (isObject(directiveValue)) { - // This directive is trying to add an isolated scope. - // Check that there is no scope of any kind already - assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, - directive, $compileNode); - newIsolateScopeDirective = directive; - } else { - // This directive is trying to add a child scope. - // Check that there is no isolated scope already - assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, - $compileNode); - } - } - - newScopeDirective = newScopeDirective || directive; - } - - directiveName = directive.name; - - if (!directive.templateUrl && directive.controller) { - directiveValue = directive.controller; - controllerDirectives = controllerDirectives || createMap(); - assertNoDuplicate("'" + directiveName + "' controller", - controllerDirectives[directiveName], directive, $compileNode); - controllerDirectives[directiveName] = directive; - } - - if (directiveValue = directive.transclude) { - hasTranscludeDirective = true; - - // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. - // This option should only be used by directives that know how to safely handle element transclusion, - // where the transcluded nodes are added or replaced after linking. - if (!directive.$$tlb) { - assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); - nonTlbTranscludeDirective = directive; - } - - if (directiveValue == 'element') { - hasElementTranscludeDirective = true; - terminalPriority = directive.priority; - $template = $compileNode; - $compileNode = templateAttrs.$$element = - jqLite(document.createComment(' ' + directiveName + ': ' + - templateAttrs[directiveName] + ' ')); - compileNode = $compileNode[0]; - replaceWith(jqCollection, sliceArgs($template), compileNode); - - childTranscludeFn = compile($template, transcludeFn, terminalPriority, - replaceDirective && replaceDirective.name, { - // Don't pass in: - // - controllerDirectives - otherwise we'll create duplicates controllers - // - newIsolateScopeDirective or templateDirective - combining templates with - // element transclusion doesn't make sense. - // - // We need only nonTlbTranscludeDirective so that we prevent putting transclusion - // on the same element more than once. - nonTlbTranscludeDirective: nonTlbTranscludeDirective - }); - } else { - $template = jqLite(jqLiteClone(compileNode)).contents(); - $compileNode.empty(); // clear contents - childTranscludeFn = compile($template, transcludeFn); - } - } - - if (directive.template) { - hasTemplate = true; - assertNoDuplicate('template', templateDirective, directive, $compileNode); - templateDirective = directive; - - directiveValue = (isFunction(directive.template)) - ? directive.template($compileNode, templateAttrs) - : directive.template; - - directiveValue = denormalizeTemplate(directiveValue); - - if (directive.replace) { - replaceDirective = directive; - if (jqLiteIsTextNode(directiveValue)) { - $template = []; - } else { - $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); - } - compileNode = $template[0]; - - if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { - throw $compileMinErr('tplrt', - "Template for directive '{0}' must have exactly one root element. {1}", - directiveName, ''); - } - - replaceWith(jqCollection, $compileNode, compileNode); - - var newTemplateAttrs = {$attr: {}}; - - // combine directives from the original node and from the template: - // - take the array of directives for this element - // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) - // - collect directives from the template and sort them by priority - // - combine directives as: processed + template + unprocessed - var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); - var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); - - if (newIsolateScopeDirective) { - markDirectivesAsIsolate(templateDirectives); - } - directives = directives.concat(templateDirectives).concat(unprocessedDirectives); - mergeTemplateAttributes(templateAttrs, newTemplateAttrs); - - ii = directives.length; - } else { - $compileNode.html(directiveValue); - } - } - - if (directive.templateUrl) { - hasTemplate = true; - assertNoDuplicate('template', templateDirective, directive, $compileNode); - templateDirective = directive; - - if (directive.replace) { - replaceDirective = directive; - } - - nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, - templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { - controllerDirectives: controllerDirectives, - newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, - newIsolateScopeDirective: newIsolateScopeDirective, - templateDirective: templateDirective, - nonTlbTranscludeDirective: nonTlbTranscludeDirective - }); - ii = directives.length; - } else if (directive.compile) { - try { - linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); - if (isFunction(linkFn)) { - addLinkFns(null, linkFn, attrStart, attrEnd); - } else if (linkFn) { - addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); - } - } catch (e) { - $exceptionHandler(e, startingTag($compileNode)); - } - } - - if (directive.terminal) { - nodeLinkFn.terminal = true; - terminalPriority = Math.max(terminalPriority, directive.priority); - } - - } - - nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; - nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; - nodeLinkFn.templateOnThisElement = hasTemplate; - nodeLinkFn.transclude = childTranscludeFn; - - previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; - - // might be normal or delayed nodeLinkFn depending on if templateUrl is present - return nodeLinkFn; - - //////////////////// - - function addLinkFns(pre, post, attrStart, attrEnd) { - if (pre) { - if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); - pre.require = directive.require; - pre.directiveName = directiveName; - if (newIsolateScopeDirective === directive || directive.$$isolateScope) { - pre = cloneAndAnnotateFn(pre, {isolateScope: true}); - } - preLinkFns.push(pre); - } - if (post) { - if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); - post.require = directive.require; - post.directiveName = directiveName; - if (newIsolateScopeDirective === directive || directive.$$isolateScope) { - post = cloneAndAnnotateFn(post, {isolateScope: true}); - } - postLinkFns.push(post); - } - } - - - function getControllers(directiveName, require, $element, elementControllers) { - var value; - - if (isString(require)) { - var match = require.match(REQUIRE_PREFIX_REGEXP); - var name = require.substring(match[0].length); - var inheritType = match[1] || match[3]; - var optional = match[2] === '?'; - - //If only parents then start at the parent element - if (inheritType === '^^') { - $element = $element.parent(); - //Otherwise attempt getting the controller from elementControllers in case - //the element is transcluded (and has no data) and to avoid .data if possible - } else { - value = elementControllers && elementControllers[name]; - value = value && value.instance; - } - - if (!value) { - var dataName = '$' + name + 'Controller'; - value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); - } - - if (!value && !optional) { - throw $compileMinErr('ctreq', - "Controller '{0}', required by directive '{1}', can't be found!", - name, directiveName); - } - } else if (isArray(require)) { - value = []; - for (var i = 0, ii = require.length; i < ii; i++) { - value[i] = getControllers(directiveName, require[i], $element, elementControllers); - } - } - - return value || null; - } - - function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) { - var elementControllers = createMap(); - for (var controllerKey in controllerDirectives) { - var directive = controllerDirectives[controllerKey]; - var locals = { - $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, - $element: $element, - $attrs: attrs, - $transclude: transcludeFn - }; - - var controller = directive.controller; - if (controller == '@') { - controller = attrs[directive.name]; - } - - var controllerInstance = $controller(controller, locals, true, directive.controllerAs); - - // For directives with element transclusion the element is a comment, - // but jQuery .data doesn't support attaching data to comment nodes as it's hard to - // clean up (http://bugs.jquery.com/ticket/8335). - // Instead, we save the controllers for the element in a local hash and attach to .data - // later, once we have the actual element. - elementControllers[directive.name] = controllerInstance; - if (!hasElementTranscludeDirective) { - $element.data('$' + directive.name + 'Controller', controllerInstance.instance); - } - } - return elementControllers; - } - - function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn, - thisLinkFn) { - var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element, - attrs; - - if (compileNode === linkNode) { - attrs = templateAttrs; - $element = templateAttrs.$$element; - } else { - $element = jqLite(linkNode); - attrs = new Attributes($element, templateAttrs); - } - - if (newIsolateScopeDirective) { - isolateScope = scope.$new(true); - } - - if (boundTranscludeFn) { - // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` - // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` - transcludeFn = controllersBoundTransclude; - transcludeFn.$$boundTransclude = boundTranscludeFn; - } - - if (controllerDirectives) { - elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope); - } - - if (newIsolateScopeDirective) { - // Initialize isolate scope bindings for new isolate scope directive. - compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || - templateDirective === newIsolateScopeDirective.$$originalDirective))); - compile.$$addScopeClass($element, true); - isolateScope.$$isolateBindings = - newIsolateScopeDirective.$$isolateBindings; - initializeDirectiveBindings(scope, attrs, isolateScope, - isolateScope.$$isolateBindings, - newIsolateScopeDirective, isolateScope); - } - if (elementControllers) { - // Initialize bindToController bindings for new/isolate scopes - var scopeDirective = newIsolateScopeDirective || newScopeDirective; - var bindings; - var controllerForBindings; - if (scopeDirective && elementControllers[scopeDirective.name]) { - bindings = scopeDirective.$$bindings.bindToController; - controller = elementControllers[scopeDirective.name]; - - if (controller && controller.identifier && bindings) { - controllerForBindings = controller; - thisLinkFn.$$destroyBindings = - initializeDirectiveBindings(scope, attrs, controller.instance, - bindings, scopeDirective); - } - } - for (i in elementControllers) { - controller = elementControllers[i]; - var controllerResult = controller(); - - if (controllerResult !== controller.instance) { - // If the controller constructor has a return value, overwrite the instance - // from setupControllers and update the element data - controller.instance = controllerResult; - $element.data('$' + i + 'Controller', controllerResult); - if (controller === controllerForBindings) { - // Remove and re-install bindToController bindings - thisLinkFn.$$destroyBindings(); - thisLinkFn.$$destroyBindings = - initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective); - } - } - } - } - - // PRELINKING - for (i = 0, ii = preLinkFns.length; i < ii; i++) { - linkFn = preLinkFns[i]; - invokeLinkFn(linkFn, - linkFn.isolateScope ? isolateScope : scope, - $element, - attrs, - linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), - transcludeFn - ); - } - - // RECURSION - // We only pass the isolate scope, if the isolate directive has a template, - // otherwise the child elements do not belong to the isolate directive. - var scopeToChild = scope; - if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { - scopeToChild = isolateScope; - } - childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); - - // POSTLINKING - for (i = postLinkFns.length - 1; i >= 0; i--) { - linkFn = postLinkFns[i]; - invokeLinkFn(linkFn, - linkFn.isolateScope ? isolateScope : scope, - $element, - attrs, - linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), - transcludeFn - ); - } - - // This is the function that is injected as `$transclude`. - // Note: all arguments are optional! - function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) { - var transcludeControllers; - - // No scope passed in: - if (!isScope(scope)) { - futureParentElement = cloneAttachFn; - cloneAttachFn = scope; - scope = undefined; - } - - if (hasElementTranscludeDirective) { - transcludeControllers = elementControllers; - } - if (!futureParentElement) { - futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; - } - return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); - } - } - } - - function markDirectivesAsIsolate(directives) { - // mark all directives as needing isolate scope. - for (var j = 0, jj = directives.length; j < jj; j++) { - directives[j] = inherit(directives[j], {$$isolateScope: true}); - } - } - - /** - * looks up the directive and decorates it with exception handling and proper parameters. We - * call this the boundDirective. - * - * @param {string} name name of the directive to look up. - * @param {string} location The directive must be found in specific format. - * String containing any of theses characters: - * - * * `E`: element name - * * `A': attribute - * * `C`: class - * * `M`: comment - * @returns {boolean} true if directive was added. - */ - function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, - endAttrName) { - if (name === ignoreDirective) return null; - var match = null; - if (hasDirectives.hasOwnProperty(name)) { - for (var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i < ii; i++) { - try { - directive = directives[i]; - if ((isUndefined(maxPriority) || maxPriority > directive.priority) && - directive.restrict.indexOf(location) != -1) { - if (startAttrName) { - directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); - } - tDirectives.push(directive); - match = directive; - } - } catch (e) { $exceptionHandler(e); } - } - } - return match; - } - - - /** - * looks up the directive and returns true if it is a multi-element directive, - * and therefore requires DOM nodes between -start and -end markers to be grouped - * together. - * - * @param {string} name name of the directive to look up. - * @returns true if directive was registered as multi-element. - */ - function directiveIsMultiElement(name) { - if (hasDirectives.hasOwnProperty(name)) { - for (var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i < ii; i++) { - directive = directives[i]; - if (directive.multiElement) { - return true; - } - } - } - return false; - } - - /** - * When the element is replaced with HTML template then the new attributes - * on the template need to be merged with the existing attributes in the DOM. - * The desired effect is to have both of the attributes present. - * - * @param {object} dst destination attributes (original DOM) - * @param {object} src source attributes (from the directive template) - */ - function mergeTemplateAttributes(dst, src) { - var srcAttr = src.$attr, - dstAttr = dst.$attr, - $element = dst.$$element; - - // reapply the old attributes to the new element - forEach(dst, function(value, key) { - if (key.charAt(0) != '$') { - if (src[key] && src[key] !== value) { - value += (key === 'style' ? ';' : ' ') + src[key]; - } - dst.$set(key, value, true, srcAttr[key]); - } - }); - - // copy the new attributes on the old attrs object - forEach(src, function(value, key) { - if (key == 'class') { - safeAddClass($element, value); - dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; - } else if (key == 'style') { - $element.attr('style', $element.attr('style') + ';' + value); - dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; - // `dst` will never contain hasOwnProperty as DOM parser won't let it. - // You will get an "InvalidCharacterError: DOM Exception 5" error if you - // have an attribute like "has-own-property" or "data-has-own-property", etc. - } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { - dst[key] = value; - dstAttr[key] = srcAttr[key]; - } - }); - } - - - function compileTemplateUrl(directives, $compileNode, tAttrs, - $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { - var linkQueue = [], - afterTemplateNodeLinkFn, - afterTemplateChildLinkFn, - beforeTemplateCompileNode = $compileNode[0], - origAsyncDirective = directives.shift(), - derivedSyncDirective = inherit(origAsyncDirective, { - templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective - }), - templateUrl = (isFunction(origAsyncDirective.templateUrl)) - ? origAsyncDirective.templateUrl($compileNode, tAttrs) - : origAsyncDirective.templateUrl, - templateNamespace = origAsyncDirective.templateNamespace; - - $compileNode.empty(); - - $templateRequest(templateUrl) - .then(function(content) { - var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; - - content = denormalizeTemplate(content); - - if (origAsyncDirective.replace) { - if (jqLiteIsTextNode(content)) { - $template = []; - } else { - $template = removeComments(wrapTemplate(templateNamespace, trim(content))); - } - compileNode = $template[0]; - - if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { - throw $compileMinErr('tplrt', - "Template for directive '{0}' must have exactly one root element. {1}", - origAsyncDirective.name, templateUrl); - } - - tempTemplateAttrs = {$attr: {}}; - replaceWith($rootElement, $compileNode, compileNode); - var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); - - if (isObject(origAsyncDirective.scope)) { - markDirectivesAsIsolate(templateDirectives); - } - directives = templateDirectives.concat(directives); - mergeTemplateAttributes(tAttrs, tempTemplateAttrs); - } else { - compileNode = beforeTemplateCompileNode; - $compileNode.html(content); - } - - directives.unshift(derivedSyncDirective); - - afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, - childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, - previousCompileContext); - forEach($rootElement, function(node, i) { - if (node == compileNode) { - $rootElement[i] = $compileNode[0]; - } - }); - afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); - - while (linkQueue.length) { - var scope = linkQueue.shift(), - beforeTemplateLinkNode = linkQueue.shift(), - linkRootElement = linkQueue.shift(), - boundTranscludeFn = linkQueue.shift(), - linkNode = $compileNode[0]; - - if (scope.$$destroyed) continue; - - if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { - var oldClasses = beforeTemplateLinkNode.className; - - if (!(previousCompileContext.hasElementTranscludeDirective && - origAsyncDirective.replace)) { - // it was cloned therefore we have to clone as well. - linkNode = jqLiteClone(compileNode); - } - replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); - - // Copy in CSS classes from original node - safeAddClass(jqLite(linkNode), oldClasses); - } - if (afterTemplateNodeLinkFn.transcludeOnThisElement) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); - } else { - childBoundTranscludeFn = boundTranscludeFn; - } - afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, - childBoundTranscludeFn, afterTemplateNodeLinkFn); - } - linkQueue = null; - }); - - return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { - var childBoundTranscludeFn = boundTranscludeFn; - if (scope.$$destroyed) return; - if (linkQueue) { - linkQueue.push(scope, - node, - rootElement, - childBoundTranscludeFn); - } else { - if (afterTemplateNodeLinkFn.transcludeOnThisElement) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); - } - afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn, - afterTemplateNodeLinkFn); - } - }; - } - - - /** - * Sorting function for bound directives. - */ - function byPriority(a, b) { - var diff = b.priority - a.priority; - if (diff !== 0) return diff; - if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; - return a.index - b.index; - } - - function assertNoDuplicate(what, previousDirective, directive, element) { - - function wrapModuleNameIfDefined(moduleName) { - return moduleName ? - (' (module: ' + moduleName + ')') : - ''; - } - - if (previousDirective) { - throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', - previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), - directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); - } - } - - - function addTextInterpolateDirective(directives, text) { - var interpolateFn = $interpolate(text, true); - if (interpolateFn) { - directives.push({ - priority: 0, - compile: function textInterpolateCompileFn(templateNode) { - var templateNodeParent = templateNode.parent(), - hasCompileParent = !!templateNodeParent.length; - - // When transcluding a template that has bindings in the root - // we don't have a parent and thus need to add the class during linking fn. - if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); - - return function textInterpolateLinkFn(scope, node) { - var parent = node.parent(); - if (!hasCompileParent) compile.$$addBindingClass(parent); - compile.$$addBindingInfo(parent, interpolateFn.expressions); - scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { - node[0].nodeValue = value; - }); - }; - } - }); - } - } - - - function wrapTemplate(type, template) { - type = lowercase(type || 'html'); - switch (type) { - case 'svg': - case 'math': - var wrapper = document.createElement('div'); - wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; - return wrapper.childNodes[0].childNodes; - default: - return template; - } - } - - - function getTrustedContext(node, attrNormalizedName) { - if (attrNormalizedName == "srcdoc") { - return $sce.HTML; - } - var tag = nodeName_(node); - // maction[xlink:href] can source SVG. It's not limited to <maction>. - if (attrNormalizedName == "xlinkHref" || - (tag == "form" && attrNormalizedName == "action") || - (tag != "img" && (attrNormalizedName == "src" || - attrNormalizedName == "ngSrc"))) { - return $sce.RESOURCE_URL; - } - } - - - function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { - var trustedContext = getTrustedContext(node, name); - allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; - - var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); - - // no interpolation found -> ignore - if (!interpolateFn) return; - - - if (name === "multiple" && nodeName_(node) === "select") { - throw $compileMinErr("selmulti", - "Binding to the 'multiple' attribute is not supported. Element: {0}", - startingTag(node)); - } - - directives.push({ - priority: 100, - compile: function() { - return { - pre: function attrInterpolatePreLinkFn(scope, element, attr) { - var $$observers = (attr.$$observers || (attr.$$observers = createMap())); - - if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { - throw $compileMinErr('nodomevents', - "Interpolations for HTML DOM event attributes are disallowed. Please use the " + - "ng- versions (such as ng-click instead of onclick) instead."); - } - - // If the attribute has changed since last $interpolate()ed - var newValue = attr[name]; - if (newValue !== value) { - // we need to interpolate again since the attribute value has been updated - // (e.g. by another directive's compile function) - // ensure unset/empty values make interpolateFn falsy - interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); - value = newValue; - } - - // if attribute was updated so that there is no interpolation going on we don't want to - // register any observers - if (!interpolateFn) return; - - // initialize attr object so that it's ready in case we need the value for isolate - // scope initialization, otherwise the value would not be available from isolate - // directive's linking fn during linking phase - attr[name] = interpolateFn(scope); - - ($$observers[name] || ($$observers[name] = [])).$$inter = true; - (attr.$$observers && attr.$$observers[name].$$scope || scope). - $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { - //special case for class attribute addition + removal - //so that class changes can tap into the animation - //hooks provided by the $animate service. Be sure to - //skip animations when the first digest occurs (when - //both the new and the old values are the same) since - //the CSS classes are the non-interpolated values - if (name === 'class' && newValue != oldValue) { - attr.$updateClass(newValue, oldValue); - } else { - attr.$set(name, newValue); - } - }); - } - }; - } - }); - } - - - /** - * This is a special jqLite.replaceWith, which can replace items which - * have no parents, provided that the containing jqLite collection is provided. - * - * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes - * in the root of the tree. - * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep - * the shell, but replace its DOM node reference. - * @param {Node} newNode The new DOM node. - */ - function replaceWith($rootElement, elementsToRemove, newNode) { - var firstElementToRemove = elementsToRemove[0], - removeCount = elementsToRemove.length, - parent = firstElementToRemove.parentNode, - i, ii; - - if ($rootElement) { - for (i = 0, ii = $rootElement.length; i < ii; i++) { - if ($rootElement[i] == firstElementToRemove) { - $rootElement[i++] = newNode; - for (var j = i, j2 = j + removeCount - 1, - jj = $rootElement.length; - j < jj; j++, j2++) { - if (j2 < jj) { - $rootElement[j] = $rootElement[j2]; - } else { - delete $rootElement[j]; - } - } - $rootElement.length -= removeCount - 1; - - // If the replaced element is also the jQuery .context then replace it - // .context is a deprecated jQuery api, so we should set it only when jQuery set it - // http://api.jquery.com/context/ - if ($rootElement.context === firstElementToRemove) { - $rootElement.context = newNode; - } - break; - } - } - } - - if (parent) { - parent.replaceChild(newNode, firstElementToRemove); - } - - // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it? - var fragment = document.createDocumentFragment(); - fragment.appendChild(firstElementToRemove); - - if (jqLite.hasData(firstElementToRemove)) { - // Copy over user data (that includes Angular's $scope etc.). Don't copy private - // data here because there's no public interface in jQuery to do that and copying over - // event listeners (which is the main use of private data) wouldn't work anyway. - jqLite(newNode).data(jqLite(firstElementToRemove).data()); - - // Remove data of the replaced element. We cannot just call .remove() - // on the element it since that would deallocate scope that is needed - // for the new node. Instead, remove the data "manually". - if (!jQuery) { - delete jqLite.cache[firstElementToRemove[jqLite.expando]]; - } else { - // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after - // the replaced element. The cleanData version monkey-patched by Angular would cause - // the scope to be trashed and we do need the very same scope to work with the new - // element. However, we cannot just cache the non-patched version and use it here as - // that would break if another library patches the method after Angular does (one - // example is jQuery UI). Instead, set a flag indicating scope destroying should be - // skipped this one time. - skipDestroyOnNextJQueryCleanData = true; - jQuery.cleanData([firstElementToRemove]); - } - } - - for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { - var element = elementsToRemove[k]; - jqLite(element).remove(); // must do this way to clean up expando - fragment.appendChild(element); - delete elementsToRemove[k]; - } - - elementsToRemove[0] = newNode; - elementsToRemove.length = 1; - } - - - function cloneAndAnnotateFn(fn, annotation) { - return extend(function() { return fn.apply(null, arguments); }, fn, annotation); - } - - - function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { - try { - linkFn(scope, $element, attrs, controllers, transcludeFn); - } catch (e) { - $exceptionHandler(e, startingTag($element)); - } - } - - - // Set up $watches for isolate scope and controller bindings. This process - // only occurs for isolate scopes and new scopes with controllerAs. - function initializeDirectiveBindings(scope, attrs, destination, bindings, - directive, newScope) { - var onNewScopeDestroyed; - forEach(bindings, function(definition, scopeName) { - var attrName = definition.attrName, - optional = definition.optional, - mode = definition.mode, // @, =, or & - lastValue, - parentGet, parentSet, compare; - - switch (mode) { - - case '@': - if (!optional && !hasOwnProperty.call(attrs, attrName)) { - destination[scopeName] = attrs[attrName] = void 0; - } - attrs.$observe(attrName, function(value) { - if (isString(value)) { - destination[scopeName] = value; - } - }); - attrs.$$observers[attrName].$$scope = scope; - if (isString(attrs[attrName])) { - // If the attribute has been provided then we trigger an interpolation to ensure - // the value is there for use in the link fn - destination[scopeName] = $interpolate(attrs[attrName])(scope); - } - break; - - case '=': - if (!hasOwnProperty.call(attrs, attrName)) { - if (optional) break; - attrs[attrName] = void 0; - } - if (optional && !attrs[attrName]) break; - - parentGet = $parse(attrs[attrName]); - if (parentGet.literal) { - compare = equals; - } else { - compare = function(a, b) { return a === b || (a !== a && b !== b); }; - } - parentSet = parentGet.assign || function() { - // reset the change, or we will throw this exception on every $digest - lastValue = destination[scopeName] = parentGet(scope); - throw $compileMinErr('nonassign', - "Expression '{0}' used with directive '{1}' is non-assignable!", - attrs[attrName], directive.name); - }; - lastValue = destination[scopeName] = parentGet(scope); - var parentValueWatch = function parentValueWatch(parentValue) { - if (!compare(parentValue, destination[scopeName])) { - // we are out of sync and need to copy - if (!compare(parentValue, lastValue)) { - // parent changed and it has precedence - destination[scopeName] = parentValue; - } else { - // if the parent can be assigned then do so - parentSet(scope, parentValue = destination[scopeName]); - } - } - return lastValue = parentValue; - }; - parentValueWatch.$stateful = true; - var unwatch; - if (definition.collection) { - unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch); - } else { - unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); - } - onNewScopeDestroyed = (onNewScopeDestroyed || []); - onNewScopeDestroyed.push(unwatch); - break; - - case '&': - // Don't assign Object.prototype method to scope - parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; - - // Don't assign noop to destination if expression is not valid - if (parentGet === noop && optional) break; - - destination[scopeName] = function(locals) { - return parentGet(scope, locals); - }; - break; - } - }); - var destroyBindings = onNewScopeDestroyed ? function destroyBindings() { - for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) { - onNewScopeDestroyed[i](); - } - } : noop; - if (newScope && destroyBindings !== noop) { - newScope.$on('$destroy', destroyBindings); - return noop; - } - return destroyBindings; - } - }]; -} - -var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; -/** - * Converts all accepted directives format into proper directive name. - * @param name Name to normalize - */ -function directiveNormalize(name) { - return camelCase(name.replace(PREFIX_REGEXP, '')); -} - -/** - * @ngdoc type - * @name $compile.directive.Attributes - * - * @description - * A shared object between directive compile / linking functions which contains normalized DOM - * element attributes. The values reflect current binding state `{{ }}`. The normalization is - * needed since all of these are treated as equivalent in Angular: - * - * ``` - * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> - * ``` - */ - -/** - * @ngdoc property - * @name $compile.directive.Attributes#$attr - * - * @description - * A map of DOM element attribute names to the normalized name. This is - * needed to do reverse lookup from normalized name back to actual name. - */ - - -/** - * @ngdoc method - * @name $compile.directive.Attributes#$set - * @kind function - * - * @description - * Set DOM element attribute value. - * - * - * @param {string} name Normalized element attribute name of the property to modify. The name is - * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} - * property to the original name. - * @param {string} value Value to set the attribute to. The value can be an interpolated string. - */ - - - -/** - * Closure compiler type information - */ - -function nodesetLinkingFn( - /* angular.Scope */ scope, - /* NodeList */ nodeList, - /* Element */ rootElement, - /* function(Function) */ boundTranscludeFn -) {} - -function directiveLinkingFn( - /* nodesetLinkingFn */ nodesetLinkingFn, - /* angular.Scope */ scope, - /* Node */ node, - /* Element */ rootElement, - /* function(Function) */ boundTranscludeFn -) {} - -function tokenDifference(str1, str2) { - var values = '', - tokens1 = str1.split(/\s+/), - tokens2 = str2.split(/\s+/); - - outer: - for (var i = 0; i < tokens1.length; i++) { - var token = tokens1[i]; - for (var j = 0; j < tokens2.length; j++) { - if (token == tokens2[j]) continue outer; - } - values += (values.length > 0 ? ' ' : '') + token; - } - return values; -} - -function removeComments(jqNodes) { - jqNodes = jqLite(jqNodes); - var i = jqNodes.length; - - if (i <= 1) { - return jqNodes; - } - - while (i--) { - var node = jqNodes[i]; - if (node.nodeType === NODE_TYPE_COMMENT) { - splice.call(jqNodes, i, 1); - } - } - return jqNodes; -} - -var $controllerMinErr = minErr('$controller'); - - -var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; -function identifierForController(controller, ident) { - if (ident && isString(ident)) return ident; - if (isString(controller)) { - var match = CNTRL_REG.exec(controller); - if (match) return match[3]; - } -} - - -/** - * @ngdoc provider - * @name $controllerProvider - * @description - * The {@link ng.$controller $controller service} is used by Angular to create new - * controllers. - * - * This provider allows controller registration via the - * {@link ng.$controllerProvider#register register} method. - */ -function $ControllerProvider() { - var controllers = {}, - globals = false; - - /** - * @ngdoc method - * @name $controllerProvider#register - * @param {string|Object} name Controller name, or an object map of controllers where the keys are - * the names and the values are the constructors. - * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI - * annotations in the array notation). - */ - this.register = function(name, constructor) { - assertNotHasOwnProperty(name, 'controller'); - if (isObject(name)) { - extend(controllers, name); - } else { - controllers[name] = constructor; - } - }; - - /** - * @ngdoc method - * @name $controllerProvider#allowGlobals - * @description If called, allows `$controller` to find controller constructors on `window` - */ - this.allowGlobals = function() { - globals = true; - }; - - - this.$get = ['$injector', '$window', function($injector, $window) { - - /** - * @ngdoc service - * @name $controller - * @requires $injector - * - * @param {Function|string} constructor If called with a function then it's considered to be the - * controller constructor function. Otherwise it's considered to be a string which is used - * to retrieve the controller constructor using the following steps: - * - * * check if a controller with given name is registered via `$controllerProvider` - * * check if evaluating the string on the current scope returns a constructor - * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global - * `window` object (not recommended) - * - * The string can use the `controller as property` syntax, where the controller instance is published - * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this - * to work correctly. - * - * @param {Object} locals Injection locals for Controller. - * @return {Object} Instance of given controller. - * - * @description - * `$controller` service is responsible for instantiating controllers. - * - * It's just a simple call to {@link auto.$injector $injector}, but extracted into - * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). - */ - return function(expression, locals, later, ident) { - // PRIVATE API: - // param `later` --- indicates that the controller's constructor is invoked at a later time. - // If true, $controller will allocate the object with the correct - // prototype chain, but will not invoke the controller until a returned - // callback is invoked. - // param `ident` --- An optional label which overrides the label parsed from the controller - // expression, if any. - var instance, match, constructor, identifier; - later = later === true; - if (ident && isString(ident)) { - identifier = ident; - } - - if (isString(expression)) { - match = expression.match(CNTRL_REG); - if (!match) { - throw $controllerMinErr('ctrlfmt', - "Badly formed controller string '{0}'. " + - "Must match `__name__ as __id__` or `__name__`.", expression); - } - constructor = match[1], - identifier = identifier || match[3]; - expression = controllers.hasOwnProperty(constructor) - ? controllers[constructor] - : getter(locals.$scope, constructor, true) || - (globals ? getter($window, constructor, true) : undefined); - - assertArgFn(expression, constructor, true); - } - - if (later) { - // Instantiate controller later: - // This machinery is used to create an instance of the object before calling the - // controller's constructor itself. - // - // This allows properties to be added to the controller before the constructor is - // invoked. Primarily, this is used for isolate scope bindings in $compile. - // - // This feature is not intended for use by applications, and is thus not documented - // publicly. - // Object creation: http://jsperf.com/create-constructor/2 - var controllerPrototype = (isArray(expression) ? - expression[expression.length - 1] : expression).prototype; - instance = Object.create(controllerPrototype || null); - - if (identifier) { - addIdentifier(locals, identifier, instance, constructor || expression.name); - } - - var instantiate; - return instantiate = extend(function() { - var result = $injector.invoke(expression, instance, locals, constructor); - if (result !== instance && (isObject(result) || isFunction(result))) { - instance = result; - if (identifier) { - // If result changed, re-assign controllerAs value to scope. - addIdentifier(locals, identifier, instance, constructor || expression.name); - } - } - return instance; - }, { - instance: instance, - identifier: identifier - }); - } - - instance = $injector.instantiate(expression, locals, constructor); - - if (identifier) { - addIdentifier(locals, identifier, instance, constructor || expression.name); - } - - return instance; - }; - - function addIdentifier(locals, identifier, instance, name) { - if (!(locals && isObject(locals.$scope))) { - throw minErr('$controller')('noscp', - "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", - name, identifier); - } - - locals.$scope[identifier] = instance; - } - }]; -} - -/** - * @ngdoc service - * @name $document - * @requires $window - * - * @description - * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. - * - * @example - <example module="documentExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <p>$document title: <b ng-bind="title"></b></p> - <p>window.document title: <b ng-bind="windowTitle"></b></p> - </div> - </file> - <file name="script.js"> - angular.module('documentExample', []) - .controller('ExampleController', ['$scope', '$document', function($scope, $document) { - $scope.title = $document[0].title; - $scope.windowTitle = angular.element(window.document)[0].title; - }]); - </file> - </example> - */ -function $DocumentProvider() { - this.$get = ['$window', function(window) { - return jqLite(window.document); - }]; -} - -/** - * @ngdoc service - * @name $exceptionHandler - * @requires ng.$log - * - * @description - * Any uncaught exception in angular expressions is delegated to this service. - * The default implementation simply delegates to `$log.error` which logs it into - * the browser console. - * - * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by - * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. - * - * ## Example: - * - * ```js - * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { - * return function(exception, cause) { - * exception.message += ' (caused by "' + cause + '")'; - * throw exception; - * }; - * }); - * ``` - * - * This example will override the normal action of `$exceptionHandler`, to make angular - * exceptions fail hard when they happen, instead of just logging to the console. - * - * <hr /> - * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` - * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} - * (unless executed during a digest). - * - * If you wish, you can manually delegate exceptions, e.g. - * `try { ... } catch(e) { $exceptionHandler(e); }` - * - * @param {Error} exception Exception associated with the error. - * @param {string=} cause optional information about the context in which - * the error was thrown. - * - */ -function $ExceptionHandlerProvider() { - this.$get = ['$log', function($log) { - return function(exception, cause) { - $log.error.apply($log, arguments); - }; - }]; -} - -var $$ForceReflowProvider = function() { - this.$get = ['$document', function($document) { - return function(domNode) { - //the line below will force the browser to perform a repaint so - //that all the animated elements within the animation frame will - //be properly updated and drawn on screen. This is required to - //ensure that the preparation animation is properly flushed so that - //the active state picks up from there. DO NOT REMOVE THIS LINE. - //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH - //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND - //WILL TAKE YEARS AWAY FROM YOUR LIFE. - if (domNode) { - if (!domNode.nodeType && domNode instanceof jqLite) { - domNode = domNode[0]; - } - } else { - domNode = $document[0].body; - } - return domNode.offsetWidth + 1; - }; - }]; -}; - -var APPLICATION_JSON = 'application/json'; -var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; -var JSON_START = /^\[|^\{(?!\{)/; -var JSON_ENDS = { - '[': /]$/, - '{': /}$/ -}; -var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; -var $httpMinErr = minErr('$http'); -var $httpMinErrLegacyFn = function(method) { - return function() { - throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method); - }; -}; - -function serializeValue(v) { - if (isObject(v)) { - return isDate(v) ? v.toISOString() : toJson(v); - } - return v; -} - - -function $HttpParamSerializerProvider() { - /** - * @ngdoc service - * @name $httpParamSerializer - * @description - * - * Default {@link $http `$http`} params serializer that converts objects to strings - * according to the following rules: - * - * * `{'foo': 'bar'}` results in `foo=bar` - * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) - * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) - * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object) - * - * Note that serializer will sort the request parameters alphabetically. - * */ - - this.$get = function() { - return function ngParamSerializer(params) { - if (!params) return ''; - var parts = []; - forEachSorted(params, function(value, key) { - if (value === null || isUndefined(value)) return; - if (isArray(value)) { - forEach(value, function(v, k) { - parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); - }); - } else { - parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); - } - }); - - return parts.join('&'); - }; - }; -} - -function $HttpParamSerializerJQLikeProvider() { - /** - * @ngdoc service - * @name $httpParamSerializerJQLike - * @description - * - * Alternative {@link $http `$http`} params serializer that follows - * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. - * The serializer will also sort the params alphabetically. - * - * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: - * - * ```js - * $http({ - * url: myUrl, - * method: 'GET', - * params: myParams, - * paramSerializer: '$httpParamSerializerJQLike' - * }); - * ``` - * - * It is also possible to set it as the default `paramSerializer` in the - * {@link $httpProvider#defaults `$httpProvider`}. - * - * Additionally, you can inject the serializer and use it explicitly, for example to serialize - * form data for submission: - * - * ```js - * .controller(function($http, $httpParamSerializerJQLike) { - * //... - * - * $http({ - * url: myUrl, - * method: 'POST', - * data: $httpParamSerializerJQLike(myData), - * headers: { - * 'Content-Type': 'application/x-www-form-urlencoded' - * } - * }); - * - * }); - * ``` - * - * */ - this.$get = function() { - return function jQueryLikeParamSerializer(params) { - if (!params) return ''; - var parts = []; - serialize(params, '', true); - return parts.join('&'); - - function serialize(toSerialize, prefix, topLevel) { - if (toSerialize === null || isUndefined(toSerialize)) return; - if (isArray(toSerialize)) { - forEach(toSerialize, function(value, index) { - serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); - }); - } else if (isObject(toSerialize) && !isDate(toSerialize)) { - forEachSorted(toSerialize, function(value, key) { - serialize(value, prefix + - (topLevel ? '' : '[') + - key + - (topLevel ? '' : ']')); - }); - } else { - parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); - } - } - }; - }; -} - -function defaultHttpResponseTransform(data, headers) { - if (isString(data)) { - // Strip json vulnerability protection prefix and trim whitespace - var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); - - if (tempData) { - var contentType = headers('Content-Type'); - if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { - data = fromJson(tempData); - } - } - } - - return data; -} - -function isJsonLike(str) { - var jsonStart = str.match(JSON_START); - return jsonStart && JSON_ENDS[jsonStart[0]].test(str); -} - -/** - * Parse headers into key value object - * - * @param {string} headers Raw headers as a string - * @returns {Object} Parsed headers as key value object - */ -function parseHeaders(headers) { - var parsed = createMap(), i; - - function fillInParsed(key, val) { - if (key) { - parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; - } - } - - if (isString(headers)) { - forEach(headers.split('\n'), function(line) { - i = line.indexOf(':'); - fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); - }); - } else if (isObject(headers)) { - forEach(headers, function(headerVal, headerKey) { - fillInParsed(lowercase(headerKey), trim(headerVal)); - }); - } - - return parsed; -} - - -/** - * Returns a function that provides access to parsed headers. - * - * Headers are lazy parsed when first requested. - * @see parseHeaders - * - * @param {(string|Object)} headers Headers to provide access to. - * @returns {function(string=)} Returns a getter function which if called with: - * - * - if called with single an argument returns a single header value or null - * - if called with no arguments returns an object containing all headers. - */ -function headersGetter(headers) { - var headersObj; - - return function(name) { - if (!headersObj) headersObj = parseHeaders(headers); - - if (name) { - var value = headersObj[lowercase(name)]; - if (value === void 0) { - value = null; - } - return value; - } - - return headersObj; - }; -} - - -/** - * Chain all given functions - * - * This function is used for both request and response transforming - * - * @param {*} data Data to transform. - * @param {function(string=)} headers HTTP headers getter fn. - * @param {number} status HTTP status code of the response. - * @param {(Function|Array.<Function>)} fns Function or an array of functions. - * @returns {*} Transformed data. - */ -function transformData(data, headers, status, fns) { - if (isFunction(fns)) { - return fns(data, headers, status); - } - - forEach(fns, function(fn) { - data = fn(data, headers, status); - }); - - return data; -} - - -function isSuccess(status) { - return 200 <= status && status < 300; -} - - -/** - * @ngdoc provider - * @name $httpProvider - * @description - * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. - * */ -function $HttpProvider() { - /** - * @ngdoc property - * @name $httpProvider#defaults - * @description - * - * Object containing default values for all {@link ng.$http $http} requests. - * - * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} - * that will provide the cache for all requests who set their `cache` property to `true`. - * If you set the `defaults.cache = false` then only requests that specify their own custom - * cache object will be cached. See {@link $http#caching $http Caching} for more information. - * - * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. - * Defaults value is `'XSRF-TOKEN'`. - * - * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the - * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. - * - * - **`defaults.headers`** - {Object} - Default headers for all $http requests. - * Refer to {@link ng.$http#setting-http-headers $http} for documentation on - * setting default headers. - * - **`defaults.headers.common`** - * - **`defaults.headers.post`** - * - **`defaults.headers.put`** - * - **`defaults.headers.patch`** - * - * - * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function - * used to the prepare string representation of request parameters (specified as an object). - * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. - * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. - * - **/ - var defaults = this.defaults = { - // transform incoming response data - transformResponse: [defaultHttpResponseTransform], - - // transform outgoing request data - transformRequest: [function(d) { - return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; - }], - - // default headers - headers: { - common: { - 'Accept': 'application/json, text/plain, */*' - }, - post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), - put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), - patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) - }, - - xsrfCookieName: 'XSRF-TOKEN', - xsrfHeaderName: 'X-XSRF-TOKEN', - - paramSerializer: '$httpParamSerializer' - }; - - var useApplyAsync = false; - /** - * @ngdoc method - * @name $httpProvider#useApplyAsync - * @description - * - * Configure $http service to combine processing of multiple http responses received at around - * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in - * significant performance improvement for bigger applications that make many HTTP requests - * concurrently (common during application bootstrap). - * - * Defaults to false. If no value is specified, returns the current configured value. - * - * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred - * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window - * to load and share the same digest cycle. - * - * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. - * otherwise, returns the current configured value. - **/ - this.useApplyAsync = function(value) { - if (isDefined(value)) { - useApplyAsync = !!value; - return this; - } - return useApplyAsync; - }; - - var useLegacyPromise = true; - /** - * @ngdoc method - * @name $httpProvider#useLegacyPromiseExtensions - * @description - * - * Configure `$http` service to return promises without the shorthand methods `success` and `error`. - * This should be used to make sure that applications work without these methods. - * - * Defaults to false. If no value is specified, returns the current configured value. - * - * @param {boolean=} value If true, `$http` will return a normal promise without the `success` and `error` methods. - * - * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. - * otherwise, returns the current configured value. - **/ - this.useLegacyPromiseExtensions = function(value) { - if (isDefined(value)) { - useLegacyPromise = !!value; - return this; - } - return useLegacyPromise; - }; - - /** - * @ngdoc property - * @name $httpProvider#interceptors - * @description - * - * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} - * pre-processing of request or postprocessing of responses. - * - * These service factories are ordered by request, i.e. they are applied in the same order as the - * array, on request, but reverse order, on response. - * - * {@link ng.$http#interceptors Interceptors detailed info} - **/ - var interceptorFactories = this.interceptors = []; - - this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', - function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { - - var defaultCache = $cacheFactory('$http'); - - /** - * Make sure that default param serializer is exposed as a function - */ - defaults.paramSerializer = isString(defaults.paramSerializer) ? - $injector.get(defaults.paramSerializer) : defaults.paramSerializer; - - /** - * Interceptors stored in reverse order. Inner interceptors before outer interceptors. - * The reversal is needed so that we can build up the interception chain around the - * server request. - */ - var reversedInterceptors = []; - - forEach(interceptorFactories, function(interceptorFactory) { - reversedInterceptors.unshift(isString(interceptorFactory) - ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); - }); - - /** - * @ngdoc service - * @kind function - * @name $http - * @requires ng.$httpBackend - * @requires $cacheFactory - * @requires $rootScope - * @requires $q - * @requires $injector - * - * @description - * The `$http` service is a core Angular service that facilitates communication with the remote - * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) - * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). - * - * For unit testing applications that use `$http` service, see - * {@link ngMock.$httpBackend $httpBackend mock}. - * - * For a higher level of abstraction, please check out the {@link ngResource.$resource - * $resource} service. - * - * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by - * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage - * it is important to familiarize yourself with these APIs and the guarantees they provide. - * - * - * ## General usage - * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} — - * that is used to generate an HTTP request and returns a {@link ng.$q promise}. - * - * ```js - * // Simple GET request example: - * $http({ - * method: 'GET', - * url: '/someUrl' - * }).then(function successCallback(response) { - * // this callback will be called asynchronously - * // when the response is available - * }, function errorCallback(response) { - * // called asynchronously if an error occurs - * // or server returns response with an error status. - * }); - * ``` - * - * The response object has these properties: - * - * - **data** – `{string|Object}` – The response body transformed with the transform - * functions. - * - **status** – `{number}` – HTTP status code of the response. - * - **headers** – `{function([headerName])}` – Header getter function. - * - **config** – `{Object}` – The configuration object that was used to generate the request. - * - **statusText** – `{string}` – HTTP status text of the response. - * - * A response status code between 200 and 299 is considered a success status and - * will result in the success callback being called. Note that if the response is a redirect, - * XMLHttpRequest will transparently follow it, meaning that the error callback will not be - * called for such responses. - * - * - * ## Shortcut methods - * - * Shortcut methods are also available. All shortcut methods require passing in the URL, and - * request data must be passed in for POST/PUT requests. An optional config can be passed as the - * last argument. - * - * ```js - * $http.get('/someUrl', config).then(successCallback, errorCallback); - * $http.post('/someUrl', data, config).then(successCallback, errorCallback); - * ``` - * - * Complete list of shortcut methods: - * - * - {@link ng.$http#get $http.get} - * - {@link ng.$http#head $http.head} - * - {@link ng.$http#post $http.post} - * - {@link ng.$http#put $http.put} - * - {@link ng.$http#delete $http.delete} - * - {@link ng.$http#jsonp $http.jsonp} - * - {@link ng.$http#patch $http.patch} - * - * - * ## Writing Unit Tests that use $http - * When unit testing (using {@link ngMock ngMock}), it is necessary to call - * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending - * request using trained responses. - * - * ``` - * $httpBackend.expectGET(...); - * $http.get(...); - * $httpBackend.flush(); - * ``` - * - * ## Deprecation Notice - * <div class="alert alert-danger"> - * The `$http` legacy promise methods `success` and `error` have been deprecated. - * Use the standard `then` method instead. - * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to - * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error. - * </div> - * - * ## Setting HTTP Headers - * - * The $http service will automatically add certain HTTP headers to all requests. These defaults - * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration - * object, which currently contains this default configuration: - * - * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): - * - `Accept: application/json, text/plain, * / *` - * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) - * - `Content-Type: application/json` - * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) - * - `Content-Type: application/json` - * - * To add or overwrite these defaults, simply add or remove a property from these configuration - * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object - * with the lowercased HTTP method name as the key, e.g. - * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. - * - * The defaults can also be set at runtime via the `$http.defaults` object in the same - * fashion. For example: - * - * ``` - * module.run(function($http) { - * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' - * }); - * ``` - * - * In addition, you can supply a `headers` property in the config object passed when - * calling `$http(config)`, which overrides the defaults without changing them globally. - * - * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, - * Use the `headers` property, setting the desired header to `undefined`. For example: - * - * ```js - * var req = { - * method: 'POST', - * url: 'http://example.com', - * headers: { - * 'Content-Type': undefined - * }, - * data: { test: 'test' } - * } - * - * $http(req).then(function(){...}, function(){...}); - * ``` - * - * ## Transforming Requests and Responses - * - * Both requests and responses can be transformed using transformation functions: `transformRequest` - * and `transformResponse`. These properties can be a single function that returns - * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, - * which allows you to `push` or `unshift` a new transformation function into the transformation chain. - * - * ### Default Transformations - * - * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and - * `defaults.transformResponse` properties. If a request does not provide its own transformations - * then these will be applied. - * - * You can augment or replace the default transformations by modifying these properties by adding to or - * replacing the array. - * - * Angular provides the following default transformations: - * - * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): - * - * - If the `data` property of the request configuration object contains an object, serialize it - * into JSON format. - * - * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): - * - * - If XSRF prefix is detected, strip it (see Security Considerations section below). - * - If JSON response is detected, deserialize it using a JSON parser. - * - * - * ### Overriding the Default Transformations Per Request - * - * If you wish override the request/response transformations only for a single request then provide - * `transformRequest` and/or `transformResponse` properties on the configuration object passed - * into `$http`. - * - * Note that if you provide these properties on the config object the default transformations will be - * overwritten. If you wish to augment the default transformations then you must include them in your - * local transformation array. - * - * The following code demonstrates adding a new response transformation to be run after the default response - * transformations have been run. - * - * ```js - * function appendTransform(defaults, transform) { - * - * // We can't guarantee that the default transformation is an array - * defaults = angular.isArray(defaults) ? defaults : [defaults]; - * - * // Append the new transformation to the defaults - * return defaults.concat(transform); - * } - * - * $http({ - * url: '...', - * method: 'GET', - * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { - * return doTransform(value); - * }) - * }); - * ``` - * - * - * ## Caching - * - * To enable caching, set the request configuration `cache` property to `true` (to use default - * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). - * When the cache is enabled, `$http` stores the response from the server in the specified - * cache. The next time the same request is made, the response is served from the cache without - * sending a request to the server. - * - * Note that even if the response is served from cache, delivery of the data is asynchronous in - * the same way that real requests are. - * - * If there are multiple GET requests for the same URL that should be cached using the same - * cache, but the cache is not populated yet, only one request to the server will be made and - * the remaining requests will be fulfilled using the response from the first request. - * - * You can change the default cache to a new object (built with - * {@link ng.$cacheFactory `$cacheFactory`}) by updating the - * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set - * their `cache` property to `true` will now use this cache object. - * - * If you set the default cache to `false` then only requests that specify their own custom - * cache object will be cached. - * - * ## Interceptors - * - * Before you start creating interceptors, be sure to understand the - * {@link ng.$q $q and deferred/promise APIs}. - * - * For purposes of global error handling, authentication, or any kind of synchronous or - * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be - * able to intercept requests before they are handed to the server and - * responses before they are handed over to the application code that - * initiated these requests. The interceptors leverage the {@link ng.$q - * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. - * - * The interceptors are service factories that are registered with the `$httpProvider` by - * adding them to the `$httpProvider.interceptors` array. The factory is called and - * injected with dependencies (if specified) and returns the interceptor. - * - * There are two kinds of interceptors (and two kinds of rejection interceptors): - * - * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to - * modify the `config` object or create a new one. The function needs to return the `config` - * object directly, or a promise containing the `config` or a new `config` object. - * * `requestError`: interceptor gets called when a previous interceptor threw an error or - * resolved with a rejection. - * * `response`: interceptors get called with http `response` object. The function is free to - * modify the `response` object or create a new one. The function needs to return the `response` - * object directly, or as a promise containing the `response` or a new `response` object. - * * `responseError`: interceptor gets called when a previous interceptor threw an error or - * resolved with a rejection. - * - * - * ```js - * // register the interceptor as a service - * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { - * return { - * // optional method - * 'request': function(config) { - * // do something on success - * return config; - * }, - * - * // optional method - * 'requestError': function(rejection) { - * // do something on error - * if (canRecover(rejection)) { - * return responseOrNewPromise - * } - * return $q.reject(rejection); - * }, - * - * - * - * // optional method - * 'response': function(response) { - * // do something on success - * return response; - * }, - * - * // optional method - * 'responseError': function(rejection) { - * // do something on error - * if (canRecover(rejection)) { - * return responseOrNewPromise - * } - * return $q.reject(rejection); - * } - * }; - * }); - * - * $httpProvider.interceptors.push('myHttpInterceptor'); - * - * - * // alternatively, register the interceptor via an anonymous factory - * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { - * return { - * 'request': function(config) { - * // same as above - * }, - * - * 'response': function(response) { - * // same as above - * } - * }; - * }); - * ``` - * - * ## Security Considerations - * - * When designing web applications, consider security threats from: - * - * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) - * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) - * - * Both server and the client must cooperate in order to eliminate these threats. Angular comes - * pre-configured with strategies that address these issues, but for this to work backend server - * cooperation is required. - * - * ### JSON Vulnerability Protection - * - * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) - * allows third party website to turn your JSON resource URL into - * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To - * counter this your server can prefix all JSON requests with following string `")]}',\n"`. - * Angular will automatically strip the prefix before processing it as JSON. - * - * For example if your server needs to return: - * ```js - * ['one','two'] - * ``` - * - * which is vulnerable to attack, your server can return: - * ```js - * )]}', - * ['one','two'] - * ``` - * - * Angular will strip the prefix, before processing the JSON. - * - * - * ### Cross Site Request Forgery (XSRF) Protection - * - * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which - * an unauthorized site can gain your user's private data. Angular provides a mechanism - * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie - * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only - * JavaScript that runs on your domain could read the cookie, your server can be assured that - * the XHR came from JavaScript running on your domain. The header will not be set for - * cross-domain requests. - * - * To take advantage of this, your server needs to set a token in a JavaScript readable session - * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the - * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure - * that only JavaScript running on your domain could have sent the request. The token must be - * unique for each user and must be verifiable by the server (to prevent the JavaScript from - * making up its own tokens). We recommend that the token is a digest of your site's - * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) - * for added security. - * - * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName - * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, - * or the per-request config object. - * - * In order to prevent collisions in environments where multiple Angular apps share the - * same domain or subdomain, we recommend that each application uses unique cookie name. - * - * @param {object} config Object describing the request to be made and how it should be - * processed. The object has following properties: - * - * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) - * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. - * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized - * with the `paramSerializer` and appended as GET parameters. - * - **data** – `{string|Object}` – Data to be sent as the request message data. - * - **headers** – `{Object}` – Map of strings or functions which return strings representing - * HTTP headers to send to the server. If the return value of a function is null, the - * header will not be sent. Functions accept a config object as an argument. - * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. - * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. - * - **transformRequest** – - * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – - * transform function or an array of such functions. The transform function takes the http - * request body and headers and returns its transformed (typically serialized) version. - * See {@link ng.$http#overriding-the-default-transformations-per-request - * Overriding the Default Transformations} - * - **transformResponse** – - * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` – - * transform function or an array of such functions. The transform function takes the http - * response body, headers and status and returns its transformed (typically deserialized) version. - * See {@link ng.$http#overriding-the-default-transformations-per-request - * Overriding the Default TransformationjqLiks} - * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to - * prepare the string representation of request parameters (specified as an object). - * If specified as string, it is interpreted as function registered with the - * {@link $injector $injector}, which means you can create your own serializer - * by registering it as a {@link auto.$provide#service service}. - * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; - * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} - * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the - * GET request, otherwise if a cache instance built with - * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for - * caching. - * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} - * that should abort the request when resolved. - * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the - * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) - * for more information. - * - **responseType** - `{string}` - see - * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). - * - * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object - * when the request succeeds or fails. - * - * - * @property {Array.<Object>} pendingRequests Array of config objects for currently pending - * requests. This is primarily meant to be used for debugging purposes. - * - * - * @example -<example module="httpExample"> -<file name="index.html"> - <div ng-controller="FetchController"> - <select ng-model="method" aria-label="Request method"> - <option>GET</option> - <option>JSONP</option> - </select> - <input type="text" ng-model="url" size="80" aria-label="URL" /> - <button id="fetchbtn" ng-click="fetch()">fetch</button><br> - <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> - <button id="samplejsonpbtn" - ng-click="updateModel('JSONP', - 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> - Sample JSONP - </button> - <button id="invalidjsonpbtn" - ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> - Invalid JSONP - </button> - <pre>http status code: {{status}}</pre> - <pre>http response data: {{data}}</pre> - </div> -</file> -<file name="script.js"> - angular.module('httpExample', []) - .controller('FetchController', ['$scope', '$http', '$templateCache', - function($scope, $http, $templateCache) { - $scope.method = 'GET'; - $scope.url = 'http-hello.html'; - - $scope.fetch = function() { - $scope.code = null; - $scope.response = null; - - $http({method: $scope.method, url: $scope.url, cache: $templateCache}). - then(function(response) { - $scope.status = response.status; - $scope.data = response.data; - }, function(response) { - $scope.data = response.data || "Request failed"; - $scope.status = response.status; - }); - }; - - $scope.updateModel = function(method, url) { - $scope.method = method; - $scope.url = url; - }; - }]); -</file> -<file name="http-hello.html"> - Hello, $http! -</file> -<file name="protractor.js" type="protractor"> - var status = element(by.binding('status')); - var data = element(by.binding('data')); - var fetchBtn = element(by.id('fetchbtn')); - var sampleGetBtn = element(by.id('samplegetbtn')); - var sampleJsonpBtn = element(by.id('samplejsonpbtn')); - var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); - - it('should make an xhr GET request', function() { - sampleGetBtn.click(); - fetchBtn.click(); - expect(status.getText()).toMatch('200'); - expect(data.getText()).toMatch(/Hello, \$http!/); - }); - -// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 -// it('should make a JSONP request to angularjs.org', function() { -// sampleJsonpBtn.click(); -// fetchBtn.click(); -// expect(status.getText()).toMatch('200'); -// expect(data.getText()).toMatch(/Super Hero!/); -// }); - - it('should make JSONP request to invalid URL and invoke the error handler', - function() { - invalidJsonpBtn.click(); - fetchBtn.click(); - expect(status.getText()).toMatch('0'); - expect(data.getText()).toMatch('Request failed'); - }); -</file> -</example> - */ - function $http(requestConfig) { - - if (!angular.isObject(requestConfig)) { - throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); - } - - var config = extend({ - method: 'get', - transformRequest: defaults.transformRequest, - transformResponse: defaults.transformResponse, - paramSerializer: defaults.paramSerializer - }, requestConfig); - - config.headers = mergeHeaders(requestConfig); - config.method = uppercase(config.method); - config.paramSerializer = isString(config.paramSerializer) ? - $injector.get(config.paramSerializer) : config.paramSerializer; - - var serverRequest = function(config) { - var headers = config.headers; - var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); - - // strip content-type if data is undefined - if (isUndefined(reqData)) { - forEach(headers, function(value, header) { - if (lowercase(header) === 'content-type') { - delete headers[header]; - } - }); - } - - if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { - config.withCredentials = defaults.withCredentials; - } - - // send request - return sendReq(config, reqData).then(transformResponse, transformResponse); - }; - - var chain = [serverRequest, undefined]; - var promise = $q.when(config); - - // apply interceptors - forEach(reversedInterceptors, function(interceptor) { - if (interceptor.request || interceptor.requestError) { - chain.unshift(interceptor.request, interceptor.requestError); - } - if (interceptor.response || interceptor.responseError) { - chain.push(interceptor.response, interceptor.responseError); - } - }); - - while (chain.length) { - var thenFn = chain.shift(); - var rejectFn = chain.shift(); - - promise = promise.then(thenFn, rejectFn); - } - - if (useLegacyPromise) { - promise.success = function(fn) { - assertArgFn(fn, 'fn'); - - promise.then(function(response) { - fn(response.data, response.status, response.headers, config); - }); - return promise; - }; - - promise.error = function(fn) { - assertArgFn(fn, 'fn'); - - promise.then(null, function(response) { - fn(response.data, response.status, response.headers, config); - }); - return promise; - }; - } else { - promise.success = $httpMinErrLegacyFn('success'); - promise.error = $httpMinErrLegacyFn('error'); - } - - return promise; - - function transformResponse(response) { - // make a copy since the response must be cacheable - var resp = extend({}, response); - if (!response.data) { - resp.data = response.data; - } else { - resp.data = transformData(response.data, response.headers, response.status, config.transformResponse); - } - return (isSuccess(response.status)) - ? resp - : $q.reject(resp); - } - - function executeHeaderFns(headers, config) { - var headerContent, processedHeaders = {}; - - forEach(headers, function(headerFn, header) { - if (isFunction(headerFn)) { - headerContent = headerFn(config); - if (headerContent != null) { - processedHeaders[header] = headerContent; - } - } else { - processedHeaders[header] = headerFn; - } - }); - - return processedHeaders; - } - - function mergeHeaders(config) { - var defHeaders = defaults.headers, - reqHeaders = extend({}, config.headers), - defHeaderName, lowercaseDefHeaderName, reqHeaderName; - - defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); - - // using for-in instead of forEach to avoid unecessary iteration after header has been found - defaultHeadersIteration: - for (defHeaderName in defHeaders) { - lowercaseDefHeaderName = lowercase(defHeaderName); - - for (reqHeaderName in reqHeaders) { - if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { - continue defaultHeadersIteration; - } - } - - reqHeaders[defHeaderName] = defHeaders[defHeaderName]; - } - - // execute if header value is a function for merged headers - return executeHeaderFns(reqHeaders, shallowCopy(config)); - } - } - - $http.pendingRequests = []; - - /** - * @ngdoc method - * @name $http#get - * - * @description - * Shortcut method to perform `GET` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#delete - * - * @description - * Shortcut method to perform `DELETE` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#head - * - * @description - * Shortcut method to perform `HEAD` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#jsonp - * - * @description - * Shortcut method to perform `JSONP` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request. - * The name of the callback should be the string `JSON_CALLBACK`. - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - createShortMethods('get', 'delete', 'head', 'jsonp'); - - /** - * @ngdoc method - * @name $http#post - * - * @description - * Shortcut method to perform `POST` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#put - * - * @description - * Shortcut method to perform `PUT` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#patch - * - * @description - * Shortcut method to perform `PATCH` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - createShortMethodsWithData('post', 'put', 'patch'); - - /** - * @ngdoc property - * @name $http#defaults - * - * @description - * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of - * default headers, withCredentials as well as request and response transformations. - * - * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. - */ - $http.defaults = defaults; - - - return $http; - - - function createShortMethods(names) { - forEach(arguments, function(name) { - $http[name] = function(url, config) { - return $http(extend({}, config || {}, { - method: name, - url: url - })); - }; - }); - } - - - function createShortMethodsWithData(name) { - forEach(arguments, function(name) { - $http[name] = function(url, data, config) { - return $http(extend({}, config || {}, { - method: name, - url: url, - data: data - })); - }; - }); - } - - - /** - * Makes the request. - * - * !!! ACCESSES CLOSURE VARS: - * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests - */ - function sendReq(config, reqData) { - var deferred = $q.defer(), - promise = deferred.promise, - cache, - cachedResp, - reqHeaders = config.headers, - url = buildUrl(config.url, config.paramSerializer(config.params)); - - $http.pendingRequests.push(config); - promise.then(removePendingReq, removePendingReq); - - - if ((config.cache || defaults.cache) && config.cache !== false && - (config.method === 'GET' || config.method === 'JSONP')) { - cache = isObject(config.cache) ? config.cache - : isObject(defaults.cache) ? defaults.cache - : defaultCache; - } - - if (cache) { - cachedResp = cache.get(url); - if (isDefined(cachedResp)) { - if (isPromiseLike(cachedResp)) { - // cached request has already been sent, but there is no response yet - cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); - } else { - // serving from cache - if (isArray(cachedResp)) { - resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); - } else { - resolvePromise(cachedResp, 200, {}, 'OK'); - } - } - } else { - // put the promise for the non-transformed response into cache as a placeholder - cache.put(url, promise); - } - } - - - // if we won't have the response in cache, set the xsrf headers and - // send the request to the backend - if (isUndefined(cachedResp)) { - var xsrfValue = urlIsSameOrigin(config.url) - ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] - : undefined; - if (xsrfValue) { - reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; - } - - $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, - config.withCredentials, config.responseType); - } - - return promise; - - - /** - * Callback registered to $httpBackend(): - * - caches the response if desired - * - resolves the raw $http promise - * - calls $apply - */ - function done(status, response, headersString, statusText) { - if (cache) { - if (isSuccess(status)) { - cache.put(url, [status, response, parseHeaders(headersString), statusText]); - } else { - // remove promise from the cache - cache.remove(url); - } - } - - function resolveHttpPromise() { - resolvePromise(response, status, headersString, statusText); - } - - if (useApplyAsync) { - $rootScope.$applyAsync(resolveHttpPromise); - } else { - resolveHttpPromise(); - if (!$rootScope.$$phase) $rootScope.$apply(); - } - } - - - /** - * Resolves the raw $http promise. - */ - function resolvePromise(response, status, headers, statusText) { - //status: HTTP response status code, 0, -1 (aborted by timeout / promise) - status = status >= -1 ? status : 0; - - (isSuccess(status) ? deferred.resolve : deferred.reject)({ - data: response, - status: status, - headers: headersGetter(headers), - config: config, - statusText: statusText - }); - } - - function resolvePromiseWithResult(result) { - resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); - } - - function removePendingReq() { - var idx = $http.pendingRequests.indexOf(config); - if (idx !== -1) $http.pendingRequests.splice(idx, 1); - } - } - - - function buildUrl(url, serializedParams) { - if (serializedParams.length > 0) { - url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams; - } - return url; - } - }]; -} - -/** - * @ngdoc service - * @name $xhrFactory - * - * @description - * Factory function used to create XMLHttpRequest objects. - * - * Replace or decorate this service to create your own custom XMLHttpRequest objects. - * - * ``` - * angular.module('myApp', []) - * .factory('$xhrFactory', function() { - * return function createXhr(method, url) { - * return new window.XMLHttpRequest({mozSystem: true}); - * }; - * }); - * ``` - * - * @param {string} method HTTP method of the request (GET, POST, PUT, ..) - * @param {string} url URL of the request. - */ -function $xhrFactoryProvider() { - this.$get = function() { - return function createXhr() { - return new window.XMLHttpRequest(); - }; - }; -} - -/** - * @ngdoc service - * @name $httpBackend - * @requires $window - * @requires $document - * @requires $xhrFactory - * - * @description - * HTTP backend used by the {@link ng.$http service} that delegates to - * XMLHttpRequest object or JSONP and deals with browser incompatibilities. - * - * You should never need to use this service directly, instead use the higher-level abstractions: - * {@link ng.$http $http} or {@link ngResource.$resource $resource}. - * - * During testing this implementation is swapped with {@link ngMock.$httpBackend mock - * $httpBackend} which can be trained with responses. - */ -function $HttpBackendProvider() { - this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) { - return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]); - }]; -} - -function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { - // TODO(vojta): fix the signature - return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { - $browser.$$incOutstandingRequestCount(); - url = url || $browser.url(); - - if (lowercase(method) == 'jsonp') { - var callbackId = '_' + (callbacks.counter++).toString(36); - callbacks[callbackId] = function(data) { - callbacks[callbackId].data = data; - callbacks[callbackId].called = true; - }; - - var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), - callbackId, function(status, text) { - completeRequest(callback, status, callbacks[callbackId].data, "", text); - callbacks[callbackId] = noop; - }); - } else { - - var xhr = createXhr(method, url); - - xhr.open(method, url, true); - forEach(headers, function(value, key) { - if (isDefined(value)) { - xhr.setRequestHeader(key, value); - } - }); - - xhr.onload = function requestLoaded() { - var statusText = xhr.statusText || ''; - - // responseText is the old-school way of retrieving response (supported by IE9) - // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) - var response = ('response' in xhr) ? xhr.response : xhr.responseText; - - // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) - var status = xhr.status === 1223 ? 204 : xhr.status; - - // fix status code when it is 0 (0 status is undocumented). - // Occurs when accessing file resources or on Android 4.1 stock browser - // while retrieving files from application cache. - if (status === 0) { - status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; - } - - completeRequest(callback, - status, - response, - xhr.getAllResponseHeaders(), - statusText); - }; - - var requestError = function() { - // The response is always empty - // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error - completeRequest(callback, -1, null, null, ''); - }; - - xhr.onerror = requestError; - xhr.onabort = requestError; - - if (withCredentials) { - xhr.withCredentials = true; - } - - if (responseType) { - try { - xhr.responseType = responseType; - } catch (e) { - // WebKit added support for the json responseType value on 09/03/2013 - // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are - // known to throw when setting the value "json" as the response type. Other older - // browsers implementing the responseType - // - // The json response type can be ignored if not supported, because JSON payloads are - // parsed on the client-side regardless. - if (responseType !== 'json') { - throw e; - } - } - } - - xhr.send(isUndefined(post) ? null : post); - } - - if (timeout > 0) { - var timeoutId = $browserDefer(timeoutRequest, timeout); - } else if (isPromiseLike(timeout)) { - timeout.then(timeoutRequest); - } - - - function timeoutRequest() { - jsonpDone && jsonpDone(); - xhr && xhr.abort(); - } - - function completeRequest(callback, status, response, headersString, statusText) { - // cancel timeout and subsequent timeout promise resolution - if (isDefined(timeoutId)) { - $browserDefer.cancel(timeoutId); - } - jsonpDone = xhr = null; - - callback(status, response, headersString, statusText); - $browser.$$completeOutstandingRequest(noop); - } - }; - - function jsonpReq(url, callbackId, done) { - // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: - // - fetches local scripts via XHR and evals them - // - adds and immediately removes script elements from the document - var script = rawDocument.createElement('script'), callback = null; - script.type = "text/javascript"; - script.src = url; - script.async = true; - - callback = function(event) { - removeEventListenerFn(script, "load", callback); - removeEventListenerFn(script, "error", callback); - rawDocument.body.removeChild(script); - script = null; - var status = -1; - var text = "unknown"; - - if (event) { - if (event.type === "load" && !callbacks[callbackId].called) { - event = { type: "error" }; - } - text = event.type; - status = event.type === "error" ? 404 : 200; - } - - if (done) { - done(status, text); - } - }; - - addEventListenerFn(script, "load", callback); - addEventListenerFn(script, "error", callback); - rawDocument.body.appendChild(script); - return callback; - } -} - -var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); -$interpolateMinErr.throwNoconcat = function(text) { - throw $interpolateMinErr('noconcat', - "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + - "interpolations that concatenate multiple expressions when a trusted value is " + - "required. See http://docs.angularjs.org/api/ng.$sce", text); -}; - -$interpolateMinErr.interr = function(text, err) { - return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); -}; - -/** - * @ngdoc provider - * @name $interpolateProvider - * - * @description - * - * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. - * - * @example -<example module="customInterpolationApp"> -<file name="index.html"> -<script> - var customInterpolationApp = angular.module('customInterpolationApp', []); - - customInterpolationApp.config(function($interpolateProvider) { - $interpolateProvider.startSymbol('//'); - $interpolateProvider.endSymbol('//'); - }); - - - customInterpolationApp.controller('DemoController', function() { - this.label = "This binding is brought you by // interpolation symbols."; - }); -</script> -<div ng-app="App" ng-controller="DemoController as demo"> - //demo.label// -</div> -</file> -<file name="protractor.js" type="protractor"> - it('should interpolate binding with custom symbols', function() { - expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); - }); -</file> -</example> - */ -function $InterpolateProvider() { - var startSymbol = '{{'; - var endSymbol = '}}'; - - /** - * @ngdoc method - * @name $interpolateProvider#startSymbol - * @description - * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. - * - * @param {string=} value new value to set the starting symbol to. - * @returns {string|self} Returns the symbol when used as getter and self if used as setter. - */ - this.startSymbol = function(value) { - if (value) { - startSymbol = value; - return this; - } else { - return startSymbol; - } - }; - - /** - * @ngdoc method - * @name $interpolateProvider#endSymbol - * @description - * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. - * - * @param {string=} value new value to set the ending symbol to. - * @returns {string|self} Returns the symbol when used as getter and self if used as setter. - */ - this.endSymbol = function(value) { - if (value) { - endSymbol = value; - return this; - } else { - return endSymbol; - } - }; - - - this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { - var startSymbolLength = startSymbol.length, - endSymbolLength = endSymbol.length, - escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), - escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); - - function escape(ch) { - return '\\\\\\' + ch; - } - - function unescapeText(text) { - return text.replace(escapedStartRegexp, startSymbol). - replace(escapedEndRegexp, endSymbol); - } - - function stringify(value) { - if (value == null) { // null || undefined - return ''; - } - switch (typeof value) { - case 'string': - break; - case 'number': - value = '' + value; - break; - default: - value = toJson(value); - } - - return value; - } - - /** - * @ngdoc service - * @name $interpolate - * @kind function - * - * @requires $parse - * @requires $sce - * - * @description - * - * Compiles a string with markup into an interpolation function. This service is used by the - * HTML {@link ng.$compile $compile} service for data binding. See - * {@link ng.$interpolateProvider $interpolateProvider} for configuring the - * interpolation markup. - * - * - * ```js - * var $interpolate = ...; // injected - * var exp = $interpolate('Hello {{name | uppercase}}!'); - * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); - * ``` - * - * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is - * `true`, the interpolation function will return `undefined` unless all embedded expressions - * evaluate to a value other than `undefined`. - * - * ```js - * var $interpolate = ...; // injected - * var context = {greeting: 'Hello', name: undefined }; - * - * // default "forgiving" mode - * var exp = $interpolate('{{greeting}} {{name}}!'); - * expect(exp(context)).toEqual('Hello !'); - * - * // "allOrNothing" mode - * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); - * expect(exp(context)).toBeUndefined(); - * context.name = 'Angular'; - * expect(exp(context)).toEqual('Hello Angular!'); - * ``` - * - * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. - * - * ####Escaped Interpolation - * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers - * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). - * It will be rendered as a regular start/end marker, and will not be interpreted as an expression - * or binding. - * - * This enables web-servers to prevent script injection attacks and defacing attacks, to some - * degree, while also enabling code examples to work without relying on the - * {@link ng.directive:ngNonBindable ngNonBindable} directive. - * - * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, - * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all - * interpolation start/end markers with their escaped counterparts.** - * - * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered - * output when the $interpolate service processes the text. So, for HTML elements interpolated - * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter - * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, - * this is typically useful only when user-data is used in rendering a template from the server, or - * when otherwise untrusted data is used by a directive. - * - * <example> - * <file name="index.html"> - * <div ng-init="username='A user'"> - * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} - * </p> - * <p><strong>{{username}}</strong> attempts to inject code which will deface the - * application, but fails to accomplish their task, because the server has correctly - * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) - * characters.</p> - * <p>Instead, the result of the attempted script injection is visible, and can be removed - * from the database by an administrator.</p> - * </div> - * </file> - * </example> - * - * @param {string} text The text with markup to interpolate. - * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have - * embedded expression in order to return an interpolation function. Strings with no - * embedded expression will return null for the interpolation function. - * @param {string=} trustedContext when provided, the returned function passes the interpolated - * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, - * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that - * provides Strict Contextual Escaping for details. - * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined - * unless all embedded expressions evaluate to a value other than `undefined`. - * @returns {function(context)} an interpolation function which is used to compute the - * interpolated string. The function has these parameters: - * - * - `context`: evaluation context for all expressions embedded in the interpolated text - */ - function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { - allOrNothing = !!allOrNothing; - var startIndex, - endIndex, - index = 0, - expressions = [], - parseFns = [], - textLength = text.length, - exp, - concat = [], - expressionPositions = []; - - while (index < textLength) { - if (((startIndex = text.indexOf(startSymbol, index)) != -1) && - ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { - if (index !== startIndex) { - concat.push(unescapeText(text.substring(index, startIndex))); - } - exp = text.substring(startIndex + startSymbolLength, endIndex); - expressions.push(exp); - parseFns.push($parse(exp, parseStringifyInterceptor)); - index = endIndex + endSymbolLength; - expressionPositions.push(concat.length); - concat.push(''); - } else { - // we did not find an interpolation, so we have to add the remainder to the separators array - if (index !== textLength) { - concat.push(unescapeText(text.substring(index))); - } - break; - } - } - - // Concatenating expressions makes it hard to reason about whether some combination of - // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a - // single expression be used for iframe[src], object[src], etc., we ensure that the value - // that's used is assigned or constructed by some JS code somewhere that is more testable or - // make it obvious that you bound the value to some user controlled value. This helps reduce - // the load when auditing for XSS issues. - if (trustedContext && concat.length > 1) { - $interpolateMinErr.throwNoconcat(text); - } - - if (!mustHaveExpression || expressions.length) { - var compute = function(values) { - for (var i = 0, ii = expressions.length; i < ii; i++) { - if (allOrNothing && isUndefined(values[i])) return; - concat[expressionPositions[i]] = values[i]; - } - return concat.join(''); - }; - - var getValue = function(value) { - return trustedContext ? - $sce.getTrusted(trustedContext, value) : - $sce.valueOf(value); - }; - - return extend(function interpolationFn(context) { - var i = 0; - var ii = expressions.length; - var values = new Array(ii); - - try { - for (; i < ii; i++) { - values[i] = parseFns[i](context); - } - - return compute(values); - } catch (err) { - $exceptionHandler($interpolateMinErr.interr(text, err)); - } - - }, { - // all of these properties are undocumented for now - exp: text, //just for compatibility with regular watchers created via $watch - expressions: expressions, - $$watchDelegate: function(scope, listener) { - var lastValue; - return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { - var currValue = compute(values); - if (isFunction(listener)) { - listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); - } - lastValue = currValue; - }); - } - }); - } - - function parseStringifyInterceptor(value) { - try { - value = getValue(value); - return allOrNothing && !isDefined(value) ? value : stringify(value); - } catch (err) { - $exceptionHandler($interpolateMinErr.interr(text, err)); - } - } - } - - - /** - * @ngdoc method - * @name $interpolate#startSymbol - * @description - * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. - * - * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change - * the symbol. - * - * @returns {string} start symbol. - */ - $interpolate.startSymbol = function() { - return startSymbol; - }; - - - /** - * @ngdoc method - * @name $interpolate#endSymbol - * @description - * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. - * - * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change - * the symbol. - * - * @returns {string} end symbol. - */ - $interpolate.endSymbol = function() { - return endSymbol; - }; - - return $interpolate; - }]; -} - -function $IntervalProvider() { - this.$get = ['$rootScope', '$window', '$q', '$$q', - function($rootScope, $window, $q, $$q) { - var intervals = {}; - - - /** - * @ngdoc service - * @name $interval - * - * @description - * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` - * milliseconds. - * - * The return value of registering an interval function is a promise. This promise will be - * notified upon each tick of the interval, and will be resolved after `count` iterations, or - * run indefinitely if `count` is not defined. The value of the notification will be the - * number of iterations that have run. - * To cancel an interval, call `$interval.cancel(promise)`. - * - * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to - * move forward by `millis` milliseconds and trigger any functions scheduled to run in that - * time. - * - * <div class="alert alert-warning"> - * **Note**: Intervals created by this service must be explicitly destroyed when you are finished - * with them. In particular they are not automatically destroyed when a controller's scope or a - * directive's element are destroyed. - * You should take this into consideration and make sure to always cancel the interval at the - * appropriate moment. See the example below for more details on how and when to do this. - * </div> - * - * @param {function()} fn A function that should be called repeatedly. - * @param {number} delay Number of milliseconds between each function call. - * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat - * indefinitely. - * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise - * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. - * @param {...*=} Pass additional parameters to the executed function. - * @returns {promise} A promise which will be notified on each iteration. - * - * @example - * <example module="intervalExample"> - * <file name="index.html"> - * <script> - * angular.module('intervalExample', []) - * .controller('ExampleController', ['$scope', '$interval', - * function($scope, $interval) { - * $scope.format = 'M/d/yy h:mm:ss a'; - * $scope.blood_1 = 100; - * $scope.blood_2 = 120; - * - * var stop; - * $scope.fight = function() { - * // Don't start a new fight if we are already fighting - * if ( angular.isDefined(stop) ) return; - * - * stop = $interval(function() { - * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { - * $scope.blood_1 = $scope.blood_1 - 3; - * $scope.blood_2 = $scope.blood_2 - 4; - * } else { - * $scope.stopFight(); - * } - * }, 100); - * }; - * - * $scope.stopFight = function() { - * if (angular.isDefined(stop)) { - * $interval.cancel(stop); - * stop = undefined; - * } - * }; - * - * $scope.resetFight = function() { - * $scope.blood_1 = 100; - * $scope.blood_2 = 120; - * }; - * - * $scope.$on('$destroy', function() { - * // Make sure that the interval is destroyed too - * $scope.stopFight(); - * }); - * }]) - * // Register the 'myCurrentTime' directive factory method. - * // We inject $interval and dateFilter service since the factory method is DI. - * .directive('myCurrentTime', ['$interval', 'dateFilter', - * function($interval, dateFilter) { - * // return the directive link function. (compile function not needed) - * return function(scope, element, attrs) { - * var format, // date format - * stopTime; // so that we can cancel the time updates - * - * // used to update the UI - * function updateTime() { - * element.text(dateFilter(new Date(), format)); - * } - * - * // watch the expression, and update the UI on change. - * scope.$watch(attrs.myCurrentTime, function(value) { - * format = value; - * updateTime(); - * }); - * - * stopTime = $interval(updateTime, 1000); - * - * // listen on DOM destroy (removal) event, and cancel the next UI update - * // to prevent updating time after the DOM element was removed. - * element.on('$destroy', function() { - * $interval.cancel(stopTime); - * }); - * } - * }]); - * </script> - * - * <div> - * <div ng-controller="ExampleController"> - * <label>Date format: <input ng-model="format"></label> <hr/> - * Current time is: <span my-current-time="format"></span> - * <hr/> - * Blood 1 : <font color='red'>{{blood_1}}</font> - * Blood 2 : <font color='red'>{{blood_2}}</font> - * <button type="button" data-ng-click="fight()">Fight</button> - * <button type="button" data-ng-click="stopFight()">StopFight</button> - * <button type="button" data-ng-click="resetFight()">resetFight</button> - * </div> - * </div> - * - * </file> - * </example> - */ - function interval(fn, delay, count, invokeApply) { - var hasParams = arguments.length > 4, - args = hasParams ? sliceArgs(arguments, 4) : [], - setInterval = $window.setInterval, - clearInterval = $window.clearInterval, - iteration = 0, - skipApply = (isDefined(invokeApply) && !invokeApply), - deferred = (skipApply ? $$q : $q).defer(), - promise = deferred.promise; - - count = isDefined(count) ? count : 0; - - promise.then(null, null, (!hasParams) ? fn : function() { - fn.apply(null, args); - }); - - promise.$$intervalId = setInterval(function tick() { - deferred.notify(iteration++); - - if (count > 0 && iteration >= count) { - deferred.resolve(iteration); - clearInterval(promise.$$intervalId); - delete intervals[promise.$$intervalId]; - } - - if (!skipApply) $rootScope.$apply(); - - }, delay); - - intervals[promise.$$intervalId] = deferred; - - return promise; - } - - - /** - * @ngdoc method - * @name $interval#cancel - * - * @description - * Cancels a task associated with the `promise`. - * - * @param {Promise=} promise returned by the `$interval` function. - * @returns {boolean} Returns `true` if the task was successfully canceled. - */ - interval.cancel = function(promise) { - if (promise && promise.$$intervalId in intervals) { - intervals[promise.$$intervalId].reject('canceled'); - $window.clearInterval(promise.$$intervalId); - delete intervals[promise.$$intervalId]; - return true; - } - return false; - }; - - return interval; - }]; -} - -/** - * @ngdoc service - * @name $locale - * - * @description - * $locale service provides localization rules for various Angular components. As of right now the - * only public api is: - * - * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) - */ - -var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, - DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; -var $locationMinErr = minErr('$location'); - - -/** - * Encode path using encodeUriSegment, ignoring forward slashes - * - * @param {string} path Path to encode - * @returns {string} - */ -function encodePath(path) { - var segments = path.split('/'), - i = segments.length; - - while (i--) { - segments[i] = encodeUriSegment(segments[i]); - } - - return segments.join('/'); -} - -function parseAbsoluteUrl(absoluteUrl, locationObj) { - var parsedUrl = urlResolve(absoluteUrl); - - locationObj.$$protocol = parsedUrl.protocol; - locationObj.$$host = parsedUrl.hostname; - locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; -} - - -function parseAppUrl(relativeUrl, locationObj) { - var prefixed = (relativeUrl.charAt(0) !== '/'); - if (prefixed) { - relativeUrl = '/' + relativeUrl; - } - var match = urlResolve(relativeUrl); - locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? - match.pathname.substring(1) : match.pathname); - locationObj.$$search = parseKeyValue(match.search); - locationObj.$$hash = decodeURIComponent(match.hash); - - // make sure path starts with '/'; - if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { - locationObj.$$path = '/' + locationObj.$$path; - } -} - - -/** - * - * @param {string} begin - * @param {string} whole - * @returns {string} returns text from whole after begin or undefined if it does not begin with - * expected string. - */ -function beginsWith(begin, whole) { - if (whole.indexOf(begin) === 0) { - return whole.substr(begin.length); - } -} - - -function stripHash(url) { - var index = url.indexOf('#'); - return index == -1 ? url : url.substr(0, index); -} - -function trimEmptyHash(url) { - return url.replace(/(#.+)|#$/, '$1'); -} - - -function stripFile(url) { - return url.substr(0, stripHash(url).lastIndexOf('/') + 1); -} - -/* return the server only (scheme://host:port) */ -function serverBase(url) { - return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); -} - - -/** - * LocationHtml5Url represents an url - * This object is exposed as $location service when HTML5 mode is enabled and supported - * - * @constructor - * @param {string} appBase application base URL - * @param {string} appBaseNoFile application base URL stripped of any filename - * @param {string} basePrefix url path prefix - */ -function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { - this.$$html5 = true; - basePrefix = basePrefix || ''; - parseAbsoluteUrl(appBase, this); - - - /** - * Parse given html5 (regular) url string into properties - * @param {string} url HTML5 url - * @private - */ - this.$$parse = function(url) { - var pathUrl = beginsWith(appBaseNoFile, url); - if (!isString(pathUrl)) { - throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, - appBaseNoFile); - } - - parseAppUrl(pathUrl, this); - - if (!this.$$path) { - this.$$path = '/'; - } - - this.$$compose(); - }; - - /** - * Compose url and update `absUrl` property - * @private - */ - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' - }; - - this.$$parseLinkUrl = function(url, relHref) { - if (relHref && relHref[0] === '#') { - // special case for links to hash fragments: - // keep the old url and only replace the hash fragment - this.hash(relHref.slice(1)); - return true; - } - var appUrl, prevAppUrl; - var rewrittenUrl; - - if (isDefined(appUrl = beginsWith(appBase, url))) { - prevAppUrl = appUrl; - if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) { - rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); - } else { - rewrittenUrl = appBase + prevAppUrl; - } - } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) { - rewrittenUrl = appBaseNoFile + appUrl; - } else if (appBaseNoFile == url + '/') { - rewrittenUrl = appBaseNoFile; - } - if (rewrittenUrl) { - this.$$parse(rewrittenUrl); - } - return !!rewrittenUrl; - }; -} - - -/** - * LocationHashbangUrl represents url - * This object is exposed as $location service when developer doesn't opt into html5 mode. - * It also serves as the base class for html5 mode fallback on legacy browsers. - * - * @constructor - * @param {string} appBase application base URL - * @param {string} appBaseNoFile application base URL stripped of any filename - * @param {string} hashPrefix hashbang prefix - */ -function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { - - parseAbsoluteUrl(appBase, this); - - - /** - * Parse given hashbang url into properties - * @param {string} url Hashbang url - * @private - */ - this.$$parse = function(url) { - var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); - var withoutHashUrl; - - if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { - - // The rest of the url starts with a hash so we have - // got either a hashbang path or a plain hash fragment - withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); - if (isUndefined(withoutHashUrl)) { - // There was no hashbang prefix so we just have a hash fragment - withoutHashUrl = withoutBaseUrl; - } - - } else { - // There was no hashbang path nor hash fragment: - // If we are in HTML5 mode we use what is left as the path; - // Otherwise we ignore what is left - if (this.$$html5) { - withoutHashUrl = withoutBaseUrl; - } else { - withoutHashUrl = ''; - if (isUndefined(withoutBaseUrl)) { - appBase = url; - this.replace(); - } - } - } - - parseAppUrl(withoutHashUrl, this); - - this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); - - this.$$compose(); - - /* - * In Windows, on an anchor node on documents loaded from - * the filesystem, the browser will return a pathname - * prefixed with the drive name ('/C:/path') when a - * pathname without a drive is set: - * * a.setAttribute('href', '/foo') - * * a.pathname === '/C:/foo' //true - * - * Inside of Angular, we're always using pathnames that - * do not include drive names for routing. - */ - function removeWindowsDriveName(path, url, base) { - /* - Matches paths for file protocol on windows, - such as /C:/foo/bar, and captures only /foo/bar. - */ - var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; - - var firstPathSegmentMatch; - - //Get the relative path from the input URL. - if (url.indexOf(base) === 0) { - url = url.replace(base, ''); - } - - // The input URL intentionally contains a first path segment that ends with a colon. - if (windowsFilePathExp.exec(url)) { - return path; - } - - firstPathSegmentMatch = windowsFilePathExp.exec(path); - return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; - } - }; - - /** - * Compose hashbang url and update `absUrl` property - * @private - */ - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); - }; - - this.$$parseLinkUrl = function(url, relHref) { - if (stripHash(appBase) == stripHash(url)) { - this.$$parse(url); - return true; - } - return false; - }; -} - - -/** - * LocationHashbangUrl represents url - * This object is exposed as $location service when html5 history api is enabled but the browser - * does not support it. - * - * @constructor - * @param {string} appBase application base URL - * @param {string} appBaseNoFile application base URL stripped of any filename - * @param {string} hashPrefix hashbang prefix - */ -function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { - this.$$html5 = true; - LocationHashbangUrl.apply(this, arguments); - - this.$$parseLinkUrl = function(url, relHref) { - if (relHref && relHref[0] === '#') { - // special case for links to hash fragments: - // keep the old url and only replace the hash fragment - this.hash(relHref.slice(1)); - return true; - } - - var rewrittenUrl; - var appUrl; - - if (appBase == stripHash(url)) { - rewrittenUrl = url; - } else if ((appUrl = beginsWith(appBaseNoFile, url))) { - rewrittenUrl = appBase + hashPrefix + appUrl; - } else if (appBaseNoFile === url + '/') { - rewrittenUrl = appBaseNoFile; - } - if (rewrittenUrl) { - this.$$parse(rewrittenUrl); - } - return !!rewrittenUrl; - }; - - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' - this.$$absUrl = appBase + hashPrefix + this.$$url; - }; - -} - - -var locationPrototype = { - - /** - * Are we in html5 mode? - * @private - */ - $$html5: false, - - /** - * Has any change been replacing? - * @private - */ - $$replace: false, - - /** - * @ngdoc method - * @name $location#absUrl - * - * @description - * This method is getter only. - * - * Return full url representation with all segments encoded according to rules specified in - * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). - * - * - * ```js - * // given url http://example.com/#/some/path?foo=bar&baz=xoxo - * var absUrl = $location.absUrl(); - * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" - * ``` - * - * @return {string} full url - */ - absUrl: locationGetter('$$absUrl'), - - /** - * @ngdoc method - * @name $location#url - * - * @description - * This method is getter / setter. - * - * Return url (e.g. `/path?a=b#hash`) when called without any parameter. - * - * Change path, search and hash, when called with parameter and return `$location`. - * - * - * ```js - * // given url http://example.com/#/some/path?foo=bar&baz=xoxo - * var url = $location.url(); - * // => "/some/path?foo=bar&baz=xoxo" - * ``` - * - * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) - * @return {string} url - */ - url: function(url) { - if (isUndefined(url)) { - return this.$$url; - } - - var match = PATH_MATCH.exec(url); - if (match[1] || url === '') this.path(decodeURIComponent(match[1])); - if (match[2] || match[1] || url === '') this.search(match[3] || ''); - this.hash(match[5] || ''); - - return this; - }, - - /** - * @ngdoc method - * @name $location#protocol - * - * @description - * This method is getter only. - * - * Return protocol of current url. - * - * - * ```js - * // given url http://example.com/#/some/path?foo=bar&baz=xoxo - * var protocol = $location.protocol(); - * // => "http" - * ``` - * - * @return {string} protocol of current url - */ - protocol: locationGetter('$$protocol'), - - /** - * @ngdoc method - * @name $location#host - * - * @description - * This method is getter only. - * - * Return host of current url. - * - * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. - * - * - * ```js - * // given url http://example.com/#/some/path?foo=bar&baz=xoxo - * var host = $location.host(); - * // => "example.com" - * - * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo - * host = $location.host(); - * // => "example.com" - * host = location.host; - * // => "example.com:8080" - * ``` - * - * @return {string} host of current url. - */ - host: locationGetter('$$host'), - - /** - * @ngdoc method - * @name $location#port - * - * @description - * This method is getter only. - * - * Return port of current url. - * - * - * ```js - * // given url http://example.com/#/some/path?foo=bar&baz=xoxo - * var port = $location.port(); - * // => 80 - * ``` - * - * @return {Number} port - */ - port: locationGetter('$$port'), - - /** - * @ngdoc method - * @name $location#path - * - * @description - * This method is getter / setter. - * - * Return path of current url when called without any parameter. - * - * Change path when called with parameter and return `$location`. - * - * Note: Path should always begin with forward slash (/), this method will add the forward slash - * if it is missing. - * - * - * ```js - * // given url http://example.com/#/some/path?foo=bar&baz=xoxo - * var path = $location.path(); - * // => "/some/path" - * ``` - * - * @param {(string|number)=} path New path - * @return {string} path - */ - path: locationGetterSetter('$$path', function(path) { - path = path !== null ? path.toString() : ''; - return path.charAt(0) == '/' ? path : '/' + path; - }), - - /** - * @ngdoc method - * @name $location#search - * - * @description - * This method is getter / setter. - * - * Return search part (as object) of current url when called without any parameter. - * - * Change search part when called with parameter and return `$location`. - * - * - * ```js - * // given url http://example.com/#/some/path?foo=bar&baz=xoxo - * var searchObject = $location.search(); - * // => {foo: 'bar', baz: 'xoxo'} - * - * // set foo to 'yipee' - * $location.search('foo', 'yipee'); - * // $location.search() => {foo: 'yipee', baz: 'xoxo'} - * ``` - * - * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or - * hash object. - * - * When called with a single argument the method acts as a setter, setting the `search` component - * of `$location` to the specified value. - * - * If the argument is a hash object containing an array of values, these values will be encoded - * as duplicate search parameters in the url. - * - * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` - * will override only a single search property. - * - * If `paramValue` is an array, it will override the property of the `search` component of - * `$location` specified via the first argument. - * - * If `paramValue` is `null`, the property specified via the first argument will be deleted. - * - * If `paramValue` is `true`, the property specified via the first argument will be added with no - * value nor trailing equal sign. - * - * @return {Object} If called with no arguments returns the parsed `search` object. If called with - * one or more arguments returns `$location` object itself. - */ - search: function(search, paramValue) { - switch (arguments.length) { - case 0: - return this.$$search; - case 1: - if (isString(search) || isNumber(search)) { - search = search.toString(); - this.$$search = parseKeyValue(search); - } else if (isObject(search)) { - search = copy(search, {}); - // remove object undefined or null properties - forEach(search, function(value, key) { - if (value == null) delete search[key]; - }); - - this.$$search = search; - } else { - throw $locationMinErr('isrcharg', - 'The first argument of the `$location#search()` call must be a string or an object.'); - } - break; - default: - if (isUndefined(paramValue) || paramValue === null) { - delete this.$$search[search]; - } else { - this.$$search[search] = paramValue; - } - } - - this.$$compose(); - return this; - }, - - /** - * @ngdoc method - * @name $location#hash - * - * @description - * This method is getter / setter. - * - * Return hash fragment when called without any parameter. - * - * Change hash fragment when called with parameter and return `$location`. - * - * - * ```js - * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue - * var hash = $location.hash(); - * // => "hashValue" - * ``` - * - * @param {(string|number)=} hash New hash fragment - * @return {string} hash - */ - hash: locationGetterSetter('$$hash', function(hash) { - return hash !== null ? hash.toString() : ''; - }), - - /** - * @ngdoc method - * @name $location#replace - * - * @description - * If called, all changes to $location during current `$digest` will be replacing current history - * record, instead of adding new one. - */ - replace: function() { - this.$$replace = true; - return this; - } -}; - -forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { - Location.prototype = Object.create(locationPrototype); - - /** - * @ngdoc method - * @name $location#state - * - * @description - * This method is getter / setter. - * - * Return the history state object when called without any parameter. - * - * Change the history state object when called with one parameter and return `$location`. - * The state object is later passed to `pushState` or `replaceState`. - * - * NOTE: This method is supported only in HTML5 mode and only in browsers supporting - * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support - * older browsers (like IE9 or Android < 4.0), don't use this method. - * - * @param {object=} state State object for pushState or replaceState - * @return {object} state - */ - Location.prototype.state = function(state) { - if (!arguments.length) { - return this.$$state; - } - - if (Location !== LocationHtml5Url || !this.$$html5) { - throw $locationMinErr('nostate', 'History API state support is available only ' + - 'in HTML5 mode and only in browsers supporting HTML5 History API'); - } - // The user might modify `stateObject` after invoking `$location.state(stateObject)` - // but we're changing the $$state reference to $browser.state() during the $digest - // so the modification window is narrow. - this.$$state = isUndefined(state) ? null : state; - - return this; - }; -}); - - -function locationGetter(property) { - return function() { - return this[property]; - }; -} - - -function locationGetterSetter(property, preprocess) { - return function(value) { - if (isUndefined(value)) { - return this[property]; - } - - this[property] = preprocess(value); - this.$$compose(); - - return this; - }; -} - - -/** - * @ngdoc service - * @name $location - * - * @requires $rootElement - * - * @description - * The $location service parses the URL in the browser address bar (based on the - * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL - * available to your application. Changes to the URL in the address bar are reflected into - * $location service and changes to $location are reflected into the browser address bar. - * - * **The $location service:** - * - * - Exposes the current URL in the browser address bar, so you can - * - Watch and observe the URL. - * - Change the URL. - * - Synchronizes the URL with the browser when the user - * - Changes the address bar. - * - Clicks the back or forward button (or clicks a History link). - * - Clicks on a link. - * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). - * - * For more information see {@link guide/$location Developer Guide: Using $location} - */ - -/** - * @ngdoc provider - * @name $locationProvider - * @description - * Use the `$locationProvider` to configure how the application deep linking paths are stored. - */ -function $LocationProvider() { - var hashPrefix = '', - html5Mode = { - enabled: false, - requireBase: true, - rewriteLinks: true - }; - - /** - * @ngdoc method - * @name $locationProvider#hashPrefix - * @description - * @param {string=} prefix Prefix for hash part (containing path and search) - * @returns {*} current value if used as getter or itself (chaining) if used as setter - */ - this.hashPrefix = function(prefix) { - if (isDefined(prefix)) { - hashPrefix = prefix; - return this; - } else { - return hashPrefix; - } - }; - - /** - * @ngdoc method - * @name $locationProvider#html5Mode - * @description - * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. - * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported - * properties: - * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to - * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not - * support `pushState`. - * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies - * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are - * true, and a base tag is not present, an error will be thrown when `$location` is injected. - * See the {@link guide/$location $location guide for more information} - * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, - * enables/disables url rewriting for relative links. - * - * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter - */ - this.html5Mode = function(mode) { - if (isBoolean(mode)) { - html5Mode.enabled = mode; - return this; - } else if (isObject(mode)) { - - if (isBoolean(mode.enabled)) { - html5Mode.enabled = mode.enabled; - } - - if (isBoolean(mode.requireBase)) { - html5Mode.requireBase = mode.requireBase; - } - - if (isBoolean(mode.rewriteLinks)) { - html5Mode.rewriteLinks = mode.rewriteLinks; - } - - return this; - } else { - return html5Mode; - } - }; - - /** - * @ngdoc event - * @name $location#$locationChangeStart - * @eventType broadcast on root scope - * @description - * Broadcasted before a URL will change. - * - * This change can be prevented by calling - * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more - * details about event object. Upon successful change - * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. - * - * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when - * the browser supports the HTML5 History API. - * - * @param {Object} angularEvent Synthetic event object. - * @param {string} newUrl New URL - * @param {string=} oldUrl URL that was before it was changed. - * @param {string=} newState New history state object - * @param {string=} oldState History state object that was before it was changed. - */ - - /** - * @ngdoc event - * @name $location#$locationChangeSuccess - * @eventType broadcast on root scope - * @description - * Broadcasted after a URL was changed. - * - * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when - * the browser supports the HTML5 History API. - * - * @param {Object} angularEvent Synthetic event object. - * @param {string} newUrl New URL - * @param {string=} oldUrl URL that was before it was changed. - * @param {string=} newState New history state object - * @param {string=} oldState History state object that was before it was changed. - */ - - this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', - function($rootScope, $browser, $sniffer, $rootElement, $window) { - var $location, - LocationMode, - baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' - initialUrl = $browser.url(), - appBase; - - if (html5Mode.enabled) { - if (!baseHref && html5Mode.requireBase) { - throw $locationMinErr('nobase', - "$location in HTML5 mode requires a <base> tag to be present!"); - } - appBase = serverBase(initialUrl) + (baseHref || '/'); - LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; - } else { - appBase = stripHash(initialUrl); - LocationMode = LocationHashbangUrl; - } - var appBaseNoFile = stripFile(appBase); - - $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); - $location.$$parseLinkUrl(initialUrl, initialUrl); - - $location.$$state = $browser.state(); - - var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; - - function setBrowserUrlWithFallback(url, replace, state) { - var oldUrl = $location.url(); - var oldState = $location.$$state; - try { - $browser.url(url, replace, state); - - // Make sure $location.state() returns referentially identical (not just deeply equal) - // state object; this makes possible quick checking if the state changed in the digest - // loop. Checking deep equality would be too expensive. - $location.$$state = $browser.state(); - } catch (e) { - // Restore old values if pushState fails - $location.url(oldUrl); - $location.$$state = oldState; - - throw e; - } - } - - $rootElement.on('click', function(event) { - // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) - // currently we open nice url link and redirect then - - if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; - - var elm = jqLite(event.target); - - // traverse the DOM up to find first A tag - while (nodeName_(elm[0]) !== 'a') { - // ignore rewriting if no A tag (reached root element, or no parent - removed from document) - if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; - } - - var absHref = elm.prop('href'); - // get the actual href attribute - see - // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx - var relHref = elm.attr('href') || elm.attr('xlink:href'); - - if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { - // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during - // an animation. - absHref = urlResolve(absHref.animVal).href; - } - - // Ignore when url is started with javascript: or mailto: - if (IGNORE_URI_REGEXP.test(absHref)) return; - - if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { - if ($location.$$parseLinkUrl(absHref, relHref)) { - // We do a preventDefault for all urls that are part of the angular application, - // in html5mode and also without, so that we are able to abort navigation without - // getting double entries in the location history. - event.preventDefault(); - // update location manually - if ($location.absUrl() != $browser.url()) { - $rootScope.$apply(); - // hack to work around FF6 bug 684208 when scenario runner clicks on links - $window.angular['ff-684208-preventDefault'] = true; - } - } - } - }); - - - // rewrite hashbang url <> html5 url - if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { - $browser.url($location.absUrl(), true); - } - - var initializing = true; - - // update $location when $browser url changes - $browser.onUrlChange(function(newUrl, newState) { - - if (isUndefined(beginsWith(appBaseNoFile, newUrl))) { - // If we are navigating outside of the app then force a reload - $window.location.href = newUrl; - return; - } - - $rootScope.$evalAsync(function() { - var oldUrl = $location.absUrl(); - var oldState = $location.$$state; - var defaultPrevented; - - $location.$$parse(newUrl); - $location.$$state = newState; - - defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, - newState, oldState).defaultPrevented; - - // if the location was changed by a `$locationChangeStart` handler then stop - // processing this location change - if ($location.absUrl() !== newUrl) return; - - if (defaultPrevented) { - $location.$$parse(oldUrl); - $location.$$state = oldState; - setBrowserUrlWithFallback(oldUrl, false, oldState); - } else { - initializing = false; - afterLocationChange(oldUrl, oldState); - } - }); - if (!$rootScope.$$phase) $rootScope.$digest(); - }); - - // update browser - $rootScope.$watch(function $locationWatch() { - var oldUrl = trimEmptyHash($browser.url()); - var newUrl = trimEmptyHash($location.absUrl()); - var oldState = $browser.state(); - var currentReplace = $location.$$replace; - var urlOrStateChanged = oldUrl !== newUrl || - ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); - - if (initializing || urlOrStateChanged) { - initializing = false; - - $rootScope.$evalAsync(function() { - var newUrl = $location.absUrl(); - var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, - $location.$$state, oldState).defaultPrevented; - - // if the location was changed by a `$locationChangeStart` handler then stop - // processing this location change - if ($location.absUrl() !== newUrl) return; - - if (defaultPrevented) { - $location.$$parse(oldUrl); - $location.$$state = oldState; - } else { - if (urlOrStateChanged) { - setBrowserUrlWithFallback(newUrl, currentReplace, - oldState === $location.$$state ? null : $location.$$state); - } - afterLocationChange(oldUrl, oldState); - } - }); - } - - $location.$$replace = false; - - // we don't need to return anything because $evalAsync will make the digest loop dirty when - // there is a change - }); - - return $location; - - function afterLocationChange(oldUrl, oldState) { - $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, - $location.$$state, oldState); - } -}]; -} - -/** - * @ngdoc service - * @name $log - * @requires $window - * - * @description - * Simple service for logging. Default implementation safely writes the message - * into the browser's console (if present). - * - * The main purpose of this service is to simplify debugging and troubleshooting. - * - * The default is to log `debug` messages. You can use - * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. - * - * @example - <example module="logExample"> - <file name="script.js"> - angular.module('logExample', []) - .controller('LogController', ['$scope', '$log', function($scope, $log) { - $scope.$log = $log; - $scope.message = 'Hello World!'; - }]); - </file> - <file name="index.html"> - <div ng-controller="LogController"> - <p>Reload this page with open console, enter text and hit the log button...</p> - <label>Message: - <input type="text" ng-model="message" /></label> - <button ng-click="$log.log(message)">log</button> - <button ng-click="$log.warn(message)">warn</button> - <button ng-click="$log.info(message)">info</button> - <button ng-click="$log.error(message)">error</button> - <button ng-click="$log.debug(message)">debug</button> - </div> - </file> - </example> - */ - -/** - * @ngdoc provider - * @name $logProvider - * @description - * Use the `$logProvider` to configure how the application logs messages - */ -function $LogProvider() { - var debug = true, - self = this; - - /** - * @ngdoc method - * @name $logProvider#debugEnabled - * @description - * @param {boolean=} flag enable or disable debug level messages - * @returns {*} current value if used as getter or itself (chaining) if used as setter - */ - this.debugEnabled = function(flag) { - if (isDefined(flag)) { - debug = flag; - return this; - } else { - return debug; - } - }; - - this.$get = ['$window', function($window) { - return { - /** - * @ngdoc method - * @name $log#log - * - * @description - * Write a log message - */ - log: consoleLog('log'), - - /** - * @ngdoc method - * @name $log#info - * - * @description - * Write an information message - */ - info: consoleLog('info'), - - /** - * @ngdoc method - * @name $log#warn - * - * @description - * Write a warning message - */ - warn: consoleLog('warn'), - - /** - * @ngdoc method - * @name $log#error - * - * @description - * Write an error message - */ - error: consoleLog('error'), - - /** - * @ngdoc method - * @name $log#debug - * - * @description - * Write a debug message - */ - debug: (function() { - var fn = consoleLog('debug'); - - return function() { - if (debug) { - fn.apply(self, arguments); - } - }; - }()) - }; - - function formatError(arg) { - if (arg instanceof Error) { - if (arg.stack) { - arg = (arg.message && arg.stack.indexOf(arg.message) === -1) - ? 'Error: ' + arg.message + '\n' + arg.stack - : arg.stack; - } else if (arg.sourceURL) { - arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; - } - } - return arg; - } - - function consoleLog(type) { - var console = $window.console || {}, - logFn = console[type] || console.log || noop, - hasApply = false; - - // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. - // The reason behind this is that console.log has type "object" in IE8... - try { - hasApply = !!logFn.apply; - } catch (e) {} - - if (hasApply) { - return function() { - var args = []; - forEach(arguments, function(arg) { - args.push(formatError(arg)); - }); - return logFn.apply(console, args); - }; - } - - // we are IE which either doesn't have window.console => this is noop and we do nothing, - // or we are IE where console.log doesn't have apply so we log at least first 2 args - return function(arg1, arg2) { - logFn(arg1, arg2 == null ? '' : arg2); - }; - } - }]; -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Any commits to this file should be reviewed with security in mind. * - * Changes to this file can potentially create security vulnerabilities. * - * An approval from 2 Core members with history of modifying * - * this file is required. * - * * - * Does the change somehow allow for arbitrary javascript to be executed? * - * Or allows for someone to change the prototype of built-in objects? * - * Or gives undesired access to variables likes document or window? * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -var $parseMinErr = minErr('$parse'); - -// Sandboxing Angular Expressions -// ------------------------------ -// Angular expressions are generally considered safe because these expressions only have direct -// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by -// obtaining a reference to native JS functions such as the Function constructor. -// -// As an example, consider the following Angular expression: -// -// {}.toString.constructor('alert("evil JS code")') -// -// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits -// against the expression language, but not to prevent exploits that were enabled by exposing -// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good -// practice and therefore we are not even trying to protect against interaction with an object -// explicitly exposed in this way. -// -// In general, it is not possible to access a Window object from an angular expression unless a -// window or some DOM object that has a reference to window is published onto a Scope. -// Similarly we prevent invocations of function known to be dangerous, as well as assignments to -// native objects. -// -// See https://docs.angularjs.org/guide/security - - -function ensureSafeMemberName(name, fullExpression) { - if (name === "__defineGetter__" || name === "__defineSetter__" - || name === "__lookupGetter__" || name === "__lookupSetter__" - || name === "__proto__") { - throw $parseMinErr('isecfld', - 'Attempting to access a disallowed field in Angular expressions! ' - + 'Expression: {0}', fullExpression); - } - return name; -} - -function getStringValue(name, fullExpression) { - // From the JavaScript docs: - // Property names must be strings. This means that non-string objects cannot be used - // as keys in an object. Any non-string object, including a number, is typecasted - // into a string via the toString method. - // - // So, to ensure that we are checking the same `name` that JavaScript would use, - // we cast it to a string, if possible. - // Doing `name + ''` can cause a repl error if the result to `toString` is not a string, - // this is, this will handle objects that misbehave. - name = name + ''; - if (!isString(name)) { - throw $parseMinErr('iseccst', - 'Cannot convert object to primitive value! ' - + 'Expression: {0}', fullExpression); - } - return name; -} - -function ensureSafeObject(obj, fullExpression) { - // nifty check if obj is Function that is fast and works across iframes and other contexts - if (obj) { - if (obj.constructor === obj) { - throw $parseMinErr('isecfn', - 'Referencing Function in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } else if (// isWindow(obj) - obj.window === obj) { - throw $parseMinErr('isecwindow', - 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } else if (// isElement(obj) - obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { - throw $parseMinErr('isecdom', - 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } else if (// block Object so that we can't get hold of dangerous Object.* methods - obj === Object) { - throw $parseMinErr('isecobj', - 'Referencing Object in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } - } - return obj; -} - -var CALL = Function.prototype.call; -var APPLY = Function.prototype.apply; -var BIND = Function.prototype.bind; - -function ensureSafeFunction(obj, fullExpression) { - if (obj) { - if (obj.constructor === obj) { - throw $parseMinErr('isecfn', - 'Referencing Function in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } else if (obj === CALL || obj === APPLY || obj === BIND) { - throw $parseMinErr('isecff', - 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } - } -} - -function ensureSafeAssignContext(obj, fullExpression) { - if (obj) { - if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor || - obj === {}.constructor || obj === [].constructor || obj === Function.constructor) { - throw $parseMinErr('isecaf', - 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression); - } - } -} - -var OPERATORS = createMap(); -forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); -var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; - - -///////////////////////////////////////// - - -/** - * @constructor - */ -var Lexer = function(options) { - this.options = options; -}; - -Lexer.prototype = { - constructor: Lexer, - - lex: function(text) { - this.text = text; - this.index = 0; - this.tokens = []; - - while (this.index < this.text.length) { - var ch = this.text.charAt(this.index); - if (ch === '"' || ch === "'") { - this.readString(ch); - } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { - this.readNumber(); - } else if (this.isIdent(ch)) { - this.readIdent(); - } else if (this.is(ch, '(){}[].,;:?')) { - this.tokens.push({index: this.index, text: ch}); - this.index++; - } else if (this.isWhitespace(ch)) { - this.index++; - } else { - var ch2 = ch + this.peek(); - var ch3 = ch2 + this.peek(2); - var op1 = OPERATORS[ch]; - var op2 = OPERATORS[ch2]; - var op3 = OPERATORS[ch3]; - if (op1 || op2 || op3) { - var token = op3 ? ch3 : (op2 ? ch2 : ch); - this.tokens.push({index: this.index, text: token, operator: true}); - this.index += token.length; - } else { - this.throwError('Unexpected next character ', this.index, this.index + 1); - } - } - } - return this.tokens; - }, - - is: function(ch, chars) { - return chars.indexOf(ch) !== -1; - }, - - peek: function(i) { - var num = i || 1; - return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; - }, - - isNumber: function(ch) { - return ('0' <= ch && ch <= '9') && typeof ch === "string"; - }, - - isWhitespace: function(ch) { - // IE treats non-breaking space as \u00A0 - return (ch === ' ' || ch === '\r' || ch === '\t' || - ch === '\n' || ch === '\v' || ch === '\u00A0'); - }, - - isIdent: function(ch) { - return ('a' <= ch && ch <= 'z' || - 'A' <= ch && ch <= 'Z' || - '_' === ch || ch === '$'); - }, - - isExpOperator: function(ch) { - return (ch === '-' || ch === '+' || this.isNumber(ch)); - }, - - throwError: function(error, start, end) { - end = end || this.index; - var colStr = (isDefined(start) - ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' - : ' ' + end); - throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', - error, colStr, this.text); - }, - - readNumber: function() { - var number = ''; - var start = this.index; - while (this.index < this.text.length) { - var ch = lowercase(this.text.charAt(this.index)); - if (ch == '.' || this.isNumber(ch)) { - number += ch; - } else { - var peekCh = this.peek(); - if (ch == 'e' && this.isExpOperator(peekCh)) { - number += ch; - } else if (this.isExpOperator(ch) && - peekCh && this.isNumber(peekCh) && - number.charAt(number.length - 1) == 'e') { - number += ch; - } else if (this.isExpOperator(ch) && - (!peekCh || !this.isNumber(peekCh)) && - number.charAt(number.length - 1) == 'e') { - this.throwError('Invalid exponent'); - } else { - break; - } - } - this.index++; - } - this.tokens.push({ - index: start, - text: number, - constant: true, - value: Number(number) - }); - }, - - readIdent: function() { - var start = this.index; - while (this.index < this.text.length) { - var ch = this.text.charAt(this.index); - if (!(this.isIdent(ch) || this.isNumber(ch))) { - break; - } - this.index++; - } - this.tokens.push({ - index: start, - text: this.text.slice(start, this.index), - identifier: true - }); - }, - - readString: function(quote) { - var start = this.index; - this.index++; - var string = ''; - var rawString = quote; - var escape = false; - while (this.index < this.text.length) { - var ch = this.text.charAt(this.index); - rawString += ch; - if (escape) { - if (ch === 'u') { - var hex = this.text.substring(this.index + 1, this.index + 5); - if (!hex.match(/[\da-f]{4}/i)) { - this.throwError('Invalid unicode escape [\\u' + hex + ']'); - } - this.index += 4; - string += String.fromCharCode(parseInt(hex, 16)); - } else { - var rep = ESCAPE[ch]; - string = string + (rep || ch); - } - escape = false; - } else if (ch === '\\') { - escape = true; - } else if (ch === quote) { - this.index++; - this.tokens.push({ - index: start, - text: rawString, - constant: true, - value: string - }); - return; - } else { - string += ch; - } - this.index++; - } - this.throwError('Unterminated quote', start); - } -}; - -var AST = function(lexer, options) { - this.lexer = lexer; - this.options = options; -}; - -AST.Program = 'Program'; -AST.ExpressionStatement = 'ExpressionStatement'; -AST.AssignmentExpression = 'AssignmentExpression'; -AST.ConditionalExpression = 'ConditionalExpression'; -AST.LogicalExpression = 'LogicalExpression'; -AST.BinaryExpression = 'BinaryExpression'; -AST.UnaryExpression = 'UnaryExpression'; -AST.CallExpression = 'CallExpression'; -AST.MemberExpression = 'MemberExpression'; -AST.Identifier = 'Identifier'; -AST.Literal = 'Literal'; -AST.ArrayExpression = 'ArrayExpression'; -AST.Property = 'Property'; -AST.ObjectExpression = 'ObjectExpression'; -AST.ThisExpression = 'ThisExpression'; - -// Internal use only -AST.NGValueParameter = 'NGValueParameter'; - -AST.prototype = { - ast: function(text) { - this.text = text; - this.tokens = this.lexer.lex(text); - - var value = this.program(); - - if (this.tokens.length !== 0) { - this.throwError('is an unexpected token', this.tokens[0]); - } - - return value; - }, - - program: function() { - var body = []; - while (true) { - if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) - body.push(this.expressionStatement()); - if (!this.expect(';')) { - return { type: AST.Program, body: body}; - } - } - }, - - expressionStatement: function() { - return { type: AST.ExpressionStatement, expression: this.filterChain() }; - }, - - filterChain: function() { - var left = this.expression(); - var token; - while ((token = this.expect('|'))) { - left = this.filter(left); - } - return left; - }, - - expression: function() { - return this.assignment(); - }, - - assignment: function() { - var result = this.ternary(); - if (this.expect('=')) { - result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; - } - return result; - }, - - ternary: function() { - var test = this.logicalOR(); - var alternate; - var consequent; - if (this.expect('?')) { - alternate = this.expression(); - if (this.consume(':')) { - consequent = this.expression(); - return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; - } - } - return test; - }, - - logicalOR: function() { - var left = this.logicalAND(); - while (this.expect('||')) { - left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; - } - return left; - }, - - logicalAND: function() { - var left = this.equality(); - while (this.expect('&&')) { - left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; - } - return left; - }, - - equality: function() { - var left = this.relational(); - var token; - while ((token = this.expect('==','!=','===','!=='))) { - left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; - } - return left; - }, - - relational: function() { - var left = this.additive(); - var token; - while ((token = this.expect('<', '>', '<=', '>='))) { - left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; - } - return left; - }, - - additive: function() { - var left = this.multiplicative(); - var token; - while ((token = this.expect('+','-'))) { - left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; - } - return left; - }, - - multiplicative: function() { - var left = this.unary(); - var token; - while ((token = this.expect('*','/','%'))) { - left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; - } - return left; - }, - - unary: function() { - var token; - if ((token = this.expect('+', '-', '!'))) { - return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; - } else { - return this.primary(); - } - }, - - primary: function() { - var primary; - if (this.expect('(')) { - primary = this.filterChain(); - this.consume(')'); - } else if (this.expect('[')) { - primary = this.arrayDeclaration(); - } else if (this.expect('{')) { - primary = this.object(); - } else if (this.constants.hasOwnProperty(this.peek().text)) { - primary = copy(this.constants[this.consume().text]); - } else if (this.peek().identifier) { - primary = this.identifier(); - } else if (this.peek().constant) { - primary = this.constant(); - } else { - this.throwError('not a primary expression', this.peek()); - } - - var next; - while ((next = this.expect('(', '[', '.'))) { - if (next.text === '(') { - primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; - this.consume(')'); - } else if (next.text === '[') { - primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; - this.consume(']'); - } else if (next.text === '.') { - primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; - } else { - this.throwError('IMPOSSIBLE'); - } - } - return primary; - }, - - filter: function(baseExpression) { - var args = [baseExpression]; - var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; - - while (this.expect(':')) { - args.push(this.expression()); - } - - return result; - }, - - parseArguments: function() { - var args = []; - if (this.peekToken().text !== ')') { - do { - args.push(this.expression()); - } while (this.expect(',')); - } - return args; - }, - - identifier: function() { - var token = this.consume(); - if (!token.identifier) { - this.throwError('is not a valid identifier', token); - } - return { type: AST.Identifier, name: token.text }; - }, - - constant: function() { - // TODO check that it is a constant - return { type: AST.Literal, value: this.consume().value }; - }, - - arrayDeclaration: function() { - var elements = []; - if (this.peekToken().text !== ']') { - do { - if (this.peek(']')) { - // Support trailing commas per ES5.1. - break; - } - elements.push(this.expression()); - } while (this.expect(',')); - } - this.consume(']'); - - return { type: AST.ArrayExpression, elements: elements }; - }, - - object: function() { - var properties = [], property; - if (this.peekToken().text !== '}') { - do { - if (this.peek('}')) { - // Support trailing commas per ES5.1. - break; - } - property = {type: AST.Property, kind: 'init'}; - if (this.peek().constant) { - property.key = this.constant(); - } else if (this.peek().identifier) { - property.key = this.identifier(); - } else { - this.throwError("invalid key", this.peek()); - } - this.consume(':'); - property.value = this.expression(); - properties.push(property); - } while (this.expect(',')); - } - this.consume('}'); - - return {type: AST.ObjectExpression, properties: properties }; - }, - - throwError: function(msg, token) { - throw $parseMinErr('syntax', - 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', - token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); - }, - - consume: function(e1) { - if (this.tokens.length === 0) { - throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); - } - - var token = this.expect(e1); - if (!token) { - this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); - } - return token; - }, - - peekToken: function() { - if (this.tokens.length === 0) { - throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); - } - return this.tokens[0]; - }, - - peek: function(e1, e2, e3, e4) { - return this.peekAhead(0, e1, e2, e3, e4); - }, - - peekAhead: function(i, e1, e2, e3, e4) { - if (this.tokens.length > i) { - var token = this.tokens[i]; - var t = token.text; - if (t === e1 || t === e2 || t === e3 || t === e4 || - (!e1 && !e2 && !e3 && !e4)) { - return token; - } - } - return false; - }, - - expect: function(e1, e2, e3, e4) { - var token = this.peek(e1, e2, e3, e4); - if (token) { - this.tokens.shift(); - return token; - } - return false; - }, - - - /* `undefined` is not a constant, it is an identifier, - * but using it as an identifier is not supported - */ - constants: { - 'true': { type: AST.Literal, value: true }, - 'false': { type: AST.Literal, value: false }, - 'null': { type: AST.Literal, value: null }, - 'undefined': {type: AST.Literal, value: undefined }, - 'this': {type: AST.ThisExpression } - } -}; - -function ifDefined(v, d) { - return typeof v !== 'undefined' ? v : d; -} - -function plusFn(l, r) { - if (typeof l === 'undefined') return r; - if (typeof r === 'undefined') return l; - return l + r; -} - -function isStateless($filter, filterName) { - var fn = $filter(filterName); - return !fn.$stateful; -} - -function findConstantAndWatchExpressions(ast, $filter) { - var allConstants; - var argsToWatch; - switch (ast.type) { - case AST.Program: - allConstants = true; - forEach(ast.body, function(expr) { - findConstantAndWatchExpressions(expr.expression, $filter); - allConstants = allConstants && expr.expression.constant; - }); - ast.constant = allConstants; - break; - case AST.Literal: - ast.constant = true; - ast.toWatch = []; - break; - case AST.UnaryExpression: - findConstantAndWatchExpressions(ast.argument, $filter); - ast.constant = ast.argument.constant; - ast.toWatch = ast.argument.toWatch; - break; - case AST.BinaryExpression: - findConstantAndWatchExpressions(ast.left, $filter); - findConstantAndWatchExpressions(ast.right, $filter); - ast.constant = ast.left.constant && ast.right.constant; - ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); - break; - case AST.LogicalExpression: - findConstantAndWatchExpressions(ast.left, $filter); - findConstantAndWatchExpressions(ast.right, $filter); - ast.constant = ast.left.constant && ast.right.constant; - ast.toWatch = ast.constant ? [] : [ast]; - break; - case AST.ConditionalExpression: - findConstantAndWatchExpressions(ast.test, $filter); - findConstantAndWatchExpressions(ast.alternate, $filter); - findConstantAndWatchExpressions(ast.consequent, $filter); - ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; - ast.toWatch = ast.constant ? [] : [ast]; - break; - case AST.Identifier: - ast.constant = false; - ast.toWatch = [ast]; - break; - case AST.MemberExpression: - findConstantAndWatchExpressions(ast.object, $filter); - if (ast.computed) { - findConstantAndWatchExpressions(ast.property, $filter); - } - ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); - ast.toWatch = [ast]; - break; - case AST.CallExpression: - allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false; - argsToWatch = []; - forEach(ast.arguments, function(expr) { - findConstantAndWatchExpressions(expr, $filter); - allConstants = allConstants && expr.constant; - if (!expr.constant) { - argsToWatch.push.apply(argsToWatch, expr.toWatch); - } - }); - ast.constant = allConstants; - ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast]; - break; - case AST.AssignmentExpression: - findConstantAndWatchExpressions(ast.left, $filter); - findConstantAndWatchExpressions(ast.right, $filter); - ast.constant = ast.left.constant && ast.right.constant; - ast.toWatch = [ast]; - break; - case AST.ArrayExpression: - allConstants = true; - argsToWatch = []; - forEach(ast.elements, function(expr) { - findConstantAndWatchExpressions(expr, $filter); - allConstants = allConstants && expr.constant; - if (!expr.constant) { - argsToWatch.push.apply(argsToWatch, expr.toWatch); - } - }); - ast.constant = allConstants; - ast.toWatch = argsToWatch; - break; - case AST.ObjectExpression: - allConstants = true; - argsToWatch = []; - forEach(ast.properties, function(property) { - findConstantAndWatchExpressions(property.value, $filter); - allConstants = allConstants && property.value.constant; - if (!property.value.constant) { - argsToWatch.push.apply(argsToWatch, property.value.toWatch); - } - }); - ast.constant = allConstants; - ast.toWatch = argsToWatch; - break; - case AST.ThisExpression: - ast.constant = false; - ast.toWatch = []; - break; - } -} - -function getInputs(body) { - if (body.length != 1) return; - var lastExpression = body[0].expression; - var candidate = lastExpression.toWatch; - if (candidate.length !== 1) return candidate; - return candidate[0] !== lastExpression ? candidate : undefined; -} - -function isAssignable(ast) { - return ast.type === AST.Identifier || ast.type === AST.MemberExpression; -} - -function assignableAST(ast) { - if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { - return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; - } -} - -function isLiteral(ast) { - return ast.body.length === 0 || - ast.body.length === 1 && ( - ast.body[0].expression.type === AST.Literal || - ast.body[0].expression.type === AST.ArrayExpression || - ast.body[0].expression.type === AST.ObjectExpression); -} - -function isConstant(ast) { - return ast.constant; -} - -function ASTCompiler(astBuilder, $filter) { - this.astBuilder = astBuilder; - this.$filter = $filter; -} - -ASTCompiler.prototype = { - compile: function(expression, expensiveChecks) { - var self = this; - var ast = this.astBuilder.ast(expression); - this.state = { - nextId: 0, - filters: {}, - expensiveChecks: expensiveChecks, - fn: {vars: [], body: [], own: {}}, - assign: {vars: [], body: [], own: {}}, - inputs: [] - }; - findConstantAndWatchExpressions(ast, self.$filter); - var extra = ''; - var assignable; - this.stage = 'assign'; - if ((assignable = assignableAST(ast))) { - this.state.computing = 'assign'; - var result = this.nextId(); - this.recurse(assignable, result); - this.return_(result); - extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); - } - var toWatch = getInputs(ast.body); - self.stage = 'inputs'; - forEach(toWatch, function(watch, key) { - var fnKey = 'fn' + key; - self.state[fnKey] = {vars: [], body: [], own: {}}; - self.state.computing = fnKey; - var intoId = self.nextId(); - self.recurse(watch, intoId); - self.return_(intoId); - self.state.inputs.push(fnKey); - watch.watchId = key; - }); - this.state.computing = 'fn'; - this.stage = 'main'; - this.recurse(ast); - var fnString = - // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. - // This is a workaround for this until we do a better job at only removing the prefix only when we should. - '"' + this.USE + ' ' + this.STRICT + '";\n' + - this.filterPrefix() + - 'var fn=' + this.generateFunction('fn', 's,l,a,i') + - extra + - this.watchFns() + - 'return fn;'; - - /* jshint -W054 */ - var fn = (new Function('$filter', - 'ensureSafeMemberName', - 'ensureSafeObject', - 'ensureSafeFunction', - 'getStringValue', - 'ensureSafeAssignContext', - 'ifDefined', - 'plus', - 'text', - fnString))( - this.$filter, - ensureSafeMemberName, - ensureSafeObject, - ensureSafeFunction, - getStringValue, - ensureSafeAssignContext, - ifDefined, - plusFn, - expression); - /* jshint +W054 */ - this.state = this.stage = undefined; - fn.literal = isLiteral(ast); - fn.constant = isConstant(ast); - return fn; - }, - - USE: 'use', - - STRICT: 'strict', - - watchFns: function() { - var result = []; - var fns = this.state.inputs; - var self = this; - forEach(fns, function(name) { - result.push('var ' + name + '=' + self.generateFunction(name, 's')); - }); - if (fns.length) { - result.push('fn.inputs=[' + fns.join(',') + '];'); - } - return result.join(''); - }, - - generateFunction: function(name, params) { - return 'function(' + params + '){' + - this.varsPrefix(name) + - this.body(name) + - '};'; - }, - - filterPrefix: function() { - var parts = []; - var self = this; - forEach(this.state.filters, function(id, filter) { - parts.push(id + '=$filter(' + self.escape(filter) + ')'); - }); - if (parts.length) return 'var ' + parts.join(',') + ';'; - return ''; - }, - - varsPrefix: function(section) { - return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; - }, - - body: function(section) { - return this.state[section].body.join(''); - }, - - recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { - var left, right, self = this, args, expression; - recursionFn = recursionFn || noop; - if (!skipWatchIdCheck && isDefined(ast.watchId)) { - intoId = intoId || this.nextId(); - this.if_('i', - this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), - this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) - ); - return; - } - switch (ast.type) { - case AST.Program: - forEach(ast.body, function(expression, pos) { - self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); - if (pos !== ast.body.length - 1) { - self.current().body.push(right, ';'); - } else { - self.return_(right); - } - }); - break; - case AST.Literal: - expression = this.escape(ast.value); - this.assign(intoId, expression); - recursionFn(expression); - break; - case AST.UnaryExpression: - this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); - expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; - this.assign(intoId, expression); - recursionFn(expression); - break; - case AST.BinaryExpression: - this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); - this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); - if (ast.operator === '+') { - expression = this.plus(left, right); - } else if (ast.operator === '-') { - expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); - } else { - expression = '(' + left + ')' + ast.operator + '(' + right + ')'; - } - this.assign(intoId, expression); - recursionFn(expression); - break; - case AST.LogicalExpression: - intoId = intoId || this.nextId(); - self.recurse(ast.left, intoId); - self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); - recursionFn(intoId); - break; - case AST.ConditionalExpression: - intoId = intoId || this.nextId(); - self.recurse(ast.test, intoId); - self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); - recursionFn(intoId); - break; - case AST.Identifier: - intoId = intoId || this.nextId(); - if (nameId) { - nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); - nameId.computed = false; - nameId.name = ast.name; - } - ensureSafeMemberName(ast.name); - self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), - function() { - self.if_(self.stage === 'inputs' || 's', function() { - if (create && create !== 1) { - self.if_( - self.not(self.nonComputedMember('s', ast.name)), - self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); - } - self.assign(intoId, self.nonComputedMember('s', ast.name)); - }); - }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) - ); - if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { - self.addEnsureSafeObject(intoId); - } - recursionFn(intoId); - break; - case AST.MemberExpression: - left = nameId && (nameId.context = this.nextId()) || this.nextId(); - intoId = intoId || this.nextId(); - self.recurse(ast.object, left, undefined, function() { - self.if_(self.notNull(left), function() { - if (ast.computed) { - right = self.nextId(); - self.recurse(ast.property, right); - self.getStringValue(right); - self.addEnsureSafeMemberName(right); - if (create && create !== 1) { - self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); - } - expression = self.ensureSafeObject(self.computedMember(left, right)); - self.assign(intoId, expression); - if (nameId) { - nameId.computed = true; - nameId.name = right; - } - } else { - ensureSafeMemberName(ast.property.name); - if (create && create !== 1) { - self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); - } - expression = self.nonComputedMember(left, ast.property.name); - if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { - expression = self.ensureSafeObject(expression); - } - self.assign(intoId, expression); - if (nameId) { - nameId.computed = false; - nameId.name = ast.property.name; - } - } - }, function() { - self.assign(intoId, 'undefined'); - }); - recursionFn(intoId); - }, !!create); - break; - case AST.CallExpression: - intoId = intoId || this.nextId(); - if (ast.filter) { - right = self.filter(ast.callee.name); - args = []; - forEach(ast.arguments, function(expr) { - var argument = self.nextId(); - self.recurse(expr, argument); - args.push(argument); - }); - expression = right + '(' + args.join(',') + ')'; - self.assign(intoId, expression); - recursionFn(intoId); - } else { - right = self.nextId(); - left = {}; - args = []; - self.recurse(ast.callee, right, left, function() { - self.if_(self.notNull(right), function() { - self.addEnsureSafeFunction(right); - forEach(ast.arguments, function(expr) { - self.recurse(expr, self.nextId(), undefined, function(argument) { - args.push(self.ensureSafeObject(argument)); - }); - }); - if (left.name) { - if (!self.state.expensiveChecks) { - self.addEnsureSafeObject(left.context); - } - expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; - } else { - expression = right + '(' + args.join(',') + ')'; - } - expression = self.ensureSafeObject(expression); - self.assign(intoId, expression); - }, function() { - self.assign(intoId, 'undefined'); - }); - recursionFn(intoId); - }); - } - break; - case AST.AssignmentExpression: - right = this.nextId(); - left = {}; - if (!isAssignable(ast.left)) { - throw $parseMinErr('lval', 'Trying to assing a value to a non l-value'); - } - this.recurse(ast.left, undefined, left, function() { - self.if_(self.notNull(left.context), function() { - self.recurse(ast.right, right); - self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); - self.addEnsureSafeAssignContext(left.context); - expression = self.member(left.context, left.name, left.computed) + ast.operator + right; - self.assign(intoId, expression); - recursionFn(intoId || expression); - }); - }, 1); - break; - case AST.ArrayExpression: - args = []; - forEach(ast.elements, function(expr) { - self.recurse(expr, self.nextId(), undefined, function(argument) { - args.push(argument); - }); - }); - expression = '[' + args.join(',') + ']'; - this.assign(intoId, expression); - recursionFn(expression); - break; - case AST.ObjectExpression: - args = []; - forEach(ast.properties, function(property) { - self.recurse(property.value, self.nextId(), undefined, function(expr) { - args.push(self.escape( - property.key.type === AST.Identifier ? property.key.name : - ('' + property.key.value)) + - ':' + expr); - }); - }); - expression = '{' + args.join(',') + '}'; - this.assign(intoId, expression); - recursionFn(expression); - break; - case AST.ThisExpression: - this.assign(intoId, 's'); - recursionFn('s'); - break; - case AST.NGValueParameter: - this.assign(intoId, 'v'); - recursionFn('v'); - break; - } - }, - - getHasOwnProperty: function(element, property) { - var key = element + '.' + property; - var own = this.current().own; - if (!own.hasOwnProperty(key)) { - own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); - } - return own[key]; - }, - - assign: function(id, value) { - if (!id) return; - this.current().body.push(id, '=', value, ';'); - return id; - }, - - filter: function(filterName) { - if (!this.state.filters.hasOwnProperty(filterName)) { - this.state.filters[filterName] = this.nextId(true); - } - return this.state.filters[filterName]; - }, - - ifDefined: function(id, defaultValue) { - return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; - }, - - plus: function(left, right) { - return 'plus(' + left + ',' + right + ')'; - }, - - return_: function(id) { - this.current().body.push('return ', id, ';'); - }, - - if_: function(test, alternate, consequent) { - if (test === true) { - alternate(); - } else { - var body = this.current().body; - body.push('if(', test, '){'); - alternate(); - body.push('}'); - if (consequent) { - body.push('else{'); - consequent(); - body.push('}'); - } - } - }, - - not: function(expression) { - return '!(' + expression + ')'; - }, - - notNull: function(expression) { - return expression + '!=null'; - }, - - nonComputedMember: function(left, right) { - return left + '.' + right; - }, - - computedMember: function(left, right) { - return left + '[' + right + ']'; - }, - - member: function(left, right, computed) { - if (computed) return this.computedMember(left, right); - return this.nonComputedMember(left, right); - }, - - addEnsureSafeObject: function(item) { - this.current().body.push(this.ensureSafeObject(item), ';'); - }, - - addEnsureSafeMemberName: function(item) { - this.current().body.push(this.ensureSafeMemberName(item), ';'); - }, - - addEnsureSafeFunction: function(item) { - this.current().body.push(this.ensureSafeFunction(item), ';'); - }, - - addEnsureSafeAssignContext: function(item) { - this.current().body.push(this.ensureSafeAssignContext(item), ';'); - }, - - ensureSafeObject: function(item) { - return 'ensureSafeObject(' + item + ',text)'; - }, - - ensureSafeMemberName: function(item) { - return 'ensureSafeMemberName(' + item + ',text)'; - }, - - ensureSafeFunction: function(item) { - return 'ensureSafeFunction(' + item + ',text)'; - }, - - getStringValue: function(item) { - this.assign(item, 'getStringValue(' + item + ',text)'); - }, - - ensureSafeAssignContext: function(item) { - return 'ensureSafeAssignContext(' + item + ',text)'; - }, - - lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { - var self = this; - return function() { - self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); - }; - }, - - lazyAssign: function(id, value) { - var self = this; - return function() { - self.assign(id, value); - }; - }, - - stringEscapeRegex: /[^ a-zA-Z0-9]/g, - - stringEscapeFn: function(c) { - return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); - }, - - escape: function(value) { - if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'"; - if (isNumber(value)) return value.toString(); - if (value === true) return 'true'; - if (value === false) return 'false'; - if (value === null) return 'null'; - if (typeof value === 'undefined') return 'undefined'; - - throw $parseMinErr('esc', 'IMPOSSIBLE'); - }, - - nextId: function(skip, init) { - var id = 'v' + (this.state.nextId++); - if (!skip) { - this.current().vars.push(id + (init ? '=' + init : '')); - } - return id; - }, - - current: function() { - return this.state[this.state.computing]; - } -}; - - -function ASTInterpreter(astBuilder, $filter) { - this.astBuilder = astBuilder; - this.$filter = $filter; -} - -ASTInterpreter.prototype = { - compile: function(expression, expensiveChecks) { - var self = this; - var ast = this.astBuilder.ast(expression); - this.expression = expression; - this.expensiveChecks = expensiveChecks; - findConstantAndWatchExpressions(ast, self.$filter); - var assignable; - var assign; - if ((assignable = assignableAST(ast))) { - assign = this.recurse(assignable); - } - var toWatch = getInputs(ast.body); - var inputs; - if (toWatch) { - inputs = []; - forEach(toWatch, function(watch, key) { - var input = self.recurse(watch); - watch.input = input; - inputs.push(input); - watch.watchId = key; - }); - } - var expressions = []; - forEach(ast.body, function(expression) { - expressions.push(self.recurse(expression.expression)); - }); - var fn = ast.body.length === 0 ? function() {} : - ast.body.length === 1 ? expressions[0] : - function(scope, locals) { - var lastValue; - forEach(expressions, function(exp) { - lastValue = exp(scope, locals); - }); - return lastValue; - }; - if (assign) { - fn.assign = function(scope, value, locals) { - return assign(scope, locals, value); - }; - } - if (inputs) { - fn.inputs = inputs; - } - fn.literal = isLiteral(ast); - fn.constant = isConstant(ast); - return fn; - }, - - recurse: function(ast, context, create) { - var left, right, self = this, args, expression; - if (ast.input) { - return this.inputs(ast.input, ast.watchId); - } - switch (ast.type) { - case AST.Literal: - return this.value(ast.value, context); - case AST.UnaryExpression: - right = this.recurse(ast.argument); - return this['unary' + ast.operator](right, context); - case AST.BinaryExpression: - left = this.recurse(ast.left); - right = this.recurse(ast.right); - return this['binary' + ast.operator](left, right, context); - case AST.LogicalExpression: - left = this.recurse(ast.left); - right = this.recurse(ast.right); - return this['binary' + ast.operator](left, right, context); - case AST.ConditionalExpression: - return this['ternary?:']( - this.recurse(ast.test), - this.recurse(ast.alternate), - this.recurse(ast.consequent), - context - ); - case AST.Identifier: - ensureSafeMemberName(ast.name, self.expression); - return self.identifier(ast.name, - self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), - context, create, self.expression); - case AST.MemberExpression: - left = this.recurse(ast.object, false, !!create); - if (!ast.computed) { - ensureSafeMemberName(ast.property.name, self.expression); - right = ast.property.name; - } - if (ast.computed) right = this.recurse(ast.property); - return ast.computed ? - this.computedMember(left, right, context, create, self.expression) : - this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); - case AST.CallExpression: - args = []; - forEach(ast.arguments, function(expr) { - args.push(self.recurse(expr)); - }); - if (ast.filter) right = this.$filter(ast.callee.name); - if (!ast.filter) right = this.recurse(ast.callee, true); - return ast.filter ? - function(scope, locals, assign, inputs) { - var values = []; - for (var i = 0; i < args.length; ++i) { - values.push(args[i](scope, locals, assign, inputs)); - } - var value = right.apply(undefined, values, inputs); - return context ? {context: undefined, name: undefined, value: value} : value; - } : - function(scope, locals, assign, inputs) { - var rhs = right(scope, locals, assign, inputs); - var value; - if (rhs.value != null) { - ensureSafeObject(rhs.context, self.expression); - ensureSafeFunction(rhs.value, self.expression); - var values = []; - for (var i = 0; i < args.length; ++i) { - values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); - } - value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); - } - return context ? {value: value} : value; - }; - case AST.AssignmentExpression: - left = this.recurse(ast.left, true, 1); - right = this.recurse(ast.right); - return function(scope, locals, assign, inputs) { - var lhs = left(scope, locals, assign, inputs); - var rhs = right(scope, locals, assign, inputs); - ensureSafeObject(lhs.value, self.expression); - ensureSafeAssignContext(lhs.context); - lhs.context[lhs.name] = rhs; - return context ? {value: rhs} : rhs; - }; - case AST.ArrayExpression: - args = []; - forEach(ast.elements, function(expr) { - args.push(self.recurse(expr)); - }); - return function(scope, locals, assign, inputs) { - var value = []; - for (var i = 0; i < args.length; ++i) { - value.push(args[i](scope, locals, assign, inputs)); - } - return context ? {value: value} : value; - }; - case AST.ObjectExpression: - args = []; - forEach(ast.properties, function(property) { - args.push({key: property.key.type === AST.Identifier ? - property.key.name : - ('' + property.key.value), - value: self.recurse(property.value) - }); - }); - return function(scope, locals, assign, inputs) { - var value = {}; - for (var i = 0; i < args.length; ++i) { - value[args[i].key] = args[i].value(scope, locals, assign, inputs); - } - return context ? {value: value} : value; - }; - case AST.ThisExpression: - return function(scope) { - return context ? {value: scope} : scope; - }; - case AST.NGValueParameter: - return function(scope, locals, assign, inputs) { - return context ? {value: assign} : assign; - }; - } - }, - - 'unary+': function(argument, context) { - return function(scope, locals, assign, inputs) { - var arg = argument(scope, locals, assign, inputs); - if (isDefined(arg)) { - arg = +arg; - } else { - arg = 0; - } - return context ? {value: arg} : arg; - }; - }, - 'unary-': function(argument, context) { - return function(scope, locals, assign, inputs) { - var arg = argument(scope, locals, assign, inputs); - if (isDefined(arg)) { - arg = -arg; - } else { - arg = 0; - } - return context ? {value: arg} : arg; - }; - }, - 'unary!': function(argument, context) { - return function(scope, locals, assign, inputs) { - var arg = !argument(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary+': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var lhs = left(scope, locals, assign, inputs); - var rhs = right(scope, locals, assign, inputs); - var arg = plusFn(lhs, rhs); - return context ? {value: arg} : arg; - }; - }, - 'binary-': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var lhs = left(scope, locals, assign, inputs); - var rhs = right(scope, locals, assign, inputs); - var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); - return context ? {value: arg} : arg; - }; - }, - 'binary*': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary/': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary%': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary===': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary!==': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary==': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary!=': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary<': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary>': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary<=': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary>=': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary&&': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'binary||': function(left, right, context) { - return function(scope, locals, assign, inputs) { - var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - 'ternary?:': function(test, alternate, consequent, context) { - return function(scope, locals, assign, inputs) { - var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); - return context ? {value: arg} : arg; - }; - }, - value: function(value, context) { - return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; - }, - identifier: function(name, expensiveChecks, context, create, expression) { - return function(scope, locals, assign, inputs) { - var base = locals && (name in locals) ? locals : scope; - if (create && create !== 1 && base && !(base[name])) { - base[name] = {}; - } - var value = base ? base[name] : undefined; - if (expensiveChecks) { - ensureSafeObject(value, expression); - } - if (context) { - return {context: base, name: name, value: value}; - } else { - return value; - } - }; - }, - computedMember: function(left, right, context, create, expression) { - return function(scope, locals, assign, inputs) { - var lhs = left(scope, locals, assign, inputs); - var rhs; - var value; - if (lhs != null) { - rhs = right(scope, locals, assign, inputs); - rhs = getStringValue(rhs); - ensureSafeMemberName(rhs, expression); - if (create && create !== 1 && lhs && !(lhs[rhs])) { - lhs[rhs] = {}; - } - value = lhs[rhs]; - ensureSafeObject(value, expression); - } - if (context) { - return {context: lhs, name: rhs, value: value}; - } else { - return value; - } - }; - }, - nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { - return function(scope, locals, assign, inputs) { - var lhs = left(scope, locals, assign, inputs); - if (create && create !== 1 && lhs && !(lhs[right])) { - lhs[right] = {}; - } - var value = lhs != null ? lhs[right] : undefined; - if (expensiveChecks || isPossiblyDangerousMemberName(right)) { - ensureSafeObject(value, expression); - } - if (context) { - return {context: lhs, name: right, value: value}; - } else { - return value; - } - }; - }, - inputs: function(input, watchId) { - return function(scope, value, locals, inputs) { - if (inputs) return inputs[watchId]; - return input(scope, value, locals); - }; - } -}; - -/** - * @constructor - */ -var Parser = function(lexer, $filter, options) { - this.lexer = lexer; - this.$filter = $filter; - this.options = options; - this.ast = new AST(this.lexer); - this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : - new ASTCompiler(this.ast, $filter); -}; - -Parser.prototype = { - constructor: Parser, - - parse: function(text) { - return this.astCompiler.compile(text, this.options.expensiveChecks); - } -}; - -var getterFnCacheDefault = createMap(); -var getterFnCacheExpensive = createMap(); - -function isPossiblyDangerousMemberName(name) { - return name == 'constructor'; -} - -var objectValueOf = Object.prototype.valueOf; - -function getValueOf(value) { - return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); -} - -/////////////////////////////////// - -/** - * @ngdoc service - * @name $parse - * @kind function - * - * @description - * - * Converts Angular {@link guide/expression expression} into a function. - * - * ```js - * var getter = $parse('user.name'); - * var setter = getter.assign; - * var context = {user:{name:'angular'}}; - * var locals = {user:{name:'local'}}; - * - * expect(getter(context)).toEqual('angular'); - * setter(context, 'newValue'); - * expect(context.user.name).toEqual('newValue'); - * expect(getter(context, locals)).toEqual('local'); - * ``` - * - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - * - * The returned function also has the following properties: - * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript - * literal. - * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript - * constant literals. - * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be - * set to a function to change its value on the given context. - * - */ - - -/** - * @ngdoc provider - * @name $parseProvider - * - * @description - * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} - * service. - */ -function $ParseProvider() { - var cacheDefault = createMap(); - var cacheExpensive = createMap(); - - this.$get = ['$filter', function($filter) { - var noUnsafeEval = csp().noUnsafeEval; - var $parseOptions = { - csp: noUnsafeEval, - expensiveChecks: false - }, - $parseOptionsExpensive = { - csp: noUnsafeEval, - expensiveChecks: true - }; - - return function $parse(exp, interceptorFn, expensiveChecks) { - var parsedExpression, oneTime, cacheKey; - - switch (typeof exp) { - case 'string': - exp = exp.trim(); - cacheKey = exp; - - var cache = (expensiveChecks ? cacheExpensive : cacheDefault); - parsedExpression = cache[cacheKey]; - - if (!parsedExpression) { - if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { - oneTime = true; - exp = exp.substring(2); - } - var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; - var lexer = new Lexer(parseOptions); - var parser = new Parser(lexer, $filter, parseOptions); - parsedExpression = parser.parse(exp); - if (parsedExpression.constant) { - parsedExpression.$$watchDelegate = constantWatchDelegate; - } else if (oneTime) { - parsedExpression.$$watchDelegate = parsedExpression.literal ? - oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; - } else if (parsedExpression.inputs) { - parsedExpression.$$watchDelegate = inputsWatchDelegate; - } - cache[cacheKey] = parsedExpression; - } - return addInterceptor(parsedExpression, interceptorFn); - - case 'function': - return addInterceptor(exp, interceptorFn); - - default: - return noop; - } - }; - - function expressionInputDirtyCheck(newValue, oldValueOfValue) { - - if (newValue == null || oldValueOfValue == null) { // null/undefined - return newValue === oldValueOfValue; - } - - if (typeof newValue === 'object') { - - // attempt to convert the value to a primitive type - // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can - // be cheaply dirty-checked - newValue = getValueOf(newValue); - - if (typeof newValue === 'object') { - // objects/arrays are not supported - deep-watching them would be too expensive - return false; - } - - // fall-through to the primitive equality check - } - - //Primitive or NaN - return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); - } - - function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { - var inputExpressions = parsedExpression.inputs; - var lastResult; - - if (inputExpressions.length === 1) { - var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails - inputExpressions = inputExpressions[0]; - return scope.$watch(function expressionInputWatch(scope) { - var newInputValue = inputExpressions(scope); - if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { - lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); - oldInputValueOf = newInputValue && getValueOf(newInputValue); - } - return lastResult; - }, listener, objectEquality, prettyPrintExpression); - } - - var oldInputValueOfValues = []; - var oldInputValues = []; - for (var i = 0, ii = inputExpressions.length; i < ii; i++) { - oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails - oldInputValues[i] = null; - } - - return scope.$watch(function expressionInputsWatch(scope) { - var changed = false; - - for (var i = 0, ii = inputExpressions.length; i < ii; i++) { - var newInputValue = inputExpressions[i](scope); - if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { - oldInputValues[i] = newInputValue; - oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); - } - } - - if (changed) { - lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); - } - - return lastResult; - }, listener, objectEquality, prettyPrintExpression); - } - - function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { - var unwatch, lastValue; - return unwatch = scope.$watch(function oneTimeWatch(scope) { - return parsedExpression(scope); - }, function oneTimeListener(value, old, scope) { - lastValue = value; - if (isFunction(listener)) { - listener.apply(this, arguments); - } - if (isDefined(value)) { - scope.$$postDigest(function() { - if (isDefined(lastValue)) { - unwatch(); - } - }); - } - }, objectEquality); - } - - function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { - var unwatch, lastValue; - return unwatch = scope.$watch(function oneTimeWatch(scope) { - return parsedExpression(scope); - }, function oneTimeListener(value, old, scope) { - lastValue = value; - if (isFunction(listener)) { - listener.call(this, value, old, scope); - } - if (isAllDefined(value)) { - scope.$$postDigest(function() { - if (isAllDefined(lastValue)) unwatch(); - }); - } - }, objectEquality); - - function isAllDefined(value) { - var allDefined = true; - forEach(value, function(val) { - if (!isDefined(val)) allDefined = false; - }); - return allDefined; - } - } - - function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { - var unwatch; - return unwatch = scope.$watch(function constantWatch(scope) { - return parsedExpression(scope); - }, function constantListener(value, old, scope) { - if (isFunction(listener)) { - listener.apply(this, arguments); - } - unwatch(); - }, objectEquality); - } - - function addInterceptor(parsedExpression, interceptorFn) { - if (!interceptorFn) return parsedExpression; - var watchDelegate = parsedExpression.$$watchDelegate; - - var regularWatch = - watchDelegate !== oneTimeLiteralWatchDelegate && - watchDelegate !== oneTimeWatchDelegate; - - var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { - var value = parsedExpression(scope, locals, assign, inputs); - return interceptorFn(value, scope, locals); - } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { - var value = parsedExpression(scope, locals, assign, inputs); - var result = interceptorFn(value, scope, locals); - // we only return the interceptor's result if the - // initial value is defined (for bind-once) - return isDefined(value) ? result : value; - }; - - // Propagate $$watchDelegates other then inputsWatchDelegate - if (parsedExpression.$$watchDelegate && - parsedExpression.$$watchDelegate !== inputsWatchDelegate) { - fn.$$watchDelegate = parsedExpression.$$watchDelegate; - } else if (!interceptorFn.$stateful) { - // If there is an interceptor, but no watchDelegate then treat the interceptor like - // we treat filters - it is assumed to be a pure function unless flagged with $stateful - fn.$$watchDelegate = inputsWatchDelegate; - fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; - } - - return fn; - } - }]; -} - -/** - * @ngdoc service - * @name $q - * @requires $rootScope - * - * @description - * A service that helps you run functions asynchronously, and use their return values (or exceptions) - * when they are done processing. - * - * This is an implementation of promises/deferred objects inspired by - * [Kris Kowal's Q](https://github.com/kriskowal/q). - * - * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred - * implementations, and the other which resembles ES6 promises to some degree. - * - * # $q constructor - * - * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` - * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, - * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). - * - * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are - * available yet. - * - * It can be used like so: - * - * ```js - * // for the purpose of this example let's assume that variables `$q` and `okToGreet` - * // are available in the current lexical scope (they could have been injected or passed in). - * - * function asyncGreet(name) { - * // perform some asynchronous operation, resolve or reject the promise when appropriate. - * return $q(function(resolve, reject) { - * setTimeout(function() { - * if (okToGreet(name)) { - * resolve('Hello, ' + name + '!'); - * } else { - * reject('Greeting ' + name + ' is not allowed.'); - * } - * }, 1000); - * }); - * } - * - * var promise = asyncGreet('Robin Hood'); - * promise.then(function(greeting) { - * alert('Success: ' + greeting); - * }, function(reason) { - * alert('Failed: ' + reason); - * }); - * ``` - * - * Note: progress/notify callbacks are not currently supported via the ES6-style interface. - * - * However, the more traditional CommonJS-style usage is still available, and documented below. - * - * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an - * interface for interacting with an object that represents the result of an action that is - * performed asynchronously, and may or may not be finished at any given point in time. - * - * From the perspective of dealing with error handling, deferred and promise APIs are to - * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. - * - * ```js - * // for the purpose of this example let's assume that variables `$q` and `okToGreet` - * // are available in the current lexical scope (they could have been injected or passed in). - * - * function asyncGreet(name) { - * var deferred = $q.defer(); - * - * setTimeout(function() { - * deferred.notify('About to greet ' + name + '.'); - * - * if (okToGreet(name)) { - * deferred.resolve('Hello, ' + name + '!'); - * } else { - * deferred.reject('Greeting ' + name + ' is not allowed.'); - * } - * }, 1000); - * - * return deferred.promise; - * } - * - * var promise = asyncGreet('Robin Hood'); - * promise.then(function(greeting) { - * alert('Success: ' + greeting); - * }, function(reason) { - * alert('Failed: ' + reason); - * }, function(update) { - * alert('Got notification: ' + update); - * }); - * ``` - * - * At first it might not be obvious why this extra complexity is worth the trouble. The payoff - * comes in the way of guarantees that promise and deferred APIs make, see - * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. - * - * Additionally the promise api allows for composition that is very hard to do with the - * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. - * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the - * section on serial or parallel joining of promises. - * - * # The Deferred API - * - * A new instance of deferred is constructed by calling `$q.defer()`. - * - * The purpose of the deferred object is to expose the associated Promise instance as well as APIs - * that can be used for signaling the successful or unsuccessful completion, as well as the status - * of the task. - * - * **Methods** - * - * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection - * constructed via `$q.reject`, the promise will be rejected instead. - * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to - * resolving it with a rejection constructed via `$q.reject`. - * - `notify(value)` - provides updates on the status of the promise's execution. This may be called - * multiple times before the promise is either resolved or rejected. - * - * **Properties** - * - * - promise – `{Promise}` – promise object associated with this deferred. - * - * - * # The Promise API - * - * A new promise instance is created when a deferred instance is created and can be retrieved by - * calling `deferred.promise`. - * - * The purpose of the promise object is to allow for interested parties to get access to the result - * of the deferred task when it completes. - * - * **Methods** - * - * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or - * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously - * as soon as the result is available. The callbacks are called with a single argument: the result - * or rejection reason. Additionally, the notify callback may be called zero or more times to - * provide a progress indication, before the promise is resolved or rejected. - * - * This method *returns a new promise* which is resolved or rejected via the return value of the - * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved - * with the value which is resolved in that promise using - * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). - * It also notifies via the return value of the `notifyCallback` method. The promise cannot be - * resolved or rejected from the notifyCallback method. - * - * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` - * - * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise, - * but to do so without modifying the final value. This is useful to release resources or do some - * clean-up that needs to be done whether the promise was rejected or resolved. See the [full - * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for - * more information. - * - * # Chaining promises - * - * Because calling the `then` method of a promise returns a new derived promise, it is easily - * possible to create a chain of promises: - * - * ```js - * promiseB = promiseA.then(function(result) { - * return result + 1; - * }); - * - * // promiseB will be resolved immediately after promiseA is resolved and its value - * // will be the result of promiseA incremented by 1 - * ``` - * - * It is possible to create chains of any length and since a promise can be resolved with another - * promise (which will defer its resolution further), it is possible to pause/defer resolution of - * the promises at any point in the chain. This makes it possible to implement powerful APIs like - * $http's response interceptors. - * - * - * # Differences between Kris Kowal's Q and $q - * - * There are two main differences: - * - * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation - * mechanism in angular, which means faster propagation of resolution or rejection into your - * models and avoiding unnecessary browser repaints, which would result in flickering UI. - * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains - * all the important functionality needed for common async tasks. - * - * # Testing - * - * ```js - * it('should simulate promise', inject(function($q, $rootScope) { - * var deferred = $q.defer(); - * var promise = deferred.promise; - * var resolvedValue; - * - * promise.then(function(value) { resolvedValue = value; }); - * expect(resolvedValue).toBeUndefined(); - * - * // Simulate resolving of promise - * deferred.resolve(123); - * // Note that the 'then' function does not get called synchronously. - * // This is because we want the promise API to always be async, whether or not - * // it got called synchronously or asynchronously. - * expect(resolvedValue).toBeUndefined(); - * - * // Propagate promise resolution to 'then' functions using $apply(). - * $rootScope.$apply(); - * expect(resolvedValue).toEqual(123); - * })); - * ``` - * - * @param {function(function, function)} resolver Function which is responsible for resolving or - * rejecting the newly created promise. The first parameter is a function which resolves the - * promise, the second parameter is a function which rejects the promise. - * - * @returns {Promise} The newly created promise. - */ -function $QProvider() { - - this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { - return qFactory(function(callback) { - $rootScope.$evalAsync(callback); - }, $exceptionHandler); - }]; -} - -function $$QProvider() { - this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { - return qFactory(function(callback) { - $browser.defer(callback); - }, $exceptionHandler); - }]; -} - -/** - * Constructs a promise manager. - * - * @param {function(function)} nextTick Function for executing functions in the next turn. - * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for - * debugging purposes. - * @returns {object} Promise manager. - */ -function qFactory(nextTick, exceptionHandler) { - var $qMinErr = minErr('$q', TypeError); - function callOnce(self, resolveFn, rejectFn) { - var called = false; - function wrap(fn) { - return function(value) { - if (called) return; - called = true; - fn.call(self, value); - }; - } - - return [wrap(resolveFn), wrap(rejectFn)]; - } - - /** - * @ngdoc method - * @name ng.$q#defer - * @kind function - * - * @description - * Creates a `Deferred` object which represents a task which will finish in the future. - * - * @returns {Deferred} Returns a new instance of deferred. - */ - var defer = function() { - return new Deferred(); - }; - - function Promise() { - this.$$state = { status: 0 }; - } - - extend(Promise.prototype, { - then: function(onFulfilled, onRejected, progressBack) { - if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { - return this; - } - var result = new Deferred(); - - this.$$state.pending = this.$$state.pending || []; - this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); - if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); - - return result.promise; - }, - - "catch": function(callback) { - return this.then(null, callback); - }, - - "finally": function(callback, progressBack) { - return this.then(function(value) { - return handleCallback(value, true, callback); - }, function(error) { - return handleCallback(error, false, callback); - }, progressBack); - } - }); - - //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native - function simpleBind(context, fn) { - return function(value) { - fn.call(context, value); - }; - } - - function processQueue(state) { - var fn, deferred, pending; - - pending = state.pending; - state.processScheduled = false; - state.pending = undefined; - for (var i = 0, ii = pending.length; i < ii; ++i) { - deferred = pending[i][0]; - fn = pending[i][state.status]; - try { - if (isFunction(fn)) { - deferred.resolve(fn(state.value)); - } else if (state.status === 1) { - deferred.resolve(state.value); - } else { - deferred.reject(state.value); - } - } catch (e) { - deferred.reject(e); - exceptionHandler(e); - } - } - } - - function scheduleProcessQueue(state) { - if (state.processScheduled || !state.pending) return; - state.processScheduled = true; - nextTick(function() { processQueue(state); }); - } - - function Deferred() { - this.promise = new Promise(); - //Necessary to support unbound execution :/ - this.resolve = simpleBind(this, this.resolve); - this.reject = simpleBind(this, this.reject); - this.notify = simpleBind(this, this.notify); - } - - extend(Deferred.prototype, { - resolve: function(val) { - if (this.promise.$$state.status) return; - if (val === this.promise) { - this.$$reject($qMinErr( - 'qcycle', - "Expected promise to be resolved with value other than itself '{0}'", - val)); - } else { - this.$$resolve(val); - } - - }, - - $$resolve: function(val) { - var then, fns; - - fns = callOnce(this, this.$$resolve, this.$$reject); - try { - if ((isObject(val) || isFunction(val))) then = val && val.then; - if (isFunction(then)) { - this.promise.$$state.status = -1; - then.call(val, fns[0], fns[1], this.notify); - } else { - this.promise.$$state.value = val; - this.promise.$$state.status = 1; - scheduleProcessQueue(this.promise.$$state); - } - } catch (e) { - fns[1](e); - exceptionHandler(e); - } - }, - - reject: function(reason) { - if (this.promise.$$state.status) return; - this.$$reject(reason); - }, - - $$reject: function(reason) { - this.promise.$$state.value = reason; - this.promise.$$state.status = 2; - scheduleProcessQueue(this.promise.$$state); - }, - - notify: function(progress) { - var callbacks = this.promise.$$state.pending; - - if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { - nextTick(function() { - var callback, result; - for (var i = 0, ii = callbacks.length; i < ii; i++) { - result = callbacks[i][0]; - callback = callbacks[i][3]; - try { - result.notify(isFunction(callback) ? callback(progress) : progress); - } catch (e) { - exceptionHandler(e); - } - } - }); - } - } - }); - - /** - * @ngdoc method - * @name $q#reject - * @kind function - * - * @description - * Creates a promise that is resolved as rejected with the specified `reason`. This api should be - * used to forward rejection in a chain of promises. If you are dealing with the last promise in - * a promise chain, you don't need to worry about it. - * - * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of - * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via - * a promise error callback and you want to forward the error to the promise derived from the - * current promise, you have to "rethrow" the error by returning a rejection constructed via - * `reject`. - * - * ```js - * promiseB = promiseA.then(function(result) { - * // success: do something and resolve promiseB - * // with the old or a new result - * return result; - * }, function(reason) { - * // error: handle the error if possible and - * // resolve promiseB with newPromiseOrValue, - * // otherwise forward the rejection to promiseB - * if (canHandle(reason)) { - * // handle the error and recover - * return newPromiseOrValue; - * } - * return $q.reject(reason); - * }); - * ``` - * - * @param {*} reason Constant, message, exception or an object representing the rejection reason. - * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. - */ - var reject = function(reason) { - var result = new Deferred(); - result.reject(reason); - return result.promise; - }; - - var makePromise = function makePromise(value, resolved) { - var result = new Deferred(); - if (resolved) { - result.resolve(value); - } else { - result.reject(value); - } - return result.promise; - }; - - var handleCallback = function handleCallback(value, isResolved, callback) { - var callbackOutput = null; - try { - if (isFunction(callback)) callbackOutput = callback(); - } catch (e) { - return makePromise(e, false); - } - if (isPromiseLike(callbackOutput)) { - return callbackOutput.then(function() { - return makePromise(value, isResolved); - }, function(error) { - return makePromise(error, false); - }); - } else { - return makePromise(value, isResolved); - } - }; - - /** - * @ngdoc method - * @name $q#when - * @kind function - * - * @description - * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. - * This is useful when you are dealing with an object that might or might not be a promise, or if - * the promise comes from a source that can't be trusted. - * - * @param {*} value Value or a promise - * @param {Function=} successCallback - * @param {Function=} errorCallback - * @param {Function=} progressCallback - * @returns {Promise} Returns a promise of the passed value or promise - */ - - - var when = function(value, callback, errback, progressBack) { - var result = new Deferred(); - result.resolve(value); - return result.promise.then(callback, errback, progressBack); - }; - - /** - * @ngdoc method - * @name $q#resolve - * @kind function - * - * @description - * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. - * - * @param {*} value Value or a promise - * @param {Function=} successCallback - * @param {Function=} errorCallback - * @param {Function=} progressCallback - * @returns {Promise} Returns a promise of the passed value or promise - */ - var resolve = when; - - /** - * @ngdoc method - * @name $q#all - * @kind function - * - * @description - * Combines multiple promises into a single promise that is resolved when all of the input - * promises are resolved. - * - * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. - * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, - * each value corresponding to the promise at the same index/key in the `promises` array/hash. - * If any of the promises is resolved with a rejection, this resulting promise will be rejected - * with the same rejection value. - */ - - function all(promises) { - var deferred = new Deferred(), - counter = 0, - results = isArray(promises) ? [] : {}; - - forEach(promises, function(promise, key) { - counter++; - when(promise).then(function(value) { - if (results.hasOwnProperty(key)) return; - results[key] = value; - if (!(--counter)) deferred.resolve(results); - }, function(reason) { - if (results.hasOwnProperty(key)) return; - deferred.reject(reason); - }); - }); - - if (counter === 0) { - deferred.resolve(results); - } - - return deferred.promise; - } - - var $Q = function Q(resolver) { - if (!isFunction(resolver)) { - throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); - } - - if (!(this instanceof Q)) { - // More useful when $Q is the Promise itself. - return new Q(resolver); - } - - var deferred = new Deferred(); - - function resolveFn(value) { - deferred.resolve(value); - } - - function rejectFn(reason) { - deferred.reject(reason); - } - - resolver(resolveFn, rejectFn); - - return deferred.promise; - }; - - $Q.defer = defer; - $Q.reject = reject; - $Q.when = when; - $Q.resolve = resolve; - $Q.all = all; - - return $Q; -} - -function $$RAFProvider() { //rAF - this.$get = ['$window', '$timeout', function($window, $timeout) { - var requestAnimationFrame = $window.requestAnimationFrame || - $window.webkitRequestAnimationFrame; - - var cancelAnimationFrame = $window.cancelAnimationFrame || - $window.webkitCancelAnimationFrame || - $window.webkitCancelRequestAnimationFrame; - - var rafSupported = !!requestAnimationFrame; - var raf = rafSupported - ? function(fn) { - var id = requestAnimationFrame(fn); - return function() { - cancelAnimationFrame(id); - }; - } - : function(fn) { - var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 - return function() { - $timeout.cancel(timer); - }; - }; - - raf.supported = rafSupported; - - return raf; - }]; -} - -/** - * DESIGN NOTES - * - * The design decisions behind the scope are heavily favored for speed and memory consumption. - * - * The typical use of scope is to watch the expressions, which most of the time return the same - * value as last time so we optimize the operation. - * - * Closures construction is expensive in terms of speed as well as memory: - * - No closures, instead use prototypical inheritance for API - * - Internal state needs to be stored on scope directly, which means that private state is - * exposed as $$____ properties - * - * Loop operations are optimized by using while(count--) { ... } - * - this means that in order to keep the same order of execution as addition we have to add - * items to the array at the beginning (unshift) instead of at the end (push) - * - * Child scopes are created and removed often - * - Using an array would be slow since inserts in middle are expensive so we use linked list - * - * There are few watches then a lot of observers. This is why you don't want the observer to be - * implemented in the same way as watch. Watch requires return of initialization function which - * are expensive to construct. - */ - - -/** - * @ngdoc provider - * @name $rootScopeProvider - * @description - * - * Provider for the $rootScope service. - */ - -/** - * @ngdoc method - * @name $rootScopeProvider#digestTtl - * @description - * - * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and - * assuming that the model is unstable. - * - * The current default is 10 iterations. - * - * In complex applications it's possible that the dependencies between `$watch`s will result in - * several digest iterations. However if an application needs more than the default 10 digest - * iterations for its model to stabilize then you should investigate what is causing the model to - * continuously change during the digest. - * - * Increasing the TTL could have performance implications, so you should not change it without - * proper justification. - * - * @param {number} limit The number of digest iterations. - */ - - -/** - * @ngdoc service - * @name $rootScope - * @description - * - * Every application has a single root {@link ng.$rootScope.Scope scope}. - * All other scopes are descendant scopes of the root scope. Scopes provide separation - * between the model and the view, via a mechanism for watching the model for changes. - * They also provide an event emission/broadcast and subscription facility. See the - * {@link guide/scope developer guide on scopes}. - */ -function $RootScopeProvider() { - var TTL = 10; - var $rootScopeMinErr = minErr('$rootScope'); - var lastDirtyWatch = null; - var applyAsyncId = null; - - this.digestTtl = function(value) { - if (arguments.length) { - TTL = value; - } - return TTL; - }; - - function createChildScopeClass(parent) { - function ChildScope() { - this.$$watchers = this.$$nextSibling = - this.$$childHead = this.$$childTail = null; - this.$$listeners = {}; - this.$$listenerCount = {}; - this.$$watchersCount = 0; - this.$id = nextUid(); - this.$$ChildScope = null; - } - ChildScope.prototype = parent; - return ChildScope; - } - - this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', - function($injector, $exceptionHandler, $parse, $browser) { - - function destroyChildScope($event) { - $event.currentScope.$$destroyed = true; - } - - /** - * @ngdoc type - * @name $rootScope.Scope - * - * @description - * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the - * {@link auto.$injector $injector}. Child scopes are created using the - * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when - * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for - * an in-depth introduction and usage examples. - * - * - * # Inheritance - * A scope can inherit from a parent scope, as in this example: - * ```js - var parent = $rootScope; - var child = parent.$new(); - - parent.salutation = "Hello"; - expect(child.salutation).toEqual('Hello'); - - child.salutation = "Welcome"; - expect(child.salutation).toEqual('Welcome'); - expect(parent.salutation).toEqual('Hello'); - * ``` - * - * When interacting with `Scope` in tests, additional helper methods are available on the - * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional - * details. - * - * - * @param {Object.<string, function()>=} providers Map of service factory which need to be - * provided for the current scope. Defaults to {@link ng}. - * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should - * append/override services provided by `providers`. This is handy - * when unit-testing and having the need to override a default - * service. - * @returns {Object} Newly created scope. - * - */ - function Scope() { - this.$id = nextUid(); - this.$$phase = this.$parent = this.$$watchers = - this.$$nextSibling = this.$$prevSibling = - this.$$childHead = this.$$childTail = null; - this.$root = this; - this.$$destroyed = false; - this.$$listeners = {}; - this.$$listenerCount = {}; - this.$$watchersCount = 0; - this.$$isolateBindings = null; - } - - /** - * @ngdoc property - * @name $rootScope.Scope#$id - * - * @description - * Unique scope ID (monotonically increasing) useful for debugging. - */ - - /** - * @ngdoc property - * @name $rootScope.Scope#$parent - * - * @description - * Reference to the parent scope. - */ - - /** - * @ngdoc property - * @name $rootScope.Scope#$root - * - * @description - * Reference to the root scope. - */ - - Scope.prototype = { - constructor: Scope, - /** - * @ngdoc method - * @name $rootScope.Scope#$new - * @kind function - * - * @description - * Creates a new child {@link ng.$rootScope.Scope scope}. - * - * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. - * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. - * - * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is - * desired for the scope and its child scopes to be permanently detached from the parent and - * thus stop participating in model change detection and listener notification by invoking. - * - * @param {boolean} isolate If true, then the scope does not prototypically inherit from the - * parent scope. The scope is isolated, as it can not see parent scope properties. - * When creating widgets, it is useful for the widget to not accidentally read parent - * state. - * - * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` - * of the newly created scope. Defaults to `this` scope if not provided. - * This is used when creating a transclude scope to correctly place it - * in the scope hierarchy while maintaining the correct prototypical - * inheritance. - * - * @returns {Object} The newly created child scope. - * - */ - $new: function(isolate, parent) { - var child; - - parent = parent || this; - - if (isolate) { - child = new Scope(); - child.$root = this.$root; - } else { - // Only create a child scope class if somebody asks for one, - // but cache it to allow the VM to optimize lookups. - if (!this.$$ChildScope) { - this.$$ChildScope = createChildScopeClass(this); - } - child = new this.$$ChildScope(); - } - child.$parent = parent; - child.$$prevSibling = parent.$$childTail; - if (parent.$$childHead) { - parent.$$childTail.$$nextSibling = child; - parent.$$childTail = child; - } else { - parent.$$childHead = parent.$$childTail = child; - } - - // When the new scope is not isolated or we inherit from `this`, and - // the parent scope is destroyed, the property `$$destroyed` is inherited - // prototypically. In all other cases, this property needs to be set - // when the parent scope is destroyed. - // The listener needs to be added after the parent is set - if (isolate || parent != this) child.$on('$destroy', destroyChildScope); - - return child; - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$watch - * @kind function - * - * @description - * Registers a `listener` callback to be executed whenever the `watchExpression` changes. - * - * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest - * $digest()} and should return the value that will be watched. (`watchExpression` should not change - * its value when executed multiple times with the same input because it may be executed multiple - * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be - * [idempotent](http://en.wikipedia.org/wiki/Idempotence). - * - The `listener` is called only when the value from the current `watchExpression` and the - * previous call to `watchExpression` are not equal (with the exception of the initial run, - * see below). Inequality is determined according to reference inequality, - * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) - * via the `!==` Javascript operator, unless `objectEquality == true` - * (see next point) - * - When `objectEquality == true`, inequality of the `watchExpression` is determined - * according to the {@link angular.equals} function. To save the value of the object for - * later comparison, the {@link angular.copy} function is used. This therefore means that - * watching complex objects will have adverse memory and performance implications. - * - The watch `listener` may change the model, which may trigger other `listener`s to fire. - * This is achieved by rerunning the watchers until no changes are detected. The rerun - * iteration limit is 10 to prevent an infinite loop deadlock. - * - * - * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, - * you can register a `watchExpression` function with no `listener`. (Be prepared for - * multiple calls to your `watchExpression` because it will execute multiple times in a - * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) - * - * After a watcher is registered with the scope, the `listener` fn is called asynchronously - * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the - * watcher. In rare cases, this is undesirable because the listener is called when the result - * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you - * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the - * listener was called due to initialization. - * - * - * - * # Example - * ```js - // let's assume that scope was dependency injected as the $rootScope - var scope = $rootScope; - scope.name = 'misko'; - scope.counter = 0; - - expect(scope.counter).toEqual(0); - scope.$watch('name', function(newValue, oldValue) { - scope.counter = scope.counter + 1; - }); - expect(scope.counter).toEqual(0); - - scope.$digest(); - // the listener is always called during the first $digest loop after it was registered - expect(scope.counter).toEqual(1); - - scope.$digest(); - // but now it will not be called unless the value changes - expect(scope.counter).toEqual(1); - - scope.name = 'adam'; - scope.$digest(); - expect(scope.counter).toEqual(2); - - - - // Using a function as a watchExpression - var food; - scope.foodCounter = 0; - expect(scope.foodCounter).toEqual(0); - scope.$watch( - // This function returns the value being watched. It is called for each turn of the $digest loop - function() { return food; }, - // This is the change listener, called when the value returned from the above function changes - function(newValue, oldValue) { - if ( newValue !== oldValue ) { - // Only increment the counter if the value changed - scope.foodCounter = scope.foodCounter + 1; - } - } - ); - // No digest has been run so the counter will be zero - expect(scope.foodCounter).toEqual(0); - - // Run the digest but since food has not changed count will still be zero - scope.$digest(); - expect(scope.foodCounter).toEqual(0); - - // Update food and run digest. Now the counter will increment - food = 'cheeseburger'; - scope.$digest(); - expect(scope.foodCounter).toEqual(1); - - * ``` - * - * - * - * @param {(function()|string)} watchExpression Expression that is evaluated on each - * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers - * a call to the `listener`. - * - * - `string`: Evaluated as {@link guide/expression expression} - * - `function(scope)`: called with current `scope` as a parameter. - * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value - * of `watchExpression` changes. - * - * - `newVal` contains the current value of the `watchExpression` - * - `oldVal` contains the previous value of the `watchExpression` - * - `scope` refers to the current scope - * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of - * comparing for reference equality. - * @returns {function()} Returns a deregistration function for this listener. - */ - $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { - var get = $parse(watchExp); - - if (get.$$watchDelegate) { - return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); - } - var scope = this, - array = scope.$$watchers, - watcher = { - fn: listener, - last: initWatchVal, - get: get, - exp: prettyPrintExpression || watchExp, - eq: !!objectEquality - }; - - lastDirtyWatch = null; - - if (!isFunction(listener)) { - watcher.fn = noop; - } - - if (!array) { - array = scope.$$watchers = []; - } - // we use unshift since we use a while loop in $digest for speed. - // the while loop reads in reverse order. - array.unshift(watcher); - incrementWatchersCount(this, 1); - - return function deregisterWatch() { - if (arrayRemove(array, watcher) >= 0) { - incrementWatchersCount(scope, -1); - } - lastDirtyWatch = null; - }; - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$watchGroup - * @kind function - * - * @description - * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. - * If any one expression in the collection changes the `listener` is executed. - * - * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every - * call to $digest() to see if any items changes. - * - The `listener` is called whenever any expression in the `watchExpressions` array changes. - * - * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually - * watched using {@link ng.$rootScope.Scope#$watch $watch()} - * - * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any - * expression in `watchExpressions` changes - * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching - * those of `watchExpression` - * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching - * those of `watchExpression` - * The `scope` refers to the current scope. - * @returns {function()} Returns a de-registration function for all listeners. - */ - $watchGroup: function(watchExpressions, listener) { - var oldValues = new Array(watchExpressions.length); - var newValues = new Array(watchExpressions.length); - var deregisterFns = []; - var self = this; - var changeReactionScheduled = false; - var firstRun = true; - - if (!watchExpressions.length) { - // No expressions means we call the listener ASAP - var shouldCall = true; - self.$evalAsync(function() { - if (shouldCall) listener(newValues, newValues, self); - }); - return function deregisterWatchGroup() { - shouldCall = false; - }; - } - - if (watchExpressions.length === 1) { - // Special case size of one - return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { - newValues[0] = value; - oldValues[0] = oldValue; - listener(newValues, (value === oldValue) ? newValues : oldValues, scope); - }); - } - - forEach(watchExpressions, function(expr, i) { - var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { - newValues[i] = value; - oldValues[i] = oldValue; - if (!changeReactionScheduled) { - changeReactionScheduled = true; - self.$evalAsync(watchGroupAction); - } - }); - deregisterFns.push(unwatchFn); - }); - - function watchGroupAction() { - changeReactionScheduled = false; - - if (firstRun) { - firstRun = false; - listener(newValues, newValues, self); - } else { - listener(newValues, oldValues, self); - } - } - - return function deregisterWatchGroup() { - while (deregisterFns.length) { - deregisterFns.shift()(); - } - }; - }, - - - /** - * @ngdoc method - * @name $rootScope.Scope#$watchCollection - * @kind function - * - * @description - * Shallow watches the properties of an object and fires whenever any of the properties change - * (for arrays, this implies watching the array items; for object maps, this implies watching - * the properties). If a change is detected, the `listener` callback is fired. - * - * - The `obj` collection is observed via standard $watch operation and is examined on every - * call to $digest() to see if any items have been added, removed, or moved. - * - The `listener` is called whenever anything within the `obj` has changed. Examples include - * adding, removing, and moving items belonging to an object or array. - * - * - * # Example - * ```js - $scope.names = ['igor', 'matias', 'misko', 'james']; - $scope.dataCount = 4; - - $scope.$watchCollection('names', function(newNames, oldNames) { - $scope.dataCount = newNames.length; - }); - - expect($scope.dataCount).toEqual(4); - $scope.$digest(); - - //still at 4 ... no changes - expect($scope.dataCount).toEqual(4); - - $scope.names.pop(); - $scope.$digest(); - - //now there's been a change - expect($scope.dataCount).toEqual(3); - * ``` - * - * - * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The - * expression value should evaluate to an object or an array which is observed on each - * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the - * collection will trigger a call to the `listener`. - * - * @param {function(newCollection, oldCollection, scope)} listener a callback function called - * when a change is detected. - * - The `newCollection` object is the newly modified data obtained from the `obj` expression - * - The `oldCollection` object is a copy of the former collection data. - * Due to performance considerations, the`oldCollection` value is computed only if the - * `listener` function declares two or more arguments. - * - The `scope` argument refers to the current scope. - * - * @returns {function()} Returns a de-registration function for this listener. When the - * de-registration function is executed, the internal watch operation is terminated. - */ - $watchCollection: function(obj, listener) { - $watchCollectionInterceptor.$stateful = true; - - var self = this; - // the current value, updated on each dirty-check run - var newValue; - // a shallow copy of the newValue from the last dirty-check run, - // updated to match newValue during dirty-check run - var oldValue; - // a shallow copy of the newValue from when the last change happened - var veryOldValue; - // only track veryOldValue if the listener is asking for it - var trackVeryOldValue = (listener.length > 1); - var changeDetected = 0; - var changeDetector = $parse(obj, $watchCollectionInterceptor); - var internalArray = []; - var internalObject = {}; - var initRun = true; - var oldLength = 0; - - function $watchCollectionInterceptor(_value) { - newValue = _value; - var newLength, key, bothNaN, newItem, oldItem; - - // If the new value is undefined, then return undefined as the watch may be a one-time watch - if (isUndefined(newValue)) return; - - if (!isObject(newValue)) { // if primitive - if (oldValue !== newValue) { - oldValue = newValue; - changeDetected++; - } - } else if (isArrayLike(newValue)) { - if (oldValue !== internalArray) { - // we are transitioning from something which was not an array into array. - oldValue = internalArray; - oldLength = oldValue.length = 0; - changeDetected++; - } - - newLength = newValue.length; - - if (oldLength !== newLength) { - // if lengths do not match we need to trigger change notification - changeDetected++; - oldValue.length = oldLength = newLength; - } - // copy the items to oldValue and look for changes. - for (var i = 0; i < newLength; i++) { - oldItem = oldValue[i]; - newItem = newValue[i]; - - bothNaN = (oldItem !== oldItem) && (newItem !== newItem); - if (!bothNaN && (oldItem !== newItem)) { - changeDetected++; - oldValue[i] = newItem; - } - } - } else { - if (oldValue !== internalObject) { - // we are transitioning from something which was not an object into object. - oldValue = internalObject = {}; - oldLength = 0; - changeDetected++; - } - // copy the items to oldValue and look for changes. - newLength = 0; - for (key in newValue) { - if (hasOwnProperty.call(newValue, key)) { - newLength++; - newItem = newValue[key]; - oldItem = oldValue[key]; - - if (key in oldValue) { - bothNaN = (oldItem !== oldItem) && (newItem !== newItem); - if (!bothNaN && (oldItem !== newItem)) { - changeDetected++; - oldValue[key] = newItem; - } - } else { - oldLength++; - oldValue[key] = newItem; - changeDetected++; - } - } - } - if (oldLength > newLength) { - // we used to have more keys, need to find them and destroy them. - changeDetected++; - for (key in oldValue) { - if (!hasOwnProperty.call(newValue, key)) { - oldLength--; - delete oldValue[key]; - } - } - } - } - return changeDetected; - } - - function $watchCollectionAction() { - if (initRun) { - initRun = false; - listener(newValue, newValue, self); - } else { - listener(newValue, veryOldValue, self); - } - - // make a copy for the next time a collection is changed - if (trackVeryOldValue) { - if (!isObject(newValue)) { - //primitive - veryOldValue = newValue; - } else if (isArrayLike(newValue)) { - veryOldValue = new Array(newValue.length); - for (var i = 0; i < newValue.length; i++) { - veryOldValue[i] = newValue[i]; - } - } else { // if object - veryOldValue = {}; - for (var key in newValue) { - if (hasOwnProperty.call(newValue, key)) { - veryOldValue[key] = newValue[key]; - } - } - } - } - } - - return this.$watch(changeDetector, $watchCollectionAction); - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$digest - * @kind function - * - * @description - * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and - * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change - * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} - * until no more listeners are firing. This means that it is possible to get into an infinite - * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of - * iterations exceeds 10. - * - * Usually, you don't call `$digest()` directly in - * {@link ng.directive:ngController controllers} or in - * {@link ng.$compileProvider#directive directives}. - * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within - * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. - * - * If you want to be notified whenever `$digest()` is called, - * you can register a `watchExpression` function with - * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. - * - * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. - * - * # Example - * ```js - var scope = ...; - scope.name = 'misko'; - scope.counter = 0; - - expect(scope.counter).toEqual(0); - scope.$watch('name', function(newValue, oldValue) { - scope.counter = scope.counter + 1; - }); - expect(scope.counter).toEqual(0); - - scope.$digest(); - // the listener is always called during the first $digest loop after it was registered - expect(scope.counter).toEqual(1); - - scope.$digest(); - // but now it will not be called unless the value changes - expect(scope.counter).toEqual(1); - - scope.name = 'adam'; - scope.$digest(); - expect(scope.counter).toEqual(2); - * ``` - * - */ - $digest: function() { - var watch, value, last, - watchers, - length, - dirty, ttl = TTL, - next, current, target = this, - watchLog = [], - logIdx, logMsg, asyncTask; - - beginPhase('$digest'); - // Check for changes to browser url that happened in sync before the call to $digest - $browser.$$checkUrlChange(); - - if (this === $rootScope && applyAsyncId !== null) { - // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then - // cancel the scheduled $apply and flush the queue of expressions to be evaluated. - $browser.defer.cancel(applyAsyncId); - flushApplyAsync(); - } - - lastDirtyWatch = null; - - do { // "while dirty" loop - dirty = false; - current = target; - - while (asyncQueue.length) { - try { - asyncTask = asyncQueue.shift(); - asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); - } catch (e) { - $exceptionHandler(e); - } - lastDirtyWatch = null; - } - - traverseScopesLoop: - do { // "traverse the scopes" loop - if ((watchers = current.$$watchers)) { - // process our watches - length = watchers.length; - while (length--) { - try { - watch = watchers[length]; - // Most common watches are on primitives, in which case we can short - // circuit it with === operator, only when === fails do we use .equals - if (watch) { - if ((value = watch.get(current)) !== (last = watch.last) && - !(watch.eq - ? equals(value, last) - : (typeof value === 'number' && typeof last === 'number' - && isNaN(value) && isNaN(last)))) { - dirty = true; - lastDirtyWatch = watch; - watch.last = watch.eq ? copy(value, null) : value; - watch.fn(value, ((last === initWatchVal) ? value : last), current); - if (ttl < 5) { - logIdx = 4 - ttl; - if (!watchLog[logIdx]) watchLog[logIdx] = []; - watchLog[logIdx].push({ - msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, - newVal: value, - oldVal: last - }); - } - } else if (watch === lastDirtyWatch) { - // If the most recently dirty watcher is now clean, short circuit since the remaining watchers - // have already been tested. - dirty = false; - break traverseScopesLoop; - } - } - } catch (e) { - $exceptionHandler(e); - } - } - } - - // Insanity Warning: scope depth-first traversal - // yes, this code is a bit crazy, but it works and we have tests to prove it! - // this piece should be kept in sync with the traversal in $broadcast - if (!(next = ((current.$$watchersCount && current.$$childHead) || - (current !== target && current.$$nextSibling)))) { - while (current !== target && !(next = current.$$nextSibling)) { - current = current.$parent; - } - } - } while ((current = next)); - - // `break traverseScopesLoop;` takes us to here - - if ((dirty || asyncQueue.length) && !(ttl--)) { - clearPhase(); - throw $rootScopeMinErr('infdig', - '{0} $digest() iterations reached. Aborting!\n' + - 'Watchers fired in the last 5 iterations: {1}', - TTL, watchLog); - } - - } while (dirty || asyncQueue.length); - - clearPhase(); - - while (postDigestQueue.length) { - try { - postDigestQueue.shift()(); - } catch (e) { - $exceptionHandler(e); - } - } - }, - - - /** - * @ngdoc event - * @name $rootScope.Scope#$destroy - * @eventType broadcast on scope being destroyed - * - * @description - * Broadcasted when a scope and its children are being destroyed. - * - * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to - * clean up DOM bindings before an element is removed from the DOM. - */ - - /** - * @ngdoc method - * @name $rootScope.Scope#$destroy - * @kind function - * - * @description - * Removes the current scope (and all of its children) from the parent scope. Removal implies - * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer - * propagate to the current scope and its children. Removal also implies that the current - * scope is eligible for garbage collection. - * - * The `$destroy()` is usually used by directives such as - * {@link ng.directive:ngRepeat ngRepeat} for managing the - * unrolling of the loop. - * - * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. - * Application code can register a `$destroy` event handler that will give it a chance to - * perform any necessary cleanup. - * - * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to - * clean up DOM bindings before an element is removed from the DOM. - */ - $destroy: function() { - // We can't destroy a scope that has been already destroyed. - if (this.$$destroyed) return; - var parent = this.$parent; - - this.$broadcast('$destroy'); - this.$$destroyed = true; - - if (this === $rootScope) { - //Remove handlers attached to window when $rootScope is removed - $browser.$$applicationDestroyed(); - } - - incrementWatchersCount(this, -this.$$watchersCount); - for (var eventName in this.$$listenerCount) { - decrementListenerCount(this, this.$$listenerCount[eventName], eventName); - } - - // sever all the references to parent scopes (after this cleanup, the current scope should - // not be retained by any of our references and should be eligible for garbage collection) - if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; - if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; - if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; - if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; - - // Disable listeners, watchers and apply/digest methods - this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; - this.$on = this.$watch = this.$watchGroup = function() { return noop; }; - this.$$listeners = {}; - - // All of the code below is bogus code that works around V8's memory leak via optimized code - // and inline caches. - // - // see: - // - https://code.google.com/p/v8/issues/detail?id=2073#c26 - // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 - // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 - - this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = - this.$$childTail = this.$root = this.$$watchers = null; - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$eval - * @kind function - * - * @description - * Executes the `expression` on the current scope and returns the result. Any exceptions in - * the expression are propagated (uncaught). This is useful when evaluating Angular - * expressions. - * - * # Example - * ```js - var scope = ng.$rootScope.Scope(); - scope.a = 1; - scope.b = 2; - - expect(scope.$eval('a+b')).toEqual(3); - expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); - * ``` - * - * @param {(string|function())=} expression An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with the current `scope` parameter. - * - * @param {(object)=} locals Local variables object, useful for overriding values in scope. - * @returns {*} The result of evaluating the expression. - */ - $eval: function(expr, locals) { - return $parse(expr)(this, locals); - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$evalAsync - * @kind function - * - * @description - * Executes the expression on the current scope at a later point in time. - * - * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only - * that: - * - * - it will execute after the function that scheduled the evaluation (preferably before DOM - * rendering). - * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after - * `expression` execution. - * - * Any exceptions from the execution of the expression are forwarded to the - * {@link ng.$exceptionHandler $exceptionHandler} service. - * - * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle - * will be scheduled. However, it is encouraged to always call code that changes the model - * from within an `$apply` call. That includes code evaluated via `$evalAsync`. - * - * @param {(string|function())=} expression An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with the current `scope` parameter. - * - * @param {(object)=} locals Local variables object, useful for overriding values in scope. - */ - $evalAsync: function(expr, locals) { - // if we are outside of an $digest loop and this is the first time we are scheduling async - // task also schedule async auto-flush - if (!$rootScope.$$phase && !asyncQueue.length) { - $browser.defer(function() { - if (asyncQueue.length) { - $rootScope.$digest(); - } - }); - } - - asyncQueue.push({scope: this, expression: expr, locals: locals}); - }, - - $$postDigest: function(fn) { - postDigestQueue.push(fn); - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$apply - * @kind function - * - * @description - * `$apply()` is used to execute an expression in angular from outside of the angular - * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). - * Because we are calling into the angular framework we need to perform proper scope life - * cycle of {@link ng.$exceptionHandler exception handling}, - * {@link ng.$rootScope.Scope#$digest executing watches}. - * - * ## Life cycle - * - * # Pseudo-Code of `$apply()` - * ```js - function $apply(expr) { - try { - return $eval(expr); - } catch (e) { - $exceptionHandler(e); - } finally { - $root.$digest(); - } - } - * ``` - * - * - * Scope's `$apply()` method transitions through the following stages: - * - * 1. The {@link guide/expression expression} is executed using the - * {@link ng.$rootScope.Scope#$eval $eval()} method. - * 2. Any exceptions from the execution of the expression are forwarded to the - * {@link ng.$exceptionHandler $exceptionHandler} service. - * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the - * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. - * - * - * @param {(string|function())=} exp An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with current `scope` parameter. - * - * @returns {*} The result of evaluating the expression. - */ - $apply: function(expr) { - try { - beginPhase('$apply'); - try { - return this.$eval(expr); - } finally { - clearPhase(); - } - } catch (e) { - $exceptionHandler(e); - } finally { - try { - $rootScope.$digest(); - } catch (e) { - $exceptionHandler(e); - throw e; - } - } - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$applyAsync - * @kind function - * - * @description - * Schedule the invocation of $apply to occur at a later time. The actual time difference - * varies across browsers, but is typically around ~10 milliseconds. - * - * This can be used to queue up multiple expressions which need to be evaluated in the same - * digest. - * - * @param {(string|function())=} exp An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with current `scope` parameter. - */ - $applyAsync: function(expr) { - var scope = this; - expr && applyAsyncQueue.push($applyAsyncExpression); - scheduleApplyAsync(); - - function $applyAsyncExpression() { - scope.$eval(expr); - } - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$on - * @kind function - * - * @description - * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for - * discussion of event life cycle. - * - * The event listener function format is: `function(event, args...)`. The `event` object - * passed into the listener has the following attributes: - * - * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or - * `$broadcast`-ed. - * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the - * event propagates through the scope hierarchy, this property is set to null. - * - `name` - `{string}`: name of the event. - * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel - * further event propagation (available only for events that were `$emit`-ed). - * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag - * to true. - * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. - * - * @param {string} name Event name to listen on. - * @param {function(event, ...args)} listener Function to call when the event is emitted. - * @returns {function()} Returns a deregistration function for this listener. - */ - $on: function(name, listener) { - var namedListeners = this.$$listeners[name]; - if (!namedListeners) { - this.$$listeners[name] = namedListeners = []; - } - namedListeners.push(listener); - - var current = this; - do { - if (!current.$$listenerCount[name]) { - current.$$listenerCount[name] = 0; - } - current.$$listenerCount[name]++; - } while ((current = current.$parent)); - - var self = this; - return function() { - var indexOfListener = namedListeners.indexOf(listener); - if (indexOfListener !== -1) { - namedListeners[indexOfListener] = null; - decrementListenerCount(self, 1, name); - } - }; - }, - - - /** - * @ngdoc method - * @name $rootScope.Scope#$emit - * @kind function - * - * @description - * Dispatches an event `name` upwards through the scope hierarchy notifying the - * registered {@link ng.$rootScope.Scope#$on} listeners. - * - * The event life cycle starts at the scope on which `$emit` was called. All - * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get - * notified. Afterwards, the event traverses upwards toward the root scope and calls all - * registered listeners along the way. The event will stop propagating if one of the listeners - * cancels it. - * - * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed - * onto the {@link ng.$exceptionHandler $exceptionHandler} service. - * - * @param {string} name Event name to emit. - * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. - * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). - */ - $emit: function(name, args) { - var empty = [], - namedListeners, - scope = this, - stopPropagation = false, - event = { - name: name, - targetScope: scope, - stopPropagation: function() {stopPropagation = true;}, - preventDefault: function() { - event.defaultPrevented = true; - }, - defaultPrevented: false - }, - listenerArgs = concat([event], arguments, 1), - i, length; - - do { - namedListeners = scope.$$listeners[name] || empty; - event.currentScope = scope; - for (i = 0, length = namedListeners.length; i < length; i++) { - - // if listeners were deregistered, defragment the array - if (!namedListeners[i]) { - namedListeners.splice(i, 1); - i--; - length--; - continue; - } - try { - //allow all listeners attached to the current scope to run - namedListeners[i].apply(null, listenerArgs); - } catch (e) { - $exceptionHandler(e); - } - } - //if any listener on the current scope stops propagation, prevent bubbling - if (stopPropagation) { - event.currentScope = null; - return event; - } - //traverse upwards - scope = scope.$parent; - } while (scope); - - event.currentScope = null; - - return event; - }, - - - /** - * @ngdoc method - * @name $rootScope.Scope#$broadcast - * @kind function - * - * @description - * Dispatches an event `name` downwards to all child scopes (and their children) notifying the - * registered {@link ng.$rootScope.Scope#$on} listeners. - * - * The event life cycle starts at the scope on which `$broadcast` was called. All - * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get - * notified. Afterwards, the event propagates to all direct and indirect scopes of the current - * scope and calls all registered listeners along the way. The event cannot be canceled. - * - * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed - * onto the {@link ng.$exceptionHandler $exceptionHandler} service. - * - * @param {string} name Event name to broadcast. - * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. - * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} - */ - $broadcast: function(name, args) { - var target = this, - current = target, - next = target, - event = { - name: name, - targetScope: target, - preventDefault: function() { - event.defaultPrevented = true; - }, - defaultPrevented: false - }; - - if (!target.$$listenerCount[name]) return event; - - var listenerArgs = concat([event], arguments, 1), - listeners, i, length; - - //down while you can, then up and next sibling or up and next sibling until back at root - while ((current = next)) { - event.currentScope = current; - listeners = current.$$listeners[name] || []; - for (i = 0, length = listeners.length; i < length; i++) { - // if listeners were deregistered, defragment the array - if (!listeners[i]) { - listeners.splice(i, 1); - i--; - length--; - continue; - } - - try { - listeners[i].apply(null, listenerArgs); - } catch (e) { - $exceptionHandler(e); - } - } - - // Insanity Warning: scope depth-first traversal - // yes, this code is a bit crazy, but it works and we have tests to prove it! - // this piece should be kept in sync with the traversal in $digest - // (though it differs due to having the extra check for $$listenerCount) - if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || - (current !== target && current.$$nextSibling)))) { - while (current !== target && !(next = current.$$nextSibling)) { - current = current.$parent; - } - } - } - - event.currentScope = null; - return event; - } - }; - - var $rootScope = new Scope(); - - //The internal queues. Expose them on the $rootScope for debugging/testing purposes. - var asyncQueue = $rootScope.$$asyncQueue = []; - var postDigestQueue = $rootScope.$$postDigestQueue = []; - var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; - - return $rootScope; - - - function beginPhase(phase) { - if ($rootScope.$$phase) { - throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); - } - - $rootScope.$$phase = phase; - } - - function clearPhase() { - $rootScope.$$phase = null; - } - - function incrementWatchersCount(current, count) { - do { - current.$$watchersCount += count; - } while ((current = current.$parent)); - } - - function decrementListenerCount(current, count, name) { - do { - current.$$listenerCount[name] -= count; - - if (current.$$listenerCount[name] === 0) { - delete current.$$listenerCount[name]; - } - } while ((current = current.$parent)); - } - - /** - * function used as an initial value for watchers. - * because it's unique we can easily tell it apart from other values - */ - function initWatchVal() {} - - function flushApplyAsync() { - while (applyAsyncQueue.length) { - try { - applyAsyncQueue.shift()(); - } catch (e) { - $exceptionHandler(e); - } - } - applyAsyncId = null; - } - - function scheduleApplyAsync() { - if (applyAsyncId === null) { - applyAsyncId = $browser.defer(function() { - $rootScope.$apply(flushApplyAsync); - }); - } - } - }]; -} - -/** - * @description - * Private service to sanitize uris for links and images. Used by $compile and $sanitize. - */ -function $$SanitizeUriProvider() { - var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, - imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; - - /** - * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during a[href] sanitization. - * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. - * - * Any url about to be assigned to a[href] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.aHrefSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - aHrefSanitizationWhitelist = regexp; - return this; - } - return aHrefSanitizationWhitelist; - }; - - - /** - * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during img[src] sanitization. - * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. - * - * Any url about to be assigned to img[src] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.imgSrcSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - imgSrcSanitizationWhitelist = regexp; - return this; - } - return imgSrcSanitizationWhitelist; - }; - - this.$get = function() { - return function sanitizeUri(uri, isImage) { - var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; - var normalizedVal; - normalizedVal = urlResolve(uri).href; - if (normalizedVal !== '' && !normalizedVal.match(regex)) { - return 'unsafe:' + normalizedVal; - } - return uri; - }; - }; -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Any commits to this file should be reviewed with security in mind. * - * Changes to this file can potentially create security vulnerabilities. * - * An approval from 2 Core members with history of modifying * - * this file is required. * - * * - * Does the change somehow allow for arbitrary javascript to be executed? * - * Or allows for someone to change the prototype of built-in objects? * - * Or gives undesired access to variables likes document or window? * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -var $sceMinErr = minErr('$sce'); - -var SCE_CONTEXTS = { - HTML: 'html', - CSS: 'css', - URL: 'url', - // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a - // url. (e.g. ng-include, script src, templateUrl) - RESOURCE_URL: 'resourceUrl', - JS: 'js' -}; - -// Helper functions follow. - -function adjustMatcher(matcher) { - if (matcher === 'self') { - return matcher; - } else if (isString(matcher)) { - // Strings match exactly except for 2 wildcards - '*' and '**'. - // '*' matches any character except those from the set ':/.?&'. - // '**' matches any character (like .* in a RegExp). - // More than 2 *'s raises an error as it's ill defined. - if (matcher.indexOf('***') > -1) { - throw $sceMinErr('iwcard', - 'Illegal sequence *** in string matcher. String: {0}', matcher); - } - matcher = escapeForRegexp(matcher). - replace('\\*\\*', '.*'). - replace('\\*', '[^:/.?&;]*'); - return new RegExp('^' + matcher + '$'); - } else if (isRegExp(matcher)) { - // The only other type of matcher allowed is a Regexp. - // Match entire URL / disallow partial matches. - // Flags are reset (i.e. no global, ignoreCase or multiline) - return new RegExp('^' + matcher.source + '$'); - } else { - throw $sceMinErr('imatcher', - 'Matchers may only be "self", string patterns or RegExp objects'); - } -} - - -function adjustMatchers(matchers) { - var adjustedMatchers = []; - if (isDefined(matchers)) { - forEach(matchers, function(matcher) { - adjustedMatchers.push(adjustMatcher(matcher)); - }); - } - return adjustedMatchers; -} - - -/** - * @ngdoc service - * @name $sceDelegate - * @kind function - * - * @description - * - * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict - * Contextual Escaping (SCE)} services to AngularJS. - * - * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of - * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is - * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to - * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things - * work because `$sce` delegates to `$sceDelegate` for these operations. - * - * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. - * - * The default instance of `$sceDelegate` should work out of the box with little pain. While you - * can override it completely to change the behavior of `$sce`, the common case would - * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting - * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as - * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist - * $sceDelegateProvider.resourceUrlWhitelist} and {@link - * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} - */ - -/** - * @ngdoc provider - * @name $sceDelegateProvider - * @description - * - * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate - * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure - * that the URLs used for sourcing Angular templates are safe. Refer {@link - * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and - * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} - * - * For the general details about this service in Angular, read the main page for {@link ng.$sce - * Strict Contextual Escaping (SCE)}. - * - * **Example**: Consider the following case. <a name="example"></a> - * - * - your app is hosted at url `http://myapp.example.com/` - * - but some of your templates are hosted on other domains you control such as - * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. - * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. - * - * Here is what a secure configuration for this scenario might look like: - * - * ``` - * angular.module('myApp', []).config(function($sceDelegateProvider) { - * $sceDelegateProvider.resourceUrlWhitelist([ - * // Allow same origin resource loads. - * 'self', - * // Allow loading from our assets domain. Notice the difference between * and **. - * 'http://srv*.assets.example.com/**' - * ]); - * - * // The blacklist overrides the whitelist so the open redirect here is blocked. - * $sceDelegateProvider.resourceUrlBlacklist([ - * 'http://myapp.example.com/clickThru**' - * ]); - * }); - * ``` - */ - -function $SceDelegateProvider() { - this.SCE_CONTEXTS = SCE_CONTEXTS; - - // Resource URLs can also be trusted by policy. - var resourceUrlWhitelist = ['self'], - resourceUrlBlacklist = []; - - /** - * @ngdoc method - * @name $sceDelegateProvider#resourceUrlWhitelist - * @kind function - * - * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value - * provided. This must be an array or null. A snapshot of this array is used so further - * changes to the array are ignored. - * - * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items - * allowed in this array. - * - * Note: **an empty whitelist array will block all URLs**! - * - * @return {Array} the currently set whitelist array. - * - * The **default value** when no whitelist has been explicitly set is `['self']` allowing only - * same origin resource requests. - * - * @description - * Sets/Gets the whitelist of trusted resource URLs. - */ - this.resourceUrlWhitelist = function(value) { - if (arguments.length) { - resourceUrlWhitelist = adjustMatchers(value); - } - return resourceUrlWhitelist; - }; - - /** - * @ngdoc method - * @name $sceDelegateProvider#resourceUrlBlacklist - * @kind function - * - * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value - * provided. This must be an array or null. A snapshot of this array is used so further - * changes to the array are ignored. - * - * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items - * allowed in this array. - * - * The typical usage for the blacklist is to **block - * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as - * these would otherwise be trusted but actually return content from the redirected domain. - * - * Finally, **the blacklist overrides the whitelist** and has the final say. - * - * @return {Array} the currently set blacklist array. - * - * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there - * is no blacklist.) - * - * @description - * Sets/Gets the blacklist of trusted resource URLs. - */ - - this.resourceUrlBlacklist = function(value) { - if (arguments.length) { - resourceUrlBlacklist = adjustMatchers(value); - } - return resourceUrlBlacklist; - }; - - this.$get = ['$injector', function($injector) { - - var htmlSanitizer = function htmlSanitizer(html) { - throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); - }; - - if ($injector.has('$sanitize')) { - htmlSanitizer = $injector.get('$sanitize'); - } - - - function matchUrl(matcher, parsedUrl) { - if (matcher === 'self') { - return urlIsSameOrigin(parsedUrl); - } else { - // definitely a regex. See adjustMatchers() - return !!matcher.exec(parsedUrl.href); - } - } - - function isResourceUrlAllowedByPolicy(url) { - var parsedUrl = urlResolve(url.toString()); - var i, n, allowed = false; - // Ensure that at least one item from the whitelist allows this url. - for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { - if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { - allowed = true; - break; - } - } - if (allowed) { - // Ensure that no item from the blacklist blocked this url. - for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { - if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { - allowed = false; - break; - } - } - } - return allowed; - } - - function generateHolderType(Base) { - var holderType = function TrustedValueHolderType(trustedValue) { - this.$$unwrapTrustedValue = function() { - return trustedValue; - }; - }; - if (Base) { - holderType.prototype = new Base(); - } - holderType.prototype.valueOf = function sceValueOf() { - return this.$$unwrapTrustedValue(); - }; - holderType.prototype.toString = function sceToString() { - return this.$$unwrapTrustedValue().toString(); - }; - return holderType; - } - - var trustedValueHolderBase = generateHolderType(), - byType = {}; - - byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); - - /** - * @ngdoc method - * @name $sceDelegate#trustAs - * - * @description - * Returns an object that is trusted by angular for use in specified strict - * contextual escaping contexts (such as ng-bind-html, ng-include, any src - * attribute interpolation, any dom event binding attribute interpolation - * such as for onclick, etc.) that uses the provided value. - * See {@link ng.$sce $sce} for enabling strict contextual escaping. - * - * @param {string} type The kind of context in which this value is safe for use. e.g. url, - * resourceUrl, html, js and css. - * @param {*} value The value that that should be considered trusted/safe. - * @returns {*} A value that can be used to stand in for the provided `value` in places - * where Angular expects a $sce.trustAs() return value. - */ - function trustAs(type, trustedValue) { - var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); - if (!Constructor) { - throw $sceMinErr('icontext', - 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', - type, trustedValue); - } - if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { - return trustedValue; - } - // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting - // mutable objects, we ensure here that the value passed in is actually a string. - if (typeof trustedValue !== 'string') { - throw $sceMinErr('itype', - 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', - type); - } - return new Constructor(trustedValue); - } - - /** - * @ngdoc method - * @name $sceDelegate#valueOf - * - * @description - * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link - * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. - * - * If the passed parameter is not a value that had been returned by {@link - * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. - * - * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} - * call or anything else. - * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns - * `value` unchanged. - */ - function valueOf(maybeTrusted) { - if (maybeTrusted instanceof trustedValueHolderBase) { - return maybeTrusted.$$unwrapTrustedValue(); - } else { - return maybeTrusted; - } - } - - /** - * @ngdoc method - * @name $sceDelegate#getTrusted - * - * @description - * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and - * returns the originally supplied value if the queried context type is a supertype of the - * created type. If this condition isn't satisfied, throws an exception. - * - * @param {string} type The kind of context in which this value is to be used. - * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} call. - * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. - */ - function getTrusted(type, maybeTrusted) { - if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { - return maybeTrusted; - } - var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); - if (constructor && maybeTrusted instanceof constructor) { - return maybeTrusted.$$unwrapTrustedValue(); - } - // If we get here, then we may only take one of two actions. - // 1. sanitize the value for the requested type, or - // 2. throw an exception. - if (type === SCE_CONTEXTS.RESOURCE_URL) { - if (isResourceUrlAllowedByPolicy(maybeTrusted)) { - return maybeTrusted; - } else { - throw $sceMinErr('insecurl', - 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', - maybeTrusted.toString()); - } - } else if (type === SCE_CONTEXTS.HTML) { - return htmlSanitizer(maybeTrusted); - } - throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); - } - - return { trustAs: trustAs, - getTrusted: getTrusted, - valueOf: valueOf }; - }]; -} - - -/** - * @ngdoc provider - * @name $sceProvider - * @description - * - * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. - * - enable/disable Strict Contextual Escaping (SCE) in a module - * - override the default implementation with a custom delegate - * - * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. - */ - -/* jshint maxlen: false*/ - -/** - * @ngdoc service - * @name $sce - * @kind function - * - * @description - * - * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. - * - * # Strict Contextual Escaping - * - * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain - * contexts to result in a value that is marked as safe to use for that context. One example of - * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer - * to these contexts as privileged or SCE contexts. - * - * As of version 1.2, Angular ships with SCE enabled by default. - * - * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow - * one to execute arbitrary javascript by the use of the expression() syntax. Refer - * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them. - * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` - * to the top of your HTML document. - * - * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for - * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. - * - * Here's an example of a binding in a privileged context: - * - * ``` - * <input ng-model="userHtml" aria-label="User input"> - * <div ng-bind-html="userHtml"></div> - * ``` - * - * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE - * disabled, this application allows the user to render arbitrary HTML into the DIV. - * In a more realistic example, one may be rendering user comments, blog articles, etc. via - * bindings. (HTML is just one example of a context where rendering user controlled input creates - * security vulnerabilities.) - * - * For the case of HTML, you might use a library, either on the client side, or on the server side, - * to sanitize unsafe HTML before binding to the value and rendering it in the document. - * - * How would you ensure that every place that used these types of bindings was bound to a value that - * was sanitized by your library (or returned as safe for rendering by your server?) How can you - * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some - * properties/fields and forgot to update the binding to the sanitized value? - * - * To be secure by default, you want to ensure that any such bindings are disallowed unless you can - * determine that something explicitly says it's safe to use a value for binding in that - * context. You can then audit your code (a simple grep would do) to ensure that this is only done - * for those values that you can easily tell are safe - because they were received from your server, - * sanitized by your library, etc. You can organize your codebase to help with this - perhaps - * allowing only the files in a specific directory to do this. Ensuring that the internal API - * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. - * - * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} - * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to - * obtain values that will be accepted by SCE / privileged contexts. - * - * - * ## How does it work? - * - * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted - * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link - * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the - * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. - * - * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link - * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly - * simplified): - * - * ``` - * var ngBindHtmlDirective = ['$sce', function($sce) { - * return function(scope, element, attr) { - * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { - * element.html(value || ''); - * }); - * }; - * }]; - * ``` - * - * ## Impact on loading templates - * - * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as - * `templateUrl`'s specified by {@link guide/directive directives}. - * - * By default, Angular only loads templates from the same domain and protocol as the application - * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl - * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or - * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist - * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. - * - * *Please note*: - * The browser's - * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) - * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) - * policy apply in addition to this and may further restrict whether the template is successfully - * loaded. This means that without the right CORS policy, loading templates from a different domain - * won't work on all browsers. Also, loading templates from `file://` URL does not work on some - * browsers. - * - * ## This feels like too much overhead - * - * It's important to remember that SCE only applies to interpolation expressions. - * - * If your expressions are constant literals, they're automatically trusted and you don't need to - * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. - * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. - * - * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them - * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. - * - * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load - * templates in `ng-include` from your application's domain without having to even know about SCE. - * It blocks loading templates from other domains or loading templates over http from an https - * served document. You can change these by setting your own custom {@link - * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link - * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. - * - * This significantly reduces the overhead. It is far easier to pay the small overhead and have an - * application that's secure and can be audited to verify that with much more ease than bolting - * security onto an application later. - * - * <a name="contexts"></a> - * ## What trusted context types are supported? - * - * | Context | Notes | - * |---------------------|----------------| - * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | - * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | - * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't constitute an SCE context. | - * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | - * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | - * - * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> - * - * Each element in these arrays must be one of the following: - * - * - **'self'** - * - The special **string**, `'self'`, can be used to match against all URLs of the **same - * domain** as the application document using the **same protocol**. - * - **String** (except the special value `'self'`) - * - The string is matched against the full *normalized / absolute URL* of the resource - * being tested (substring matches are not good enough.) - * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters - * match themselves. - * - `*`: matches zero or more occurrences of any character other than one of the following 6 - * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use - * in a whitelist. - * - `**`: matches zero or more occurrences of *any* character. As such, it's not - * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. - * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might - * not have been the intention.) Its usage at the very end of the path is ok. (e.g. - * http://foo.example.com/templates/**). - * - **RegExp** (*see caveat below*) - * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax - * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to - * accidentally introduce a bug when one updates a complex expression (imho, all regexes should - * have good test coverage). For instance, the use of `.` in the regex is correct only in a - * small number of cases. A `.` character in the regex used when matching the scheme or a - * subdomain could be matched against a `:` or literal `.` that was likely not intended. It - * is highly recommended to use the string patterns and only fall back to regular expressions - * as a last resort. - * - The regular expression must be an instance of RegExp (i.e. not a string.) It is - * matched against the **entire** *normalized / absolute URL* of the resource being tested - * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags - * present on the RegExp (such as multiline, global, ignoreCase) are ignored. - * - If you are generating your JavaScript from some other templating engine (not - * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), - * remember to escape your regular expression (and be aware that you might need more than - * one level of escaping depending on your templating engine and the way you interpolated - * the value.) Do make use of your platform's escaping mechanism as it might be good - * enough before coding your own. E.g. Ruby has - * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) - * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). - * Javascript lacks a similar built in function for escaping. Take a look at Google - * Closure library's [goog.string.regExpEscape(s)]( - * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). - * - * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. - * - * ## Show me an example using SCE. - * - * <example module="mySceApp" deps="angular-sanitize.js"> - * <file name="index.html"> - * <div ng-controller="AppController as myCtrl"> - * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> - * <b>User comments</b><br> - * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when - * $sanitize is available. If $sanitize isn't available, this results in an error instead of an - * exploit. - * <div class="well"> - * <div ng-repeat="userComment in myCtrl.userComments"> - * <b>{{userComment.name}}</b>: - * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> - * <br> - * </div> - * </div> - * </div> - * </file> - * - * <file name="script.js"> - * angular.module('mySceApp', ['ngSanitize']) - * .controller('AppController', ['$http', '$templateCache', '$sce', - * function($http, $templateCache, $sce) { - * var self = this; - * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { - * self.userComments = userComments; - * }); - * self.explicitlyTrustedHtml = $sce.trustAsHtml( - * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + - * 'sanitization."">Hover over this text.</span>'); - * }]); - * </file> - * - * <file name="test_data.json"> - * [ - * { "name": "Alice", - * "htmlComment": - * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" - * }, - * { "name": "Bob", - * "htmlComment": "<i>Yes!</i> Am I the only other one?" - * } - * ] - * </file> - * - * <file name="protractor.js" type="protractor"> - * describe('SCE doc demo', function() { - * it('should sanitize untrusted values', function() { - * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) - * .toBe('<span>Is <i>anyone</i> reading this?</span>'); - * }); - * - * it('should NOT sanitize explicitly trusted values', function() { - * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( - * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + - * 'sanitization."">Hover over this text.</span>'); - * }); - * }); - * </file> - * </example> - * - * - * - * ## Can I disable SCE completely? - * - * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits - * for little coding overhead. It will be much harder to take an SCE disabled application and - * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE - * for cases where you have a lot of existing code that was written before SCE was introduced and - * you're migrating them a module at a time. - * - * That said, here's how you can completely disable SCE: - * - * ``` - * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { - * // Completely disable SCE. For demonstration purposes only! - * // Do not use in new projects. - * $sceProvider.enabled(false); - * }); - * ``` - * - */ -/* jshint maxlen: 100 */ - -function $SceProvider() { - var enabled = true; - - /** - * @ngdoc method - * @name $sceProvider#enabled - * @kind function - * - * @param {boolean=} value If provided, then enables/disables SCE. - * @return {boolean} true if SCE is enabled, false otherwise. - * - * @description - * Enables/disables SCE and returns the current value. - */ - this.enabled = function(value) { - if (arguments.length) { - enabled = !!value; - } - return enabled; - }; - - - /* Design notes on the default implementation for SCE. - * - * The API contract for the SCE delegate - * ------------------------------------- - * The SCE delegate object must provide the following 3 methods: - * - * - trustAs(contextEnum, value) - * This method is used to tell the SCE service that the provided value is OK to use in the - * contexts specified by contextEnum. It must return an object that will be accepted by - * getTrusted() for a compatible contextEnum and return this value. - * - * - valueOf(value) - * For values that were not produced by trustAs(), return them as is. For values that were - * produced by trustAs(), return the corresponding input value to trustAs. Basically, if - * trustAs is wrapping the given values into some type, this operation unwraps it when given - * such a value. - * - * - getTrusted(contextEnum, value) - * This function should return the a value that is safe to use in the context specified by - * contextEnum or throw and exception otherwise. - * - * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be - * opaque or wrapped in some holder object. That happens to be an implementation detail. For - * instance, an implementation could maintain a registry of all trusted objects by context. In - * such a case, trustAs() would return the same object that was passed in. getTrusted() would - * return the same object passed in if it was found in the registry under a compatible context or - * throw an exception otherwise. An implementation might only wrap values some of the time based - * on some criteria. getTrusted() might return a value and not throw an exception for special - * constants or objects even if not wrapped. All such implementations fulfill this contract. - * - * - * A note on the inheritance model for SCE contexts - * ------------------------------------------------ - * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This - * is purely an implementation details. - * - * The contract is simply this: - * - * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) - * will also succeed. - * - * Inheritance happens to capture this in a natural way. In some future, we - * may not use inheritance anymore. That is OK because no code outside of - * sce.js and sceSpecs.js would need to be aware of this detail. - */ - - this.$get = ['$parse', '$sceDelegate', function( - $parse, $sceDelegate) { - // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow - // the "expression(javascript expression)" syntax which is insecure. - if (enabled && msie < 8) { - throw $sceMinErr('iequirks', - 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + - 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + - 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); - } - - var sce = shallowCopy(SCE_CONTEXTS); - - /** - * @ngdoc method - * @name $sce#isEnabled - * @kind function - * - * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you - * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. - * - * @description - * Returns a boolean indicating if SCE is enabled. - */ - sce.isEnabled = function() { - return enabled; - }; - sce.trustAs = $sceDelegate.trustAs; - sce.getTrusted = $sceDelegate.getTrusted; - sce.valueOf = $sceDelegate.valueOf; - - if (!enabled) { - sce.trustAs = sce.getTrusted = function(type, value) { return value; }; - sce.valueOf = identity; - } - - /** - * @ngdoc method - * @name $sce#parseAs - * - * @description - * Converts Angular {@link guide/expression expression} into a function. This is like {@link - * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it - * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, - * *result*)} - * - * @param {string} type The kind of SCE context in which this result will be used. - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - sce.parseAs = function sceParseAs(type, expr) { - var parsed = $parse(expr); - if (parsed.literal && parsed.constant) { - return parsed; - } else { - return $parse(expr, function(value) { - return sce.getTrusted(type, value); - }); - } - }; - - /** - * @ngdoc method - * @name $sce#trustAs - * - * @description - * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, - * returns an object that is trusted by angular for use in specified strict contextual - * escaping contexts (such as ng-bind-html, ng-include, any src attribute - * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) - * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual - * escaping. - * - * @param {string} type The kind of context in which this value is safe for use. e.g. url, - * resourceUrl, html, js and css. - * @param {*} value The value that that should be considered trusted/safe. - * @returns {*} A value that can be used to stand in for the provided `value` in places - * where Angular expects a $sce.trustAs() return value. - */ - - /** - * @ngdoc method - * @name $sce#trustAsHtml - * - * @description - * Shorthand method. `$sce.trustAsHtml(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml - * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#trustAsUrl - * - * @description - * Shorthand method. `$sce.trustAsUrl(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl - * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#trustAsResourceUrl - * - * @description - * Shorthand method. `$sce.trustAsResourceUrl(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl - * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the return - * value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#trustAsJs - * - * @description - * Shorthand method. `$sce.trustAsJs(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs - * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#getTrusted - * - * @description - * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, - * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the - * originally supplied value if the queried context type is a supertype of the created type. - * If this condition isn't satisfied, throws an exception. - * - * @param {string} type The kind of context in which this value is to be used. - * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} - * call. - * @returns {*} The value the was originally provided to - * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. - * Otherwise, throws an exception. - */ - - /** - * @ngdoc method - * @name $sce#getTrustedHtml - * - * @description - * Shorthand method. `$sce.getTrustedHtml(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedCss - * - * @description - * Shorthand method. `$sce.getTrustedCss(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedUrl - * - * @description - * Shorthand method. `$sce.getTrustedUrl(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedResourceUrl - * - * @description - * Shorthand method. `$sce.getTrustedResourceUrl(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} - * - * @param {*} value The value to pass to `$sceDelegate.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedJs - * - * @description - * Shorthand method. `$sce.getTrustedJs(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` - */ - - /** - * @ngdoc method - * @name $sce#parseAsHtml - * - * @description - * Shorthand method. `$sce.parseAsHtml(expression string)` → - * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsCss - * - * @description - * Shorthand method. `$sce.parseAsCss(value)` → - * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsUrl - * - * @description - * Shorthand method. `$sce.parseAsUrl(value)` → - * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsResourceUrl - * - * @description - * Shorthand method. `$sce.parseAsResourceUrl(value)` → - * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsJs - * - * @description - * Shorthand method. `$sce.parseAsJs(value)` → - * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - // Shorthand delegations. - var parse = sce.parseAs, - getTrusted = sce.getTrusted, - trustAs = sce.trustAs; - - forEach(SCE_CONTEXTS, function(enumValue, name) { - var lName = lowercase(name); - sce[camelCase("parse_as_" + lName)] = function(expr) { - return parse(enumValue, expr); - }; - sce[camelCase("get_trusted_" + lName)] = function(value) { - return getTrusted(enumValue, value); - }; - sce[camelCase("trust_as_" + lName)] = function(value) { - return trustAs(enumValue, value); - }; - }); - - return sce; - }]; -} - -/** - * !!! This is an undocumented "private" service !!! - * - * @name $sniffer - * @requires $window - * @requires $document - * - * @property {boolean} history Does the browser support html5 history api ? - * @property {boolean} transitions Does the browser support CSS transition events ? - * @property {boolean} animations Does the browser support CSS animation events ? - * - * @description - * This is very simple implementation of testing browser's features. - */ -function $SnifferProvider() { - this.$get = ['$window', '$document', function($window, $document) { - var eventSupport = {}, - android = - toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), - boxee = /Boxee/i.test(($window.navigator || {}).userAgent), - document = $document[0] || {}, - vendorPrefix, - vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, - bodyStyle = document.body && document.body.style, - transitions = false, - animations = false, - match; - - if (bodyStyle) { - for (var prop in bodyStyle) { - if (match = vendorRegex.exec(prop)) { - vendorPrefix = match[0]; - vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); - break; - } - } - - if (!vendorPrefix) { - vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; - } - - transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); - animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); - - if (android && (!transitions || !animations)) { - transitions = isString(bodyStyle.webkitTransition); - animations = isString(bodyStyle.webkitAnimation); - } - } - - - return { - // Android has history.pushState, but it does not update location correctly - // so let's not use the history API at all. - // http://code.google.com/p/android/issues/detail?id=17471 - // https://github.com/angular/angular.js/issues/904 - - // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has - // so let's not use the history API also - // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined - // jshint -W018 - history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), - // jshint +W018 - hasEvent: function(event) { - // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have - // it. In particular the event is not fired when backspace or delete key are pressed or - // when cut operation is performed. - // IE10+ implements 'input' event but it erroneously fires under various situations, - // e.g. when placeholder changes, or a form is focused. - if (event === 'input' && msie <= 11) return false; - - if (isUndefined(eventSupport[event])) { - var divElm = document.createElement('div'); - eventSupport[event] = 'on' + event in divElm; - } - - return eventSupport[event]; - }, - csp: csp(), - vendorPrefix: vendorPrefix, - transitions: transitions, - animations: animations, - android: android - }; - }]; -} - -var $compileMinErr = minErr('$compile'); - -/** - * @ngdoc service - * @name $templateRequest - * - * @description - * The `$templateRequest` service runs security checks then downloads the provided template using - * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request - * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the - * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the - * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted - * when `tpl` is of type string and `$templateCache` has the matching entry. - * - * @param {string|TrustedResourceUrl} tpl The HTTP request template URL - * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty - * - * @return {Promise} a promise for the HTTP response data of the given URL. - * - * @property {number} totalPendingRequests total amount of pending template requests being downloaded. - */ -function $TemplateRequestProvider() { - this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { - function handleRequestFn(tpl, ignoreRequestError) { - handleRequestFn.totalPendingRequests++; - - // We consider the template cache holds only trusted templates, so - // there's no need to go through whitelisting again for keys that already - // are included in there. This also makes Angular accept any script - // directive, no matter its name. However, we still need to unwrap trusted - // types. - if (!isString(tpl) || !$templateCache.get(tpl)) { - tpl = $sce.getTrustedResourceUrl(tpl); - } - - var transformResponse = $http.defaults && $http.defaults.transformResponse; - - if (isArray(transformResponse)) { - transformResponse = transformResponse.filter(function(transformer) { - return transformer !== defaultHttpResponseTransform; - }); - } else if (transformResponse === defaultHttpResponseTransform) { - transformResponse = null; - } - - var httpOptions = { - cache: $templateCache, - transformResponse: transformResponse - }; - - return $http.get(tpl, httpOptions) - ['finally'](function() { - handleRequestFn.totalPendingRequests--; - }) - .then(function(response) { - $templateCache.put(tpl, response.data); - return response.data; - }, handleError); - - function handleError(resp) { - if (!ignoreRequestError) { - throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', - tpl, resp.status, resp.statusText); - } - return $q.reject(resp); - } - } - - handleRequestFn.totalPendingRequests = 0; - - return handleRequestFn; - }]; -} - -function $$TestabilityProvider() { - this.$get = ['$rootScope', '$browser', '$location', - function($rootScope, $browser, $location) { - - /** - * @name $testability - * - * @description - * The private $$testability service provides a collection of methods for use when debugging - * or by automated test and debugging tools. - */ - var testability = {}; - - /** - * @name $$testability#findBindings - * - * @description - * Returns an array of elements that are bound (via ng-bind or {{}}) - * to expressions matching the input. - * - * @param {Element} element The element root to search from. - * @param {string} expression The binding expression to match. - * @param {boolean} opt_exactMatch If true, only returns exact matches - * for the expression. Filters and whitespace are ignored. - */ - testability.findBindings = function(element, expression, opt_exactMatch) { - var bindings = element.getElementsByClassName('ng-binding'); - var matches = []; - forEach(bindings, function(binding) { - var dataBinding = angular.element(binding).data('$binding'); - if (dataBinding) { - forEach(dataBinding, function(bindingName) { - if (opt_exactMatch) { - var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); - if (matcher.test(bindingName)) { - matches.push(binding); - } - } else { - if (bindingName.indexOf(expression) != -1) { - matches.push(binding); - } - } - }); - } - }); - return matches; - }; - - /** - * @name $$testability#findModels - * - * @description - * Returns an array of elements that are two-way found via ng-model to - * expressions matching the input. - * - * @param {Element} element The element root to search from. - * @param {string} expression The model expression to match. - * @param {boolean} opt_exactMatch If true, only returns exact matches - * for the expression. - */ - testability.findModels = function(element, expression, opt_exactMatch) { - var prefixes = ['ng-', 'data-ng-', 'ng\\:']; - for (var p = 0; p < prefixes.length; ++p) { - var attributeEquals = opt_exactMatch ? '=' : '*='; - var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; - var elements = element.querySelectorAll(selector); - if (elements.length) { - return elements; - } - } - }; - - /** - * @name $$testability#getLocation - * - * @description - * Shortcut for getting the location in a browser agnostic way. Returns - * the path, search, and hash. (e.g. /path?a=b#hash) - */ - testability.getLocation = function() { - return $location.url(); - }; - - /** - * @name $$testability#setLocation - * - * @description - * Shortcut for navigating to a location without doing a full page reload. - * - * @param {string} url The location url (path, search and hash, - * e.g. /path?a=b#hash) to go to. - */ - testability.setLocation = function(url) { - if (url !== $location.url()) { - $location.url(url); - $rootScope.$digest(); - } - }; - - /** - * @name $$testability#whenStable - * - * @description - * Calls the callback when $timeout and $http requests are completed. - * - * @param {function} callback - */ - testability.whenStable = function(callback) { - $browser.notifyWhenNoOutstandingRequests(callback); - }; - - return testability; - }]; -} - -function $TimeoutProvider() { - this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', - function($rootScope, $browser, $q, $$q, $exceptionHandler) { - - var deferreds = {}; - - - /** - * @ngdoc service - * @name $timeout - * - * @description - * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch - * block and delegates any exceptions to - * {@link ng.$exceptionHandler $exceptionHandler} service. - * - * The return value of calling `$timeout` is a promise, which will be resolved when - * the delay has passed and the timeout function, if provided, is executed. - * - * To cancel a timeout request, call `$timeout.cancel(promise)`. - * - * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to - * synchronously flush the queue of deferred functions. - * - * If you only want a promise that will be resolved after some specified delay - * then you can call `$timeout` without the `fn` function. - * - * @param {function()=} fn A function, whose execution should be delayed. - * @param {number=} [delay=0] Delay in milliseconds. - * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise - * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. - * @param {...*=} Pass additional parameters to the executed function. - * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this - * promise will be resolved with is the return value of the `fn` function. - * - */ - function timeout(fn, delay, invokeApply) { - if (!isFunction(fn)) { - invokeApply = delay; - delay = fn; - fn = noop; - } - - var args = sliceArgs(arguments, 3), - skipApply = (isDefined(invokeApply) && !invokeApply), - deferred = (skipApply ? $$q : $q).defer(), - promise = deferred.promise, - timeoutId; - - timeoutId = $browser.defer(function() { - try { - deferred.resolve(fn.apply(null, args)); - } catch (e) { - deferred.reject(e); - $exceptionHandler(e); - } - finally { - delete deferreds[promise.$$timeoutId]; - } - - if (!skipApply) $rootScope.$apply(); - }, delay); - - promise.$$timeoutId = timeoutId; - deferreds[timeoutId] = deferred; - - return promise; - } - - - /** - * @ngdoc method - * @name $timeout#cancel - * - * @description - * Cancels a task associated with the `promise`. As a result of this, the promise will be - * resolved with a rejection. - * - * @param {Promise=} promise Promise returned by the `$timeout` function. - * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully - * canceled. - */ - timeout.cancel = function(promise) { - if (promise && promise.$$timeoutId in deferreds) { - deferreds[promise.$$timeoutId].reject('canceled'); - delete deferreds[promise.$$timeoutId]; - return $browser.defer.cancel(promise.$$timeoutId); - } - return false; - }; - - return timeout; - }]; -} - -// NOTE: The usage of window and document instead of $window and $document here is -// deliberate. This service depends on the specific behavior of anchor nodes created by the -// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and -// cause us to break tests. In addition, when the browser resolves a URL for XHR, it -// doesn't know about mocked locations and resolves URLs to the real document - which is -// exactly the behavior needed here. There is little value is mocking these out for this -// service. -var urlParsingNode = document.createElement("a"); -var originUrl = urlResolve(window.location.href); - - -/** - * - * Implementation Notes for non-IE browsers - * ---------------------------------------- - * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, - * results both in the normalizing and parsing of the URL. Normalizing means that a relative - * URL will be resolved into an absolute URL in the context of the application document. - * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related - * properties are all populated to reflect the normalized URL. This approach has wide - * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See - * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html - * - * Implementation Notes for IE - * --------------------------- - * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other - * browsers. However, the parsed components will not be set if the URL assigned did not specify - * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We - * work around that by performing the parsing in a 2nd step by taking a previously normalized - * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the - * properties such as protocol, hostname, port, etc. - * - * References: - * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement - * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html - * http://url.spec.whatwg.org/#urlutils - * https://github.com/angular/angular.js/pull/2902 - * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ - * - * @kind function - * @param {string} url The URL to be parsed. - * @description Normalizes and parses a URL. - * @returns {object} Returns the normalized URL as a dictionary. - * - * | member name | Description | - * |---------------|----------------| - * | href | A normalized version of the provided URL if it was not an absolute URL | - * | protocol | The protocol including the trailing colon | - * | host | The host and port (if the port is non-default) of the normalizedUrl | - * | search | The search params, minus the question mark | - * | hash | The hash string, minus the hash symbol - * | hostname | The hostname - * | port | The port, without ":" - * | pathname | The pathname, beginning with "/" - * - */ -function urlResolve(url) { - var href = url; - - if (msie) { - // Normalize before parse. Refer Implementation Notes on why this is - // done in two steps on IE. - urlParsingNode.setAttribute("href", href); - href = urlParsingNode.href; - } - - urlParsingNode.setAttribute('href', href); - - // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils - return { - href: urlParsingNode.href, - protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', - host: urlParsingNode.host, - search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', - hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', - hostname: urlParsingNode.hostname, - port: urlParsingNode.port, - pathname: (urlParsingNode.pathname.charAt(0) === '/') - ? urlParsingNode.pathname - : '/' + urlParsingNode.pathname - }; -} - -/** - * Parse a request URL and determine whether this is a same-origin request as the application document. - * - * @param {string|object} requestUrl The url of the request as a string that will be resolved - * or a parsed URL object. - * @returns {boolean} Whether the request is for the same origin as the application document. - */ -function urlIsSameOrigin(requestUrl) { - var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; - return (parsed.protocol === originUrl.protocol && - parsed.host === originUrl.host); -} - -/** - * @ngdoc service - * @name $window - * - * @description - * A reference to the browser's `window` object. While `window` - * is globally available in JavaScript, it causes testability problems, because - * it is a global variable. In angular we always refer to it through the - * `$window` service, so it may be overridden, removed or mocked for testing. - * - * Expressions, like the one defined for the `ngClick` directive in the example - * below, are evaluated with respect to the current scope. Therefore, there is - * no risk of inadvertently coding in a dependency on a global value in such an - * expression. - * - * @example - <example module="windowExample"> - <file name="index.html"> - <script> - angular.module('windowExample', []) - .controller('ExampleController', ['$scope', '$window', function($scope, $window) { - $scope.greeting = 'Hello, World!'; - $scope.doGreeting = function(greeting) { - $window.alert(greeting); - }; - }]); - </script> - <div ng-controller="ExampleController"> - <input type="text" ng-model="greeting" aria-label="greeting" /> - <button ng-click="doGreeting(greeting)">ALERT</button> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should display the greeting in the input box', function() { - element(by.model('greeting')).sendKeys('Hello, E2E Tests'); - // If we click the button it will block the test runner - // element(':button').click(); - }); - </file> - </example> - */ -function $WindowProvider() { - this.$get = valueFn(window); -} - -/** - * @name $$cookieReader - * @requires $document - * - * @description - * This is a private service for reading cookies used by $http and ngCookies - * - * @return {Object} a key/value map of the current cookies - */ -function $$CookieReader($document) { - var rawDocument = $document[0] || {}; - var lastCookies = {}; - var lastCookieString = ''; - - function safeDecodeURIComponent(str) { - try { - return decodeURIComponent(str); - } catch (e) { - return str; - } - } - - return function() { - var cookieArray, cookie, i, index, name; - var currentCookieString = rawDocument.cookie || ''; - - if (currentCookieString !== lastCookieString) { - lastCookieString = currentCookieString; - cookieArray = lastCookieString.split('; '); - lastCookies = {}; - - for (i = 0; i < cookieArray.length; i++) { - cookie = cookieArray[i]; - index = cookie.indexOf('='); - if (index > 0) { //ignore nameless cookies - name = safeDecodeURIComponent(cookie.substring(0, index)); - // the first value that is seen for a cookie is the most - // specific one. values for the same cookie name that - // follow are for less specific paths. - if (isUndefined(lastCookies[name])) { - lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); - } - } - } - } - return lastCookies; - }; -} - -$$CookieReader.$inject = ['$document']; - -function $$CookieReaderProvider() { - this.$get = $$CookieReader; -} - -/* global currencyFilter: true, - dateFilter: true, - filterFilter: true, - jsonFilter: true, - limitToFilter: true, - lowercaseFilter: true, - numberFilter: true, - orderByFilter: true, - uppercaseFilter: true, - */ - -/** - * @ngdoc provider - * @name $filterProvider - * @description - * - * Filters are just functions which transform input to an output. However filters need to be - * Dependency Injected. To achieve this a filter definition consists of a factory function which is - * annotated with dependencies and is responsible for creating a filter function. - * - * <div class="alert alert-warning"> - * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. - * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace - * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores - * (`myapp_subsection_filterx`). - * </div> - * - * ```js - * // Filter registration - * function MyModule($provide, $filterProvider) { - * // create a service to demonstrate injection (not always needed) - * $provide.value('greet', function(name){ - * return 'Hello ' + name + '!'; - * }); - * - * // register a filter factory which uses the - * // greet service to demonstrate DI. - * $filterProvider.register('greet', function(greet){ - * // return the filter function which uses the greet service - * // to generate salutation - * return function(text) { - * // filters need to be forgiving so check input validity - * return text && greet(text) || text; - * }; - * }); - * } - * ``` - * - * The filter function is registered with the `$injector` under the filter name suffix with - * `Filter`. - * - * ```js - * it('should be the same instance', inject( - * function($filterProvider) { - * $filterProvider.register('reverse', function(){ - * return ...; - * }); - * }, - * function($filter, reverseFilter) { - * expect($filter('reverse')).toBe(reverseFilter); - * }); - * ``` - * - * - * For more information about how angular filters work, and how to create your own filters, see - * {@link guide/filter Filters} in the Angular Developer Guide. - */ - -/** - * @ngdoc service - * @name $filter - * @kind function - * @description - * Filters are used for formatting data displayed to the user. - * - * The general syntax in templates is as follows: - * - * {{ expression [| filter_name[:parameter_value] ... ] }} - * - * @param {String} name Name of the filter function to retrieve - * @return {Function} the filter function - * @example - <example name="$filter" module="filterExample"> - <file name="index.html"> - <div ng-controller="MainCtrl"> - <h3>{{ originalText }}</h3> - <h3>{{ filteredText }}</h3> - </div> - </file> - - <file name="script.js"> - angular.module('filterExample', []) - .controller('MainCtrl', function($scope, $filter) { - $scope.originalText = 'hello'; - $scope.filteredText = $filter('uppercase')($scope.originalText); - }); - </file> - </example> - */ -$FilterProvider.$inject = ['$provide']; -function $FilterProvider($provide) { - var suffix = 'Filter'; - - /** - * @ngdoc method - * @name $filterProvider#register - * @param {string|Object} name Name of the filter function, or an object map of filters where - * the keys are the filter names and the values are the filter factories. - * - * <div class="alert alert-warning"> - * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. - * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace - * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores - * (`myapp_subsection_filterx`). - * </div> - * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. - * @returns {Object} Registered filter instance, or if a map of filters was provided then a map - * of the registered filter instances. - */ - function register(name, factory) { - if (isObject(name)) { - var filters = {}; - forEach(name, function(filter, key) { - filters[key] = register(key, filter); - }); - return filters; - } else { - return $provide.factory(name + suffix, factory); - } - } - this.register = register; - - this.$get = ['$injector', function($injector) { - return function(name) { - return $injector.get(name + suffix); - }; - }]; - - //////////////////////////////////////// - - /* global - currencyFilter: false, - dateFilter: false, - filterFilter: false, - jsonFilter: false, - limitToFilter: false, - lowercaseFilter: false, - numberFilter: false, - orderByFilter: false, - uppercaseFilter: false, - */ - - register('currency', currencyFilter); - register('date', dateFilter); - register('filter', filterFilter); - register('json', jsonFilter); - register('limitTo', limitToFilter); - register('lowercase', lowercaseFilter); - register('number', numberFilter); - register('orderBy', orderByFilter); - register('uppercase', uppercaseFilter); -} - -/** - * @ngdoc filter - * @name filter - * @kind function - * - * @description - * Selects a subset of items from `array` and returns it as a new array. - * - * @param {Array} array The source array. - * @param {string|Object|function()} expression The predicate to be used for selecting items from - * `array`. - * - * Can be one of: - * - * - `string`: The string is used for matching against the contents of the `array`. All strings or - * objects with string properties in `array` that match this string will be returned. This also - * applies to nested object properties. - * The predicate can be negated by prefixing the string with `!`. - * - * - `Object`: A pattern object can be used to filter specific properties on objects contained - * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items - * which have property `name` containing "M" and property `phone` containing "1". A special - * property name `$` can be used (as in `{$:"text"}`) to accept a match against any - * property of the object or its nested object properties. That's equivalent to the simple - * substring match with a `string` as described above. The predicate can be negated by prefixing - * the string with `!`. - * For example `{name: "!M"}` predicate will return an array of items which have property `name` - * not containing "M". - * - * Note that a named property will match properties on the same level only, while the special - * `$` property will match properties on the same level or deeper. E.g. an array item like - * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but - * **will** be matched by `{$: 'John'}`. - * - * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. - * The function is called for each element of the array, with the element, its index, and - * the entire array itself as arguments. - * - * The final result is an array of those elements that the predicate returned true for. - * - * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in - * determining if the expected value (from the filter expression) and actual value (from - * the object in the array) should be considered a match. - * - * Can be one of: - * - * - `function(actual, expected)`: - * The function will be given the object value and the predicate value to compare and - * should return true if both values should be considered equal. - * - * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. - * This is essentially strict comparison of expected and actual. - * - * - `false|undefined`: A short hand for a function which will look for a substring match in case - * insensitive way. - * - * Primitive values are converted to strings. Objects are not compared against primitives, - * unless they have a custom `toString` method (e.g. `Date` objects). - * - * @example - <example> - <file name="index.html"> - <div ng-init="friends = [{name:'John', phone:'555-1276'}, - {name:'Mary', phone:'800-BIG-MARY'}, - {name:'Mike', phone:'555-4321'}, - {name:'Adam', phone:'555-5678'}, - {name:'Julie', phone:'555-8765'}, - {name:'Juliette', phone:'555-5678'}]"></div> - - <label>Search: <input ng-model="searchText"></label> - <table id="searchTextResults"> - <tr><th>Name</th><th>Phone</th></tr> - <tr ng-repeat="friend in friends | filter:searchText"> - <td>{{friend.name}}</td> - <td>{{friend.phone}}</td> - </tr> - </table> - <hr> - <label>Any: <input ng-model="search.$"></label> <br> - <label>Name only <input ng-model="search.name"></label><br> - <label>Phone only <input ng-model="search.phone"></label><br> - <label>Equality <input type="checkbox" ng-model="strict"></label><br> - <table id="searchObjResults"> - <tr><th>Name</th><th>Phone</th></tr> - <tr ng-repeat="friendObj in friends | filter:search:strict"> - <td>{{friendObj.name}}</td> - <td>{{friendObj.phone}}</td> - </tr> - </table> - </file> - <file name="protractor.js" type="protractor"> - var expectFriendNames = function(expectedNames, key) { - element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { - arr.forEach(function(wd, i) { - expect(wd.getText()).toMatch(expectedNames[i]); - }); - }); - }; - - it('should search across all fields when filtering with a string', function() { - var searchText = element(by.model('searchText')); - searchText.clear(); - searchText.sendKeys('m'); - expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); - - searchText.clear(); - searchText.sendKeys('76'); - expectFriendNames(['John', 'Julie'], 'friend'); - }); - - it('should search in specific fields when filtering with a predicate object', function() { - var searchAny = element(by.model('search.$')); - searchAny.clear(); - searchAny.sendKeys('i'); - expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); - }); - it('should use a equal comparison when comparator is true', function() { - var searchName = element(by.model('search.name')); - var strict = element(by.model('strict')); - searchName.clear(); - searchName.sendKeys('Julie'); - strict.click(); - expectFriendNames(['Julie'], 'friendObj'); - }); - </file> - </example> - */ -function filterFilter() { - return function(array, expression, comparator) { - if (!isArrayLike(array)) { - if (array == null) { - return array; - } else { - throw minErr('filter')('notarray', 'Expected array but received: {0}', array); - } - } - - var expressionType = getTypeForFilter(expression); - var predicateFn; - var matchAgainstAnyProp; - - switch (expressionType) { - case 'function': - predicateFn = expression; - break; - case 'boolean': - case 'null': - case 'number': - case 'string': - matchAgainstAnyProp = true; - //jshint -W086 - case 'object': - //jshint +W086 - predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); - break; - default: - return array; - } - - return Array.prototype.filter.call(array, predicateFn); - }; -} - -// Helper functions for `filterFilter` -function createPredicateFn(expression, comparator, matchAgainstAnyProp) { - var shouldMatchPrimitives = isObject(expression) && ('$' in expression); - var predicateFn; - - if (comparator === true) { - comparator = equals; - } else if (!isFunction(comparator)) { - comparator = function(actual, expected) { - if (isUndefined(actual)) { - // No substring matching against `undefined` - return false; - } - if ((actual === null) || (expected === null)) { - // No substring matching against `null`; only match against `null` - return actual === expected; - } - if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { - // Should not compare primitives against objects, unless they have custom `toString` method - return false; - } - - actual = lowercase('' + actual); - expected = lowercase('' + expected); - return actual.indexOf(expected) !== -1; - }; - } - - predicateFn = function(item) { - if (shouldMatchPrimitives && !isObject(item)) { - return deepCompare(item, expression.$, comparator, false); - } - return deepCompare(item, expression, comparator, matchAgainstAnyProp); - }; - - return predicateFn; -} - -function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { - var actualType = getTypeForFilter(actual); - var expectedType = getTypeForFilter(expected); - - if ((expectedType === 'string') && (expected.charAt(0) === '!')) { - return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); - } else if (isArray(actual)) { - // In case `actual` is an array, consider it a match - // if ANY of it's items matches `expected` - return actual.some(function(item) { - return deepCompare(item, expected, comparator, matchAgainstAnyProp); - }); - } - - switch (actualType) { - case 'object': - var key; - if (matchAgainstAnyProp) { - for (key in actual) { - if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { - return true; - } - } - return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); - } else if (expectedType === 'object') { - for (key in expected) { - var expectedVal = expected[key]; - if (isFunction(expectedVal) || isUndefined(expectedVal)) { - continue; - } - - var matchAnyProperty = key === '$'; - var actualVal = matchAnyProperty ? actual : actual[key]; - if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { - return false; - } - } - return true; - } else { - return comparator(actual, expected); - } - break; - case 'function': - return false; - default: - return comparator(actual, expected); - } -} - -// Used for easily differentiating between `null` and actual `object` -function getTypeForFilter(val) { - return (val === null) ? 'null' : typeof val; -} - -/** - * @ngdoc filter - * @name currency - * @kind function - * - * @description - * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default - * symbol for current locale is used. - * - * @param {number} amount Input to filter. - * @param {string=} symbol Currency symbol or identifier to be displayed. - * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale - * @returns {string} Formatted number. - * - * - * @example - <example module="currencyExample"> - <file name="index.html"> - <script> - angular.module('currencyExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.amount = 1234.56; - }]); - </script> - <div ng-controller="ExampleController"> - <input type="number" ng-model="amount" aria-label="amount"> <br> - default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> - custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span> - no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should init with 1234.56', function() { - expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); - expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); - expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); - }); - it('should update', function() { - if (browser.params.browser == 'safari') { - // Safari does not understand the minus key. See - // https://github.com/angular/protractor/issues/481 - return; - } - element(by.model('amount')).clear(); - element(by.model('amount')).sendKeys('-1234'); - expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); - expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); - expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); - }); - </file> - </example> - */ -currencyFilter.$inject = ['$locale']; -function currencyFilter($locale) { - var formats = $locale.NUMBER_FORMATS; - return function(amount, currencySymbol, fractionSize) { - if (isUndefined(currencySymbol)) { - currencySymbol = formats.CURRENCY_SYM; - } - - if (isUndefined(fractionSize)) { - fractionSize = formats.PATTERNS[1].maxFrac; - } - - // if null or undefined pass it through - return (amount == null) - ? amount - : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). - replace(/\u00A4/g, currencySymbol); - }; -} - -/** - * @ngdoc filter - * @name number - * @kind function - * - * @description - * Formats a number as text. - * - * If the input is null or undefined, it will just be returned. - * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned. - * If the input is not a number an empty string is returned. - * - * - * @param {number|string} number Number to format. - * @param {(number|string)=} fractionSize Number of decimal places to round the number to. - * If this is not provided then the fraction size is computed from the current locale's number - * formatting pattern. In the case of the default locale, it will be 3. - * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. - * - * @example - <example module="numberFilterExample"> - <file name="index.html"> - <script> - angular.module('numberFilterExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.val = 1234.56789; - }]); - </script> - <div ng-controller="ExampleController"> - <label>Enter number: <input ng-model='val'></label><br> - Default formatting: <span id='number-default'>{{val | number}}</span><br> - No fractions: <span>{{val | number:0}}</span><br> - Negative number: <span>{{-val | number:4}}</span> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should format numbers', function() { - expect(element(by.id('number-default')).getText()).toBe('1,234.568'); - expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); - expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); - }); - - it('should update', function() { - element(by.model('val')).clear(); - element(by.model('val')).sendKeys('3374.333'); - expect(element(by.id('number-default')).getText()).toBe('3,374.333'); - expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); - expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); - }); - </file> - </example> - */ - - -numberFilter.$inject = ['$locale']; -function numberFilter($locale) { - var formats = $locale.NUMBER_FORMATS; - return function(number, fractionSize) { - - // if null or undefined pass it through - return (number == null) - ? number - : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, - fractionSize); - }; -} - -var DECIMAL_SEP = '.'; -function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { - if (isObject(number)) return ''; - - var isNegative = number < 0; - number = Math.abs(number); - - var isInfinity = number === Infinity; - if (!isInfinity && !isFinite(number)) return ''; - - var numStr = number + '', - formatedText = '', - hasExponent = false, - parts = []; - - if (isInfinity) formatedText = '\u221e'; - - if (!isInfinity && numStr.indexOf('e') !== -1) { - var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); - if (match && match[2] == '-' && match[3] > fractionSize + 1) { - number = 0; - } else { - formatedText = numStr; - hasExponent = true; - } - } - - if (!isInfinity && !hasExponent) { - var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; - - // determine fractionSize if it is not specified - if (isUndefined(fractionSize)) { - fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); - } - - // safely round numbers in JS without hitting imprecisions of floating-point arithmetics - // inspired by: - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round - number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); - - var fraction = ('' + number).split(DECIMAL_SEP); - var whole = fraction[0]; - fraction = fraction[1] || ''; - - var i, pos = 0, - lgroup = pattern.lgSize, - group = pattern.gSize; - - if (whole.length >= (lgroup + group)) { - pos = whole.length - lgroup; - for (i = 0; i < pos; i++) { - if ((pos - i) % group === 0 && i !== 0) { - formatedText += groupSep; - } - formatedText += whole.charAt(i); - } - } - - for (i = pos; i < whole.length; i++) { - if ((whole.length - i) % lgroup === 0 && i !== 0) { - formatedText += groupSep; - } - formatedText += whole.charAt(i); - } - - // format fraction part. - while (fraction.length < fractionSize) { - fraction += '0'; - } - - if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); - } else { - if (fractionSize > 0 && number < 1) { - formatedText = number.toFixed(fractionSize); - number = parseFloat(formatedText); - formatedText = formatedText.replace(DECIMAL_SEP, decimalSep); - } - } - - if (number === 0) { - isNegative = false; - } - - parts.push(isNegative ? pattern.negPre : pattern.posPre, - formatedText, - isNegative ? pattern.negSuf : pattern.posSuf); - return parts.join(''); -} - -function padNumber(num, digits, trim) { - var neg = ''; - if (num < 0) { - neg = '-'; - num = -num; - } - num = '' + num; - while (num.length < digits) num = '0' + num; - if (trim) { - num = num.substr(num.length - digits); - } - return neg + num; -} - - -function dateGetter(name, size, offset, trim) { - offset = offset || 0; - return function(date) { - var value = date['get' + name](); - if (offset > 0 || value > -offset) { - value += offset; - } - if (value === 0 && offset == -12) value = 12; - return padNumber(value, size, trim); - }; -} - -function dateStrGetter(name, shortForm) { - return function(date, formats) { - var value = date['get' + name](); - var get = uppercase(shortForm ? ('SHORT' + name) : name); - - return formats[get][value]; - }; -} - -function timeZoneGetter(date, formats, offset) { - var zone = -1 * offset; - var paddedZone = (zone >= 0) ? "+" : ""; - - paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + - padNumber(Math.abs(zone % 60), 2); - - return paddedZone; -} - -function getFirstThursdayOfYear(year) { - // 0 = index of January - var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); - // 4 = index of Thursday (+1 to account for 1st = 5) - // 11 = index of *next* Thursday (+1 account for 1st = 12) - return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); -} - -function getThursdayThisWeek(datetime) { - return new Date(datetime.getFullYear(), datetime.getMonth(), - // 4 = index of Thursday - datetime.getDate() + (4 - datetime.getDay())); -} - -function weekGetter(size) { - return function(date) { - var firstThurs = getFirstThursdayOfYear(date.getFullYear()), - thisThurs = getThursdayThisWeek(date); - - var diff = +thisThurs - +firstThurs, - result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week - - return padNumber(result, size); - }; -} - -function ampmGetter(date, formats) { - return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; -} - -function eraGetter(date, formats) { - return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; -} - -function longEraGetter(date, formats) { - return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; -} - -var DATE_FORMATS = { - yyyy: dateGetter('FullYear', 4), - yy: dateGetter('FullYear', 2, 0, true), - y: dateGetter('FullYear', 1), - MMMM: dateStrGetter('Month'), - MMM: dateStrGetter('Month', true), - MM: dateGetter('Month', 2, 1), - M: dateGetter('Month', 1, 1), - dd: dateGetter('Date', 2), - d: dateGetter('Date', 1), - HH: dateGetter('Hours', 2), - H: dateGetter('Hours', 1), - hh: dateGetter('Hours', 2, -12), - h: dateGetter('Hours', 1, -12), - mm: dateGetter('Minutes', 2), - m: dateGetter('Minutes', 1), - ss: dateGetter('Seconds', 2), - s: dateGetter('Seconds', 1), - // while ISO 8601 requires fractions to be prefixed with `.` or `,` - // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions - sss: dateGetter('Milliseconds', 3), - EEEE: dateStrGetter('Day'), - EEE: dateStrGetter('Day', true), - a: ampmGetter, - Z: timeZoneGetter, - ww: weekGetter(2), - w: weekGetter(1), - G: eraGetter, - GG: eraGetter, - GGG: eraGetter, - GGGG: longEraGetter -}; - -var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, - NUMBER_STRING = /^\-?\d+$/; - -/** - * @ngdoc filter - * @name date - * @kind function - * - * @description - * Formats `date` to a string based on the requested `format`. - * - * `format` string can be composed of the following elements: - * - * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) - * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) - * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) - * * `'MMMM'`: Month in year (January-December) - * * `'MMM'`: Month in year (Jan-Dec) - * * `'MM'`: Month in year, padded (01-12) - * * `'M'`: Month in year (1-12) - * * `'dd'`: Day in month, padded (01-31) - * * `'d'`: Day in month (1-31) - * * `'EEEE'`: Day in Week,(Sunday-Saturday) - * * `'EEE'`: Day in Week, (Sun-Sat) - * * `'HH'`: Hour in day, padded (00-23) - * * `'H'`: Hour in day (0-23) - * * `'hh'`: Hour in AM/PM, padded (01-12) - * * `'h'`: Hour in AM/PM, (1-12) - * * `'mm'`: Minute in hour, padded (00-59) - * * `'m'`: Minute in hour (0-59) - * * `'ss'`: Second in minute, padded (00-59) - * * `'s'`: Second in minute (0-59) - * * `'sss'`: Millisecond in second, padded (000-999) - * * `'a'`: AM/PM marker - * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) - * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year - * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year - * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') - * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') - * - * `format` string can also be one of the following predefined - * {@link guide/i18n localizable formats}: - * - * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale - * (e.g. Sep 3, 2010 12:05:08 PM) - * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) - * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale - * (e.g. Friday, September 3, 2010) - * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) - * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) - * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) - * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) - * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) - * - * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. - * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence - * (e.g. `"h 'o''clock'"`). - * - * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or - * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its - * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is - * specified in the string input, the time is considered to be in the local timezone. - * @param {string=} format Formatting rules (see Description). If not specified, - * `mediumDate` is used. - * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the - * continental US time zone abbreviations, but for general use, use a time zone offset, for - * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) - * If not specified, the timezone of the browser will be used. - * @returns {string} Formatted string or the input if input is not recognized as date/millis. - * - * @example - <example> - <file name="index.html"> - <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: - <span>{{1288323623006 | date:'medium'}}</span><br> - <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: - <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> - <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: - <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> - <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: - <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> - </file> - <file name="protractor.js" type="protractor"> - it('should format date', function() { - expect(element(by.binding("1288323623006 | date:'medium'")).getText()). - toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); - expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). - toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); - expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). - toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); - expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). - toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); - }); - </file> - </example> - */ -dateFilter.$inject = ['$locale']; -function dateFilter($locale) { - - - var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; - // 1 2 3 4 5 6 7 8 9 10 11 - function jsonStringToDate(string) { - var match; - if (match = string.match(R_ISO8601_STR)) { - var date = new Date(0), - tzHour = 0, - tzMin = 0, - dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, - timeSetter = match[8] ? date.setUTCHours : date.setHours; - - if (match[9]) { - tzHour = toInt(match[9] + match[10]); - tzMin = toInt(match[9] + match[11]); - } - dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); - var h = toInt(match[4] || 0) - tzHour; - var m = toInt(match[5] || 0) - tzMin; - var s = toInt(match[6] || 0); - var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); - timeSetter.call(date, h, m, s, ms); - return date; - } - return string; - } - - - return function(date, format, timezone) { - var text = '', - parts = [], - fn, match; - - format = format || 'mediumDate'; - format = $locale.DATETIME_FORMATS[format] || format; - if (isString(date)) { - date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); - } - - if (isNumber(date)) { - date = new Date(date); - } - - if (!isDate(date) || !isFinite(date.getTime())) { - return date; - } - - while (format) { - match = DATE_FORMATS_SPLIT.exec(format); - if (match) { - parts = concat(parts, match, 1); - format = parts.pop(); - } else { - parts.push(format); - format = null; - } - } - - var dateTimezoneOffset = date.getTimezoneOffset(); - if (timezone) { - dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); - date = convertTimezoneToLocal(date, timezone, true); - } - forEach(parts, function(value) { - fn = DATE_FORMATS[value]; - text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) - : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); - }); - - return text; - }; -} - - -/** - * @ngdoc filter - * @name json - * @kind function - * - * @description - * Allows you to convert a JavaScript object into JSON string. - * - * This filter is mostly useful for debugging. When using the double curly {{value}} notation - * the binding is automatically converted to JSON. - * - * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. - * @param {number=} spacing The number of spaces to use per indentation, defaults to 2. - * @returns {string} JSON string. - * - * - * @example - <example> - <file name="index.html"> - <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> - <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> - </file> - <file name="protractor.js" type="protractor"> - it('should jsonify filtered objects', function() { - expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); - expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); - }); - </file> - </example> - * - */ -function jsonFilter() { - return function(object, spacing) { - if (isUndefined(spacing)) { - spacing = 2; - } - return toJson(object, spacing); - }; -} - - -/** - * @ngdoc filter - * @name lowercase - * @kind function - * @description - * Converts string to lowercase. - * @see angular.lowercase - */ -var lowercaseFilter = valueFn(lowercase); - - -/** - * @ngdoc filter - * @name uppercase - * @kind function - * @description - * Converts string to uppercase. - * @see angular.uppercase - */ -var uppercaseFilter = valueFn(uppercase); - -/** - * @ngdoc filter - * @name limitTo - * @kind function - * - * @description - * Creates a new array or string containing only a specified number of elements. The elements - * are taken from either the beginning or the end of the source array, string or number, as specified by - * the value and sign (positive or negative) of `limit`. If a number is used as input, it is - * converted to a string. - * - * @param {Array|string|number} input Source array, string or number to be limited. - * @param {string|number} limit The length of the returned array or string. If the `limit` number - * is positive, `limit` number of items from the beginning of the source array/string are copied. - * If the number is negative, `limit` number of items from the end of the source array/string - * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, - * the input will be returned unchanged. - * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin` - * indicates an offset from the end of `input`. Defaults to `0`. - * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array - * had less than `limit` elements. - * - * @example - <example module="limitToExample"> - <file name="index.html"> - <script> - angular.module('limitToExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.numbers = [1,2,3,4,5,6,7,8,9]; - $scope.letters = "abcdefghi"; - $scope.longNumber = 2345432342; - $scope.numLimit = 3; - $scope.letterLimit = 3; - $scope.longNumberLimit = 3; - }]); - </script> - <div ng-controller="ExampleController"> - <label> - Limit {{numbers}} to: - <input type="number" step="1" ng-model="numLimit"> - </label> - <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> - <label> - Limit {{letters}} to: - <input type="number" step="1" ng-model="letterLimit"> - </label> - <p>Output letters: {{ letters | limitTo:letterLimit }}</p> - <label> - Limit {{longNumber}} to: - <input type="number" step="1" ng-model="longNumberLimit"> - </label> - <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> - </div> - </file> - <file name="protractor.js" type="protractor"> - var numLimitInput = element(by.model('numLimit')); - var letterLimitInput = element(by.model('letterLimit')); - var longNumberLimitInput = element(by.model('longNumberLimit')); - var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); - var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); - var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); - - it('should limit the number array to first three items', function() { - expect(numLimitInput.getAttribute('value')).toBe('3'); - expect(letterLimitInput.getAttribute('value')).toBe('3'); - expect(longNumberLimitInput.getAttribute('value')).toBe('3'); - expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); - expect(limitedLetters.getText()).toEqual('Output letters: abc'); - expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); - }); - - // There is a bug in safari and protractor that doesn't like the minus key - // it('should update the output when -3 is entered', function() { - // numLimitInput.clear(); - // numLimitInput.sendKeys('-3'); - // letterLimitInput.clear(); - // letterLimitInput.sendKeys('-3'); - // longNumberLimitInput.clear(); - // longNumberLimitInput.sendKeys('-3'); - // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); - // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); - // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); - // }); - - it('should not exceed the maximum size of input array', function() { - numLimitInput.clear(); - numLimitInput.sendKeys('100'); - letterLimitInput.clear(); - letterLimitInput.sendKeys('100'); - longNumberLimitInput.clear(); - longNumberLimitInput.sendKeys('100'); - expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); - expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); - expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); - }); - </file> - </example> -*/ -function limitToFilter() { - return function(input, limit, begin) { - if (Math.abs(Number(limit)) === Infinity) { - limit = Number(limit); - } else { - limit = toInt(limit); - } - if (isNaN(limit)) return input; - - if (isNumber(input)) input = input.toString(); - if (!isArray(input) && !isString(input)) return input; - - begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); - begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin; - - if (limit >= 0) { - return input.slice(begin, begin + limit); - } else { - if (begin === 0) { - return input.slice(limit, input.length); - } else { - return input.slice(Math.max(0, begin + limit), begin); - } - } - }; -} - -/** - * @ngdoc filter - * @name orderBy - * @kind function - * - * @description - * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically - * for strings and numerically for numbers. Note: if you notice numbers are not being sorted - * as expected, make sure they are actually being saved as numbers and not strings. - * - * @param {Array} array The array to sort. - * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be - * used by the comparator to determine the order of elements. - * - * Can be one of: - * - * - `function`: Getter function. The result of this function will be sorted using the - * `<`, `===`, `>` operator. - * - `string`: An Angular expression. The result of this expression is used to compare elements - * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by - * 3 first characters of a property called `name`). The result of a constant expression - * is interpreted as a property name to be used in comparisons (for example `"special name"` - * to sort object by the value of their `special name` property). An expression can be - * optionally prefixed with `+` or `-` to control ascending or descending sort order - * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array - * element itself is used to compare where sorting. - * - `Array`: An array of function or string predicates. The first predicate in the array - * is used for sorting, but when two items are equivalent, the next predicate is used. - * - * If the predicate is missing or empty then it defaults to `'+'`. - * - * @param {boolean=} reverse Reverse the order of the array. - * @returns {Array} Sorted copy of the source array. - * - * - * @example - * The example below demonstrates a simple ngRepeat, where the data is sorted - * by age in descending order (predicate is set to `'-age'`). - * `reverse` is not set, which means it defaults to `false`. - <example module="orderByExample"> - <file name="index.html"> - <script> - angular.module('orderByExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.friends = - [{name:'John', phone:'555-1212', age:10}, - {name:'Mary', phone:'555-9876', age:19}, - {name:'Mike', phone:'555-4321', age:21}, - {name:'Adam', phone:'555-5678', age:35}, - {name:'Julie', phone:'555-8765', age:29}]; - }]); - </script> - <div ng-controller="ExampleController"> - <table class="friend"> - <tr> - <th>Name</th> - <th>Phone Number</th> - <th>Age</th> - </tr> - <tr ng-repeat="friend in friends | orderBy:'-age'"> - <td>{{friend.name}}</td> - <td>{{friend.phone}}</td> - <td>{{friend.age}}</td> - </tr> - </table> - </div> - </file> - </example> - * - * The predicate and reverse parameters can be controlled dynamically through scope properties, - * as shown in the next example. - * @example - <example module="orderByExample"> - <file name="index.html"> - <script> - angular.module('orderByExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.friends = - [{name:'John', phone:'555-1212', age:10}, - {name:'Mary', phone:'555-9876', age:19}, - {name:'Mike', phone:'555-4321', age:21}, - {name:'Adam', phone:'555-5678', age:35}, - {name:'Julie', phone:'555-8765', age:29}]; - $scope.predicate = 'age'; - $scope.reverse = true; - $scope.order = function(predicate) { - $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; - $scope.predicate = predicate; - }; - }]); - </script> - <style type="text/css"> - .sortorder:after { - content: '\25b2'; - } - .sortorder.reverse:after { - content: '\25bc'; - } - </style> - <div ng-controller="ExampleController"> - <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> - <hr/> - [ <a href="" ng-click="predicate=''">unsorted</a> ] - <table class="friend"> - <tr> - <th> - <a href="" ng-click="order('name')">Name</a> - <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> - </th> - <th> - <a href="" ng-click="order('phone')">Phone Number</a> - <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> - </th> - <th> - <a href="" ng-click="order('age')">Age</a> - <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> - </th> - </tr> - <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> - <td>{{friend.name}}</td> - <td>{{friend.phone}}</td> - <td>{{friend.age}}</td> - </tr> - </table> - </div> - </file> - </example> - * - * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the - * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the - * desired parameters. - * - * Example: - * - * @example - <example module="orderByExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <table class="friend"> - <tr> - <th><a href="" ng-click="reverse=false;order('name', false)">Name</a> - (<a href="" ng-click="order('-name',false)">^</a>)</th> - <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th> - <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th> - </tr> - <tr ng-repeat="friend in friends"> - <td>{{friend.name}}</td> - <td>{{friend.phone}}</td> - <td>{{friend.age}}</td> - </tr> - </table> - </div> - </file> - - <file name="script.js"> - angular.module('orderByExample', []) - .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { - var orderBy = $filter('orderBy'); - $scope.friends = [ - { name: 'John', phone: '555-1212', age: 10 }, - { name: 'Mary', phone: '555-9876', age: 19 }, - { name: 'Mike', phone: '555-4321', age: 21 }, - { name: 'Adam', phone: '555-5678', age: 35 }, - { name: 'Julie', phone: '555-8765', age: 29 } - ]; - $scope.order = function(predicate, reverse) { - $scope.friends = orderBy($scope.friends, predicate, reverse); - }; - $scope.order('-age',false); - }]); - </file> -</example> - */ -orderByFilter.$inject = ['$parse']; -function orderByFilter($parse) { - return function(array, sortPredicate, reverseOrder) { - - if (!(isArrayLike(array))) return array; - - if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } - if (sortPredicate.length === 0) { sortPredicate = ['+']; } - - var predicates = processPredicates(sortPredicate, reverseOrder); - // Add a predicate at the end that evaluates to the element index. This makes the - // sort stable as it works as a tie-breaker when all the input predicates cannot - // distinguish between two elements. - predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1}); - - // The next three lines are a version of a Swartzian Transform idiom from Perl - // (sometimes called the Decorate-Sort-Undecorate idiom) - // See https://en.wikipedia.org/wiki/Schwartzian_transform - var compareValues = Array.prototype.map.call(array, getComparisonObject); - compareValues.sort(doComparison); - array = compareValues.map(function(item) { return item.value; }); - - return array; - - function getComparisonObject(value, index) { - return { - value: value, - predicateValues: predicates.map(function(predicate) { - return getPredicateValue(predicate.get(value), index); - }) - }; - } - - function doComparison(v1, v2) { - var result = 0; - for (var index=0, length = predicates.length; index < length; ++index) { - result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending; - if (result) break; - } - return result; - } - }; - - function processPredicates(sortPredicate, reverseOrder) { - reverseOrder = reverseOrder ? -1 : 1; - return sortPredicate.map(function(predicate) { - var descending = 1, get = identity; - - if (isFunction(predicate)) { - get = predicate; - } else if (isString(predicate)) { - if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { - descending = predicate.charAt(0) == '-' ? -1 : 1; - predicate = predicate.substring(1); - } - if (predicate !== '') { - get = $parse(predicate); - if (get.constant) { - var key = get(); - get = function(value) { return value[key]; }; - } - } - } - return { get: get, descending: descending * reverseOrder }; - }); - } - - function isPrimitive(value) { - switch (typeof value) { - case 'number': /* falls through */ - case 'boolean': /* falls through */ - case 'string': - return true; - default: - return false; - } - } - - function objectValue(value, index) { - // If `valueOf` is a valid function use that - if (typeof value.valueOf === 'function') { - value = value.valueOf(); - if (isPrimitive(value)) return value; - } - // If `toString` is a valid function and not the one from `Object.prototype` use that - if (hasCustomToString(value)) { - value = value.toString(); - if (isPrimitive(value)) return value; - } - // We have a basic object so we use the position of the object in the collection - return index; - } - - function getPredicateValue(value, index) { - var type = typeof value; - if (value === null) { - type = 'string'; - value = 'null'; - } else if (type === 'string') { - value = value.toLowerCase(); - } else if (type === 'object') { - value = objectValue(value, index); - } - return { value: value, type: type }; - } - - function compare(v1, v2) { - var result = 0; - if (v1.type === v2.type) { - if (v1.value !== v2.value) { - result = v1.value < v2.value ? -1 : 1; - } - } else { - result = v1.type < v2.type ? -1 : 1; - } - return result; - } -} - -function ngDirective(directive) { - if (isFunction(directive)) { - directive = { - link: directive - }; - } - directive.restrict = directive.restrict || 'AC'; - return valueFn(directive); -} - -/** - * @ngdoc directive - * @name a - * @restrict E - * - * @description - * Modifies the default behavior of the html A tag so that the default action is prevented when - * the href attribute is empty. - * - * This change permits the easy creation of action links with the `ngClick` directive - * without changing the location or causing page reloads, e.g.: - * `<a href="" ng-click="list.addItem()">Add Item</a>` - */ -var htmlAnchorDirective = valueFn({ - restrict: 'E', - compile: function(element, attr) { - if (!attr.href && !attr.xlinkHref) { - return function(scope, element) { - // If the linked element is not an anchor tag anymore, do nothing - if (element[0].nodeName.toLowerCase() !== 'a') return; - - // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. - var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? - 'xlink:href' : 'href'; - element.on('click', function(event) { - // if we have no href url, then don't navigate anywhere. - if (!element.attr(href)) { - event.preventDefault(); - } - }); - }; - } - } -}); - -/** - * @ngdoc directive - * @name ngHref - * @restrict A - * @priority 99 - * - * @description - * Using Angular markup like `{{hash}}` in an href attribute will - * make the link go to the wrong URL if the user clicks it before - * Angular has a chance to replace the `{{hash}}` markup with its - * value. Until Angular replaces the markup the link will be broken - * and will most likely return a 404 error. The `ngHref` directive - * solves this problem. - * - * The wrong way to write it: - * ```html - * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> - * ``` - * - * The correct way to write it: - * ```html - * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> - * ``` - * - * @element A - * @param {template} ngHref any string which can contain `{{}}` markup. - * - * @example - * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes - * in links and their different behaviors: - <example> - <file name="index.html"> - <input ng-model="value" /><br /> - <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> - <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> - <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> - <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> - <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> - <a id="link-6" ng-href="{{value}}">link</a> (link, change location) - </file> - <file name="protractor.js" type="protractor"> - it('should execute ng-click but not reload when href without value', function() { - element(by.id('link-1')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('1'); - expect(element(by.id('link-1')).getAttribute('href')).toBe(''); - }); - - it('should execute ng-click but not reload when href empty string', function() { - element(by.id('link-2')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('2'); - expect(element(by.id('link-2')).getAttribute('href')).toBe(''); - }); - - it('should execute ng-click and change url when ng-href specified', function() { - expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); - - element(by.id('link-3')).click(); - - // At this point, we navigate away from an Angular page, so we need - // to use browser.driver to get the base webdriver. - - browser.wait(function() { - return browser.driver.getCurrentUrl().then(function(url) { - return url.match(/\/123$/); - }); - }, 5000, 'page should navigate to /123'); - }); - - it('should execute ng-click but not reload when href empty string and name specified', function() { - element(by.id('link-4')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('4'); - expect(element(by.id('link-4')).getAttribute('href')).toBe(''); - }); - - it('should execute ng-click but not reload when no href but name specified', function() { - element(by.id('link-5')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('5'); - expect(element(by.id('link-5')).getAttribute('href')).toBe(null); - }); - - it('should only change url when only ng-href', function() { - element(by.model('value')).clear(); - element(by.model('value')).sendKeys('6'); - expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); - - element(by.id('link-6')).click(); - - // At this point, we navigate away from an Angular page, so we need - // to use browser.driver to get the base webdriver. - browser.wait(function() { - return browser.driver.getCurrentUrl().then(function(url) { - return url.match(/\/6$/); - }); - }, 5000, 'page should navigate to /6'); - }); - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngSrc - * @restrict A - * @priority 99 - * - * @description - * Using Angular markup like `{{hash}}` in a `src` attribute doesn't - * work right: The browser will fetch from the URL with the literal - * text `{{hash}}` until Angular replaces the expression inside - * `{{hash}}`. The `ngSrc` directive solves this problem. - * - * The buggy way to write it: - * ```html - * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> - * ``` - * - * The correct way to write it: - * ```html - * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> - * ``` - * - * @element IMG - * @param {template} ngSrc any string which can contain `{{}}` markup. - */ - -/** - * @ngdoc directive - * @name ngSrcset - * @restrict A - * @priority 99 - * - * @description - * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't - * work right: The browser will fetch from the URL with the literal - * text `{{hash}}` until Angular replaces the expression inside - * `{{hash}}`. The `ngSrcset` directive solves this problem. - * - * The buggy way to write it: - * ```html - * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> - * ``` - * - * The correct way to write it: - * ```html - * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> - * ``` - * - * @element IMG - * @param {template} ngSrcset any string which can contain `{{}}` markup. - */ - -/** - * @ngdoc directive - * @name ngDisabled - * @restrict A - * @priority 100 - * - * @description - * - * This directive sets the `disabled` attribute on the element if the - * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. - * - * A special directive is necessary because we cannot use interpolation inside the `disabled` - * attribute. The following example would make the button enabled on Chrome/Firefox - * but not on older IEs: - * - * ```html - * <!-- See below for an example of ng-disabled being used correctly --> - * <div ng-init="isDisabled = false"> - * <button disabled="{{isDisabled}}">Disabled</button> - * </div> - * ``` - * - * This is because the HTML specification does not require browsers to preserve the values of - * boolean attributes such as `disabled` (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * - * @example - <example> - <file name="index.html"> - <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> - <button ng-model="button" ng-disabled="checked">Button</button> - </file> - <file name="protractor.js" type="protractor"> - it('should toggle button', function() { - expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); - element(by.model('checked')).click(); - expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); - }); - </file> - </example> - * - * @element INPUT - * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, - * then the `disabled` attribute will be set on the element - */ - - -/** - * @ngdoc directive - * @name ngChecked - * @restrict A - * @priority 100 - * - * @description - * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. - * - * Note that this directive should not be used together with {@link ngModel `ngModel`}, - * as this can lead to unexpected behavior. - * - * ### Why do we need `ngChecked`? - * - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as checked. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngChecked` directive solves this problem for the `checked` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * @example - <example> - <file name="index.html"> - <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/> - <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input"> - </file> - <file name="protractor.js" type="protractor"> - it('should check both checkBoxes', function() { - expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); - element(by.model('master')).click(); - expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); - }); - </file> - </example> - * - * @element INPUT - * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, - * then the `checked` attribute will be set on the element - */ - - -/** - * @ngdoc directive - * @name ngReadonly - * @restrict A - * @priority 100 - * - * @description - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as readonly. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngReadonly` directive solves this problem for the `readonly` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * @example - <example> - <file name="index.html"> - <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> - <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" /> - </file> - <file name="protractor.js" type="protractor"> - it('should toggle readonly attr', function() { - expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); - element(by.model('checked')).click(); - expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); - }); - </file> - </example> - * - * @element INPUT - * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, - * then special attribute "readonly" will be set on the element - */ - - -/** - * @ngdoc directive - * @name ngSelected - * @restrict A - * @priority 100 - * - * @description - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as selected. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngSelected` directive solves this problem for the `selected` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * - * @example - <example> - <file name="index.html"> - <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> - <select aria-label="ngSelected demo"> - <option>Hello!</option> - <option id="greet" ng-selected="selected">Greetings!</option> - </select> - </file> - <file name="protractor.js" type="protractor"> - it('should select Greetings!', function() { - expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); - element(by.model('selected')).click(); - expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); - }); - </file> - </example> - * - * @element OPTION - * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, - * then special attribute "selected" will be set on the element - */ - -/** - * @ngdoc directive - * @name ngOpen - * @restrict A - * @priority 100 - * - * @description - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as open. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngOpen` directive solves this problem for the `open` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * @example - <example> - <file name="index.html"> - <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/> - <details id="details" ng-open="open"> - <summary>Show/Hide me</summary> - </details> - </file> - <file name="protractor.js" type="protractor"> - it('should toggle open', function() { - expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); - element(by.model('open')).click(); - expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); - }); - </file> - </example> - * - * @element DETAILS - * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, - * then special attribute "open" will be set on the element - */ - -var ngAttributeAliasDirectives = {}; - -// boolean attrs are evaluated -forEach(BOOLEAN_ATTR, function(propName, attrName) { - // binding to multiple is not supported - if (propName == "multiple") return; - - function defaultLinkFn(scope, element, attr) { - scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { - attr.$set(attrName, !!value); - }); - } - - var normalized = directiveNormalize('ng-' + attrName); - var linkFn = defaultLinkFn; - - if (propName === 'checked') { - linkFn = function(scope, element, attr) { - // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input - if (attr.ngModel !== attr[normalized]) { - defaultLinkFn(scope, element, attr); - } - }; - } - - ngAttributeAliasDirectives[normalized] = function() { - return { - restrict: 'A', - priority: 100, - link: linkFn - }; - }; -}); - -// aliased input attrs are evaluated -forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { - ngAttributeAliasDirectives[ngAttr] = function() { - return { - priority: 100, - link: function(scope, element, attr) { - //special case ngPattern when a literal regular expression value - //is used as the expression (this way we don't have to watch anything). - if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { - var match = attr.ngPattern.match(REGEX_STRING_REGEXP); - if (match) { - attr.$set("ngPattern", new RegExp(match[1], match[2])); - return; - } - } - - scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { - attr.$set(ngAttr, value); - }); - } - }; - }; -}); - -// ng-src, ng-srcset, ng-href are interpolated -forEach(['src', 'srcset', 'href'], function(attrName) { - var normalized = directiveNormalize('ng-' + attrName); - ngAttributeAliasDirectives[normalized] = function() { - return { - priority: 99, // it needs to run after the attributes are interpolated - link: function(scope, element, attr) { - var propName = attrName, - name = attrName; - - if (attrName === 'href' && - toString.call(element.prop('href')) === '[object SVGAnimatedString]') { - name = 'xlinkHref'; - attr.$attr[name] = 'xlink:href'; - propName = null; - } - - attr.$observe(normalized, function(value) { - if (!value) { - if (attrName === 'href') { - attr.$set(name, null); - } - return; - } - - attr.$set(name, value); - - // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist - // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need - // to set the property as well to achieve the desired effect. - // we use attr[attrName] value since $set can sanitize the url. - if (msie && propName) element.prop(propName, attr[name]); - }); - } - }; - }; -}); - -/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true - */ -var nullFormCtrl = { - $addControl: noop, - $$renameControl: nullFormRenameControl, - $removeControl: noop, - $setValidity: noop, - $setDirty: noop, - $setPristine: noop, - $setSubmitted: noop -}, -SUBMITTED_CLASS = 'ng-submitted'; - -function nullFormRenameControl(control, name) { - control.$name = name; -} - -/** - * @ngdoc type - * @name form.FormController - * - * @property {boolean} $pristine True if user has not interacted with the form yet. - * @property {boolean} $dirty True if user has already interacted with the form. - * @property {boolean} $valid True if all of the containing forms and controls are valid. - * @property {boolean} $invalid True if at least one containing control or form is invalid. - * @property {boolean} $pending True if at least one containing control or form is pending. - * @property {boolean} $submitted True if user has submitted the form even if its invalid. - * - * @property {Object} $error Is an object hash, containing references to controls or - * forms with failing validators, where: - * - * - keys are validation tokens (error names), - * - values are arrays of controls or forms that have a failing validator for given error name. - * - * Built-in validation tokens: - * - * - `email` - * - `max` - * - `maxlength` - * - `min` - * - `minlength` - * - `number` - * - `pattern` - * - `required` - * - `url` - * - `date` - * - `datetimelocal` - * - `time` - * - `week` - * - `month` - * - * @description - * `FormController` keeps track of all its controls and nested forms as well as the state of them, - * such as being valid/invalid or dirty/pristine. - * - * Each {@link ng.directive:form form} directive creates an instance - * of `FormController`. - * - */ -//asks for $scope to fool the BC controller module -FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; -function FormController(element, attrs, $scope, $animate, $interpolate) { - var form = this, - controls = []; - - // init state - form.$error = {}; - form.$$success = {}; - form.$pending = undefined; - form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); - form.$dirty = false; - form.$pristine = true; - form.$valid = true; - form.$invalid = false; - form.$submitted = false; - form.$$parentForm = nullFormCtrl; - - /** - * @ngdoc method - * @name form.FormController#$rollbackViewValue - * - * @description - * Rollback all form controls pending updates to the `$modelValue`. - * - * Updates may be pending by a debounced event or because the input is waiting for a some future - * event defined in `ng-model-options`. This method is typically needed by the reset button of - * a form that uses `ng-model-options` to pend updates. - */ - form.$rollbackViewValue = function() { - forEach(controls, function(control) { - control.$rollbackViewValue(); - }); - }; - - /** - * @ngdoc method - * @name form.FormController#$commitViewValue - * - * @description - * Commit all form controls pending updates to the `$modelValue`. - * - * Updates may be pending by a debounced event or because the input is waiting for a some future - * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` - * usually handles calling this in response to input events. - */ - form.$commitViewValue = function() { - forEach(controls, function(control) { - control.$commitViewValue(); - }); - }; - - /** - * @ngdoc method - * @name form.FormController#$addControl - * @param {object} control control object, either a {@link form.FormController} or an - * {@link ngModel.NgModelController} - * - * @description - * Register a control with the form. Input elements using ngModelController do this automatically - * when they are linked. - * - * Note that the current state of the control will not be reflected on the new parent form. This - * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` - * state. - * - * However, if the method is used programmatically, for example by adding dynamically created controls, - * or controls that have been previously removed without destroying their corresponding DOM element, - * it's the developers responsiblity to make sure the current state propagates to the parent form. - * - * For example, if an input control is added that is already `$dirty` and has `$error` properties, - * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. - */ - form.$addControl = function(control) { - // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored - // and not added to the scope. Now we throw an error. - assertNotHasOwnProperty(control.$name, 'input'); - controls.push(control); - - if (control.$name) { - form[control.$name] = control; - } - - control.$$parentForm = form; - }; - - // Private API: rename a form control - form.$$renameControl = function(control, newName) { - var oldName = control.$name; - - if (form[oldName] === control) { - delete form[oldName]; - } - form[newName] = control; - control.$name = newName; - }; - - /** - * @ngdoc method - * @name form.FormController#$removeControl - * @param {object} control control object, either a {@link form.FormController} or an - * {@link ngModel.NgModelController} - * - * @description - * Deregister a control from the form. - * - * Input elements using ngModelController do this automatically when they are destroyed. - * - * Note that only the removed control's validation state (`$errors`etc.) will be removed from the - * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be - * different from case to case. For example, removing the only `$dirty` control from a form may or - * may not mean that the form is still `$dirty`. - */ - form.$removeControl = function(control) { - if (control.$name && form[control.$name] === control) { - delete form[control.$name]; - } - forEach(form.$pending, function(value, name) { - form.$setValidity(name, null, control); - }); - forEach(form.$error, function(value, name) { - form.$setValidity(name, null, control); - }); - forEach(form.$$success, function(value, name) { - form.$setValidity(name, null, control); - }); - - arrayRemove(controls, control); - control.$$parentForm = nullFormCtrl; - }; - - - /** - * @ngdoc method - * @name form.FormController#$setValidity - * - * @description - * Sets the validity of a form control. - * - * This method will also propagate to parent forms. - */ - addSetValidityMethod({ - ctrl: this, - $element: element, - set: function(object, property, controller) { - var list = object[property]; - if (!list) { - object[property] = [controller]; - } else { - var index = list.indexOf(controller); - if (index === -1) { - list.push(controller); - } - } - }, - unset: function(object, property, controller) { - var list = object[property]; - if (!list) { - return; - } - arrayRemove(list, controller); - if (list.length === 0) { - delete object[property]; - } - }, - $animate: $animate - }); - - /** - * @ngdoc method - * @name form.FormController#$setDirty - * - * @description - * Sets the form to a dirty state. - * - * This method can be called to add the 'ng-dirty' class and set the form to a dirty - * state (ng-dirty class). This method will also propagate to parent forms. - */ - form.$setDirty = function() { - $animate.removeClass(element, PRISTINE_CLASS); - $animate.addClass(element, DIRTY_CLASS); - form.$dirty = true; - form.$pristine = false; - form.$$parentForm.$setDirty(); - }; - - /** - * @ngdoc method - * @name form.FormController#$setPristine - * - * @description - * Sets the form to its pristine state. - * - * This method can be called to remove the 'ng-dirty' class and set the form to its pristine - * state (ng-pristine class). This method will also propagate to all the controls contained - * in this form. - * - * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after - * saving or resetting it. - */ - form.$setPristine = function() { - $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); - form.$dirty = false; - form.$pristine = true; - form.$submitted = false; - forEach(controls, function(control) { - control.$setPristine(); - }); - }; - - /** - * @ngdoc method - * @name form.FormController#$setUntouched - * - * @description - * Sets the form to its untouched state. - * - * This method can be called to remove the 'ng-touched' class and set the form controls to their - * untouched state (ng-untouched class). - * - * Setting a form controls back to their untouched state is often useful when setting the form - * back to its pristine state. - */ - form.$setUntouched = function() { - forEach(controls, function(control) { - control.$setUntouched(); - }); - }; - - /** - * @ngdoc method - * @name form.FormController#$setSubmitted - * - * @description - * Sets the form to its submitted state. - */ - form.$setSubmitted = function() { - $animate.addClass(element, SUBMITTED_CLASS); - form.$submitted = true; - form.$$parentForm.$setSubmitted(); - }; -} - -/** - * @ngdoc directive - * @name ngForm - * @restrict EAC - * - * @description - * Nestable alias of {@link ng.directive:form `form`} directive. HTML - * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a - * sub-group of controls needs to be determined. - * - * Note: the purpose of `ngForm` is to group controls, - * but not to be a replacement for the `<form>` tag with all of its capabilities - * (e.g. posting to the server, ...). - * - * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into - * related scope, under this name. - * - */ - - /** - * @ngdoc directive - * @name form - * @restrict E - * - * @description - * Directive that instantiates - * {@link form.FormController FormController}. - * - * If the `name` attribute is specified, the form controller is published onto the current scope under - * this name. - * - * # Alias: {@link ng.directive:ngForm `ngForm`} - * - * In Angular, forms can be nested. This means that the outer form is valid when all of the child - * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so - * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to - * `<form>` but can be nested. This allows you to have nested forms, which is very useful when - * using Angular validation directives in forms that are dynamically generated using the - * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` - * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an - * `ngForm` directive and nest these in an outer `form` element. - * - * - * # CSS classes - * - `ng-valid` is set if the form is valid. - * - `ng-invalid` is set if the form is invalid. - * - `ng-pending` is set if the form is pending. - * - `ng-pristine` is set if the form is pristine. - * - `ng-dirty` is set if the form is dirty. - * - `ng-submitted` is set if the form was submitted. - * - * Keep in mind that ngAnimate can detect each of these classes when added and removed. - * - * - * # Submitting a form and preventing the default action - * - * Since the role of forms in client-side Angular applications is different than in classical - * roundtrip apps, it is desirable for the browser not to translate the form submission into a full - * page reload that sends the data to the server. Instead some javascript logic should be triggered - * to handle the form submission in an application-specific way. - * - * For this reason, Angular prevents the default action (form submission to the server) unless the - * `<form>` element has an `action` attribute specified. - * - * You can use one of the following two ways to specify what javascript method should be called when - * a form is submitted: - * - * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element - * - {@link ng.directive:ngClick ngClick} directive on the first - * button or input field of type submit (input[type=submit]) - * - * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} - * or {@link ng.directive:ngClick ngClick} directives. - * This is because of the following form submission rules in the HTML specification: - * - * - If a form has only one input field then hitting enter in this field triggers form submit - * (`ngSubmit`) - * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter - * doesn't trigger submit - * - if a form has one or more input fields and one or more buttons or input[type=submit] then - * hitting enter in any of the input fields will trigger the click handler on the *first* button or - * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) - * - * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is - * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` - * to have access to the updated model. - * - * ## Animation Hooks - * - * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. - * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any - * other validations that are performed within the form. Animations in ngForm are similar to how - * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well - * as JS animations. - * - * The following example shows a simple way to utilize CSS transitions to style a form element - * that has been rendered as invalid after it has been validated: - * - * <pre> - * //be sure to include ngAnimate as a module to hook into more - * //advanced animations - * .my-form { - * transition:0.5s linear all; - * background: white; - * } - * .my-form.ng-invalid { - * background: red; - * color:white; - * } - * </pre> - * - * @example - <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> - <file name="index.html"> - <script> - angular.module('formExample', []) - .controller('FormController', ['$scope', function($scope) { - $scope.userType = 'guest'; - }]); - </script> - <style> - .my-form { - transition:all linear 0.5s; - background: transparent; - } - .my-form.ng-invalid { - background: red; - } - </style> - <form name="myForm" ng-controller="FormController" class="my-form"> - userType: <input name="input" ng-model="userType" required> - <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> - <code>userType = {{userType}}</code><br> - <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> - <code>myForm.input.$error = {{myForm.input.$error}}</code><br> - <code>myForm.$valid = {{myForm.$valid}}</code><br> - <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> - </form> - </file> - <file name="protractor.js" type="protractor"> - it('should initialize to model', function() { - var userType = element(by.binding('userType')); - var valid = element(by.binding('myForm.input.$valid')); - - expect(userType.getText()).toContain('guest'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - var userType = element(by.binding('userType')); - var valid = element(by.binding('myForm.input.$valid')); - var userInput = element(by.model('userType')); - - userInput.clear(); - userInput.sendKeys(''); - - expect(userType.getText()).toEqual('userType ='); - expect(valid.getText()).toContain('false'); - }); - </file> - </example> - * - * @param {string=} name Name of the form. If specified, the form controller will be published into - * related scope, under this name. - */ -var formDirectiveFactory = function(isNgForm) { - return ['$timeout', '$parse', function($timeout, $parse) { - var formDirective = { - name: 'form', - restrict: isNgForm ? 'EAC' : 'E', - require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form - controller: FormController, - compile: function ngFormCompile(formElement, attr) { - // Setup initial state of the control - formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); - - var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); - - return { - pre: function ngFormPreLink(scope, formElement, attr, ctrls) { - var controller = ctrls[0]; - - // if `action` attr is not present on the form, prevent the default action (submission) - if (!('action' in attr)) { - // we can't use jq events because if a form is destroyed during submission the default - // action is not prevented. see #1238 - // - // IE 9 is not affected because it doesn't fire a submit event and try to do a full - // page reload if the form was destroyed by submission of the form via a click handler - // on a button in the form. Looks like an IE9 specific bug. - var handleFormSubmission = function(event) { - scope.$apply(function() { - controller.$commitViewValue(); - controller.$setSubmitted(); - }); - - event.preventDefault(); - }; - - addEventListenerFn(formElement[0], 'submit', handleFormSubmission); - - // unregister the preventDefault listener so that we don't not leak memory but in a - // way that will achieve the prevention of the default action. - formElement.on('$destroy', function() { - $timeout(function() { - removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); - }, 0, false); - }); - } - - var parentFormCtrl = ctrls[1] || controller.$$parentForm; - parentFormCtrl.$addControl(controller); - - var setter = nameAttr ? getSetter(controller.$name) : noop; - - if (nameAttr) { - setter(scope, controller); - attr.$observe(nameAttr, function(newValue) { - if (controller.$name === newValue) return; - setter(scope, undefined); - controller.$$parentForm.$$renameControl(controller, newValue); - setter = getSetter(controller.$name); - setter(scope, controller); - }); - } - formElement.on('$destroy', function() { - controller.$$parentForm.$removeControl(controller); - setter(scope, undefined); - extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards - }); - } - }; - } - }; - - return formDirective; - - function getSetter(expression) { - if (expression === '') { - //create an assignable expression, so forms with an empty name can be renamed later - return $parse('this[""]').assign; - } - return $parse(expression).assign || noop; - } - }]; -}; - -var formDirective = formDirectiveFactory(); -var ngFormDirective = formDirectiveFactory(true); - -/* global VALID_CLASS: false, - INVALID_CLASS: false, - PRISTINE_CLASS: false, - DIRTY_CLASS: false, - UNTOUCHED_CLASS: false, - TOUCHED_CLASS: false, - ngModelMinErr: false, -*/ - -// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 -var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/; -var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; -var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; -var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; -var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; -var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; -var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; -var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; -var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; - -var inputType = { - - /** - * @ngdoc input - * @name input[text] - * - * @description - * Standard HTML text input with angular data binding, inherited by most of the `input` elements. - * - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Adds `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of - * any length. - * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string - * that contains the regular expression body that will be converted to a regular expression - * as in the ngPattern directive. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. - * If the expression evaluates to a RegExp object, then this is used directly. - * If the expression evaluates to a string, then it will be converted to a RegExp - * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to - * `new RegExp('^abc$')`.<br /> - * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to - * start at the index of the last search's match, thus not taking the whole input value into - * account. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. - * This parameter is ignored for input[type=password] controls, which will never trim the - * input. - * - * @example - <example name="text-input-directive" module="textInputExample"> - <file name="index.html"> - <script> - angular.module('textInputExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.example = { - text: 'guest', - word: /^\s*\w*\s*$/ - }; - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - <label>Single word: - <input type="text" name="input" ng-model="example.text" - ng-pattern="example.word" required ng-trim="false"> - </label> - <div role="alert"> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.pattern"> - Single word only!</span> - </div> - <tt>text = {{example.text}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var text = element(by.binding('example.text')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('example.text')); - - it('should initialize to model', function() { - expect(text.getText()).toContain('guest'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - - expect(text.getText()).toEqual('text ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if multi word', function() { - input.clear(); - input.sendKeys('hello world'); - - expect(valid.getText()).toContain('false'); - }); - </file> - </example> - */ - 'text': textInputType, - - /** - * @ngdoc input - * @name input[date] - * - * @description - * Input with date validation and transformation. In browsers that do not yet support - * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 - * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many - * modern browsers do not yet support this input type, it is important to provide cues to users on the - * expected input format via a placeholder or label. - * - * The model must always be a Date object, otherwise Angular will throw an error. - * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. - * - * The timezone to be used to read/write the `Date` instance in the model can be defined using - * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a - * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute - * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 - * constraint validation. - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be - * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute - * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 - * constraint validation. - * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string - * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. - * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string - * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="date-input-directive" module="dateInputExample"> - <file name="index.html"> - <script> - angular.module('dateInputExample', []) - .controller('DateController', ['$scope', function($scope) { - $scope.example = { - value: new Date(2013, 9, 22) - }; - }]); - </script> - <form name="myForm" ng-controller="DateController as dateCtrl"> - <label for="exampleInput">Pick a date in 2013:</label> - <input type="date" id="exampleInput" name="input" ng-model="example.value" - placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> - <div role="alert"> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.date"> - Not a valid date!</span> - </div> - <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('example.value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (see https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2013-10-22'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-01-01'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - </file> - </example> - */ - 'date': createDateInputType('date', DATE_REGEXP, - createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), - 'yyyy-MM-dd'), - - /** - * @ngdoc input - * @name input[datetime-local] - * - * @description - * Input with datetime validation and transformation. In browsers that do not yet support - * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. - * - * The model must always be a Date object, otherwise Angular will throw an error. - * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. - * - * The timezone to be used to read/write the `Date` instance in the model can be defined using - * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. - * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation - * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). - * Note that `min` will also add native HTML5 constraint validation. - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. - * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation - * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). - * Note that `max` will also add native HTML5 constraint validation. - * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string - * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. - * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string - * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="datetimelocal-input-directive" module="dateExample"> - <file name="index.html"> - <script> - angular.module('dateExample', []) - .controller('DateController', ['$scope', function($scope) { - $scope.example = { - value: new Date(2010, 11, 28, 14, 57) - }; - }]); - </script> - <form name="myForm" ng-controller="DateController as dateCtrl"> - <label for="exampleInput">Pick a date between in 2013:</label> - <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" - placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> - <div role="alert"> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.datetimelocal"> - Not a valid date!</span> - </div> - <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('example.value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2010-12-28T14:57:00'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-01-01T23:59:00'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - </file> - </example> - */ - 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, - createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), - 'yyyy-MM-ddTHH:mm:ss.sss'), - - /** - * @ngdoc input - * @name input[time] - * - * @description - * Input with time validation and transformation. In browsers that do not yet support - * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a - * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. - * - * The model must always be a Date object, otherwise Angular will throw an error. - * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. - * - * The timezone to be used to read/write the `Date` instance in the model can be defined using - * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. - * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this - * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add - * native HTML5 constraint validation. - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. - * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this - * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add - * native HTML5 constraint validation. - * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the - * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. - * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the - * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="time-input-directive" module="timeExample"> - <file name="index.html"> - <script> - angular.module('timeExample', []) - .controller('DateController', ['$scope', function($scope) { - $scope.example = { - value: new Date(1970, 0, 1, 14, 57, 0) - }; - }]); - </script> - <form name="myForm" ng-controller="DateController as dateCtrl"> - <label for="exampleInput">Pick a between 8am and 5pm:</label> - <input type="time" id="exampleInput" name="input" ng-model="example.value" - placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> - <div role="alert"> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.time"> - Not a valid date!</span> - </div> - <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('example.value | date: "HH:mm:ss"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('example.value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('14:57:00'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('23:59:00'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - </file> - </example> - */ - 'time': createDateInputType('time', TIME_REGEXP, - createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), - 'HH:mm:ss.sss'), - - /** - * @ngdoc input - * @name input[week] - * - * @description - * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support - * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * week format (yyyy-W##), for example: `2013-W02`. - * - * The model must always be a Date object, otherwise Angular will throw an error. - * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. - * - * The timezone to be used to read/write the `Date` instance in the model can be defined using - * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. - * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this - * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add - * native HTML5 constraint validation. - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. - * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this - * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add - * native HTML5 constraint validation. - * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string - * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. - * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string - * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="week-input-directive" module="weekExample"> - <file name="index.html"> - <script> - angular.module('weekExample', []) - .controller('DateController', ['$scope', function($scope) { - $scope.example = { - value: new Date(2013, 0, 3) - }; - }]); - </script> - <form name="myForm" ng-controller="DateController as dateCtrl"> - <label>Pick a date between in 2013: - <input id="exampleInput" type="week" name="input" ng-model="example.value" - placeholder="YYYY-W##" min="2012-W32" - max="2013-W52" required /> - </label> - <div role="alert"> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.week"> - Not a valid date!</span> - </div> - <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('example.value | date: "yyyy-Www"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('example.value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2013-W01'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-W01'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - </file> - </example> - */ - 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), - - /** - * @ngdoc input - * @name input[month] - * - * @description - * Input with month validation and transformation. In browsers that do not yet support - * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * month format (yyyy-MM), for example: `2009-01`. - * - * The model must always be a Date object, otherwise Angular will throw an error. - * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. - * If the model is not set to the first of the month, the next view to model update will set it - * to the first of the month. - * - * The timezone to be used to read/write the `Date` instance in the model can be defined using - * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. - * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this - * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add - * native HTML5 constraint validation. - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. - * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this - * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add - * native HTML5 constraint validation. - * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string - * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. - * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string - * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. - - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="month-input-directive" module="monthExample"> - <file name="index.html"> - <script> - angular.module('monthExample', []) - .controller('DateController', ['$scope', function($scope) { - $scope.example = { - value: new Date(2013, 9, 1) - }; - }]); - </script> - <form name="myForm" ng-controller="DateController as dateCtrl"> - <label for="exampleInput">Pick a month in 2013:</label> - <input id="exampleInput" type="month" name="input" ng-model="example.value" - placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> - <div role="alert"> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.month"> - Not a valid month!</span> - </div> - <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('example.value | date: "yyyy-MM"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('example.value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2013-10'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-01'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - </file> - </example> - */ - 'month': createDateInputType('month', MONTH_REGEXP, - createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), - 'yyyy-MM'), - - /** - * @ngdoc input - * @name input[number] - * - * @description - * Text input with number validation and transformation. Sets the `number` validation - * error if not a valid number. - * - * <div class="alert alert-warning"> - * The model must always be of type `number` otherwise Angular will throw an error. - * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} - * error docs for more information and an example of how to convert your model if necessary. - * </div> - * - * ## Issues with HTML5 constraint validation - * - * In browsers that follow the - * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), - * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. - * If a non-number is entered in the input, the browser will report the value as an empty string, - * which means the view / model values in `ngModel` and subsequently the scope value - * will also be an empty string. - * - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of - * any length. - * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string - * that contains the regular expression body that will be converted to a regular expression - * as in the ngPattern directive. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. - * If the expression evaluates to a RegExp object, then this is used directly. - * If the expression evaluates to a string, then it will be converted to a RegExp - * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to - * `new RegExp('^abc$')`.<br /> - * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to - * start at the index of the last search's match, thus not taking the whole input value into - * account. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="number-input-directive" module="numberExample"> - <file name="index.html"> - <script> - angular.module('numberExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.example = { - value: 12 - }; - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - <label>Number: - <input type="number" name="input" ng-model="example.value" - min="0" max="99" required> - </label> - <div role="alert"> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.number"> - Not valid number!</span> - </div> - <tt>value = {{example.value}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var value = element(by.binding('example.value')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('example.value')); - - it('should initialize to model', function() { - expect(value.getText()).toContain('12'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if over max', function() { - input.clear(); - input.sendKeys('123'); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('false'); - }); - </file> - </example> - */ - 'number': numberInputType, - - - /** - * @ngdoc input - * @name input[url] - * - * @description - * Text input with URL validation. Sets the `url` validation error key if the content is not a - * valid URL. - * - * <div class="alert alert-warning"> - * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex - * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify - * the built-in validators (see the {@link guide/forms Forms guide}) - * </div> - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of - * any length. - * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string - * that contains the regular expression body that will be converted to a regular expression - * as in the ngPattern directive. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. - * If the expression evaluates to a RegExp object, then this is used directly. - * If the expression evaluates to a string, then it will be converted to a RegExp - * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to - * `new RegExp('^abc$')`.<br /> - * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to - * start at the index of the last search's match, thus not taking the whole input value into - * account. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="url-input-directive" module="urlExample"> - <file name="index.html"> - <script> - angular.module('urlExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.url = { - text: 'http://google.com' - }; - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - <label>URL: - <input type="url" name="input" ng-model="url.text" required> - <label> - <div role="alert"> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.url"> - Not valid url!</span> - </div> - <tt>text = {{url.text}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var text = element(by.binding('url.text')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('url.text')); - - it('should initialize to model', function() { - expect(text.getText()).toContain('http://google.com'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - - expect(text.getText()).toEqual('text ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if not url', function() { - input.clear(); - input.sendKeys('box'); - - expect(valid.getText()).toContain('false'); - }); - </file> - </example> - */ - 'url': urlInputType, - - - /** - * @ngdoc input - * @name input[email] - * - * @description - * Text input with email validation. Sets the `email` validation error key if not a valid email - * address. - * - * <div class="alert alert-warning"> - * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex - * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can - * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) - * </div> - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of - * any length. - * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string - * that contains the regular expression body that will be converted to a regular expression - * as in the ngPattern directive. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. - * If the expression evaluates to a RegExp object, then this is used directly. - * If the expression evaluates to a string, then it will be converted to a RegExp - * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to - * `new RegExp('^abc$')`.<br /> - * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to - * start at the index of the last search's match, thus not taking the whole input value into - * account. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="email-input-directive" module="emailExample"> - <file name="index.html"> - <script> - angular.module('emailExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.email = { - text: 'me@example.com' - }; - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - <label>Email: - <input type="email" name="input" ng-model="email.text" required> - </label> - <div role="alert"> - <span class="error" ng-show="myForm.input.$error.required"> - Required!</span> - <span class="error" ng-show="myForm.input.$error.email"> - Not valid email!</span> - </div> - <tt>text = {{email.text}}</tt><br/> - <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> - <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - var text = element(by.binding('email.text')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('email.text')); - - it('should initialize to model', function() { - expect(text.getText()).toContain('me@example.com'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - expect(text.getText()).toEqual('text ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if not email', function() { - input.clear(); - input.sendKeys('xxx'); - - expect(valid.getText()).toContain('false'); - }); - </file> - </example> - */ - 'email': emailInputType, - - - /** - * @ngdoc input - * @name input[radio] - * - * @description - * HTML radio button. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string} value The value to which the `ngModel` expression should be set when selected. - * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, - * too. Use `ngValue` if you need complex models (`number`, `object`, ...). - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio - * is selected. Should be used instead of the `value` attribute if you need - * a non-string `ngModel` (`boolean`, `array`, ...). - * - * @example - <example name="radio-input-directive" module="radioExample"> - <file name="index.html"> - <script> - angular.module('radioExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.color = { - name: 'blue' - }; - $scope.specialValue = { - "id": "12345", - "value": "green" - }; - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - <label> - <input type="radio" ng-model="color.name" value="red"> - Red - </label><br/> - <label> - <input type="radio" ng-model="color.name" ng-value="specialValue"> - Green - </label><br/> - <label> - <input type="radio" ng-model="color.name" value="blue"> - Blue - </label><br/> - <tt>color = {{color.name | json}}</tt><br/> - </form> - Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. - </file> - <file name="protractor.js" type="protractor"> - it('should change state', function() { - var color = element(by.binding('color.name')); - - expect(color.getText()).toContain('blue'); - - element.all(by.model('color.name')).get(0).click(); - - expect(color.getText()).toContain('red'); - }); - </file> - </example> - */ - 'radio': radioInputType, - - - /** - * @ngdoc input - * @name input[checkbox] - * - * @description - * HTML checkbox. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {expression=} ngTrueValue The value to which the expression should be set when selected. - * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - <example name="checkbox-input-directive" module="checkboxExample"> - <file name="index.html"> - <script> - angular.module('checkboxExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.checkboxModel = { - value1 : true, - value2 : 'YES' - }; - }]); - </script> - <form name="myForm" ng-controller="ExampleController"> - <label>Value1: - <input type="checkbox" ng-model="checkboxModel.value1"> - </label><br/> - <label>Value2: - <input type="checkbox" ng-model="checkboxModel.value2" - ng-true-value="'YES'" ng-false-value="'NO'"> - </label><br/> - <tt>value1 = {{checkboxModel.value1}}</tt><br/> - <tt>value2 = {{checkboxModel.value2}}</tt><br/> - </form> - </file> - <file name="protractor.js" type="protractor"> - it('should change state', function() { - var value1 = element(by.binding('checkboxModel.value1')); - var value2 = element(by.binding('checkboxModel.value2')); - - expect(value1.getText()).toContain('true'); - expect(value2.getText()).toContain('YES'); - - element(by.model('checkboxModel.value1')).click(); - element(by.model('checkboxModel.value2')).click(); - - expect(value1.getText()).toContain('false'); - expect(value2.getText()).toContain('NO'); - }); - </file> - </example> - */ - 'checkbox': checkboxInputType, - - 'hidden': noop, - 'button': noop, - 'submit': noop, - 'reset': noop, - 'file': noop -}; - -function stringBasedInputType(ctrl) { - ctrl.$formatters.push(function(value) { - return ctrl.$isEmpty(value) ? value : value.toString(); - }); -} - -function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - stringBasedInputType(ctrl); -} - -function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { - var type = lowercase(element[0].type); - - // In composition mode, users are still inputing intermediate text buffer, - // hold the listener until composition is done. - // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent - if (!$sniffer.android) { - var composing = false; - - element.on('compositionstart', function(data) { - composing = true; - }); - - element.on('compositionend', function() { - composing = false; - listener(); - }); - } - - var listener = function(ev) { - if (timeout) { - $browser.defer.cancel(timeout); - timeout = null; - } - if (composing) return; - var value = element.val(), - event = ev && ev.type; - - // By default we will trim the value - // If the attribute ng-trim exists we will avoid trimming - // If input type is 'password', the value is never trimmed - if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { - value = trim(value); - } - - // If a control is suffering from bad input (due to native validators), browsers discard its - // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the - // control's value is the same empty value twice in a row. - if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { - ctrl.$setViewValue(value, event); - } - }; - - // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the - // input event on backspace, delete or cut - if ($sniffer.hasEvent('input')) { - element.on('input', listener); - } else { - var timeout; - - var deferListener = function(ev, input, origValue) { - if (!timeout) { - timeout = $browser.defer(function() { - timeout = null; - if (!input || input.value !== origValue) { - listener(ev); - } - }); - } - }; - - element.on('keydown', function(event) { - var key = event.keyCode; - - // ignore - // command modifiers arrows - if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; - - deferListener(event, this, this.value); - }); - - // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it - if ($sniffer.hasEvent('paste')) { - element.on('paste cut', deferListener); - } - } - - // if user paste into input using mouse on older browser - // or form autocomplete on newer browser, we need "change" event to catch it - element.on('change', listener); - - ctrl.$render = function() { - // Workaround for Firefox validation #12102. - var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; - if (element.val() !== value) { - element.val(value); - } - }; -} - -function weekParser(isoWeek, existingDate) { - if (isDate(isoWeek)) { - return isoWeek; - } - - if (isString(isoWeek)) { - WEEK_REGEXP.lastIndex = 0; - var parts = WEEK_REGEXP.exec(isoWeek); - if (parts) { - var year = +parts[1], - week = +parts[2], - hours = 0, - minutes = 0, - seconds = 0, - milliseconds = 0, - firstThurs = getFirstThursdayOfYear(year), - addDays = (week - 1) * 7; - - if (existingDate) { - hours = existingDate.getHours(); - minutes = existingDate.getMinutes(); - seconds = existingDate.getSeconds(); - milliseconds = existingDate.getMilliseconds(); - } - - return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); - } - } - - return NaN; -} - -function createDateParser(regexp, mapping) { - return function(iso, date) { - var parts, map; - - if (isDate(iso)) { - return iso; - } - - if (isString(iso)) { - // When a date is JSON'ified to wraps itself inside of an extra - // set of double quotes. This makes the date parsing code unable - // to match the date string and parse it as a date. - if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { - iso = iso.substring(1, iso.length - 1); - } - if (ISO_DATE_REGEXP.test(iso)) { - return new Date(iso); - } - regexp.lastIndex = 0; - parts = regexp.exec(iso); - - if (parts) { - parts.shift(); - if (date) { - map = { - yyyy: date.getFullYear(), - MM: date.getMonth() + 1, - dd: date.getDate(), - HH: date.getHours(), - mm: date.getMinutes(), - ss: date.getSeconds(), - sss: date.getMilliseconds() / 1000 - }; - } else { - map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; - } - - forEach(parts, function(part, index) { - if (index < mapping.length) { - map[mapping[index]] = +part; - } - }); - return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); - } - } - - return NaN; - }; -} - -function createDateInputType(type, regexp, parseDate, format) { - return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { - badInputChecker(scope, element, attr, ctrl); - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; - var previousDate; - - ctrl.$$parserName = type; - ctrl.$parsers.push(function(value) { - if (ctrl.$isEmpty(value)) return null; - if (regexp.test(value)) { - // Note: We cannot read ctrl.$modelValue, as there might be a different - // parser/formatter in the processing chain so that the model - // contains some different data format! - var parsedDate = parseDate(value, previousDate); - if (timezone) { - parsedDate = convertTimezoneToLocal(parsedDate, timezone); - } - return parsedDate; - } - return undefined; - }); - - ctrl.$formatters.push(function(value) { - if (value && !isDate(value)) { - throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); - } - if (isValidDate(value)) { - previousDate = value; - if (previousDate && timezone) { - previousDate = convertTimezoneToLocal(previousDate, timezone, true); - } - return $filter('date')(value, format, timezone); - } else { - previousDate = null; - return ''; - } - }); - - if (isDefined(attr.min) || attr.ngMin) { - var minVal; - ctrl.$validators.min = function(value) { - return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; - }; - attr.$observe('min', function(val) { - minVal = parseObservedDateValue(val); - ctrl.$validate(); - }); - } - - if (isDefined(attr.max) || attr.ngMax) { - var maxVal; - ctrl.$validators.max = function(value) { - return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; - }; - attr.$observe('max', function(val) { - maxVal = parseObservedDateValue(val); - ctrl.$validate(); - }); - } - - function isValidDate(value) { - // Invalid Date: getTime() returns NaN - return value && !(value.getTime && value.getTime() !== value.getTime()); - } - - function parseObservedDateValue(val) { - return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; - } - }; -} - -function badInputChecker(scope, element, attr, ctrl) { - var node = element[0]; - var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); - if (nativeValidation) { - ctrl.$parsers.push(function(value) { - var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; - // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430): - // - also sets validity.badInput (should only be validity.typeMismatch). - // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email) - // - can ignore this case as we can still read out the erroneous email... - return validity.badInput && !validity.typeMismatch ? undefined : value; - }); - } -} - -function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { - badInputChecker(scope, element, attr, ctrl); - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - - ctrl.$$parserName = 'number'; - ctrl.$parsers.push(function(value) { - if (ctrl.$isEmpty(value)) return null; - if (NUMBER_REGEXP.test(value)) return parseFloat(value); - return undefined; - }); - - ctrl.$formatters.push(function(value) { - if (!ctrl.$isEmpty(value)) { - if (!isNumber(value)) { - throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); - } - value = value.toString(); - } - return value; - }); - - if (isDefined(attr.min) || attr.ngMin) { - var minVal; - ctrl.$validators.min = function(value) { - return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; - }; - - attr.$observe('min', function(val) { - if (isDefined(val) && !isNumber(val)) { - val = parseFloat(val, 10); - } - minVal = isNumber(val) && !isNaN(val) ? val : undefined; - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); - }); - } - - if (isDefined(attr.max) || attr.ngMax) { - var maxVal; - ctrl.$validators.max = function(value) { - return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; - }; - - attr.$observe('max', function(val) { - if (isDefined(val) && !isNumber(val)) { - val = parseFloat(val, 10); - } - maxVal = isNumber(val) && !isNaN(val) ? val : undefined; - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); - }); - } -} - -function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { - // Note: no badInputChecker here by purpose as `url` is only a validation - // in browsers, i.e. we can always read out input.value even if it is not valid! - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - stringBasedInputType(ctrl); - - ctrl.$$parserName = 'url'; - ctrl.$validators.url = function(modelValue, viewValue) { - var value = modelValue || viewValue; - return ctrl.$isEmpty(value) || URL_REGEXP.test(value); - }; -} - -function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { - // Note: no badInputChecker here by purpose as `url` is only a validation - // in browsers, i.e. we can always read out input.value even if it is not valid! - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - stringBasedInputType(ctrl); - - ctrl.$$parserName = 'email'; - ctrl.$validators.email = function(modelValue, viewValue) { - var value = modelValue || viewValue; - return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); - }; -} - -function radioInputType(scope, element, attr, ctrl) { - // make the name unique, if not defined - if (isUndefined(attr.name)) { - element.attr('name', nextUid()); - } - - var listener = function(ev) { - if (element[0].checked) { - ctrl.$setViewValue(attr.value, ev && ev.type); - } - }; - - element.on('click', listener); - - ctrl.$render = function() { - var value = attr.value; - element[0].checked = (value == ctrl.$viewValue); - }; - - attr.$observe('value', ctrl.$render); -} - -function parseConstantExpr($parse, context, name, expression, fallback) { - var parseFn; - if (isDefined(expression)) { - parseFn = $parse(expression); - if (!parseFn.constant) { - throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + - '`{1}`.', name, expression); - } - return parseFn(context); - } - return fallback; -} - -function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { - var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); - var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); - - var listener = function(ev) { - ctrl.$setViewValue(element[0].checked, ev && ev.type); - }; - - element.on('click', listener); - - ctrl.$render = function() { - element[0].checked = ctrl.$viewValue; - }; - - // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` - // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert - // it to a boolean. - ctrl.$isEmpty = function(value) { - return value === false; - }; - - ctrl.$formatters.push(function(value) { - return equals(value, trueValue); - }); - - ctrl.$parsers.push(function(value) { - return value ? trueValue : falseValue; - }); -} - - -/** - * @ngdoc directive - * @name textarea - * @restrict E - * - * @description - * HTML textarea element control with angular data-binding. The data-binding and validation - * properties of this element are exactly the same as those of the - * {@link ng.directive:input input element}. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any - * length. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. - * If the expression evaluates to a RegExp object, then this is used directly. - * If the expression evaluates to a string, then it will be converted to a RegExp - * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to - * `new RegExp('^abc$')`.<br /> - * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to - * start at the index of the last search's match, thus not taking the whole input value into - * account. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. - */ - - -/** - * @ngdoc directive - * @name input - * @restrict E - * - * @description - * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, - * input state control, and validation. - * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. - * - * <div class="alert alert-warning"> - * **Note:** Not every feature offered is available for all input types. - * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. - * </div> - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {boolean=} ngRequired Sets `required` attribute if set to true - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any - * length. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. - * If the expression evaluates to a RegExp object, then this is used directly. - * If the expression evaluates to a string, then it will be converted to a RegExp - * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to - * `new RegExp('^abc$')`.<br /> - * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to - * start at the index of the last search's match, thus not taking the whole input value into - * account. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. - * This parameter is ignored for input[type=password] controls, which will never trim the - * input. - * - * @example - <example name="input-directive" module="inputExample"> - <file name="index.html"> - <script> - angular.module('inputExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.user = {name: 'guest', last: 'visitor'}; - }]); - </script> - <div ng-controller="ExampleController"> - <form name="myForm"> - <label> - User name: - <input type="text" name="userName" ng-model="user.name" required> - </label> - <div role="alert"> - <span class="error" ng-show="myForm.userName.$error.required"> - Required!</span> - </div> - <label> - Last name: - <input type="text" name="lastName" ng-model="user.last" - ng-minlength="3" ng-maxlength="10"> - </label> - <div role="alert"> - <span class="error" ng-show="myForm.lastName.$error.minlength"> - Too short!</span> - <span class="error" ng-show="myForm.lastName.$error.maxlength"> - Too long!</span> - </div> - </form> - <hr> - <tt>user = {{user}}</tt><br/> - <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> - <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> - <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> - <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> - <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> - <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> - </div> - </file> - <file name="protractor.js" type="protractor"> - var user = element(by.exactBinding('user')); - var userNameValid = element(by.binding('myForm.userName.$valid')); - var lastNameValid = element(by.binding('myForm.lastName.$valid')); - var lastNameError = element(by.binding('myForm.lastName.$error')); - var formValid = element(by.binding('myForm.$valid')); - var userNameInput = element(by.model('user.name')); - var userLastInput = element(by.model('user.last')); - - it('should initialize to model', function() { - expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); - expect(userNameValid.getText()).toContain('true'); - expect(formValid.getText()).toContain('true'); - }); - - it('should be invalid if empty when required', function() { - userNameInput.clear(); - userNameInput.sendKeys(''); - - expect(user.getText()).toContain('{"last":"visitor"}'); - expect(userNameValid.getText()).toContain('false'); - expect(formValid.getText()).toContain('false'); - }); - - it('should be valid if empty when min length is set', function() { - userLastInput.clear(); - userLastInput.sendKeys(''); - - expect(user.getText()).toContain('{"name":"guest","last":""}'); - expect(lastNameValid.getText()).toContain('true'); - expect(formValid.getText()).toContain('true'); - }); - - it('should be invalid if less than required min length', function() { - userLastInput.clear(); - userLastInput.sendKeys('xx'); - - expect(user.getText()).toContain('{"name":"guest"}'); - expect(lastNameValid.getText()).toContain('false'); - expect(lastNameError.getText()).toContain('minlength'); - expect(formValid.getText()).toContain('false'); - }); - - it('should be invalid if longer than max length', function() { - userLastInput.clear(); - userLastInput.sendKeys('some ridiculously long name'); - - expect(user.getText()).toContain('{"name":"guest"}'); - expect(lastNameValid.getText()).toContain('false'); - expect(lastNameError.getText()).toContain('maxlength'); - expect(formValid.getText()).toContain('false'); - }); - </file> - </example> - */ -var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', - function($browser, $sniffer, $filter, $parse) { - return { - restrict: 'E', - require: ['?ngModel'], - link: { - pre: function(scope, element, attr, ctrls) { - if (ctrls[0]) { - (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, - $browser, $filter, $parse); - } - } - } - }; -}]; - - - -var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; -/** - * @ngdoc directive - * @name ngValue - * - * @description - * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, - * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to - * the bound value. - * - * `ngValue` is useful when dynamically generating lists of radio buttons using - * {@link ngRepeat `ngRepeat`}, as shown below. - * - * Likewise, `ngValue` can be used to generate `<option>` elements for - * the {@link select `select`} element. In that case however, only strings are supported - * for the `value `attribute, so the resulting `ngModel` will always be a string. - * Support for `select` models with non-string values is available via `ngOptions`. - * - * @element input - * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute - * of the `input` element - * - * @example - <example name="ngValue-directive" module="valueExample"> - <file name="index.html"> - <script> - angular.module('valueExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.names = ['pizza', 'unicorns', 'robots']; - $scope.my = { favorite: 'unicorns' }; - }]); - </script> - <form ng-controller="ExampleController"> - <h2>Which is your favorite?</h2> - <label ng-repeat="name in names" for="{{name}}"> - {{name}} - <input type="radio" - ng-model="my.favorite" - ng-value="name" - id="{{name}}" - name="favorite"> - </label> - <div>You chose {{my.favorite}}</div> - </form> - </file> - <file name="protractor.js" type="protractor"> - var favorite = element(by.binding('my.favorite')); - - it('should initialize to model', function() { - expect(favorite.getText()).toContain('unicorns'); - }); - it('should bind the values to the inputs', function() { - element.all(by.model('my.favorite')).get(0).click(); - expect(favorite.getText()).toContain('pizza'); - }); - </file> - </example> - */ -var ngValueDirective = function() { - return { - restrict: 'A', - priority: 100, - compile: function(tpl, tplAttr) { - if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { - return function ngValueConstantLink(scope, elm, attr) { - attr.$set('value', scope.$eval(attr.ngValue)); - }; - } else { - return function ngValueLink(scope, elm, attr) { - scope.$watch(attr.ngValue, function valueWatchAction(value) { - attr.$set('value', value); - }); - }; - } - } - }; -}; - -/** - * @ngdoc directive - * @name ngBind - * @restrict AC - * - * @description - * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element - * with the value of a given expression, and to update the text content when the value of that - * expression changes. - * - * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like - * `{{ expression }}` which is similar but less verbose. - * - * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily - * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an - * element attribute, it makes the bindings invisible to the user while the page is loading. - * - * An alternative solution to this problem would be using the - * {@link ng.directive:ngCloak ngCloak} directive. - * - * - * @element ANY - * @param {expression} ngBind {@link guide/expression Expression} to evaluate. - * - * @example - * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. - <example module="bindExample"> - <file name="index.html"> - <script> - angular.module('bindExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.name = 'Whirled'; - }]); - </script> - <div ng-controller="ExampleController"> - <label>Enter name: <input type="text" ng-model="name"></label><br> - Hello <span ng-bind="name"></span>! - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-bind', function() { - var nameInput = element(by.model('name')); - - expect(element(by.binding('name')).getText()).toBe('Whirled'); - nameInput.clear(); - nameInput.sendKeys('world'); - expect(element(by.binding('name')).getText()).toBe('world'); - }); - </file> - </example> - */ -var ngBindDirective = ['$compile', function($compile) { - return { - restrict: 'AC', - compile: function ngBindCompile(templateElement) { - $compile.$$addBindingClass(templateElement); - return function ngBindLink(scope, element, attr) { - $compile.$$addBindingInfo(element, attr.ngBind); - element = element[0]; - scope.$watch(attr.ngBind, function ngBindWatchAction(value) { - element.textContent = isUndefined(value) ? '' : value; - }); - }; - } - }; -}]; - - -/** - * @ngdoc directive - * @name ngBindTemplate - * - * @description - * The `ngBindTemplate` directive specifies that the element - * text content should be replaced with the interpolation of the template - * in the `ngBindTemplate` attribute. - * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` - * expressions. This directive is needed since some HTML elements - * (such as TITLE and OPTION) cannot contain SPAN elements. - * - * @element ANY - * @param {string} ngBindTemplate template of form - * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. - * - * @example - * Try it here: enter text in text box and watch the greeting change. - <example module="bindExample"> - <file name="index.html"> - <script> - angular.module('bindExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.salutation = 'Hello'; - $scope.name = 'World'; - }]); - </script> - <div ng-controller="ExampleController"> - <label>Salutation: <input type="text" ng-model="salutation"></label><br> - <label>Name: <input type="text" ng-model="name"></label><br> - <pre ng-bind-template="{{salutation}} {{name}}!"></pre> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-bind', function() { - var salutationElem = element(by.binding('salutation')); - var salutationInput = element(by.model('salutation')); - var nameInput = element(by.model('name')); - - expect(salutationElem.getText()).toBe('Hello World!'); - - salutationInput.clear(); - salutationInput.sendKeys('Greetings'); - nameInput.clear(); - nameInput.sendKeys('user'); - - expect(salutationElem.getText()).toBe('Greetings user!'); - }); - </file> - </example> - */ -var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { - return { - compile: function ngBindTemplateCompile(templateElement) { - $compile.$$addBindingClass(templateElement); - return function ngBindTemplateLink(scope, element, attr) { - var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); - $compile.$$addBindingInfo(element, interpolateFn.expressions); - element = element[0]; - attr.$observe('ngBindTemplate', function(value) { - element.textContent = isUndefined(value) ? '' : value; - }); - }; - } - }; -}]; - - -/** - * @ngdoc directive - * @name ngBindHtml - * - * @description - * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, - * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. - * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link - * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} - * in your module's dependencies, you need to include "angular-sanitize.js" in your application. - * - * You may also bypass sanitization for values you know are safe. To do so, bind to - * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example - * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. - * - * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you - * will have an exception (instead of an exploit.) - * - * @element ANY - * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. - * - * @example - - <example module="bindHtmlExample" deps="angular-sanitize.js"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <p ng-bind-html="myHTML"></p> - </div> - </file> - - <file name="script.js"> - angular.module('bindHtmlExample', ['ngSanitize']) - .controller('ExampleController', ['$scope', function($scope) { - $scope.myHTML = - 'I am an <code>HTML</code>string with ' + - '<a href="#">links!</a> and other <em>stuff</em>'; - }]); - </file> - - <file name="protractor.js" type="protractor"> - it('should check ng-bind-html', function() { - expect(element(by.binding('myHTML')).getText()).toBe( - 'I am an HTMLstring with links! and other stuff'); - }); - </file> - </example> - */ -var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { - return { - restrict: 'A', - compile: function ngBindHtmlCompile(tElement, tAttrs) { - var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); - var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { - return (value || '').toString(); - }); - $compile.$$addBindingClass(tElement); - - return function ngBindHtmlLink(scope, element, attr) { - $compile.$$addBindingInfo(element, attr.ngBindHtml); - - scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { - // we re-evaluate the expr because we want a TrustedValueHolderType - // for $sce, not a string - element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); - }); - }; - } - }; -}]; - -/** - * @ngdoc directive - * @name ngChange - * - * @description - * Evaluate the given expression when the user changes the input. - * The expression is evaluated immediately, unlike the JavaScript onchange event - * which only triggers at the end of a change (usually, when the user leaves the - * form element or presses the return key). - * - * The `ngChange` expression is only evaluated when a change in the input value causes - * a new value to be committed to the model. - * - * It will not be evaluated: - * * if the value returned from the `$parsers` transformation pipeline has not changed - * * if the input has continued to be invalid since the model will stay `null` - * * if the model is changed programmatically and not by a change to the input value - * - * - * Note, this directive requires `ngModel` to be present. - * - * @element input - * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change - * in input value. - * - * @example - * <example name="ngChange-directive" module="changeExample"> - * <file name="index.html"> - * <script> - * angular.module('changeExample', []) - * .controller('ExampleController', ['$scope', function($scope) { - * $scope.counter = 0; - * $scope.change = function() { - * $scope.counter++; - * }; - * }]); - * </script> - * <div ng-controller="ExampleController"> - * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> - * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> - * <label for="ng-change-example2">Confirmed</label><br /> - * <tt>debug = {{confirmed}}</tt><br/> - * <tt>counter = {{counter}}</tt><br/> - * </div> - * </file> - * <file name="protractor.js" type="protractor"> - * var counter = element(by.binding('counter')); - * var debug = element(by.binding('confirmed')); - * - * it('should evaluate the expression if changing from view', function() { - * expect(counter.getText()).toContain('0'); - * - * element(by.id('ng-change-example1')).click(); - * - * expect(counter.getText()).toContain('1'); - * expect(debug.getText()).toContain('true'); - * }); - * - * it('should not evaluate the expression if changing from model', function() { - * element(by.id('ng-change-example2')).click(); - - * expect(counter.getText()).toContain('0'); - * expect(debug.getText()).toContain('true'); - * }); - * </file> - * </example> - */ -var ngChangeDirective = valueFn({ - restrict: 'A', - require: 'ngModel', - link: function(scope, element, attr, ctrl) { - ctrl.$viewChangeListeners.push(function() { - scope.$eval(attr.ngChange); - }); - } -}); - -function classDirective(name, selector) { - name = 'ngClass' + name; - return ['$animate', function($animate) { - return { - restrict: 'AC', - link: function(scope, element, attr) { - var oldVal; - - scope.$watch(attr[name], ngClassWatchAction, true); - - attr.$observe('class', function(value) { - ngClassWatchAction(scope.$eval(attr[name])); - }); - - - if (name !== 'ngClass') { - scope.$watch('$index', function($index, old$index) { - // jshint bitwise: false - var mod = $index & 1; - if (mod !== (old$index & 1)) { - var classes = arrayClasses(scope.$eval(attr[name])); - mod === selector ? - addClasses(classes) : - removeClasses(classes); - } - }); - } - - function addClasses(classes) { - var newClasses = digestClassCounts(classes, 1); - attr.$addClass(newClasses); - } - - function removeClasses(classes) { - var newClasses = digestClassCounts(classes, -1); - attr.$removeClass(newClasses); - } - - function digestClassCounts(classes, count) { - // Use createMap() to prevent class assumptions involving property - // names in Object.prototype - var classCounts = element.data('$classCounts') || createMap(); - var classesToUpdate = []; - forEach(classes, function(className) { - if (count > 0 || classCounts[className]) { - classCounts[className] = (classCounts[className] || 0) + count; - if (classCounts[className] === +(count > 0)) { - classesToUpdate.push(className); - } - } - }); - element.data('$classCounts', classCounts); - return classesToUpdate.join(' '); - } - - function updateClasses(oldClasses, newClasses) { - var toAdd = arrayDifference(newClasses, oldClasses); - var toRemove = arrayDifference(oldClasses, newClasses); - toAdd = digestClassCounts(toAdd, 1); - toRemove = digestClassCounts(toRemove, -1); - if (toAdd && toAdd.length) { - $animate.addClass(element, toAdd); - } - if (toRemove && toRemove.length) { - $animate.removeClass(element, toRemove); - } - } - - function ngClassWatchAction(newVal) { - if (selector === true || scope.$index % 2 === selector) { - var newClasses = arrayClasses(newVal || []); - if (!oldVal) { - addClasses(newClasses); - } else if (!equals(newVal,oldVal)) { - var oldClasses = arrayClasses(oldVal); - updateClasses(oldClasses, newClasses); - } - } - oldVal = shallowCopy(newVal); - } - } - }; - - function arrayDifference(tokens1, tokens2) { - var values = []; - - outer: - for (var i = 0; i < tokens1.length; i++) { - var token = tokens1[i]; - for (var j = 0; j < tokens2.length; j++) { - if (token == tokens2[j]) continue outer; - } - values.push(token); - } - return values; - } - - function arrayClasses(classVal) { - var classes = []; - if (isArray(classVal)) { - forEach(classVal, function(v) { - classes = classes.concat(arrayClasses(v)); - }); - return classes; - } else if (isString(classVal)) { - return classVal.split(' '); - } else if (isObject(classVal)) { - forEach(classVal, function(v, k) { - if (v) { - classes = classes.concat(k.split(' ')); - } - }); - return classes; - } - return classVal; - } - }]; -} - -/** - * @ngdoc directive - * @name ngClass - * @restrict AC - * - * @description - * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding - * an expression that represents all classes to be added. - * - * The directive operates in three different ways, depending on which of three types the expression - * evaluates to: - * - * 1. If the expression evaluates to a string, the string should be one or more space-delimited class - * names. - * - * 2. If the expression evaluates to an object, then for each key-value pair of the - * object with a truthy value the corresponding key is used as a class name. - * - * 3. If the expression evaluates to an array, each element of the array should either be a string as in - * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array - * to give you more control over what CSS classes appear. See the code below for an example of this. - * - * - * The directive won't add duplicate classes if a particular class was already set. - * - * When the expression changes, the previously added classes are removed and only then are the - * new classes added. - * - * @animations - * **add** - happens just before the class is applied to the elements - * - * **remove** - happens just before the class is removed from the element - * - * @element ANY - * @param {expression} ngClass {@link guide/expression Expression} to eval. The result - * of the evaluation can be a string representing space delimited class - * names, an array, or a map of class names to boolean values. In the case of a map, the - * names of the properties whose values are truthy will be added as css classes to the - * element. - * - * @example Example that demonstrates basic bindings via ngClass directive. - <example> - <file name="index.html"> - <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> - <label> - <input type="checkbox" ng-model="deleted"> - deleted (apply "strike" class) - </label><br> - <label> - <input type="checkbox" ng-model="important"> - important (apply "bold" class) - </label><br> - <label> - <input type="checkbox" ng-model="error"> - error (apply "has-error" class) - </label> - <hr> - <p ng-class="style">Using String Syntax</p> - <input type="text" ng-model="style" - placeholder="Type: bold strike red" aria-label="Type: bold strike red"> - <hr> - <p ng-class="[style1, style2, style3]">Using Array Syntax</p> - <input ng-model="style1" - placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> - <input ng-model="style2" - placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> - <input ng-model="style3" - placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> - <hr> - <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> - <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> - <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> - </file> - <file name="style.css"> - .strike { - text-decoration: line-through; - } - .bold { - font-weight: bold; - } - .red { - color: red; - } - .has-error { - color: red; - background-color: yellow; - } - .orange { - color: orange; - } - </file> - <file name="protractor.js" type="protractor"> - var ps = element.all(by.css('p')); - - it('should let you toggle the class', function() { - - expect(ps.first().getAttribute('class')).not.toMatch(/bold/); - expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); - - element(by.model('important')).click(); - expect(ps.first().getAttribute('class')).toMatch(/bold/); - - element(by.model('error')).click(); - expect(ps.first().getAttribute('class')).toMatch(/has-error/); - }); - - it('should let you toggle string example', function() { - expect(ps.get(1).getAttribute('class')).toBe(''); - element(by.model('style')).clear(); - element(by.model('style')).sendKeys('red'); - expect(ps.get(1).getAttribute('class')).toBe('red'); - }); - - it('array example should have 3 classes', function() { - expect(ps.get(2).getAttribute('class')).toBe(''); - element(by.model('style1')).sendKeys('bold'); - element(by.model('style2')).sendKeys('strike'); - element(by.model('style3')).sendKeys('red'); - expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); - }); - - it('array with map example should have 2 classes', function() { - expect(ps.last().getAttribute('class')).toBe(''); - element(by.model('style4')).sendKeys('bold'); - element(by.model('warning')).click(); - expect(ps.last().getAttribute('class')).toBe('bold orange'); - }); - </file> - </example> - - ## Animations - - The example below demonstrates how to perform animations using ngClass. - - <example module="ngAnimate" deps="angular-animate.js" animations="true"> - <file name="index.html"> - <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> - <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> - <br> - <span class="base-class" ng-class="myVar">Sample Text</span> - </file> - <file name="style.css"> - .base-class { - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - } - - .base-class.my-class { - color: red; - font-size:3em; - } - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-class', function() { - expect(element(by.css('.base-class')).getAttribute('class')).not. - toMatch(/my-class/); - - element(by.id('setbtn')).click(); - - expect(element(by.css('.base-class')).getAttribute('class')). - toMatch(/my-class/); - - element(by.id('clearbtn')).click(); - - expect(element(by.css('.base-class')).getAttribute('class')).not. - toMatch(/my-class/); - }); - </file> - </example> - - - ## ngClass and pre-existing CSS3 Transitions/Animations - The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. - Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder - any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure - to view the step by step details of {@link $animate#addClass $animate.addClass} and - {@link $animate#removeClass $animate.removeClass}. - */ -var ngClassDirective = classDirective('', true); - -/** - * @ngdoc directive - * @name ngClassOdd - * @restrict AC - * - * @description - * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link ng.directive:ngClass ngClass}, except they work in - * conjunction with `ngRepeat` and take effect only on odd (even) rows. - * - * This directive can be applied only within the scope of an - * {@link ng.directive:ngRepeat ngRepeat}. - * - * @element ANY - * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result - * of the evaluation can be a string representing space delimited class names or an array. - * - * @example - <example> - <file name="index.html"> - <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> - <li ng-repeat="name in names"> - <span ng-class-odd="'odd'" ng-class-even="'even'"> - {{name}} - </span> - </li> - </ol> - </file> - <file name="style.css"> - .odd { - color: red; - } - .even { - color: blue; - } - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-class-odd and ng-class-even', function() { - expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). - toMatch(/odd/); - expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). - toMatch(/even/); - }); - </file> - </example> - */ -var ngClassOddDirective = classDirective('Odd', 0); - -/** - * @ngdoc directive - * @name ngClassEven - * @restrict AC - * - * @description - * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link ng.directive:ngClass ngClass}, except they work in - * conjunction with `ngRepeat` and take effect only on odd (even) rows. - * - * This directive can be applied only within the scope of an - * {@link ng.directive:ngRepeat ngRepeat}. - * - * @element ANY - * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The - * result of the evaluation can be a string representing space delimited class names or an array. - * - * @example - <example> - <file name="index.html"> - <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> - <li ng-repeat="name in names"> - <span ng-class-odd="'odd'" ng-class-even="'even'"> - {{name}} - </span> - </li> - </ol> - </file> - <file name="style.css"> - .odd { - color: red; - } - .even { - color: blue; - } - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-class-odd and ng-class-even', function() { - expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). - toMatch(/odd/); - expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). - toMatch(/even/); - }); - </file> - </example> - */ -var ngClassEvenDirective = classDirective('Even', 1); - -/** - * @ngdoc directive - * @name ngCloak - * @restrict AC - * - * @description - * The `ngCloak` directive is used to prevent the Angular html template from being briefly - * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this - * directive to avoid the undesirable flicker effect caused by the html template display. - * - * The directive can be applied to the `<body>` element, but the preferred usage is to apply - * multiple `ngCloak` directives to small portions of the page to permit progressive rendering - * of the browser view. - * - * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and - * `angular.min.js`. - * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). - * - * ```css - * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { - * display: none !important; - * } - * ``` - * - * When this css rule is loaded by the browser, all html elements (including their children) that - * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive - * during the compilation of the template it deletes the `ngCloak` element attribute, making - * the compiled element visible. - * - * For the best result, the `angular.js` script must be loaded in the head section of the html - * document; alternatively, the css rule above must be included in the external stylesheet of the - * application. - * - * @element ANY - * - * @example - <example> - <file name="index.html"> - <div id="template1" ng-cloak>{{ 'hello' }}</div> - <div id="template2" class="ng-cloak">{{ 'world' }}</div> - </file> - <file name="protractor.js" type="protractor"> - it('should remove the template directive and css class', function() { - expect($('#template1').getAttribute('ng-cloak')). - toBeNull(); - expect($('#template2').getAttribute('ng-cloak')). - toBeNull(); - }); - </file> - </example> - * - */ -var ngCloakDirective = ngDirective({ - compile: function(element, attr) { - attr.$set('ngCloak', undefined); - element.removeClass('ng-cloak'); - } -}); - -/** - * @ngdoc directive - * @name ngController - * - * @description - * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular - * supports the principles behind the Model-View-Controller design pattern. - * - * MVC components in angular: - * - * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties - * are accessed through bindings. - * * View — The template (HTML with data bindings) that is rendered into the View. - * * Controller — The `ngController` directive specifies a Controller class; the class contains business - * logic behind the application to decorate the scope with functions and values - * - * Note that you can also attach controllers to the DOM by declaring it in a route definition - * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller - * again using `ng-controller` in the template itself. This will cause the controller to be attached - * and executed twice. - * - * @element ANY - * @scope - * @priority 500 - * @param {expression} ngController Name of a constructor function registered with the current - * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} - * that on the current scope evaluates to a constructor function. - * - * The controller instance can be published into a scope property by specifying - * `ng-controller="as propertyName"`. - * - * If the current `$controllerProvider` is configured to use globals (via - * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may - * also be the name of a globally accessible constructor function (not recommended). - * - * @example - * Here is a simple form for editing user contact information. Adding, removing, clearing, and - * greeting are methods declared on the controller (see source tab). These methods can - * easily be called from the angular markup. Any changes to the data are automatically reflected - * in the View without the need for a manual update. - * - * Two different declaration styles are included below: - * - * * one binds methods and properties directly onto the controller using `this`: - * `ng-controller="SettingsController1 as settings"` - * * one injects `$scope` into the controller: - * `ng-controller="SettingsController2"` - * - * The second option is more common in the Angular community, and is generally used in boilerplates - * and in this guide. However, there are advantages to binding properties directly to the controller - * and avoiding scope. - * - * * Using `controller as` makes it obvious which controller you are accessing in the template when - * multiple controllers apply to an element. - * * If you are writing your controllers as classes you have easier access to the properties and - * methods, which will appear on the scope, from inside the controller code. - * * Since there is always a `.` in the bindings, you don't have to worry about prototypal - * inheritance masking primitives. - * - * This example demonstrates the `controller as` syntax. - * - * <example name="ngControllerAs" module="controllerAsExample"> - * <file name="index.html"> - * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> - * <label>Name: <input type="text" ng-model="settings.name"/></label> - * <button ng-click="settings.greet()">greet</button><br/> - * Contact: - * <ul> - * <li ng-repeat="contact in settings.contacts"> - * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> - * <option>phone</option> - * <option>email</option> - * </select> - * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> - * <button ng-click="settings.clearContact(contact)">clear</button> - * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> - * </li> - * <li><button ng-click="settings.addContact()">add</button></li> - * </ul> - * </div> - * </file> - * <file name="app.js"> - * angular.module('controllerAsExample', []) - * .controller('SettingsController1', SettingsController1); - * - * function SettingsController1() { - * this.name = "John Smith"; - * this.contacts = [ - * {type: 'phone', value: '408 555 1212'}, - * {type: 'email', value: 'john.smith@example.org'} ]; - * } - * - * SettingsController1.prototype.greet = function() { - * alert(this.name); - * }; - * - * SettingsController1.prototype.addContact = function() { - * this.contacts.push({type: 'email', value: 'yourname@example.org'}); - * }; - * - * SettingsController1.prototype.removeContact = function(contactToRemove) { - * var index = this.contacts.indexOf(contactToRemove); - * this.contacts.splice(index, 1); - * }; - * - * SettingsController1.prototype.clearContact = function(contact) { - * contact.type = 'phone'; - * contact.value = ''; - * }; - * </file> - * <file name="protractor.js" type="protractor"> - * it('should check controller as', function() { - * var container = element(by.id('ctrl-as-exmpl')); - * expect(container.element(by.model('settings.name')) - * .getAttribute('value')).toBe('John Smith'); - * - * var firstRepeat = - * container.element(by.repeater('contact in settings.contacts').row(0)); - * var secondRepeat = - * container.element(by.repeater('contact in settings.contacts').row(1)); - * - * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe('408 555 1212'); - * - * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe('john.smith@example.org'); - * - * firstRepeat.element(by.buttonText('clear')).click(); - * - * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe(''); - * - * container.element(by.buttonText('add')).click(); - * - * expect(container.element(by.repeater('contact in settings.contacts').row(2)) - * .element(by.model('contact.value')) - * .getAttribute('value')) - * .toBe('yourname@example.org'); - * }); - * </file> - * </example> - * - * This example demonstrates the "attach to `$scope`" style of controller. - * - * <example name="ngController" module="controllerExample"> - * <file name="index.html"> - * <div id="ctrl-exmpl" ng-controller="SettingsController2"> - * <label>Name: <input type="text" ng-model="name"/></label> - * <button ng-click="greet()">greet</button><br/> - * Contact: - * <ul> - * <li ng-repeat="contact in contacts"> - * <select ng-model="contact.type" id="select_{{$index}}"> - * <option>phone</option> - * <option>email</option> - * </select> - * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> - * <button ng-click="clearContact(contact)">clear</button> - * <button ng-click="removeContact(contact)">X</button> - * </li> - * <li>[ <button ng-click="addContact()">add</button> ]</li> - * </ul> - * </div> - * </file> - * <file name="app.js"> - * angular.module('controllerExample', []) - * .controller('SettingsController2', ['$scope', SettingsController2]); - * - * function SettingsController2($scope) { - * $scope.name = "John Smith"; - * $scope.contacts = [ - * {type:'phone', value:'408 555 1212'}, - * {type:'email', value:'john.smith@example.org'} ]; - * - * $scope.greet = function() { - * alert($scope.name); - * }; - * - * $scope.addContact = function() { - * $scope.contacts.push({type:'email', value:'yourname@example.org'}); - * }; - * - * $scope.removeContact = function(contactToRemove) { - * var index = $scope.contacts.indexOf(contactToRemove); - * $scope.contacts.splice(index, 1); - * }; - * - * $scope.clearContact = function(contact) { - * contact.type = 'phone'; - * contact.value = ''; - * }; - * } - * </file> - * <file name="protractor.js" type="protractor"> - * it('should check controller', function() { - * var container = element(by.id('ctrl-exmpl')); - * - * expect(container.element(by.model('name')) - * .getAttribute('value')).toBe('John Smith'); - * - * var firstRepeat = - * container.element(by.repeater('contact in contacts').row(0)); - * var secondRepeat = - * container.element(by.repeater('contact in contacts').row(1)); - * - * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe('408 555 1212'); - * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe('john.smith@example.org'); - * - * firstRepeat.element(by.buttonText('clear')).click(); - * - * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) - * .toBe(''); - * - * container.element(by.buttonText('add')).click(); - * - * expect(container.element(by.repeater('contact in contacts').row(2)) - * .element(by.model('contact.value')) - * .getAttribute('value')) - * .toBe('yourname@example.org'); - * }); - * </file> - *</example> - - */ -var ngControllerDirective = [function() { - return { - restrict: 'A', - scope: true, - controller: '@', - priority: 500 - }; -}]; - -/** - * @ngdoc directive - * @name ngCsp - * - * @element html - * @description - * - * Angular has some features that can break certain - * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules. - * - * If you intend to implement these rules then you must tell Angular not to use these features. - * - * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. - * - * - * The following rules affect Angular: - * - * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions - * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30% - * increase in the speed of evaluating Angular expressions. - * - * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular - * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). - * To make these directives work when a CSP rule is blocking inline styles, you must link to the - * `angular-csp.css` in your HTML manually. - * - * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval - * and automatically deactivates this feature in the {@link $parse} service. This autodetection, - * however, triggers a CSP error to be logged in the console: - * - * ``` - * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of - * script in the following Content Security Policy directive: "default-src 'self'". Note that - * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. - * ``` - * - * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` - * directive on an element of the HTML document that appears before the `<script>` tag that loads - * the `angular.js` file. - * - * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* - * - * You can specify which of the CSP related Angular features should be deactivated by providing - * a value for the `ng-csp` attribute. The options are as follows: - * - * * no-inline-style: this stops Angular from injecting CSS styles into the DOM - * - * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings - * - * You can use these values in the following combinations: - * - * - * * No declaration means that Angular will assume that you can do inline styles, but it will do - * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions - * of Angular. - * - * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline - * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions - * of Angular. - * - * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject - * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`. - * - * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can - * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">` - * - * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject - * styles nor use eval, which is the same as an empty: ng-csp. - * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">` - * - * @example - * This example shows how to apply the `ngCsp` directive to the `html` tag. - ```html - <!doctype html> - <html ng-app ng-csp> - ... - ... - </html> - ``` - * @example - // Note: the suffix `.csp` in the example name triggers - // csp mode in our http server! - <example name="example.csp" module="cspExample" ng-csp="true"> - <file name="index.html"> - <div ng-controller="MainController as ctrl"> - <div> - <button ng-click="ctrl.inc()" id="inc">Increment</button> - <span id="counter"> - {{ctrl.counter}} - </span> - </div> - - <div> - <button ng-click="ctrl.evil()" id="evil">Evil</button> - <span id="evilError"> - {{ctrl.evilError}} - </span> - </div> - </div> - </file> - <file name="script.js"> - angular.module('cspExample', []) - .controller('MainController', function() { - this.counter = 0; - this.inc = function() { - this.counter++; - }; - this.evil = function() { - // jshint evil:true - try { - eval('1+2'); - } catch (e) { - this.evilError = e.message; - } - }; - }); - </file> - <file name="protractor.js" type="protractor"> - var util, webdriver; - - var incBtn = element(by.id('inc')); - var counter = element(by.id('counter')); - var evilBtn = element(by.id('evil')); - var evilError = element(by.id('evilError')); - - function getAndClearSevereErrors() { - return browser.manage().logs().get('browser').then(function(browserLog) { - return browserLog.filter(function(logEntry) { - return logEntry.level.value > webdriver.logging.Level.WARNING.value; - }); - }); - } - - function clearErrors() { - getAndClearSevereErrors(); - } - - function expectNoErrors() { - getAndClearSevereErrors().then(function(filteredLog) { - expect(filteredLog.length).toEqual(0); - if (filteredLog.length) { - console.log('browser console errors: ' + util.inspect(filteredLog)); - } - }); - } - - function expectError(regex) { - getAndClearSevereErrors().then(function(filteredLog) { - var found = false; - filteredLog.forEach(function(log) { - if (log.message.match(regex)) { - found = true; - } - }); - if (!found) { - throw new Error('expected an error that matches ' + regex); - } - }); - } - - beforeEach(function() { - util = require('util'); - webdriver = require('protractor/node_modules/selenium-webdriver'); - }); - - // For now, we only test on Chrome, - // as Safari does not load the page with Protractor's injected scripts, - // and Firefox webdriver always disables content security policy (#6358) - if (browser.params.browser !== 'chrome') { - return; - } - - it('should not report errors when the page is loaded', function() { - // clear errors so we are not dependent on previous tests - clearErrors(); - // Need to reload the page as the page is already loaded when - // we come here - browser.driver.getCurrentUrl().then(function(url) { - browser.get(url); - }); - expectNoErrors(); - }); - - it('should evaluate expressions', function() { - expect(counter.getText()).toEqual('0'); - incBtn.click(); - expect(counter.getText()).toEqual('1'); - expectNoErrors(); - }); - - it('should throw and report an error when using "eval"', function() { - evilBtn.click(); - expect(evilError.getText()).toMatch(/Content Security Policy/); - expectError(/Content Security Policy/); - }); - </file> - </example> - */ - -// ngCsp is not implemented as a proper directive any more, because we need it be processed while we -// bootstrap the system (before $parse is instantiated), for this reason we just have -// the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc - -/** - * @ngdoc directive - * @name ngClick - * - * @description - * The ngClick directive allows you to specify custom behavior when - * an element is clicked. - * - * @element ANY - * @priority 0 - * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon - * click. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-click="count = count + 1" ng-init="count=0"> - Increment - </button> - <span> - count: {{count}} - </span> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-click', function() { - expect(element(by.binding('count')).getText()).toMatch('0'); - element(by.css('button')).click(); - expect(element(by.binding('count')).getText()).toMatch('1'); - }); - </file> - </example> - */ -/* - * A collection of directives that allows creation of custom event handlers that are defined as - * angular expressions and are compiled and executed within the current scope. - */ -var ngEventDirectives = {}; - -// For events that might fire synchronously during DOM manipulation -// we need to execute their event handlers asynchronously using $evalAsync, -// so that they are not executed in an inconsistent state. -var forceAsyncEvents = { - 'blur': true, - 'focus': true -}; -forEach( - 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), - function(eventName) { - var directiveName = directiveNormalize('ng-' + eventName); - ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { - return { - restrict: 'A', - compile: function($element, attr) { - // We expose the powerful $event object on the scope that provides access to the Window, - // etc. that isn't protected by the fast paths in $parse. We explicitly request better - // checks at the cost of speed since event handler expressions are not executed as - // frequently as regular change detection. - var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); - return function ngEventHandler(scope, element) { - element.on(eventName, function(event) { - var callback = function() { - fn(scope, {$event:event}); - }; - if (forceAsyncEvents[eventName] && $rootScope.$$phase) { - scope.$evalAsync(callback); - } else { - scope.$apply(callback); - } - }); - }; - } - }; - }]; - } -); - -/** - * @ngdoc directive - * @name ngDblclick - * - * @description - * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. - * - * @element ANY - * @priority 0 - * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon - * a dblclick. (The Event object is available as `$event`) - * - * @example - <example> - <file name="index.html"> - <button ng-dblclick="count = count + 1" ng-init="count=0"> - Increment (on double click) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngMousedown - * - * @description - * The ngMousedown directive allows you to specify custom behavior on mousedown event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon - * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mousedown="count = count + 1" ng-init="count=0"> - Increment (on mouse down) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngMouseup - * - * @description - * Specify custom behavior on mouseup event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon - * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mouseup="count = count + 1" ng-init="count=0"> - Increment (on mouse up) - </button> - count: {{count}} - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngMouseover - * - * @description - * Specify custom behavior on mouseover event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon - * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mouseover="count = count + 1" ng-init="count=0"> - Increment (when mouse is over) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngMouseenter - * - * @description - * Specify custom behavior on mouseenter event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon - * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mouseenter="count = count + 1" ng-init="count=0"> - Increment (when mouse enters) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngMouseleave - * - * @description - * Specify custom behavior on mouseleave event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon - * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mouseleave="count = count + 1" ng-init="count=0"> - Increment (when mouse leaves) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngMousemove - * - * @description - * Specify custom behavior on mousemove event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon - * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <button ng-mousemove="count = count + 1" ng-init="count=0"> - Increment (when mouse moves) - </button> - count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngKeydown - * - * @description - * Specify custom behavior on keydown event. - * - * @element ANY - * @priority 0 - * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon - * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) - * - * @example - <example> - <file name="index.html"> - <input ng-keydown="count = count + 1" ng-init="count=0"> - key down count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngKeyup - * - * @description - * Specify custom behavior on keyup event. - * - * @element ANY - * @priority 0 - * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon - * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) - * - * @example - <example> - <file name="index.html"> - <p>Typing in the input box below updates the key count</p> - <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} - - <p>Typing in the input box below updates the keycode</p> - <input ng-keyup="event=$event"> - <p>event keyCode: {{ event.keyCode }}</p> - <p>event altKey: {{ event.altKey }}</p> - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngKeypress - * - * @description - * Specify custom behavior on keypress event. - * - * @element ANY - * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon - * keypress. ({@link guide/expression#-event- Event object is available as `$event`} - * and can be interrogated for keyCode, altKey, etc.) - * - * @example - <example> - <file name="index.html"> - <input ng-keypress="count = count + 1" ng-init="count=0"> - key press count: {{count}} - </file> - </example> - */ - - -/** - * @ngdoc directive - * @name ngSubmit - * - * @description - * Enables binding angular expressions to onsubmit events. - * - * Additionally it prevents the default action (which for form means sending the request to the - * server and reloading the current page), but only if the form does not contain `action`, - * `data-action`, or `x-action` attributes. - * - * <div class="alert alert-warning"> - * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and - * `ngSubmit` handlers together. See the - * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} - * for a detailed discussion of when `ngSubmit` may be triggered. - * </div> - * - * @element form - * @priority 0 - * @param {expression} ngSubmit {@link guide/expression Expression} to eval. - * ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example module="submitExample"> - <file name="index.html"> - <script> - angular.module('submitExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.list = []; - $scope.text = 'hello'; - $scope.submit = function() { - if ($scope.text) { - $scope.list.push(this.text); - $scope.text = ''; - } - }; - }]); - </script> - <form ng-submit="submit()" ng-controller="ExampleController"> - Enter text and hit enter: - <input type="text" ng-model="text" name="text" /> - <input type="submit" id="submit" value="Submit" /> - <pre>list={{list}}</pre> - </form> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-submit', function() { - expect(element(by.binding('list')).getText()).toBe('list=[]'); - element(by.css('#submit')).click(); - expect(element(by.binding('list')).getText()).toContain('hello'); - expect(element(by.model('text')).getAttribute('value')).toBe(''); - }); - it('should ignore empty strings', function() { - expect(element(by.binding('list')).getText()).toBe('list=[]'); - element(by.css('#submit')).click(); - element(by.css('#submit')).click(); - expect(element(by.binding('list')).getText()).toContain('hello'); - }); - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngFocus - * - * @description - * Specify custom behavior on focus event. - * - * Note: As the `focus` event is executed synchronously when calling `input.focus()` - * AngularJS executes the expression using `scope.$evalAsync` if the event is fired - * during an `$apply` to ensure a consistent state. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon - * focus. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - -/** - * @ngdoc directive - * @name ngBlur - * - * @description - * Specify custom behavior on blur event. - * - * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when - * an element has lost focus. - * - * Note: As the `blur` event is executed synchronously also during DOM manipulations - * (e.g. removing a focussed input), - * AngularJS executes the expression using `scope.$evalAsync` if the event is fired - * during an `$apply` to ensure a consistent state. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon - * blur. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - -/** - * @ngdoc directive - * @name ngCopy - * - * @description - * Specify custom behavior on copy event. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon - * copy. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> - copied: {{copied}} - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngCut - * - * @description - * Specify custom behavior on cut event. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon - * cut. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> - cut: {{cut}} - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngPaste - * - * @description - * Specify custom behavior on paste event. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon - * paste. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - <example> - <file name="index.html"> - <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> - pasted: {{paste}} - </file> - </example> - */ - -/** - * @ngdoc directive - * @name ngIf - * @restrict A - * @multiElement - * - * @description - * The `ngIf` directive removes or recreates a portion of the DOM tree based on an - * {expression}. If the expression assigned to `ngIf` evaluates to a false - * value then the element is removed from the DOM, otherwise a clone of the - * element is reinserted into the DOM. - * - * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the - * element in the DOM rather than changing its visibility via the `display` css property. A common - * case when this difference is significant is when using css selectors that rely on an element's - * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes. - * - * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope - * is created when the element is restored. The scope created within `ngIf` inherits from - * its parent scope using - * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). - * An important implication of this is if `ngModel` is used within `ngIf` to bind to - * a javascript primitive defined in the parent scope. In this case any modifications made to the - * variable within the child scope will override (hide) the value in the parent scope. - * - * Also, `ngIf` recreates elements using their compiled state. An example of this behavior - * is if an element's class attribute is directly modified after it's compiled, using something like - * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element - * the added class will be lost because the original compiled state is used to regenerate the element. - * - * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` - * and `leave` effects. - * - * @animations - * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container - * leave - happens just before the `ngIf` contents are removed from the DOM - * - * @element ANY - * @scope - * @priority 600 - * @param {expression} ngIf If the {@link guide/expression expression} is falsy then - * the element is removed from the DOM tree. If it is truthy a copy of the compiled - * element is added to the DOM tree. - * - * @example - <example module="ngAnimate" deps="angular-animate.js" animations="true"> - <file name="index.html"> - <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> - Show when checked: - <span ng-if="checked" class="animate-if"> - This is removed when the checkbox is unchecked. - </span> - </file> - <file name="animations.css"> - .animate-if { - background:white; - border:1px solid black; - padding:10px; - } - - .animate-if.ng-enter, .animate-if.ng-leave { - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - } - - .animate-if.ng-enter, - .animate-if.ng-leave.ng-leave-active { - opacity:0; - } - - .animate-if.ng-leave, - .animate-if.ng-enter.ng-enter-active { - opacity:1; - } - </file> - </example> - */ -var ngIfDirective = ['$animate', function($animate) { - return { - multiElement: true, - transclude: 'element', - priority: 600, - terminal: true, - restrict: 'A', - $$tlb: true, - link: function($scope, $element, $attr, ctrl, $transclude) { - var block, childScope, previousElements; - $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { - - if (value) { - if (!childScope) { - $transclude(function(clone, newScope) { - childScope = newScope; - clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); - // Note: We only need the first/last node of the cloned nodes. - // However, we need to keep the reference to the jqlite wrapper as it might be changed later - // by a directive with templateUrl when its template arrives. - block = { - clone: clone - }; - $animate.enter(clone, $element.parent(), $element); - }); - } - } else { - if (previousElements) { - previousElements.remove(); - previousElements = null; - } - if (childScope) { - childScope.$destroy(); - childScope = null; - } - if (block) { - previousElements = getBlockNodes(block.clone); - $animate.leave(previousElements).then(function() { - previousElements = null; - }); - block = null; - } - } - }); - } - }; -}]; - -/** - * @ngdoc directive - * @name ngInclude - * @restrict ECA - * - * @description - * Fetches, compiles and includes an external HTML fragment. - * - * By default, the template URL is restricted to the same domain and protocol as the - * application document. This is done by calling {@link $sce#getTrustedResourceUrl - * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols - * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or - * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link - * ng.$sce Strict Contextual Escaping}. - * - * In addition, the browser's - * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) - * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) - * policy may further restrict whether the template is successfully loaded. - * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` - * access on some browsers. - * - * @animations - * enter - animation is used to bring new content into the browser. - * leave - animation is used to animate existing content away. - * - * The enter and leave animation occur concurrently. - * - * @scope - * @priority 400 - * - * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, - * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. - * @param {string=} onload Expression to evaluate when a new partial is loaded. - * - * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll - * $anchorScroll} to scroll the viewport after the content is loaded. - * - * - If the attribute is not set, disable scrolling. - * - If the attribute is set without value, enable scrolling. - * - Otherwise enable scrolling only if the expression evaluates to truthy value. - * - * @example - <example module="includeExample" deps="angular-animate.js" animations="true"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <select ng-model="template" ng-options="t.name for t in templates"> - <option value="">(blank)</option> - </select> - url of the template: <code>{{template.url}}</code> - <hr/> - <div class="slide-animate-container"> - <div class="slide-animate" ng-include="template.url"></div> - </div> - </div> - </file> - <file name="script.js"> - angular.module('includeExample', ['ngAnimate']) - .controller('ExampleController', ['$scope', function($scope) { - $scope.templates = - [ { name: 'template1.html', url: 'template1.html'}, - { name: 'template2.html', url: 'template2.html'} ]; - $scope.template = $scope.templates[0]; - }]); - </file> - <file name="template1.html"> - Content of template1.html - </file> - <file name="template2.html"> - Content of template2.html - </file> - <file name="animations.css"> - .slide-animate-container { - position:relative; - background:white; - border:1px solid black; - height:40px; - overflow:hidden; - } - - .slide-animate { - padding:10px; - } - - .slide-animate.ng-enter, .slide-animate.ng-leave { - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - - position:absolute; - top:0; - left:0; - right:0; - bottom:0; - display:block; - padding:10px; - } - - .slide-animate.ng-enter { - top:-50px; - } - .slide-animate.ng-enter.ng-enter-active { - top:0; - } - - .slide-animate.ng-leave { - top:0; - } - .slide-animate.ng-leave.ng-leave-active { - top:50px; - } - </file> - <file name="protractor.js" type="protractor"> - var templateSelect = element(by.model('template')); - var includeElem = element(by.css('[ng-include]')); - - it('should load template1.html', function() { - expect(includeElem.getText()).toMatch(/Content of template1.html/); - }); - - it('should load template2.html', function() { - if (browser.params.browser == 'firefox') { - // Firefox can't handle using selects - // See https://github.com/angular/protractor/issues/480 - return; - } - templateSelect.click(); - templateSelect.all(by.css('option')).get(2).click(); - expect(includeElem.getText()).toMatch(/Content of template2.html/); - }); - - it('should change to blank', function() { - if (browser.params.browser == 'firefox') { - // Firefox can't handle using selects - return; - } - templateSelect.click(); - templateSelect.all(by.css('option')).get(0).click(); - expect(includeElem.isPresent()).toBe(false); - }); - </file> - </example> - */ - - -/** - * @ngdoc event - * @name ngInclude#$includeContentRequested - * @eventType emit on the scope ngInclude was declared in - * @description - * Emitted every time the ngInclude content is requested. - * - * @param {Object} angularEvent Synthetic event object. - * @param {String} src URL of content to load. - */ - - -/** - * @ngdoc event - * @name ngInclude#$includeContentLoaded - * @eventType emit on the current ngInclude scope - * @description - * Emitted every time the ngInclude content is reloaded. - * - * @param {Object} angularEvent Synthetic event object. - * @param {String} src URL of content to load. - */ - - -/** - * @ngdoc event - * @name ngInclude#$includeContentError - * @eventType emit on the scope ngInclude was declared in - * @description - * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) - * - * @param {Object} angularEvent Synthetic event object. - * @param {String} src URL of content to load. - */ -var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', - function($templateRequest, $anchorScroll, $animate) { - return { - restrict: 'ECA', - priority: 400, - terminal: true, - transclude: 'element', - controller: angular.noop, - compile: function(element, attr) { - var srcExp = attr.ngInclude || attr.src, - onloadExp = attr.onload || '', - autoScrollExp = attr.autoscroll; - - return function(scope, $element, $attr, ctrl, $transclude) { - var changeCounter = 0, - currentScope, - previousElement, - currentElement; - - var cleanupLastIncludeContent = function() { - if (previousElement) { - previousElement.remove(); - previousElement = null; - } - if (currentScope) { - currentScope.$destroy(); - currentScope = null; - } - if (currentElement) { - $animate.leave(currentElement).then(function() { - previousElement = null; - }); - previousElement = currentElement; - currentElement = null; - } - }; - - scope.$watch(srcExp, function ngIncludeWatchAction(src) { - var afterAnimation = function() { - if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { - $anchorScroll(); - } - }; - var thisChangeId = ++changeCounter; - - if (src) { - //set the 2nd param to true to ignore the template request error so that the inner - //contents and scope can be cleaned up. - $templateRequest(src, true).then(function(response) { - if (thisChangeId !== changeCounter) return; - var newScope = scope.$new(); - ctrl.template = response; - - // Note: This will also link all children of ng-include that were contained in the original - // html. If that content contains controllers, ... they could pollute/change the scope. - // However, using ng-include on an element with additional content does not make sense... - // Note: We can't remove them in the cloneAttchFn of $transclude as that - // function is called before linking the content, which would apply child - // directives to non existing elements. - var clone = $transclude(newScope, function(clone) { - cleanupLastIncludeContent(); - $animate.enter(clone, null, $element).then(afterAnimation); - }); - - currentScope = newScope; - currentElement = clone; - - currentScope.$emit('$includeContentLoaded', src); - scope.$eval(onloadExp); - }, function() { - if (thisChangeId === changeCounter) { - cleanupLastIncludeContent(); - scope.$emit('$includeContentError', src); - } - }); - scope.$emit('$includeContentRequested', src); - } else { - cleanupLastIncludeContent(); - ctrl.template = null; - } - }); - }; - } - }; -}]; - -// This directive is called during the $transclude call of the first `ngInclude` directive. -// It will replace and compile the content of the element with the loaded template. -// We need this directive so that the element content is already filled when -// the link function of another directive on the same element as ngInclude -// is called. -var ngIncludeFillContentDirective = ['$compile', - function($compile) { - return { - restrict: 'ECA', - priority: -400, - require: 'ngInclude', - link: function(scope, $element, $attr, ctrl) { - if (/SVG/.test($element[0].toString())) { - // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not - // support innerHTML, so detect this here and try to generate the contents - // specially. - $element.empty(); - $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, - function namespaceAdaptedClone(clone) { - $element.append(clone); - }, {futureParentElement: $element}); - return; - } - - $element.html(ctrl.template); - $compile($element.contents())(scope); - } - }; - }]; - -/** - * @ngdoc directive - * @name ngInit - * @restrict AC - * - * @description - * The `ngInit` directive allows you to evaluate an expression in the - * current scope. - * - * <div class="alert alert-danger"> - * This directive can be abused to add unnecessary amounts of logic into your templates. - * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of - * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via - * server side scripting. Besides these few cases, you should use {@link guide/controller controllers} - * rather than `ngInit` to initialize values on a scope. - * </div> - * - * <div class="alert alert-warning"> - * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make - * sure you have parentheses to ensure correct operator precedence: - * <pre class="prettyprint"> - * `<div ng-init="test1 = ($index | toString)"></div>` - * </pre> - * </div> - * - * @priority 450 - * - * @element ANY - * @param {expression} ngInit {@link guide/expression Expression} to eval. - * - * @example - <example module="initExample"> - <file name="index.html"> - <script> - angular.module('initExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.list = [['a', 'b'], ['c', 'd']]; - }]); - </script> - <div ng-controller="ExampleController"> - <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> - <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> - <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> - </div> - </div> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should alias index positions', function() { - var elements = element.all(by.css('.example-init')); - expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); - expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); - expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); - expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); - }); - </file> - </example> - */ -var ngInitDirective = ngDirective({ - priority: 450, - compile: function() { - return { - pre: function(scope, element, attrs) { - scope.$eval(attrs.ngInit); - } - }; - } -}); - -/** - * @ngdoc directive - * @name ngList - * - * @description - * Text input that converts between a delimited string and an array of strings. The default - * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom - * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. - * - * The behaviour of the directive is affected by the use of the `ngTrim` attribute. - * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each - * list item is respected. This implies that the user of the directive is responsible for - * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a - * tab or newline character. - * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected - * when joining the list items back together) and whitespace around each list item is stripped - * before it is added to the model. - * - * ### Example with Validation - * - * <example name="ngList-directive" module="listExample"> - * <file name="app.js"> - * angular.module('listExample', []) - * .controller('ExampleController', ['$scope', function($scope) { - * $scope.names = ['morpheus', 'neo', 'trinity']; - * }]); - * </file> - * <file name="index.html"> - * <form name="myForm" ng-controller="ExampleController"> - * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> - * <span role="alert"> - * <span class="error" ng-show="myForm.namesInput.$error.required"> - * Required!</span> - * </span> - * <br> - * <tt>names = {{names}}</tt><br/> - * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> - * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> - * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> - * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> - * </form> - * </file> - * <file name="protractor.js" type="protractor"> - * var listInput = element(by.model('names')); - * var names = element(by.exactBinding('names')); - * var valid = element(by.binding('myForm.namesInput.$valid')); - * var error = element(by.css('span.error')); - * - * it('should initialize to model', function() { - * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); - * expect(valid.getText()).toContain('true'); - * expect(error.getCssValue('display')).toBe('none'); - * }); - * - * it('should be invalid if empty', function() { - * listInput.clear(); - * listInput.sendKeys(''); - * - * expect(names.getText()).toContain(''); - * expect(valid.getText()).toContain('false'); - * expect(error.getCssValue('display')).not.toBe('none'); - * }); - * </file> - * </example> - * - * ### Example - splitting on newline - * <example name="ngList-directive-newlines"> - * <file name="index.html"> - * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> - * <pre>{{ list | json }}</pre> - * </file> - * <file name="protractor.js" type="protractor"> - * it("should split the text by newlines", function() { - * var listInput = element(by.model('list')); - * var output = element(by.binding('list | json')); - * listInput.sendKeys('abc\ndef\nghi'); - * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); - * }); - * </file> - * </example> - * - * @element input - * @param {string=} ngList optional delimiter that should be used to split the value. - */ -var ngListDirective = function() { - return { - restrict: 'A', - priority: 100, - require: 'ngModel', - link: function(scope, element, attr, ctrl) { - // We want to control whitespace trimming so we use this convoluted approach - // to access the ngList attribute, which doesn't pre-trim the attribute - var ngList = element.attr(attr.$attr.ngList) || ', '; - var trimValues = attr.ngTrim !== 'false'; - var separator = trimValues ? trim(ngList) : ngList; - - var parse = function(viewValue) { - // If the viewValue is invalid (say required but empty) it will be `undefined` - if (isUndefined(viewValue)) return; - - var list = []; - - if (viewValue) { - forEach(viewValue.split(separator), function(value) { - if (value) list.push(trimValues ? trim(value) : value); - }); - } - - return list; - }; - - ctrl.$parsers.push(parse); - ctrl.$formatters.push(function(value) { - if (isArray(value)) { - return value.join(ngList); - } - - return undefined; - }); - - // Override the standard $isEmpty because an empty array means the input is empty. - ctrl.$isEmpty = function(value) { - return !value || !value.length; - }; - } - }; -}; - -/* global VALID_CLASS: true, - INVALID_CLASS: true, - PRISTINE_CLASS: true, - DIRTY_CLASS: true, - UNTOUCHED_CLASS: true, - TOUCHED_CLASS: true, -*/ - -var VALID_CLASS = 'ng-valid', - INVALID_CLASS = 'ng-invalid', - PRISTINE_CLASS = 'ng-pristine', - DIRTY_CLASS = 'ng-dirty', - UNTOUCHED_CLASS = 'ng-untouched', - TOUCHED_CLASS = 'ng-touched', - PENDING_CLASS = 'ng-pending'; - -var ngModelMinErr = minErr('ngModel'); - -/** - * @ngdoc type - * @name ngModel.NgModelController - * - * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a - * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue - * is set. - * @property {*} $modelValue The value in the model that the control is bound to. - * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever - the control reads value from the DOM. The functions are called in array order, each passing - its return value through to the next. The last return value is forwarded to the - {@link ngModel.NgModelController#$validators `$validators`} collection. - -Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue -`$viewValue`}. - -Returning `undefined` from a parser means a parse error occurred. In that case, -no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` -will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} -is set to `true`. The parse error is stored in `ngModel.$error.parse`. - - * - * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever - the model value changes. The functions are called in reverse array order, each passing the value through to the - next. The last return value is used as the actual DOM value. - Used to format / convert values for display in the control. - * ```js - * function formatter(value) { - * if (value) { - * return value.toUpperCase(); - * } - * } - * ngModel.$formatters.push(formatter); - * ``` - * - * @property {Object.<string, function>} $validators A collection of validators that are applied - * whenever the model value changes. The key value within the object refers to the name of the - * validator while the function refers to the validation operation. The validation operation is - * provided with the model value as an argument and must return a true or false value depending - * on the response of that validation. - * - * ```js - * ngModel.$validators.validCharacters = function(modelValue, viewValue) { - * var value = modelValue || viewValue; - * return /[0-9]+/.test(value) && - * /[a-z]+/.test(value) && - * /[A-Z]+/.test(value) && - * /\W+/.test(value); - * }; - * ``` - * - * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to - * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided - * is expected to return a promise when it is run during the model validation process. Once the promise - * is delivered then the validation status will be set to true when fulfilled and false when rejected. - * When the asynchronous validators are triggered, each of the validators will run in parallel and the model - * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator - * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators - * will only run once all synchronous validators have passed. - * - * Please note that if $http is used then it is important that the server returns a success HTTP response code - * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. - * - * ```js - * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { - * var value = modelValue || viewValue; - * - * // Lookup user by username - * return $http.get('/api/users/' + value). - * then(function resolved() { - * //username exists, this means validation fails - * return $q.reject('exists'); - * }, function rejected() { - * //username does not exist, therefore this validation passes - * return true; - * }); - * }; - * ``` - * - * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the - * view value has changed. It is called with no arguments, and its return value is ignored. - * This can be used in place of additional $watches against the model value. - * - * @property {Object} $error An object hash with all failing validator ids as keys. - * @property {Object} $pending An object hash with all pending validator ids as keys. - * - * @property {boolean} $untouched True if control has not lost focus yet. - * @property {boolean} $touched True if control has lost focus. - * @property {boolean} $pristine True if user has not interacted with the control yet. - * @property {boolean} $dirty True if user has already interacted with the control. - * @property {boolean} $valid True if there is no error. - * @property {boolean} $invalid True if at least one error on the control. - * @property {string} $name The name attribute of the control. - * - * @description - * - * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. - * The controller contains services for data-binding, validation, CSS updates, and value formatting - * and parsing. It purposefully does not contain any logic which deals with DOM rendering or - * listening to DOM events. - * Such DOM related logic should be provided by other directives which make use of - * `NgModelController` for data-binding to control elements. - * Angular provides this DOM logic for most {@link input `input`} elements. - * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example - * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. - * - * @example - * ### Custom Control Example - * This example shows how to use `NgModelController` with a custom control to achieve - * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) - * collaborate together to achieve the desired result. - * - * `contenteditable` is an HTML5 attribute, which tells the browser to let the element - * contents be edited in place by the user. - * - * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} - * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). - * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks - * that content using the `$sce` service. - * - * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> - <file name="style.css"> - [contenteditable] { - border: 1px solid black; - background-color: white; - min-height: 20px; - } - - .ng-invalid { - border: 1px solid red; - } - - </file> - <file name="script.js"> - angular.module('customControl', ['ngSanitize']). - directive('contenteditable', ['$sce', function($sce) { - return { - restrict: 'A', // only activate on element attribute - require: '?ngModel', // get a hold of NgModelController - link: function(scope, element, attrs, ngModel) { - if (!ngModel) return; // do nothing if no ng-model - - // Specify how UI should be updated - ngModel.$render = function() { - element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); - }; - - // Listen for change events to enable binding - element.on('blur keyup change', function() { - scope.$evalAsync(read); - }); - read(); // initialize - - // Write data to the model - function read() { - var html = element.html(); - // When we clear the content editable the browser leaves a <br> behind - // If strip-br attribute is provided then we strip this out - if ( attrs.stripBr && html == '<br>' ) { - html = ''; - } - ngModel.$setViewValue(html); - } - } - }; - }]); - </file> - <file name="index.html"> - <form name="myForm"> - <div contenteditable - name="myWidget" ng-model="userContent" - strip-br="true" - required>Change me!</div> - <span ng-show="myForm.myWidget.$error.required">Required!</span> - <hr> - <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> - </form> - </file> - <file name="protractor.js" type="protractor"> - it('should data-bind and become invalid', function() { - if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { - // SafariDriver can't handle contenteditable - // and Firefox driver can't clear contenteditables very well - return; - } - var contentEditable = element(by.css('[contenteditable]')); - var content = 'Change me!'; - - expect(contentEditable.getText()).toEqual(content); - - contentEditable.clear(); - contentEditable.sendKeys(protractor.Key.BACK_SPACE); - expect(contentEditable.getText()).toEqual(''); - expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); - }); - </file> - * </example> - * - * - */ -var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', - function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { - this.$viewValue = Number.NaN; - this.$modelValue = Number.NaN; - this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. - this.$validators = {}; - this.$asyncValidators = {}; - this.$parsers = []; - this.$formatters = []; - this.$viewChangeListeners = []; - this.$untouched = true; - this.$touched = false; - this.$pristine = true; - this.$dirty = false; - this.$valid = true; - this.$invalid = false; - this.$error = {}; // keep invalid keys here - this.$$success = {}; // keep valid keys here - this.$pending = undefined; // keep pending keys here - this.$name = $interpolate($attr.name || '', false)($scope); - this.$$parentForm = nullFormCtrl; - - var parsedNgModel = $parse($attr.ngModel), - parsedNgModelAssign = parsedNgModel.assign, - ngModelGet = parsedNgModel, - ngModelSet = parsedNgModelAssign, - pendingDebounce = null, - parserValid, - ctrl = this; - - this.$$setOptions = function(options) { - ctrl.$options = options; - if (options && options.getterSetter) { - var invokeModelGetter = $parse($attr.ngModel + '()'), - invokeModelSetter = $parse($attr.ngModel + '($$$p)'); - - ngModelGet = function($scope) { - var modelValue = parsedNgModel($scope); - if (isFunction(modelValue)) { - modelValue = invokeModelGetter($scope); - } - return modelValue; - }; - ngModelSet = function($scope, newValue) { - if (isFunction(parsedNgModel($scope))) { - invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); - } else { - parsedNgModelAssign($scope, ctrl.$modelValue); - } - }; - } else if (!parsedNgModel.assign) { - throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", - $attr.ngModel, startingTag($element)); - } - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$render - * - * @description - * Called when the view needs to be updated. It is expected that the user of the ng-model - * directive will implement this method. - * - * The `$render()` method is invoked in the following situations: - * - * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last - * committed value then `$render()` is called to update the input control. - * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and - * the `$viewValue` are different from last time. - * - * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of - * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue` - * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be - * invoked if you only change a property on the objects. - */ - this.$render = noop; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$isEmpty - * - * @description - * This is called when we need to determine if the value of an input is empty. - * - * For instance, the required directive does this to work out if the input has data or not. - * - * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. - * - * You can override this for input directives whose concept of being empty is different from the - * default. The `checkboxInputType` directive does this because in its case a value of `false` - * implies empty. - * - * @param {*} value The value of the input to check for emptiness. - * @returns {boolean} True if `value` is "empty". - */ - this.$isEmpty = function(value) { - return isUndefined(value) || value === '' || value === null || value !== value; - }; - - var currentValidationRunId = 0; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setValidity - * - * @description - * Change the validity state, and notify the form. - * - * This method can be called within $parsers/$formatters or a custom validation implementation. - * However, in most cases it should be sufficient to use the `ngModel.$validators` and - * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. - * - * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned - * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` - * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. - * The `validationErrorKey` should be in camelCase and will get converted into dash-case - * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` - * class and can be bound to as `{{someForm.someControl.$error.myError}}` . - * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), - * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. - * Skipped is used by Angular when validators do not run because of parse errors and - * when `$asyncValidators` do not run because any of the `$validators` failed. - */ - addSetValidityMethod({ - ctrl: this, - $element: $element, - set: function(object, property) { - object[property] = true; - }, - unset: function(object, property) { - delete object[property]; - }, - $animate: $animate - }); - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setPristine - * - * @description - * Sets the control to its pristine state. - * - * This method can be called to remove the `ng-dirty` class and set the control to its pristine - * state (`ng-pristine` class). A model is considered to be pristine when the control - * has not been changed from when first compiled. - */ - this.$setPristine = function() { - ctrl.$dirty = false; - ctrl.$pristine = true; - $animate.removeClass($element, DIRTY_CLASS); - $animate.addClass($element, PRISTINE_CLASS); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setDirty - * - * @description - * Sets the control to its dirty state. - * - * This method can be called to remove the `ng-pristine` class and set the control to its dirty - * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed - * from when first compiled. - */ - this.$setDirty = function() { - ctrl.$dirty = true; - ctrl.$pristine = false; - $animate.removeClass($element, PRISTINE_CLASS); - $animate.addClass($element, DIRTY_CLASS); - ctrl.$$parentForm.$setDirty(); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setUntouched - * - * @description - * Sets the control to its untouched state. - * - * This method can be called to remove the `ng-touched` class and set the control to its - * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched - * by default, however this function can be used to restore that state if the model has - * already been touched by the user. - */ - this.$setUntouched = function() { - ctrl.$touched = false; - ctrl.$untouched = true; - $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setTouched - * - * @description - * Sets the control to its touched state. - * - * This method can be called to remove the `ng-untouched` class and set the control to its - * touched state (`ng-touched` class). A model is considered to be touched when the user has - * first focused the control element and then shifted focus away from the control (blur event). - */ - this.$setTouched = function() { - ctrl.$touched = true; - ctrl.$untouched = false; - $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$rollbackViewValue - * - * @description - * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, - * which may be caused by a pending debounced event or because the input is waiting for a some - * future event. - * - * If you have an input that uses `ng-model-options` to set up debounced events or events such - * as blur you can have a situation where there is a period when the `$viewValue` - * is out of synch with the ngModel's `$modelValue`. - * - * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue` - * programmatically before these debounced/future events have resolved/occurred, because Angular's - * dirty checking mechanism is not able to tell whether the model has actually changed or not. - * - * The `$rollbackViewValue()` method should be called before programmatically changing the model of an - * input which may have such events pending. This is important in order to make sure that the - * input field will be updated with the new model value and any pending operations are cancelled. - * - * <example name="ng-model-cancel-update" module="cancel-update-example"> - * <file name="app.js"> - * angular.module('cancel-update-example', []) - * - * .controller('CancelUpdateController', ['$scope', function($scope) { - * $scope.resetWithCancel = function(e) { - * if (e.keyCode == 27) { - * $scope.myForm.myInput1.$rollbackViewValue(); - * $scope.myValue = ''; - * } - * }; - * $scope.resetWithoutCancel = function(e) { - * if (e.keyCode == 27) { - * $scope.myValue = ''; - * } - * }; - * }]); - * </file> - * <file name="index.html"> - * <div ng-controller="CancelUpdateController"> - * <p>Try typing something in each input. See that the model only updates when you - * blur off the input. - * </p> - * <p>Now see what happens if you start typing then press the Escape key</p> - * - * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> - * <p id="inputDescription1">With $rollbackViewValue()</p> - * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue" - * ng-keydown="resetWithCancel($event)"><br/> - * myValue: "{{ myValue }}" - * - * <p id="inputDescription2">Without $rollbackViewValue()</p> - * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue" - * ng-keydown="resetWithoutCancel($event)"><br/> - * myValue: "{{ myValue }}" - * </form> - * </div> - * </file> - * </example> - */ - this.$rollbackViewValue = function() { - $timeout.cancel(pendingDebounce); - ctrl.$viewValue = ctrl.$$lastCommittedViewValue; - ctrl.$render(); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$validate - * - * @description - * Runs each of the registered validators (first synchronous validators and then - * asynchronous validators). - * If the validity changes to invalid, the model will be set to `undefined`, - * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. - * If the validity changes to valid, it will set the model to the last available valid - * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. - */ - this.$validate = function() { - // ignore $validate before model is initialized - if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { - return; - } - - var viewValue = ctrl.$$lastCommittedViewValue; - // Note: we use the $$rawModelValue as $modelValue might have been - // set to undefined during a view -> model update that found validation - // errors. We can't parse the view here, since that could change - // the model although neither viewValue nor the model on the scope changed - var modelValue = ctrl.$$rawModelValue; - - var prevValid = ctrl.$valid; - var prevModelValue = ctrl.$modelValue; - - var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; - - ctrl.$$runValidators(modelValue, viewValue, function(allValid) { - // If there was no change in validity, don't update the model - // This prevents changing an invalid modelValue to undefined - if (!allowInvalid && prevValid !== allValid) { - // Note: Don't check ctrl.$valid here, as we could have - // external validators (e.g. calculated on the server), - // that just call $setValidity and need the model value - // to calculate their validity. - ctrl.$modelValue = allValid ? modelValue : undefined; - - if (ctrl.$modelValue !== prevModelValue) { - ctrl.$$writeModelToScope(); - } - } - }); - - }; - - this.$$runValidators = function(modelValue, viewValue, doneCallback) { - currentValidationRunId++; - var localValidationRunId = currentValidationRunId; - - // check parser error - if (!processParseErrors()) { - validationDone(false); - return; - } - if (!processSyncValidators()) { - validationDone(false); - return; - } - processAsyncValidators(); - - function processParseErrors() { - var errorKey = ctrl.$$parserName || 'parse'; - if (isUndefined(parserValid)) { - setValidity(errorKey, null); - } else { - if (!parserValid) { - forEach(ctrl.$validators, function(v, name) { - setValidity(name, null); - }); - forEach(ctrl.$asyncValidators, function(v, name) { - setValidity(name, null); - }); - } - // Set the parse error last, to prevent unsetting it, should a $validators key == parserName - setValidity(errorKey, parserValid); - return parserValid; - } - return true; - } - - function processSyncValidators() { - var syncValidatorsValid = true; - forEach(ctrl.$validators, function(validator, name) { - var result = validator(modelValue, viewValue); - syncValidatorsValid = syncValidatorsValid && result; - setValidity(name, result); - }); - if (!syncValidatorsValid) { - forEach(ctrl.$asyncValidators, function(v, name) { - setValidity(name, null); - }); - return false; - } - return true; - } - - function processAsyncValidators() { - var validatorPromises = []; - var allValid = true; - forEach(ctrl.$asyncValidators, function(validator, name) { - var promise = validator(modelValue, viewValue); - if (!isPromiseLike(promise)) { - throw ngModelMinErr("$asyncValidators", - "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); - } - setValidity(name, undefined); - validatorPromises.push(promise.then(function() { - setValidity(name, true); - }, function(error) { - allValid = false; - setValidity(name, false); - })); - }); - if (!validatorPromises.length) { - validationDone(true); - } else { - $q.all(validatorPromises).then(function() { - validationDone(allValid); - }, noop); - } - } - - function setValidity(name, isValid) { - if (localValidationRunId === currentValidationRunId) { - ctrl.$setValidity(name, isValid); - } - } - - function validationDone(allValid) { - if (localValidationRunId === currentValidationRunId) { - - doneCallback(allValid); - } - } - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$commitViewValue - * - * @description - * Commit a pending update to the `$modelValue`. - * - * Updates may be pending by a debounced event or because the input is waiting for a some future - * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` - * usually handles calling this in response to input events. - */ - this.$commitViewValue = function() { - var viewValue = ctrl.$viewValue; - - $timeout.cancel(pendingDebounce); - - // If the view value has not changed then we should just exit, except in the case where there is - // a native validator on the element. In this case the validation state may have changed even though - // the viewValue has stayed empty. - if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { - return; - } - ctrl.$$lastCommittedViewValue = viewValue; - - // change to dirty - if (ctrl.$pristine) { - this.$setDirty(); - } - this.$$parseAndValidate(); - }; - - this.$$parseAndValidate = function() { - var viewValue = ctrl.$$lastCommittedViewValue; - var modelValue = viewValue; - parserValid = isUndefined(modelValue) ? undefined : true; - - if (parserValid) { - for (var i = 0; i < ctrl.$parsers.length; i++) { - modelValue = ctrl.$parsers[i](modelValue); - if (isUndefined(modelValue)) { - parserValid = false; - break; - } - } - } - if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { - // ctrl.$modelValue has not been touched yet... - ctrl.$modelValue = ngModelGet($scope); - } - var prevModelValue = ctrl.$modelValue; - var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; - ctrl.$$rawModelValue = modelValue; - - if (allowInvalid) { - ctrl.$modelValue = modelValue; - writeToModelIfNeeded(); - } - - // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. - // This can happen if e.g. $setViewValue is called from inside a parser - ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { - if (!allowInvalid) { - // Note: Don't check ctrl.$valid here, as we could have - // external validators (e.g. calculated on the server), - // that just call $setValidity and need the model value - // to calculate their validity. - ctrl.$modelValue = allValid ? modelValue : undefined; - writeToModelIfNeeded(); - } - }); - - function writeToModelIfNeeded() { - if (ctrl.$modelValue !== prevModelValue) { - ctrl.$$writeModelToScope(); - } - } - }; - - this.$$writeModelToScope = function() { - ngModelSet($scope, ctrl.$modelValue); - forEach(ctrl.$viewChangeListeners, function(listener) { - try { - listener(); - } catch (e) { - $exceptionHandler(e); - } - }); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setViewValue - * - * @description - * Update the view value. - * - * This method should be called when a control wants to change the view value; typically, - * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} - * directive calls it when the value of the input changes and {@link ng.directive:select select} - * calls it when an option is selected. - * - * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` - * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged - * value sent directly for processing, finally to be applied to `$modelValue` and then the - * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, - * in the `$viewChangeListeners` list, are called. - * - * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` - * and the `default` trigger is not listed, all those actions will remain pending until one of the - * `updateOn` events is triggered on the DOM element. - * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} - * directive is used with a custom debounce for this particular event. - * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` - * is specified, once the timer runs out. - * - * When used with standard inputs, the view value will always be a string (which is in some cases - * parsed into another type, such as a `Date` object for `input[date]`.) - * However, custom controls might also pass objects to this method. In this case, we should make - * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not - * perform a deep watch of objects, it only looks for a change of identity. If you only change - * the property of the object then ngModel will not realise that the object has changed and - * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should - * not change properties of the copy once it has been passed to `$setViewValue`. - * Otherwise you may cause the model value on the scope to change incorrectly. - * - * <div class="alert alert-info"> - * In any case, the value passed to the method should always reflect the current value - * of the control. For example, if you are calling `$setViewValue` for an input element, - * you should pass the input DOM value. Otherwise, the control and the scope model become - * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change - * the control's DOM value in any way. If we want to change the control's DOM value - * programmatically, we should update the `ngModel` scope expression. Its new value will be - * picked up by the model controller, which will run it through the `$formatters`, `$render` it - * to update the DOM, and finally call `$validate` on it. - * </div> - * - * @param {*} value value from the view. - * @param {string} trigger Event that triggered the update. - */ - this.$setViewValue = function(value, trigger) { - ctrl.$viewValue = value; - if (!ctrl.$options || ctrl.$options.updateOnDefault) { - ctrl.$$debounceViewValueCommit(trigger); - } - }; - - this.$$debounceViewValueCommit = function(trigger) { - var debounceDelay = 0, - options = ctrl.$options, - debounce; - - if (options && isDefined(options.debounce)) { - debounce = options.debounce; - if (isNumber(debounce)) { - debounceDelay = debounce; - } else if (isNumber(debounce[trigger])) { - debounceDelay = debounce[trigger]; - } else if (isNumber(debounce['default'])) { - debounceDelay = debounce['default']; - } - } - - $timeout.cancel(pendingDebounce); - if (debounceDelay) { - pendingDebounce = $timeout(function() { - ctrl.$commitViewValue(); - }, debounceDelay); - } else if ($rootScope.$$phase) { - ctrl.$commitViewValue(); - } else { - $scope.$apply(function() { - ctrl.$commitViewValue(); - }); - } - }; - - // model -> value - // Note: we cannot use a normal scope.$watch as we want to detect the following: - // 1. scope value is 'a' - // 2. user enters 'b' - // 3. ng-change kicks in and reverts scope value to 'a' - // -> scope value did not change since the last digest as - // ng-change executes in apply phase - // 4. view should be changed back to 'a' - $scope.$watch(function ngModelWatch() { - var modelValue = ngModelGet($scope); - - // if scope model value and ngModel value are out of sync - // TODO(perf): why not move this to the action fn? - if (modelValue !== ctrl.$modelValue && - // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator - (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) - ) { - ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; - parserValid = undefined; - - var formatters = ctrl.$formatters, - idx = formatters.length; - - var viewValue = modelValue; - while (idx--) { - viewValue = formatters[idx](viewValue); - } - if (ctrl.$viewValue !== viewValue) { - ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; - ctrl.$render(); - - ctrl.$$runValidators(modelValue, viewValue, noop); - } - } - - return modelValue; - }); -}]; - - -/** - * @ngdoc directive - * @name ngModel - * - * @element input - * @priority 1 - * - * @description - * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a - * property on the scope using {@link ngModel.NgModelController NgModelController}, - * which is created and exposed by this directive. - * - * `ngModel` is responsible for: - * - * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` - * require. - * - Providing validation behavior (i.e. required, number, email, url). - * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). - * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations. - * - Registering the control with its parent {@link ng.directive:form form}. - * - * Note: `ngModel` will try to bind to the property given by evaluating the expression on the - * current scope. If the property doesn't already exist on this scope, it will be created - * implicitly and added to the scope. - * - * For best practices on using `ngModel`, see: - * - * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) - * - * For basic examples, how to use `ngModel`, see: - * - * - {@link ng.directive:input input} - * - {@link input[text] text} - * - {@link input[checkbox] checkbox} - * - {@link input[radio] radio} - * - {@link input[number] number} - * - {@link input[email] email} - * - {@link input[url] url} - * - {@link input[date] date} - * - {@link input[datetime-local] datetime-local} - * - {@link input[time] time} - * - {@link input[month] month} - * - {@link input[week] week} - * - {@link ng.directive:select select} - * - {@link ng.directive:textarea textarea} - * - * # CSS classes - * The following CSS classes are added and removed on the associated input/select/textarea element - * depending on the validity of the model. - * - * - `ng-valid`: the model is valid - * - `ng-invalid`: the model is invalid - * - `ng-valid-[key]`: for each valid key added by `$setValidity` - * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` - * - `ng-pristine`: the control hasn't been interacted with yet - * - `ng-dirty`: the control has been interacted with - * - `ng-touched`: the control has been blurred - * - `ng-untouched`: the control hasn't been blurred - * - `ng-pending`: any `$asyncValidators` are unfulfilled - * - * Keep in mind that ngAnimate can detect each of these classes when added and removed. - * - * ## Animation Hooks - * - * Animations within models are triggered when any of the associated CSS classes are added and removed - * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, - * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. - * The animations that are triggered within ngModel are similar to how they work in ngClass and - * animations can be hooked into using CSS transitions, keyframes as well as JS animations. - * - * The following example shows a simple way to utilize CSS transitions to style an input element - * that has been rendered as invalid after it has been validated: - * - * <pre> - * //be sure to include ngAnimate as a module to hook into more - * //advanced animations - * .my-input { - * transition:0.5s linear all; - * background: white; - * } - * .my-input.ng-invalid { - * background: red; - * color:white; - * } - * </pre> - * - * @example - * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> - <file name="index.html"> - <script> - angular.module('inputExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.val = '1'; - }]); - </script> - <style> - .my-input { - transition:all linear 0.5s; - background: transparent; - } - .my-input.ng-invalid { - color:white; - background: red; - } - </style> - <p id="inputDescription"> - Update input to see transitions when valid/invalid. - Integer is a valid value. - </p> - <form name="testForm" ng-controller="ExampleController"> - <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" - aria-describedby="inputDescription" /> - </form> - </file> - * </example> - * - * ## Binding to a getter/setter - * - * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a - * function that returns a representation of the model when called with zero arguments, and sets - * the internal state of a model when called with an argument. It's sometimes useful to use this - * for models that have an internal representation that's different from what the model exposes - * to the view. - * - * <div class="alert alert-success"> - * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more - * frequently than other parts of your code. - * </div> - * - * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that - * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to - * a `<form>`, which will enable this behavior for all `<input>`s within it. See - * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. - * - * The following example shows how to use `ngModel` with a getter/setter: - * - * @example - * <example name="ngModel-getter-setter" module="getterSetterExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <form name="userForm"> - <label>Name: - <input type="text" name="userName" - ng-model="user.name" - ng-model-options="{ getterSetter: true }" /> - </label> - </form> - <pre>user.name = <span ng-bind="user.name()"></span></pre> - </div> - </file> - <file name="app.js"> - angular.module('getterSetterExample', []) - .controller('ExampleController', ['$scope', function($scope) { - var _name = 'Brian'; - $scope.user = { - name: function(newName) { - // Note that newName can be undefined for two reasons: - // 1. Because it is called as a getter and thus called with no arguments - // 2. Because the property should actually be set to undefined. This happens e.g. if the - // input is invalid - return arguments.length ? (_name = newName) : _name; - } - }; - }]); - </file> - * </example> - */ -var ngModelDirective = ['$rootScope', function($rootScope) { - return { - restrict: 'A', - require: ['ngModel', '^?form', '^?ngModelOptions'], - controller: NgModelController, - // Prelink needs to run before any input directive - // so that we can set the NgModelOptions in NgModelController - // before anyone else uses it. - priority: 1, - compile: function ngModelCompile(element) { - // Setup initial state of the control - element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); - - return { - pre: function ngModelPreLink(scope, element, attr, ctrls) { - var modelCtrl = ctrls[0], - formCtrl = ctrls[1] || modelCtrl.$$parentForm; - - modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); - - // notify others, especially parent forms - formCtrl.$addControl(modelCtrl); - - attr.$observe('name', function(newValue) { - if (modelCtrl.$name !== newValue) { - modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); - } - }); - - scope.$on('$destroy', function() { - modelCtrl.$$parentForm.$removeControl(modelCtrl); - }); - }, - post: function ngModelPostLink(scope, element, attr, ctrls) { - var modelCtrl = ctrls[0]; - if (modelCtrl.$options && modelCtrl.$options.updateOn) { - element.on(modelCtrl.$options.updateOn, function(ev) { - modelCtrl.$$debounceViewValueCommit(ev && ev.type); - }); - } - - element.on('blur', function(ev) { - if (modelCtrl.$touched) return; - - if ($rootScope.$$phase) { - scope.$evalAsync(modelCtrl.$setTouched); - } else { - scope.$apply(modelCtrl.$setTouched); - } - }); - } - }; - } - }; -}]; - -var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; - -/** - * @ngdoc directive - * @name ngModelOptions - * - * @description - * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of - * events that will trigger a model update and/or a debouncing delay so that the actual update only - * takes place when a timer expires; this timer will be reset after another change takes place. - * - * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might - * be different from the value in the actual model. This means that if you update the model you - * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in - * order to make sure it is synchronized with the model and that any debounced action is canceled. - * - * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} - * method is by making sure the input is placed inside a form that has a `name` attribute. This is - * important because `form` controllers are published to the related scope under the name in their - * `name` attribute. - * - * Any pending changes will take place immediately when an enclosing form is submitted via the - * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` - * to have access to the updated model. - * - * `ngModelOptions` has an effect on the element it's declared on and its descendants. - * - * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: - * - `updateOn`: string specifying which event should the input be bound to. You can set several - * events using an space delimited list. There is a special event called `default` that - * matches the default events belonging of the control. - * - `debounce`: integer value which contains the debounce model update value in milliseconds. A - * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a - * custom value for each event. For example: - * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` - * - `allowInvalid`: boolean value which indicates that the model can be set with values that did - * not validate correctly instead of the default behavior of setting the model to undefined. - * - `getterSetter`: boolean value which determines whether or not to treat functions bound to - `ngModel` as getters/setters. - * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for - * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the - * continental US time zone abbreviations, but for general use, use a time zone offset, for - * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) - * If not specified, the timezone of the browser will be used. - * - * @example - - The following example shows how to override immediate updates. Changes on the inputs within the - form will update the model only when the control loses focus (blur event). If `escape` key is - pressed while the input field is focused, the value is reset to the value in the current model. - - <example name="ngModelOptions-directive-blur" module="optionsExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <form name="userForm"> - <label>Name: - <input type="text" name="userName" - ng-model="user.name" - ng-model-options="{ updateOn: 'blur' }" - ng-keyup="cancel($event)" /> - </label><br /> - <label>Other data: - <input type="text" ng-model="user.data" /> - </label><br /> - </form> - <pre>user.name = <span ng-bind="user.name"></span></pre> - </div> - </file> - <file name="app.js"> - angular.module('optionsExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.user = { name: 'say', data: '' }; - - $scope.cancel = function(e) { - if (e.keyCode == 27) { - $scope.userForm.userName.$rollbackViewValue(); - } - }; - }]); - </file> - <file name="protractor.js" type="protractor"> - var model = element(by.binding('user.name')); - var input = element(by.model('user.name')); - var other = element(by.model('user.data')); - - it('should allow custom events', function() { - input.sendKeys(' hello'); - input.click(); - expect(model.getText()).toEqual('say'); - other.click(); - expect(model.getText()).toEqual('say hello'); - }); - - it('should $rollbackViewValue when model changes', function() { - input.sendKeys(' hello'); - expect(input.getAttribute('value')).toEqual('say hello'); - input.sendKeys(protractor.Key.ESCAPE); - expect(input.getAttribute('value')).toEqual('say'); - other.click(); - expect(model.getText()).toEqual('say'); - }); - </file> - </example> - - This one shows how to debounce model changes. Model will be updated only 1 sec after last change. - If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. - - <example name="ngModelOptions-directive-debounce" module="optionsExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <form name="userForm"> - <label>Name: - <input type="text" name="userName" - ng-model="user.name" - ng-model-options="{ debounce: 1000 }" /> - </label> - <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button> - <br /> - </form> - <pre>user.name = <span ng-bind="user.name"></span></pre> - </div> - </file> - <file name="app.js"> - angular.module('optionsExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.user = { name: 'say' }; - }]); - </file> - </example> - - This one shows how to bind to getter/setters: - - <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <form name="userForm"> - <label>Name: - <input type="text" name="userName" - ng-model="user.name" - ng-model-options="{ getterSetter: true }" /> - </label> - </form> - <pre>user.name = <span ng-bind="user.name()"></span></pre> - </div> - </file> - <file name="app.js"> - angular.module('getterSetterExample', []) - .controller('ExampleController', ['$scope', function($scope) { - var _name = 'Brian'; - $scope.user = { - name: function(newName) { - // Note that newName can be undefined for two reasons: - // 1. Because it is called as a getter and thus called with no arguments - // 2. Because the property should actually be set to undefined. This happens e.g. if the - // input is invalid - return arguments.length ? (_name = newName) : _name; - } - }; - }]); - </file> - </example> - */ -var ngModelOptionsDirective = function() { - return { - restrict: 'A', - controller: ['$scope', '$attrs', function($scope, $attrs) { - var that = this; - this.$options = copy($scope.$eval($attrs.ngModelOptions)); - // Allow adding/overriding bound events - if (isDefined(this.$options.updateOn)) { - this.$options.updateOnDefault = false; - // extract "default" pseudo-event from list of events that can trigger a model update - this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { - that.$options.updateOnDefault = true; - return ' '; - })); - } else { - this.$options.updateOnDefault = true; - } - }] - }; -}; - - - -// helper methods -function addSetValidityMethod(context) { - var ctrl = context.ctrl, - $element = context.$element, - classCache = {}, - set = context.set, - unset = context.unset, - $animate = context.$animate; - - classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); - - ctrl.$setValidity = setValidity; - - function setValidity(validationErrorKey, state, controller) { - if (isUndefined(state)) { - createAndSet('$pending', validationErrorKey, controller); - } else { - unsetAndCleanup('$pending', validationErrorKey, controller); - } - if (!isBoolean(state)) { - unset(ctrl.$error, validationErrorKey, controller); - unset(ctrl.$$success, validationErrorKey, controller); - } else { - if (state) { - unset(ctrl.$error, validationErrorKey, controller); - set(ctrl.$$success, validationErrorKey, controller); - } else { - set(ctrl.$error, validationErrorKey, controller); - unset(ctrl.$$success, validationErrorKey, controller); - } - } - if (ctrl.$pending) { - cachedToggleClass(PENDING_CLASS, true); - ctrl.$valid = ctrl.$invalid = undefined; - toggleValidationCss('', null); - } else { - cachedToggleClass(PENDING_CLASS, false); - ctrl.$valid = isObjectEmpty(ctrl.$error); - ctrl.$invalid = !ctrl.$valid; - toggleValidationCss('', ctrl.$valid); - } - - // re-read the state as the set/unset methods could have - // combined state in ctrl.$error[validationError] (used for forms), - // where setting/unsetting only increments/decrements the value, - // and does not replace it. - var combinedState; - if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { - combinedState = undefined; - } else if (ctrl.$error[validationErrorKey]) { - combinedState = false; - } else if (ctrl.$$success[validationErrorKey]) { - combinedState = true; - } else { - combinedState = null; - } - - toggleValidationCss(validationErrorKey, combinedState); - ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl); - } - - function createAndSet(name, value, controller) { - if (!ctrl[name]) { - ctrl[name] = {}; - } - set(ctrl[name], value, controller); - } - - function unsetAndCleanup(name, value, controller) { - if (ctrl[name]) { - unset(ctrl[name], value, controller); - } - if (isObjectEmpty(ctrl[name])) { - ctrl[name] = undefined; - } - } - - function cachedToggleClass(className, switchValue) { - if (switchValue && !classCache[className]) { - $animate.addClass($element, className); - classCache[className] = true; - } else if (!switchValue && classCache[className]) { - $animate.removeClass($element, className); - classCache[className] = false; - } - } - - function toggleValidationCss(validationErrorKey, isValid) { - validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; - - cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); - cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); - } -} - -function isObjectEmpty(obj) { - if (obj) { - for (var prop in obj) { - if (obj.hasOwnProperty(prop)) { - return false; - } - } - } - return true; -} - -/** - * @ngdoc directive - * @name ngNonBindable - * @restrict AC - * @priority 1000 - * - * @description - * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current - * DOM element. This is useful if the element contains what appears to be Angular directives and - * bindings but which should be ignored by Angular. This could be the case if you have a site that - * displays snippets of code, for instance. - * - * @element ANY - * - * @example - * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, - * but the one wrapped in `ngNonBindable` is left alone. - * - * @example - <example> - <file name="index.html"> - <div>Normal: {{1 + 2}}</div> - <div ng-non-bindable>Ignored: {{1 + 2}}</div> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-non-bindable', function() { - expect(element(by.binding('1 + 2')).getText()).toContain('3'); - expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); - }); - </file> - </example> - */ -var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); - -/* global jqLiteRemove */ - -var ngOptionsMinErr = minErr('ngOptions'); - -/** - * @ngdoc directive - * @name ngOptions - * @restrict A - * - * @description - * - * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` - * elements for the `<select>` element using the array or object obtained by evaluating the - * `ngOptions` comprehension expression. - * - * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a - * similar result. However, `ngOptions` provides some benefits such as reducing memory and - * increasing speed by not creating a new scope for each repeated instance, as well as providing - * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the - * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound - * to a non-string value. This is because an option element can only be bound to string values at - * present. - * - * When an item in the `<select>` menu is selected, the array element or object property - * represented by the selected option will be bound to the model identified by the `ngModel` - * directive. - * - * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can - * be nested into the `<select>` element. This element will then represent the `null` or "not selected" - * option. See example below for demonstration. - * - * ## Complex Models (objects or collections) - * - * **Note:** By default, `ngModel` watches the model by reference, not value. This is important when - * binding any input directive to a model that is an object or a collection. - * - * Since this is a common situation for `ngOptions` the directive additionally watches the model using - * `$watchCollection` when the select has the `multiple` attribute or when there is a `track by` clause in - * the options expression. This allows ngOptions to trigger a re-rendering of the options even if the actual - * object/collection has not changed identity but only a property on the object or an item in the collection - * changes. - * - * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection - * if the model is an array). This means that changing a property deeper inside the object/collection that the - * first level will not trigger a re-rendering. - * - * - * ## `select` **`as`** - * - * Using `select` **`as`** will bind the result of the `select` expression to the model, but - * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) - * or property name (for object data sources) of the value within the collection. If a **`track by`** expression - * is used, the result of that expression will be set as the value of the `option` and `select` elements. - * - * - * ### `select` **`as`** and **`track by`** - * - * <div class="alert alert-warning"> - * Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together. - * </div> - * - * Consider the following example: - * - * ```html - * <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"></select> - * ``` - * - * ```js - * $scope.values = [{ - * id: 1, - * label: 'aLabel', - * subItem: { name: 'aSubItem' } - * }, { - * id: 2, - * label: 'bLabel', - * subItem: { name: 'bSubItem' } - * }]; - * - * $scope.selected = { name: 'aSubItem' }; - * ``` - * - * With the purpose of preserving the selection, the **`track by`** expression is always applied to the element - * of the data source (to `item` in this example). To calculate whether an element is selected, we do the - * following: - * - * 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]` - * 2. Apply **`track by`** to the already selected value in `ngModel`. - * In the example: this is not possible as **`track by`** refers to `item.id`, but the selected - * value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to - * a wrong object, the selected element can't be found, `<select>` is always reset to the "not - * selected" option. - * - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required The control is considered valid only if value is entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {comprehension_expression=} ngOptions in one of the following forms: - * - * * for array data sources: - * * `label` **`for`** `value` **`in`** `array` - * * `select` **`as`** `label` **`for`** `value` **`in`** `array` - * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` - * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` - * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` - * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` - * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` - * (for including a filter with `track by`) - * * for object data sources: - * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` - * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` - * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` - * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` - * * `select` **`as`** `label` **`group by`** `group` - * **`for` `(`**`key`**`,`** `value`**`) in`** `object` - * * `select` **`as`** `label` **`disable when`** `disable` - * **`for` `(`**`key`**`,`** `value`**`) in`** `object` - * - * Where: - * - * * `array` / `object`: an expression which evaluates to an array / object to iterate over. - * * `value`: local variable which will refer to each item in the `array` or each property value - * of `object` during iteration. - * * `key`: local variable which will refer to a property name in `object` during iteration. - * * `label`: The result of this expression will be the label for `<option>` element. The - * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). - * * `select`: The result of this expression will be bound to the model of the parent `<select>` - * element. If not specified, `select` expression will default to `value`. - * * `group`: The result of this expression will be used to group options using the `<optgroup>` - * DOM element. - * * `disable`: The result of this expression will be used to disable the rendered `<option>` - * element. Return `true` to disable. - * * `trackexpr`: Used when working with an array of objects. The result of this expression will be - * used to identify the objects in the array. The `trackexpr` will most likely refer to the - * `value` variable (e.g. `value.propertyName`). With this the selection is preserved - * even when the options are recreated (e.g. reloaded from the server). - * - * @example - <example module="selectExample"> - <file name="index.html"> - <script> - angular.module('selectExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.colors = [ - {name:'black', shade:'dark'}, - {name:'white', shade:'light', notAnOption: true}, - {name:'red', shade:'dark'}, - {name:'blue', shade:'dark', notAnOption: true}, - {name:'yellow', shade:'light', notAnOption: false} - ]; - $scope.myColor = $scope.colors[2]; // red - }]); - </script> - <div ng-controller="ExampleController"> - <ul> - <li ng-repeat="color in colors"> - <label>Name: <input ng-model="color.name"></label> - <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> - <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> - </li> - <li> - <button ng-click="colors.push({})">add</button> - </li> - </ul> - <hr/> - <label>Color (null not allowed): - <select ng-model="myColor" ng-options="color.name for color in colors"></select> - </label><br/> - <label>Color (null allowed): - <span class="nullable"> - <select ng-model="myColor" ng-options="color.name for color in colors"> - <option value="">-- choose color --</option> - </select> - </span></label><br/> - - <label>Color grouped by shade: - <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> - </select> - </label><br/> - - <label>Color grouped by shade, with some disabled: - <select ng-model="myColor" - ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> - </select> - </label><br/> - - - - Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. - <br/> - <hr/> - Currently selected: {{ {selected_color:myColor} }} - <div style="border:solid 1px black; height:20px" - ng-style="{'background-color':myColor.name}"> - </div> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should check ng-options', function() { - expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); - element.all(by.model('myColor')).first().click(); - element.all(by.css('select[ng-model="myColor"] option')).first().click(); - expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); - element(by.css('.nullable select[ng-model="myColor"]')).click(); - element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); - expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); - }); - </file> - </example> - */ - -// jshint maxlen: false -// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999 -var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/; - // 1: value expression (valueFn) - // 2: label expression (displayFn) - // 3: group by expression (groupByFn) - // 4: disable when expression (disableWhenFn) - // 5: array item variable name - // 6: object item key variable name - // 7: object item value variable name - // 8: collection expression - // 9: track by expression -// jshint maxlen: 100 - - -var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { - - function parseOptionsExpression(optionsExp, selectElement, scope) { - - var match = optionsExp.match(NG_OPTIONS_REGEXP); - if (!(match)) { - throw ngOptionsMinErr('iexp', - "Expected expression in form of " + - "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + - " but got '{0}'. Element: {1}", - optionsExp, startingTag(selectElement)); - } - - // Extract the parts from the ngOptions expression - - // The variable name for the value of the item in the collection - var valueName = match[5] || match[7]; - // The variable name for the key of the item in the collection - var keyName = match[6]; - - // An expression that generates the viewValue for an option if there is a label expression - var selectAs = / as /.test(match[0]) && match[1]; - // An expression that is used to track the id of each object in the options collection - var trackBy = match[9]; - // An expression that generates the viewValue for an option if there is no label expression - var valueFn = $parse(match[2] ? match[1] : valueName); - var selectAsFn = selectAs && $parse(selectAs); - var viewValueFn = selectAsFn || valueFn; - var trackByFn = trackBy && $parse(trackBy); - - // Get the value by which we are going to track the option - // if we have a trackFn then use that (passing scope and locals) - // otherwise just hash the given viewValue - var getTrackByValueFn = trackBy ? - function(value, locals) { return trackByFn(scope, locals); } : - function getHashOfValue(value) { return hashKey(value); }; - var getTrackByValue = function(value, key) { - return getTrackByValueFn(value, getLocals(value, key)); - }; - - var displayFn = $parse(match[2] || match[1]); - var groupByFn = $parse(match[3] || ''); - var disableWhenFn = $parse(match[4] || ''); - var valuesFn = $parse(match[8]); - - var locals = {}; - var getLocals = keyName ? function(value, key) { - locals[keyName] = key; - locals[valueName] = value; - return locals; - } : function(value) { - locals[valueName] = value; - return locals; - }; - - - function Option(selectValue, viewValue, label, group, disabled) { - this.selectValue = selectValue; - this.viewValue = viewValue; - this.label = label; - this.group = group; - this.disabled = disabled; - } - - function getOptionValuesKeys(optionValues) { - var optionValuesKeys; - - if (!keyName && isArrayLike(optionValues)) { - optionValuesKeys = optionValues; - } else { - // if object, extract keys, in enumeration order, unsorted - optionValuesKeys = []; - for (var itemKey in optionValues) { - if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { - optionValuesKeys.push(itemKey); - } - } - } - return optionValuesKeys; - } - - return { - trackBy: trackBy, - getTrackByValue: getTrackByValue, - getWatchables: $parse(valuesFn, function(optionValues) { - // Create a collection of things that we would like to watch (watchedArray) - // so that they can all be watched using a single $watchCollection - // that only runs the handler once if anything changes - var watchedArray = []; - optionValues = optionValues || []; - - var optionValuesKeys = getOptionValuesKeys(optionValues); - var optionValuesLength = optionValuesKeys.length; - for (var index = 0; index < optionValuesLength; index++) { - var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; - var value = optionValues[key]; - - var locals = getLocals(optionValues[key], key); - var selectValue = getTrackByValueFn(optionValues[key], locals); - watchedArray.push(selectValue); - - // Only need to watch the displayFn if there is a specific label expression - if (match[2] || match[1]) { - var label = displayFn(scope, locals); - watchedArray.push(label); - } - - // Only need to watch the disableWhenFn if there is a specific disable expression - if (match[4]) { - var disableWhen = disableWhenFn(scope, locals); - watchedArray.push(disableWhen); - } - } - return watchedArray; - }), - - getOptions: function() { - - var optionItems = []; - var selectValueMap = {}; - - // The option values were already computed in the `getWatchables` fn, - // which must have been called to trigger `getOptions` - var optionValues = valuesFn(scope) || []; - var optionValuesKeys = getOptionValuesKeys(optionValues); - var optionValuesLength = optionValuesKeys.length; - - for (var index = 0; index < optionValuesLength; index++) { - var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; - var value = optionValues[key]; - var locals = getLocals(value, key); - var viewValue = viewValueFn(scope, locals); - var selectValue = getTrackByValueFn(viewValue, locals); - var label = displayFn(scope, locals); - var group = groupByFn(scope, locals); - var disabled = disableWhenFn(scope, locals); - var optionItem = new Option(selectValue, viewValue, label, group, disabled); - - optionItems.push(optionItem); - selectValueMap[selectValue] = optionItem; - } - - return { - items: optionItems, - selectValueMap: selectValueMap, - getOptionFromViewValue: function(value) { - return selectValueMap[getTrackByValue(value)]; - }, - getViewValueFromOption: function(option) { - // If the viewValue could be an object that may be mutated by the application, - // we need to make a copy and not return the reference to the value on the option. - return trackBy ? angular.copy(option.viewValue) : option.viewValue; - } - }; - } - }; - } - - - // we can't just jqLite('<option>') since jqLite is not smart enough - // to create it in <select> and IE barfs otherwise. - var optionTemplate = document.createElement('option'), - optGroupTemplate = document.createElement('optgroup'); - - return { - restrict: 'A', - terminal: true, - require: ['select', '?ngModel'], - link: function(scope, selectElement, attr, ctrls) { - - // if ngModel is not defined, we don't need to do anything - var ngModelCtrl = ctrls[1]; - if (!ngModelCtrl) return; - - var selectCtrl = ctrls[0]; - var multiple = attr.multiple; - - // The emptyOption allows the application developer to provide their own custom "empty" - // option when the viewValue does not match any of the option values. - var emptyOption; - for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { - if (children[i].value === '') { - emptyOption = children.eq(i); - break; - } - } - - var providedEmptyOption = !!emptyOption; - - var unknownOption = jqLite(optionTemplate.cloneNode(false)); - unknownOption.val('?'); - - var options; - var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); - - - var renderEmptyOption = function() { - if (!providedEmptyOption) { - selectElement.prepend(emptyOption); - } - selectElement.val(''); - emptyOption.prop('selected', true); // needed for IE - emptyOption.attr('selected', true); - }; - - var removeEmptyOption = function() { - if (!providedEmptyOption) { - emptyOption.remove(); - } - }; - - - var renderUnknownOption = function() { - selectElement.prepend(unknownOption); - selectElement.val('?'); - unknownOption.prop('selected', true); // needed for IE - unknownOption.attr('selected', true); - }; - - var removeUnknownOption = function() { - unknownOption.remove(); - }; - - - // Update the controller methods for multiple selectable options - if (!multiple) { - - selectCtrl.writeValue = function writeNgOptionsValue(value) { - var option = options.getOptionFromViewValue(value); - - if (option && !option.disabled) { - if (selectElement[0].value !== option.selectValue) { - removeUnknownOption(); - removeEmptyOption(); - - selectElement[0].value = option.selectValue; - option.element.selected = true; - option.element.setAttribute('selected', 'selected'); - } - } else { - if (value === null || providedEmptyOption) { - removeUnknownOption(); - renderEmptyOption(); - } else { - removeEmptyOption(); - renderUnknownOption(); - } - } - }; - - selectCtrl.readValue = function readNgOptionsValue() { - - var selectedOption = options.selectValueMap[selectElement.val()]; - - if (selectedOption && !selectedOption.disabled) { - removeEmptyOption(); - removeUnknownOption(); - return options.getViewValueFromOption(selectedOption); - } - return null; - }; - - // If we are using `track by` then we must watch the tracked value on the model - // since ngModel only watches for object identity change - if (ngOptions.trackBy) { - scope.$watch( - function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, - function() { ngModelCtrl.$render(); } - ); - } - - } else { - - ngModelCtrl.$isEmpty = function(value) { - return !value || value.length === 0; - }; - - - selectCtrl.writeValue = function writeNgOptionsMultiple(value) { - options.items.forEach(function(option) { - option.element.selected = false; - }); - - if (value) { - value.forEach(function(item) { - var option = options.getOptionFromViewValue(item); - if (option && !option.disabled) option.element.selected = true; - }); - } - }; - - - selectCtrl.readValue = function readNgOptionsMultiple() { - var selectedValues = selectElement.val() || [], - selections = []; - - forEach(selectedValues, function(value) { - var option = options.selectValueMap[value]; - if (option && !option.disabled) selections.push(options.getViewValueFromOption(option)); - }); - - return selections; - }; - - // If we are using `track by` then we must watch these tracked values on the model - // since ngModel only watches for object identity change - if (ngOptions.trackBy) { - - scope.$watchCollection(function() { - if (isArray(ngModelCtrl.$viewValue)) { - return ngModelCtrl.$viewValue.map(function(value) { - return ngOptions.getTrackByValue(value); - }); - } - }, function() { - ngModelCtrl.$render(); - }); - - } - } - - - if (providedEmptyOption) { - - // we need to remove it before calling selectElement.empty() because otherwise IE will - // remove the label from the element. wtf? - emptyOption.remove(); - - // compile the element since there might be bindings in it - $compile(emptyOption)(scope); - - // remove the class, which is added automatically because we recompile the element and it - // becomes the compilation root - emptyOption.removeClass('ng-scope'); - } else { - emptyOption = jqLite(optionTemplate.cloneNode(false)); - } - - // We need to do this here to ensure that the options object is defined - // when we first hit it in writeNgOptionsValue - updateOptions(); - - // We will re-render the option elements if the option values or labels change - scope.$watchCollection(ngOptions.getWatchables, updateOptions); - - // ------------------------------------------------------------------ // - - - function updateOptionElement(option, element) { - option.element = element; - element.disabled = option.disabled; - // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive - // selects in certain circumstances when multiple selects are next to each other and display - // the option list in listbox style, i.e. the select is [multiple], or specifies a [size]. - // See https://github.com/angular/angular.js/issues/11314 for more info. - // This is unfortunately untestable with unit / e2e tests - if (option.label !== element.label) { - element.label = option.label; - element.textContent = option.label; - } - if (option.value !== element.value) element.value = option.selectValue; - } - - function addOrReuseElement(parent, current, type, templateElement) { - var element; - // Check whether we can reuse the next element - if (current && lowercase(current.nodeName) === type) { - // The next element is the right type so reuse it - element = current; - } else { - // The next element is not the right type so create a new one - element = templateElement.cloneNode(false); - if (!current) { - // There are no more elements so just append it to the select - parent.appendChild(element); - } else { - // The next element is not a group so insert the new one - parent.insertBefore(element, current); - } - } - return element; - } - - - function removeExcessElements(current) { - var next; - while (current) { - next = current.nextSibling; - jqLiteRemove(current); - current = next; - } - } - - - function skipEmptyAndUnknownOptions(current) { - var emptyOption_ = emptyOption && emptyOption[0]; - var unknownOption_ = unknownOption && unknownOption[0]; - - if (emptyOption_ || unknownOption_) { - while (current && - (current === emptyOption_ || - current === unknownOption_ || - emptyOption_ && emptyOption_.nodeType === NODE_TYPE_COMMENT)) { - // Empty options might have directives that transclude - // and insert comments (e.g. ngIf) - current = current.nextSibling; - } - } - return current; - } - - - function updateOptions() { - - var previousValue = options && selectCtrl.readValue(); - - options = ngOptions.getOptions(); - - var groupMap = {}; - var currentElement = selectElement[0].firstChild; - - // Ensure that the empty option is always there if it was explicitly provided - if (providedEmptyOption) { - selectElement.prepend(emptyOption); - } - - currentElement = skipEmptyAndUnknownOptions(currentElement); - - options.items.forEach(function updateOption(option) { - var group; - var groupElement; - var optionElement; - - if (option.group) { - - // This option is to live in a group - // See if we have already created this group - group = groupMap[option.group]; - - if (!group) { - - // We have not already created this group - groupElement = addOrReuseElement(selectElement[0], - currentElement, - 'optgroup', - optGroupTemplate); - // Move to the next element - currentElement = groupElement.nextSibling; - - // Update the label on the group element - groupElement.label = option.group; - - // Store it for use later - group = groupMap[option.group] = { - groupElement: groupElement, - currentOptionElement: groupElement.firstChild - }; - - } - - // So now we have a group for this option we add the option to the group - optionElement = addOrReuseElement(group.groupElement, - group.currentOptionElement, - 'option', - optionTemplate); - updateOptionElement(option, optionElement); - // Move to the next element - group.currentOptionElement = optionElement.nextSibling; - - } else { - - // This option is not in a group - optionElement = addOrReuseElement(selectElement[0], - currentElement, - 'option', - optionTemplate); - updateOptionElement(option, optionElement); - // Move to the next element - currentElement = optionElement.nextSibling; - } - }); - - - // Now remove all excess options and group - Object.keys(groupMap).forEach(function(key) { - removeExcessElements(groupMap[key].currentOptionElement); - }); - removeExcessElements(currentElement); - - ngModelCtrl.$render(); - - // Check to see if the value has changed due to the update to the options - if (!ngModelCtrl.$isEmpty(previousValue)) { - var nextValue = selectCtrl.readValue(); - if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) { - ngModelCtrl.$setViewValue(nextValue); - ngModelCtrl.$render(); - } - } - - } - - } - }; -}]; - -/** - * @ngdoc directive - * @name ngPluralize - * @restrict EA - * - * @description - * `ngPluralize` is a directive that displays messages according to en-US localization rules. - * These rules are bundled with angular.js, but can be overridden - * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive - * by specifying the mappings between - * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) - * and the strings to be displayed. - * - * # Plural categories and explicit number rules - * There are two - * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) - * in Angular's default en-US locale: "one" and "other". - * - * While a plural category may match many numbers (for example, in en-US locale, "other" can match - * any number that is not 1), an explicit number rule can only match one number. For example, the - * explicit number rule for "3" matches the number 3. There are examples of plural categories - * and explicit number rules throughout the rest of this documentation. - * - * # Configuring ngPluralize - * You configure ngPluralize by providing 2 attributes: `count` and `when`. - * You can also provide an optional attribute, `offset`. - * - * The value of the `count` attribute can be either a string or an {@link guide/expression - * Angular expression}; these are evaluated on the current scope for its bound value. - * - * The `when` attribute specifies the mappings between plural categories and the actual - * string to be displayed. The value of the attribute should be a JSON object. - * - * The following example shows how to configure ngPluralize: - * - * ```html - * <ng-pluralize count="personCount" - when="{'0': 'Nobody is viewing.', - * 'one': '1 person is viewing.', - * 'other': '{} people are viewing.'}"> - * </ng-pluralize> - *``` - * - * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not - * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" - * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for - * other numbers, for example 12, so that instead of showing "12 people are viewing", you can - * show "a dozen people are viewing". - * - * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted - * into pluralized strings. In the previous example, Angular will replace `{}` with - * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder - * for <span ng-non-bindable>{{numberExpression}}</span>. - * - * If no rule is defined for a category, then an empty string is displayed and a warning is generated. - * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. - * - * # Configuring ngPluralize with offset - * The `offset` attribute allows further customization of pluralized text, which can result in - * a better user experience. For example, instead of the message "4 people are viewing this document", - * you might display "John, Kate and 2 others are viewing this document". - * The offset attribute allows you to offset a number by any desired value. - * Let's take a look at an example: - * - * ```html - * <ng-pluralize count="personCount" offset=2 - * when="{'0': 'Nobody is viewing.', - * '1': '{{person1}} is viewing.', - * '2': '{{person1}} and {{person2}} are viewing.', - * 'one': '{{person1}}, {{person2}} and one other person are viewing.', - * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> - * </ng-pluralize> - * ``` - * - * Notice that we are still using two plural categories(one, other), but we added - * three explicit number rules 0, 1 and 2. - * When one person, perhaps John, views the document, "John is viewing" will be shown. - * When three people view the document, no explicit number rule is found, so - * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. - * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" - * is shown. - * - * Note that when you specify offsets, you must provide explicit number rules for - * numbers from 0 up to and including the offset. If you use an offset of 3, for example, - * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for - * plural categories "one" and "other". - * - * @param {string|expression} count The variable to be bound to. - * @param {string} when The mapping between plural category to its corresponding strings. - * @param {number=} offset Offset to deduct from the total number. - * - * @example - <example module="pluralizeExample"> - <file name="index.html"> - <script> - angular.module('pluralizeExample', []) - .controller('ExampleController', ['$scope', function($scope) { - $scope.person1 = 'Igor'; - $scope.person2 = 'Misko'; - $scope.personCount = 1; - }]); - </script> - <div ng-controller="ExampleController"> - <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> - <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> - <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> - - <!--- Example with simple pluralization rules for en locale ---> - Without Offset: - <ng-pluralize count="personCount" - when="{'0': 'Nobody is viewing.', - 'one': '1 person is viewing.', - 'other': '{} people are viewing.'}"> - </ng-pluralize><br> - - <!--- Example with offset ---> - With Offset(2): - <ng-pluralize count="personCount" offset=2 - when="{'0': 'Nobody is viewing.', - '1': '{{person1}} is viewing.', - '2': '{{person1}} and {{person2}} are viewing.', - 'one': '{{person1}}, {{person2}} and one other person are viewing.', - 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> - </ng-pluralize> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should show correct pluralized string', function() { - var withoutOffset = element.all(by.css('ng-pluralize')).get(0); - var withOffset = element.all(by.css('ng-pluralize')).get(1); - var countInput = element(by.model('personCount')); - - expect(withoutOffset.getText()).toEqual('1 person is viewing.'); - expect(withOffset.getText()).toEqual('Igor is viewing.'); - - countInput.clear(); - countInput.sendKeys('0'); - - expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); - expect(withOffset.getText()).toEqual('Nobody is viewing.'); - - countInput.clear(); - countInput.sendKeys('2'); - - expect(withoutOffset.getText()).toEqual('2 people are viewing.'); - expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); - - countInput.clear(); - countInput.sendKeys('3'); - - expect(withoutOffset.getText()).toEqual('3 people are viewing.'); - expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); - - countInput.clear(); - countInput.sendKeys('4'); - - expect(withoutOffset.getText()).toEqual('4 people are viewing.'); - expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); - }); - it('should show data-bound names', function() { - var withOffset = element.all(by.css('ng-pluralize')).get(1); - var personCount = element(by.model('personCount')); - var person1 = element(by.model('person1')); - var person2 = element(by.model('person2')); - personCount.clear(); - personCount.sendKeys('4'); - person1.clear(); - person1.sendKeys('Di'); - person2.clear(); - person2.sendKeys('Vojta'); - expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); - }); - </file> - </example> - */ -var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { - var BRACE = /{}/g, - IS_WHEN = /^when(Minus)?(.+)$/; - - return { - link: function(scope, element, attr) { - var numberExp = attr.count, - whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs - offset = attr.offset || 0, - whens = scope.$eval(whenExp) || {}, - whensExpFns = {}, - startSymbol = $interpolate.startSymbol(), - endSymbol = $interpolate.endSymbol(), - braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, - watchRemover = angular.noop, - lastCount; - - forEach(attr, function(expression, attributeName) { - var tmpMatch = IS_WHEN.exec(attributeName); - if (tmpMatch) { - var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); - whens[whenKey] = element.attr(attr.$attr[attributeName]); - } - }); - forEach(whens, function(expression, key) { - whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); - - }); - - scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { - var count = parseFloat(newVal); - var countIsNaN = isNaN(count); - - if (!countIsNaN && !(count in whens)) { - // If an explicit number rule such as 1, 2, 3... is defined, just use it. - // Otherwise, check it against pluralization rules in $locale service. - count = $locale.pluralCat(count - offset); - } - - // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. - // In JS `NaN !== NaN`, so we have to exlicitly check. - if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) { - watchRemover(); - var whenExpFn = whensExpFns[count]; - if (isUndefined(whenExpFn)) { - if (newVal != null) { - $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp); - } - watchRemover = noop; - updateElementText(); - } else { - watchRemover = scope.$watch(whenExpFn, updateElementText); - } - lastCount = count; - } - }); - - function updateElementText(newText) { - element.text(newText || ''); - } - } - }; -}]; - -/** - * @ngdoc directive - * @name ngRepeat - * @multiElement - * - * @description - * The `ngRepeat` directive instantiates a template once per item from a collection. Each template - * instance gets its own scope, where the given loop variable is set to the current collection item, - * and `$index` is set to the item index or key. - * - * Special properties are exposed on the local scope of each template instance, including: - * - * | Variable | Type | Details | - * |-----------|-----------------|-----------------------------------------------------------------------------| - * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | - * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | - * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | - * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | - * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | - * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | - * - * <div class="alert alert-info"> - * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. - * This may be useful when, for instance, nesting ngRepeats. - * </div> - * - * - * # Iterating over object properties - * - * It is possible to get `ngRepeat` to iterate over the properties of an object using the following - * syntax: - * - * ```js - * <div ng-repeat="(key, value) in myObj"> ... </div> - * ``` - * - * You need to be aware that the JavaScript specification does not define the order of keys - * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive - * used to sort the keys alphabetically.) - * - * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser - * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing - * keys in the order in which they were defined, although there are exceptions when keys are deleted - * and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues - * - * If this is not desired, the recommended workaround is to convert your object into an array - * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could - * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) - * or implement a `$watch` on the object yourself. - * - * - * # Tracking and Duplicates - * - * When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM: - * - * * When an item is added, a new instance of the template is added to the DOM. - * * When an item is removed, its template instance is removed from the DOM. - * * When items are reordered, their respective templates are reordered in the DOM. - * - * By default, `ngRepeat` does not allow duplicate items in arrays. This is because when - * there are duplicates, it is not possible to maintain a one-to-one mapping between collection - * items and DOM elements. - * - * If you do need to repeat duplicate items, you can substitute the default tracking behavior - * with your own using the `track by` expression. - * - * For example, you may track items by the index of each item in the collection, using the - * special scope property `$index`: - * ```html - * <div ng-repeat="n in [42, 42, 43, 43] track by $index"> - * {{n}} - * </div> - * ``` - * - * You may use arbitrary expressions in `track by`, including references to custom functions - * on the scope: - * ```html - * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)"> - * {{n}} - * </div> - * ``` - * - * If you are working with objects that have an identifier property, you can track - * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` - * will not have to rebuild the DOM elements for items it has already rendered, even if the - * JavaScript objects in the collection have been substituted for new ones: - * ```html - * <div ng-repeat="model in collection track by model.id"> - * {{model.name}} - * </div> - * ``` - * - * When no `track by` expression is provided, it is equivalent to tracking by the built-in - * `$id` function, which tracks items by their identity: - * ```html - * <div ng-repeat="obj in collection track by $id(obj)"> - * {{obj.prop}} - * </div> - * ``` - * - * <div class="alert alert-warning"> - * **Note:** `track by` must always be the last expression: - * </div> - * ``` - * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id"> - * {{model.name}} - * </div> - * ``` - * - * # Special repeat start and end points - * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending - * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. - * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) - * up to and including the ending HTML tag where **ng-repeat-end** is placed. - * - * The example below makes use of this feature: - * ```html - * <header ng-repeat-start="item in items"> - * Header {{ item }} - * </header> - * <div class="body"> - * Body {{ item }} - * </div> - * <footer ng-repeat-end> - * Footer {{ item }} - * </footer> - * ``` - * - * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: - * ```html - * <header> - * Header A - * </header> - * <div class="body"> - * Body A - * </div> - * <footer> - * Footer A - * </footer> - * <header> - * Header B - * </header> - * <div class="body"> - * Body B - * </div> - * <footer> - * Footer B - * </footer> - * ``` - * - * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such - * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). - * - * @animations - * **.enter** - when a new item is added to the list or when an item is revealed after a filter - * - * **.leave** - when an item is removed from the list or when an item is filtered out - * - * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered - * - * @element ANY - * @scope - * @priority 1000 - * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These - * formats are currently supported: - * - * * `variable in expression` – where variable is the user defined loop variable and `expression` - * is a scope expression giving the collection to enumerate. - * - * For example: `album in artist.albums`. - * - * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, - * and `expression` is the scope expression giving the collection to enumerate. - * - * For example: `(name, age) in {'adam':10, 'amalie':12}`. - * - * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression - * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression - * is specified, ng-repeat associates elements by identity. It is an error to have - * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are - * mapped to the same DOM element, which is not possible.) - * - * Note that the tracking expression must come last, after any filters, and the alias expression. - * - * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements - * will be associated by item identity in the array. - * - * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique - * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements - * with the corresponding item in the array by identity. Moving the same object in array would move the DOM - * element in the same way in the DOM. - * - * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this - * case the object identity does not matter. Two objects are considered equivalent as long as their `id` - * property is same. - * - * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter - * to items in conjunction with a tracking expression. - * - * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the - * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message - * when a filter is active on the repeater, but the filtered result set is empty. - * - * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after - * the items have been processed through the filter. - * - * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end - * (and not as operator, inside an expression). - * - * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . - * - * @example - * This example initializes the scope to a list of names and - * then uses `ngRepeat` to display every person: - <example module="ngAnimate" deps="angular-animate.js" animations="true"> - <file name="index.html"> - <div ng-init="friends = [ - {name:'John', age:25, gender:'boy'}, - {name:'Jessie', age:30, gender:'girl'}, - {name:'Johanna', age:28, gender:'girl'}, - {name:'Joy', age:15, gender:'girl'}, - {name:'Mary', age:28, gender:'girl'}, - {name:'Peter', age:95, gender:'boy'}, - {name:'Sebastian', age:50, gender:'boy'}, - {name:'Erika', age:27, gender:'girl'}, - {name:'Patrick', age:40, gender:'boy'}, - {name:'Samantha', age:60, gender:'girl'} - ]"> - I have {{friends.length}} friends. They are: - <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> - <ul class="example-animate-container"> - <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> - [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. - </li> - <li class="animate-repeat" ng-if="results.length == 0"> - <strong>No results found...</strong> - </li> - </ul> - </div> - </file> - <file name="animations.css"> - .example-animate-container { - background:white; - border:1px solid black; - list-style:none; - margin:0; - padding:0 10px; - } - - .animate-repeat { - line-height:40px; - list-style:none; - box-sizing:border-box; - } - - .animate-repeat.ng-move, - .animate-repeat.ng-enter, - .animate-repeat.ng-leave { - transition:all linear 0.5s; - } - - .animate-repeat.ng-leave.ng-leave-active, - .animate-repeat.ng-move, - .animate-repeat.ng-enter { - opacity:0; - max-height:0; - } - - .animate-repeat.ng-leave, - .animate-repeat.ng-move.ng-move-active, - .animate-repeat.ng-enter.ng-enter-active { - opacity:1; - max-height:40px; - } - </file> - <file name="protractor.js" type="protractor"> - var friends = element.all(by.repeater('friend in friends')); - - it('should render initial data set', function() { - expect(friends.count()).toBe(10); - expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); - expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); - expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); - expect(element(by.binding('friends.length')).getText()) - .toMatch("I have 10 friends. They are:"); - }); - - it('should update repeater when filter predicate changes', function() { - expect(friends.count()).toBe(10); - - element(by.model('q')).sendKeys('ma'); - - expect(friends.count()).toBe(2); - expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); - expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); - }); - </file> - </example> - */ -var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { - var NG_REMOVED = '$$NG_REMOVED'; - var ngRepeatMinErr = minErr('ngRepeat'); - - var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { - // TODO(perf): generate setters to shave off ~40ms or 1-1.5% - scope[valueIdentifier] = value; - if (keyIdentifier) scope[keyIdentifier] = key; - scope.$index = index; - scope.$first = (index === 0); - scope.$last = (index === (arrayLength - 1)); - scope.$middle = !(scope.$first || scope.$last); - // jshint bitwise: false - scope.$odd = !(scope.$even = (index&1) === 0); - // jshint bitwise: true - }; - - var getBlockStart = function(block) { - return block.clone[0]; - }; - - var getBlockEnd = function(block) { - return block.clone[block.clone.length - 1]; - }; - - - return { - restrict: 'A', - multiElement: true, - transclude: 'element', - priority: 1000, - terminal: true, - $$tlb: true, - compile: function ngRepeatCompile($element, $attr) { - var expression = $attr.ngRepeat; - var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); - - var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); - - if (!match) { - throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", - expression); - } - - var lhs = match[1]; - var rhs = match[2]; - var aliasAs = match[3]; - var trackByExp = match[4]; - - match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); - - if (!match) { - throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", - lhs); - } - var valueIdentifier = match[3] || match[1]; - var keyIdentifier = match[2]; - - if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || - /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { - throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.", - aliasAs); - } - - var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; - var hashFnLocals = {$id: hashKey}; - - if (trackByExp) { - trackByExpGetter = $parse(trackByExp); - } else { - trackByIdArrayFn = function(key, value) { - return hashKey(value); - }; - trackByIdObjFn = function(key) { - return key; - }; - } - - return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { - - if (trackByExpGetter) { - trackByIdExpFn = function(key, value, index) { - // assign key, value, and $index to the locals so that they can be used in hash functions - if (keyIdentifier) hashFnLocals[keyIdentifier] = key; - hashFnLocals[valueIdentifier] = value; - hashFnLocals.$index = index; - return trackByExpGetter($scope, hashFnLocals); - }; - } - - // Store a list of elements from previous run. This is a hash where key is the item from the - // iterator, and the value is objects with following properties. - // - scope: bound scope - // - element: previous element. - // - index: position - // - // We are using no-proto object so that we don't need to guard against inherited props via - // hasOwnProperty. - var lastBlockMap = createMap(); - - //watch props - $scope.$watchCollection(rhs, function ngRepeatAction(collection) { - var index, length, - previousNode = $element[0], // node that cloned nodes should be inserted after - // initialized to the comment node anchor - nextNode, - // Same as lastBlockMap but it has the current state. It will become the - // lastBlockMap on the next iteration. - nextBlockMap = createMap(), - collectionLength, - key, value, // key/value of iteration - trackById, - trackByIdFn, - collectionKeys, - block, // last object information {scope, element, id} - nextBlockOrder, - elementsToRemove; - - if (aliasAs) { - $scope[aliasAs] = collection; - } - - if (isArrayLike(collection)) { - collectionKeys = collection; - trackByIdFn = trackByIdExpFn || trackByIdArrayFn; - } else { - trackByIdFn = trackByIdExpFn || trackByIdObjFn; - // if object, extract keys, in enumeration order, unsorted - collectionKeys = []; - for (var itemKey in collection) { - if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { - collectionKeys.push(itemKey); - } - } - } - - collectionLength = collectionKeys.length; - nextBlockOrder = new Array(collectionLength); - - // locate existing items - for (index = 0; index < collectionLength; index++) { - key = (collection === collectionKeys) ? index : collectionKeys[index]; - value = collection[key]; - trackById = trackByIdFn(key, value, index); - if (lastBlockMap[trackById]) { - // found previously seen block - block = lastBlockMap[trackById]; - delete lastBlockMap[trackById]; - nextBlockMap[trackById] = block; - nextBlockOrder[index] = block; - } else if (nextBlockMap[trackById]) { - // if collision detected. restore lastBlockMap and throw an error - forEach(nextBlockOrder, function(block) { - if (block && block.scope) lastBlockMap[block.id] = block; - }); - throw ngRepeatMinErr('dupes', - "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", - expression, trackById, value); - } else { - // new never before seen block - nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; - nextBlockMap[trackById] = true; - } - } - - // remove leftover items - for (var blockKey in lastBlockMap) { - block = lastBlockMap[blockKey]; - elementsToRemove = getBlockNodes(block.clone); - $animate.leave(elementsToRemove); - if (elementsToRemove[0].parentNode) { - // if the element was not removed yet because of pending animation, mark it as deleted - // so that we can ignore it later - for (index = 0, length = elementsToRemove.length; index < length; index++) { - elementsToRemove[index][NG_REMOVED] = true; - } - } - block.scope.$destroy(); - } - - // we are not using forEach for perf reasons (trying to avoid #call) - for (index = 0; index < collectionLength; index++) { - key = (collection === collectionKeys) ? index : collectionKeys[index]; - value = collection[key]; - block = nextBlockOrder[index]; - - if (block.scope) { - // if we have already seen this object, then we need to reuse the - // associated scope/element - - nextNode = previousNode; - - // skip nodes that are already pending removal via leave animation - do { - nextNode = nextNode.nextSibling; - } while (nextNode && nextNode[NG_REMOVED]); - - if (getBlockStart(block) != nextNode) { - // existing item which got moved - $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); - } - previousNode = getBlockEnd(block); - updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); - } else { - // new item which we don't know about - $transclude(function ngRepeatTransclude(clone, scope) { - block.scope = scope; - // http://jsperf.com/clone-vs-createcomment - var endNode = ngRepeatEndComment.cloneNode(false); - clone[clone.length++] = endNode; - - // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper? - $animate.enter(clone, null, jqLite(previousNode)); - previousNode = endNode; - // Note: We only need the first/last node of the cloned nodes. - // However, we need to keep the reference to the jqlite wrapper as it might be changed later - // by a directive with templateUrl when its template arrives. - block.clone = clone; - nextBlockMap[block.id] = block; - updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); - }); - } - } - lastBlockMap = nextBlockMap; - }); - }; - } - }; -}]; - -var NG_HIDE_CLASS = 'ng-hide'; -var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; -/** - * @ngdoc directive - * @name ngShow - * @multiElement - * - * @description - * The `ngShow` directive shows or hides the given HTML element based on the expression - * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding - * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined - * in AngularJS and sets the display style to none (using an !important flag). - * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). - * - * ```html - * <!-- when $scope.myValue is truthy (element is visible) --> - * <div ng-show="myValue"></div> - * - * <!-- when $scope.myValue is falsy (element is hidden) --> - * <div ng-show="myValue" class="ng-hide"></div> - * ``` - * - * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class - * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed - * from the element causing the element not to appear hidden. - * - * ## Why is !important used? - * - * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector - * can be easily overridden by heavier selectors. For example, something as simple - * as changing the display style on a HTML list item would make hidden elements appear visible. - * This also becomes a bigger issue when dealing with CSS frameworks. - * - * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector - * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the - * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. - * - * ### Overriding `.ng-hide` - * - * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change - * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` - * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope - * with extra animation classes that can be added. - * - * ```css - * .ng-hide:not(.ng-hide-animate) { - * /* this is just another form of hiding an element */ - * display: block!important; - * position: absolute; - * top: -9999px; - * left: -9999px; - * } - * ``` - * - * By default you don't need to override in CSS anything and the animations will work around the display style. - * - * ## A note about animations with `ngShow` - * - * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression - * is true and false. This system works like the animation system present with ngClass except that - * you must also include the !important flag to override the display property - * so that you can perform an animation when the element is hidden during the time of the animation. - * - * ```css - * // - * //a working example can be found at the bottom of this page - * // - * .my-element.ng-hide-add, .my-element.ng-hide-remove { - * /* this is required as of 1.3x to properly - * apply all styling in a show/hide animation */ - * transition: 0s linear all; - * } - * - * .my-element.ng-hide-add-active, - * .my-element.ng-hide-remove-active { - * /* the transition is defined in the active class */ - * transition: 1s linear all; - * } - * - * .my-element.ng-hide-add { ... } - * .my-element.ng-hide-add.ng-hide-add-active { ... } - * .my-element.ng-hide-remove { ... } - * .my-element.ng-hide-remove.ng-hide-remove-active { ... } - * ``` - * - * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display - * property to block during animation states--ngAnimate will handle the style toggling automatically for you. - * - * @animations - * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible - * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden - * - * @element ANY - * @param {expression} ngShow If the {@link guide/expression expression} is truthy - * then the element is shown or hidden respectively. - * - * @example - <example module="ngAnimate" deps="angular-animate.js" animations="true"> - <file name="index.html"> - Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/> - <div> - Show: - <div class="check-element animate-show" ng-show="checked"> - <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. - </div> - </div> - <div> - Hide: - <div class="check-element animate-show" ng-hide="checked"> - <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. - </div> - </div> - </file> - <file name="glyphicons.css"> - @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); - </file> - <file name="animations.css"> - .animate-show { - line-height: 20px; - opacity: 1; - padding: 10px; - border: 1px solid black; - background: white; - } - - .animate-show.ng-hide-add, .animate-show.ng-hide-remove { - transition: all linear 0.5s; - } - - .animate-show.ng-hide { - line-height: 0; - opacity: 0; - padding: 0 10px; - } - - .check-element { - padding: 10px; - border: 1px solid black; - background: white; - } - </file> - <file name="protractor.js" type="protractor"> - var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); - var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); - - it('should check ng-show / ng-hide', function() { - expect(thumbsUp.isDisplayed()).toBeFalsy(); - expect(thumbsDown.isDisplayed()).toBeTruthy(); - - element(by.model('checked')).click(); - - expect(thumbsUp.isDisplayed()).toBeTruthy(); - expect(thumbsDown.isDisplayed()).toBeFalsy(); - }); - </file> - </example> - */ -var ngShowDirective = ['$animate', function($animate) { - return { - restrict: 'A', - multiElement: true, - link: function(scope, element, attr) { - scope.$watch(attr.ngShow, function ngShowWatchAction(value) { - // we're adding a temporary, animation-specific class for ng-hide since this way - // we can control when the element is actually displayed on screen without having - // to have a global/greedy CSS selector that breaks when other animations are run. - // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 - $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { - tempClasses: NG_HIDE_IN_PROGRESS_CLASS - }); - }); - } - }; -}]; - - -/** - * @ngdoc directive - * @name ngHide - * @multiElement - * - * @description - * The `ngHide` directive shows or hides the given HTML element based on the expression - * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding - * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined - * in AngularJS and sets the display style to none (using an !important flag). - * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). - * - * ```html - * <!-- when $scope.myValue is truthy (element is hidden) --> - * <div ng-hide="myValue" class="ng-hide"></div> - * - * <!-- when $scope.myValue is falsy (element is visible) --> - * <div ng-hide="myValue"></div> - * ``` - * - * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class - * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed - * from the element causing the element not to appear hidden. - * - * ## Why is !important used? - * - * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector - * can be easily overridden by heavier selectors. For example, something as simple - * as changing the display style on a HTML list item would make hidden elements appear visible. - * This also becomes a bigger issue when dealing with CSS frameworks. - * - * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector - * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the - * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. - * - * ### Overriding `.ng-hide` - * - * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change - * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` - * class in CSS: - * - * ```css - * .ng-hide { - * /* this is just another form of hiding an element */ - * display: block!important; - * position: absolute; - * top: -9999px; - * left: -9999px; - * } - * ``` - * - * By default you don't need to override in CSS anything and the animations will work around the display style. - * - * ## A note about animations with `ngHide` - * - * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression - * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` - * CSS class is added and removed for you instead of your own CSS class. - * - * ```css - * // - * //a working example can be found at the bottom of this page - * // - * .my-element.ng-hide-add, .my-element.ng-hide-remove { - * transition: 0.5s linear all; - * } - * - * .my-element.ng-hide-add { ... } - * .my-element.ng-hide-add.ng-hide-add-active { ... } - * .my-element.ng-hide-remove { ... } - * .my-element.ng-hide-remove.ng-hide-remove-active { ... } - * ``` - * - * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display - * property to block during animation states--ngAnimate will handle the style toggling automatically for you. - * - * @animations - * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden - * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible - * - * @element ANY - * @param {expression} ngHide If the {@link guide/expression expression} is truthy then - * the element is shown or hidden respectively. - * - * @example - <example module="ngAnimate" deps="angular-animate.js" animations="true"> - <file name="index.html"> - Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/> - <div> - Show: - <div class="check-element animate-hide" ng-show="checked"> - <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. - </div> - </div> - <div> - Hide: - <div class="check-element animate-hide" ng-hide="checked"> - <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. - </div> - </div> - </file> - <file name="glyphicons.css"> - @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); - </file> - <file name="animations.css"> - .animate-hide { - transition: all linear 0.5s; - line-height: 20px; - opacity: 1; - padding: 10px; - border: 1px solid black; - background: white; - } - - .animate-hide.ng-hide { - line-height: 0; - opacity: 0; - padding: 0 10px; - } - - .check-element { - padding: 10px; - border: 1px solid black; - background: white; - } - </file> - <file name="protractor.js" type="protractor"> - var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); - var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); - - it('should check ng-show / ng-hide', function() { - expect(thumbsUp.isDisplayed()).toBeFalsy(); - expect(thumbsDown.isDisplayed()).toBeTruthy(); - - element(by.model('checked')).click(); - - expect(thumbsUp.isDisplayed()).toBeTruthy(); - expect(thumbsDown.isDisplayed()).toBeFalsy(); - }); - </file> - </example> - */ -var ngHideDirective = ['$animate', function($animate) { - return { - restrict: 'A', - multiElement: true, - link: function(scope, element, attr) { - scope.$watch(attr.ngHide, function ngHideWatchAction(value) { - // The comment inside of the ngShowDirective explains why we add and - // remove a temporary class for the show/hide animation - $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { - tempClasses: NG_HIDE_IN_PROGRESS_CLASS - }); - }); - } - }; -}]; - -/** - * @ngdoc directive - * @name ngStyle - * @restrict AC - * - * @description - * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. - * - * @element ANY - * @param {expression} ngStyle - * - * {@link guide/expression Expression} which evals to an - * object whose keys are CSS style names and values are corresponding values for those CSS - * keys. - * - * Since some CSS style names are not valid keys for an object, they must be quoted. - * See the 'background-color' style in the example below. - * - * @example - <example> - <file name="index.html"> - <input type="button" value="set color" ng-click="myStyle={color:'red'}"> - <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> - <input type="button" value="clear" ng-click="myStyle={}"> - <br/> - <span ng-style="myStyle">Sample Text</span> - <pre>myStyle={{myStyle}}</pre> - </file> - <file name="style.css"> - span { - color: black; - } - </file> - <file name="protractor.js" type="protractor"> - var colorSpan = element(by.css('span')); - - it('should check ng-style', function() { - expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); - element(by.css('input[value=\'set color\']')).click(); - expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); - element(by.css('input[value=clear]')).click(); - expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); - }); - </file> - </example> - */ -var ngStyleDirective = ngDirective(function(scope, element, attr) { - scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { - if (oldStyles && (newStyles !== oldStyles)) { - forEach(oldStyles, function(val, style) { element.css(style, '');}); - } - if (newStyles) element.css(newStyles); - }, true); -}); - -/** - * @ngdoc directive - * @name ngSwitch - * @restrict EA - * - * @description - * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. - * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location - * as specified in the template. - * - * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it - * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element - * matches the value obtained from the evaluated expression. In other words, you define a container element - * (where you place the directive), place an expression on the **`on="..."` attribute** - * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place - * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on - * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default - * attribute is displayed. - * - * <div class="alert alert-info"> - * Be aware that the attribute values to match against cannot be expressions. They are interpreted - * as literal string values to match against. - * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the - * value of the expression `$scope.someVal`. - * </div> - - * @animations - * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container - * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM - * - * @usage - * - * ``` - * <ANY ng-switch="expression"> - * <ANY ng-switch-when="matchValue1">...</ANY> - * <ANY ng-switch-when="matchValue2">...</ANY> - * <ANY ng-switch-default>...</ANY> - * </ANY> - * ``` - * - * - * @scope - * @priority 1200 - * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. - * On child elements add: - * - * * `ngSwitchWhen`: the case statement to match against. If match then this - * case will be displayed. If the same match appears multiple times, all the - * elements will be displayed. - * * `ngSwitchDefault`: the default case when no other case match. If there - * are multiple default cases, all of them will be displayed when no other - * case match. - * - * - * @example - <example module="switchExample" deps="angular-animate.js" animations="true"> - <file name="index.html"> - <div ng-controller="ExampleController"> - <select ng-model="selection" ng-options="item for item in items"> - </select> - <code>selection={{selection}}</code> - <hr/> - <div class="animate-switch-container" - ng-switch on="selection"> - <div class="animate-switch" ng-switch-when="settings">Settings Div</div> - <div class="animate-switch" ng-switch-when="home">Home Span</div> - <div class="animate-switch" ng-switch-default>default</div> - </div> - </div> - </file> - <file name="script.js"> - angular.module('switchExample', ['ngAnimate']) - .controller('ExampleController', ['$scope', function($scope) { - $scope.items = ['settings', 'home', 'other']; - $scope.selection = $scope.items[0]; - }]); - </file> - <file name="animations.css"> - .animate-switch-container { - position:relative; - background:white; - border:1px solid black; - height:40px; - overflow:hidden; - } - - .animate-switch { - padding:10px; - } - - .animate-switch.ng-animate { - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - - position:absolute; - top:0; - left:0; - right:0; - bottom:0; - } - - .animate-switch.ng-leave.ng-leave-active, - .animate-switch.ng-enter { - top:-50px; - } - .animate-switch.ng-leave, - .animate-switch.ng-enter.ng-enter-active { - top:0; - } - </file> - <file name="protractor.js" type="protractor"> - var switchElem = element(by.css('[ng-switch]')); - var select = element(by.model('selection')); - - it('should start in settings', function() { - expect(switchElem.getText()).toMatch(/Settings Div/); - }); - it('should change to home', function() { - select.all(by.css('option')).get(1).click(); - expect(switchElem.getText()).toMatch(/Home Span/); - }); - it('should select default', function() { - select.all(by.css('option')).get(2).click(); - expect(switchElem.getText()).toMatch(/default/); - }); - </file> - </example> - */ -var ngSwitchDirective = ['$animate', function($animate) { - return { - require: 'ngSwitch', - - // asks for $scope to fool the BC controller module - controller: ['$scope', function ngSwitchController() { - this.cases = {}; - }], - link: function(scope, element, attr, ngSwitchController) { - var watchExpr = attr.ngSwitch || attr.on, - selectedTranscludes = [], - selectedElements = [], - previousLeaveAnimations = [], - selectedScopes = []; - - var spliceFactory = function(array, index) { - return function() { array.splice(index, 1); }; - }; - - scope.$watch(watchExpr, function ngSwitchWatchAction(value) { - var i, ii; - for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { - $animate.cancel(previousLeaveAnimations[i]); - } - previousLeaveAnimations.length = 0; - - for (i = 0, ii = selectedScopes.length; i < ii; ++i) { - var selected = getBlockNodes(selectedElements[i].clone); - selectedScopes[i].$destroy(); - var promise = previousLeaveAnimations[i] = $animate.leave(selected); - promise.then(spliceFactory(previousLeaveAnimations, i)); - } - - selectedElements.length = 0; - selectedScopes.length = 0; - - if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { - forEach(selectedTranscludes, function(selectedTransclude) { - selectedTransclude.transclude(function(caseElement, selectedScope) { - selectedScopes.push(selectedScope); - var anchor = selectedTransclude.element; - caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: '); - var block = { clone: caseElement }; - - selectedElements.push(block); - $animate.enter(caseElement, anchor.parent(), anchor); - }); - }); - } - }); - } - }; -}]; - -var ngSwitchWhenDirective = ngDirective({ - transclude: 'element', - priority: 1200, - require: '^ngSwitch', - multiElement: true, - link: function(scope, element, attrs, ctrl, $transclude) { - ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); - ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); - } -}); - -var ngSwitchDefaultDirective = ngDirective({ - transclude: 'element', - priority: 1200, - require: '^ngSwitch', - multiElement: true, - link: function(scope, element, attr, ctrl, $transclude) { - ctrl.cases['?'] = (ctrl.cases['?'] || []); - ctrl.cases['?'].push({ transclude: $transclude, element: element }); - } -}); - -/** - * @ngdoc directive - * @name ngTransclude - * @restrict EAC - * - * @description - * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. - * - * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. - * - * @element ANY - * - * @example - <example module="transcludeExample"> - <file name="index.html"> - <script> - angular.module('transcludeExample', []) - .directive('pane', function(){ - return { - restrict: 'E', - transclude: true, - scope: { title:'@' }, - template: '<div style="border: 1px solid black;">' + - '<div style="background-color: gray">{{title}}</div>' + - '<ng-transclude></ng-transclude>' + - '</div>' - }; - }) - .controller('ExampleController', ['$scope', function($scope) { - $scope.title = 'Lorem Ipsum'; - $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; - }]); - </script> - <div ng-controller="ExampleController"> - <input ng-model="title" aria-label="title"> <br/> - <textarea ng-model="text" aria-label="text"></textarea> <br/> - <pane title="{{title}}">{{text}}</pane> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should have transcluded', function() { - var titleElement = element(by.model('title')); - titleElement.clear(); - titleElement.sendKeys('TITLE'); - var textElement = element(by.model('text')); - textElement.clear(); - textElement.sendKeys('TEXT'); - expect(element(by.binding('title')).getText()).toEqual('TITLE'); - expect(element(by.binding('text')).getText()).toEqual('TEXT'); - }); - </file> - </example> - * - */ -var ngTranscludeDirective = ngDirective({ - restrict: 'EAC', - link: function($scope, $element, $attrs, controller, $transclude) { - if (!$transclude) { - throw minErr('ngTransclude')('orphan', - 'Illegal use of ngTransclude directive in the template! ' + - 'No parent directive that requires a transclusion found. ' + - 'Element: {0}', - startingTag($element)); - } - - $transclude(function(clone) { - $element.empty(); - $element.append(clone); - }); - } -}); - -/** - * @ngdoc directive - * @name script - * @restrict E - * - * @description - * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the - * template can be used by {@link ng.directive:ngInclude `ngInclude`}, - * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the - * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be - * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. - * - * @param {string} type Must be set to `'text/ng-template'`. - * @param {string} id Cache name of the template. - * - * @example - <example> - <file name="index.html"> - <script type="text/ng-template" id="/tpl.html"> - Content of the template. - </script> - - <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> - <div id="tpl-content" ng-include src="currentTpl"></div> - </file> - <file name="protractor.js" type="protractor"> - it('should load template defined inside script tag', function() { - element(by.css('#tpl-link')).click(); - expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); - }); - </file> - </example> - */ -var scriptDirective = ['$templateCache', function($templateCache) { - return { - restrict: 'E', - terminal: true, - compile: function(element, attr) { - if (attr.type == 'text/ng-template') { - var templateUrl = attr.id, - text = element[0].text; - - $templateCache.put(templateUrl, text); - } - } - }; -}]; - -var noopNgModelController = { $setViewValue: noop, $render: noop }; - -/** - * @ngdoc type - * @name select.SelectController - * @description - * The controller for the `<select>` directive. This provides support for reading - * and writing the selected value(s) of the control and also coordinates dynamically - * added `<option>` elements, perhaps by an `ngRepeat` directive. - */ -var SelectController = - ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { - - var self = this, - optionsMap = new HashMap(); - - // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors - self.ngModelCtrl = noopNgModelController; - - // The "unknown" option is one that is prepended to the list if the viewValue - // does not match any of the options. When it is rendered the value of the unknown - // option is '? XXX ?' where XXX is the hashKey of the value that is not known. - // - // We can't just jqLite('<option>') since jqLite is not smart enough - // to create it in <select> and IE barfs otherwise. - self.unknownOption = jqLite(document.createElement('option')); - self.renderUnknownOption = function(val) { - var unknownVal = '? ' + hashKey(val) + ' ?'; - self.unknownOption.val(unknownVal); - $element.prepend(self.unknownOption); - $element.val(unknownVal); - }; - - $scope.$on('$destroy', function() { - // disable unknown option so that we don't do work when the whole select is being destroyed - self.renderUnknownOption = noop; - }); - - self.removeUnknownOption = function() { - if (self.unknownOption.parent()) self.unknownOption.remove(); - }; - - - // Read the value of the select control, the implementation of this changes depending - // upon whether the select can have multiple values and whether ngOptions is at work. - self.readValue = function readSingleValue() { - self.removeUnknownOption(); - return $element.val(); - }; - - - // Write the value to the select control, the implementation of this changes depending - // upon whether the select can have multiple values and whether ngOptions is at work. - self.writeValue = function writeSingleValue(value) { - if (self.hasOption(value)) { - self.removeUnknownOption(); - $element.val(value); - if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy - } else { - if (value == null && self.emptyOption) { - self.removeUnknownOption(); - $element.val(''); - } else { - self.renderUnknownOption(value); - } - } - }; - - - // Tell the select control that an option, with the given value, has been added - self.addOption = function(value, element) { - assertNotHasOwnProperty(value, '"option value"'); - if (value === '') { - self.emptyOption = element; - } - var count = optionsMap.get(value) || 0; - optionsMap.put(value, count + 1); - }; - - // Tell the select control that an option, with the given value, has been removed - self.removeOption = function(value) { - var count = optionsMap.get(value); - if (count) { - if (count === 1) { - optionsMap.remove(value); - if (value === '') { - self.emptyOption = undefined; - } - } else { - optionsMap.put(value, count - 1); - } - } - }; - - // Check whether the select control has an option matching the given value - self.hasOption = function(value) { - return !!optionsMap.get(value); - }; -}]; - -/** - * @ngdoc directive - * @name select - * @restrict E - * - * @description - * HTML `SELECT` element with angular data-binding. - * - * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding - * between the scope and the `<select>` control (including setting default values). - * Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or - * {@link ngOptions `ngOptions`} directives. - * - * When an item in the `<select>` menu is selected, the value of the selected option will be bound - * to the model identified by the `ngModel` directive. With static or repeated options, this is - * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing. - * If you want dynamic value attributes, you can use interpolation inside the value attribute. - * - * <div class="alert alert-warning"> - * Note that the value of a `select` directive used without `ngOptions` is always a string. - * When the model needs to be bound to a non-string value, you must either explictly convert it - * using a directive (see example below) or use `ngOptions` to specify the set of options. - * This is because an option element can only be bound to string values at present. - * </div> - * - * If the viewValue of `ngModel` does not match any of the options, then the control - * will automatically add an "unknown" option, which it then removes when the mismatch is resolved. - * - * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can - * be nested into the `<select>` element. This element will then represent the `null` or "not selected" - * option. See example below for demonstration. - * - * <div class="alert alert-info"> - * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions - * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as - * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the - * comprehension expression, and additionally in reducing memory and increasing speed by not creating - * a new scope for each repeated instance. - * </div> - * - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds required attribute and required validation constraint to - * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required - * when you want to data-bind to the required attribute. - * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user - * interaction with the select element. - * @param {string=} ngOptions sets the options that the select is populated with and defines what is - * set on the model on selection. See {@link ngOptions `ngOptions`}. - * - * @example - * ### Simple `select` elements with static options - * - * <example name="static-select" module="staticSelect"> - * <file name="index.html"> - * <div ng-controller="ExampleController"> - * <form name="myForm"> - * <label for="singleSelect"> Single select: </label><br> - * <select name="singleSelect" ng-model="data.singleSelect"> - * <option value="option-1">Option 1</option> - * <option value="option-2">Option 2</option> - * </select><br> - * - * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br> - * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect"> - * <option value="">---Please select---</option> <!-- not selected / blank option --> - * <option value="{{data.option1}}">Option 1</option> <!-- interpolation --> - * <option value="option-2">Option 2</option> - * </select><br> - * <button ng-click="forceUnknownOption()">Force unknown option</button><br> - * <tt>singleSelect = {{data.singleSelect}}</tt> - * - * <hr> - * <label for="multipleSelect"> Multiple select: </label><br> - * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple> - * <option value="option-1">Option 1</option> - * <option value="option-2">Option 2</option> - * <option value="option-3">Option 3</option> - * </select><br> - * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/> - * </form> - * </div> - * </file> - * <file name="app.js"> - * angular.module('staticSelect', []) - * .controller('ExampleController', ['$scope', function($scope) { - * $scope.data = { - * singleSelect: null, - * multipleSelect: [], - * option1: 'option-1', - * }; - * - * $scope.forceUnknownOption = function() { - * $scope.data.singleSelect = 'nonsense'; - * }; - * }]); - * </file> - *</example> - * - * ### Using `ngRepeat` to generate `select` options - * <example name="ngrepeat-select" module="ngrepeatSelect"> - * <file name="index.html"> - * <div ng-controller="ExampleController"> - * <form name="myForm"> - * <label for="repeatSelect"> Repeat select: </label> - * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect"> - * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option> - * </select> - * </form> - * <hr> - * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/> - * </div> - * </file> - * <file name="app.js"> - * angular.module('ngrepeatSelect', []) - * .controller('ExampleController', ['$scope', function($scope) { - * $scope.data = { - * repeatSelect: null, - * availableOptions: [ - * {id: '1', name: 'Option A'}, - * {id: '2', name: 'Option B'}, - * {id: '3', name: 'Option C'} - * ], - * }; - * }]); - * </file> - *</example> - * - * - * ### Using `select` with `ngOptions` and setting a default value - * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. - * - * <example name="select-with-default-values" module="defaultValueSelect"> - * <file name="index.html"> - * <div ng-controller="ExampleController"> - * <form name="myForm"> - * <label for="mySelect">Make a choice:</label> - * <select name="mySelect" id="mySelect" - * ng-options="option.name for option in data.availableOptions track by option.id" - * ng-model="data.selectedOption"></select> - * </form> - * <hr> - * <tt>option = {{data.selectedOption}}</tt><br/> - * </div> - * </file> - * <file name="app.js"> - * angular.module('defaultValueSelect', []) - * .controller('ExampleController', ['$scope', function($scope) { - * $scope.data = { - * availableOptions: [ - * {id: '1', name: 'Option A'}, - * {id: '2', name: 'Option B'}, - * {id: '3', name: 'Option C'} - * ], - * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui - * }; - * }]); - * </file> - *</example> - * - * - * ### Binding `select` to a non-string value via `ngModel` parsing / formatting - * - * <example name="select-with-non-string-options" module="nonStringSelect"> - * <file name="index.html"> - * <select ng-model="model.id" convert-to-number> - * <option value="0">Zero</option> - * <option value="1">One</option> - * <option value="2">Two</option> - * </select> - * {{ model }} - * </file> - * <file name="app.js"> - * angular.module('nonStringSelect', []) - * .run(function($rootScope) { - * $rootScope.model = { id: 2 }; - * }) - * .directive('convertToNumber', function() { - * return { - * require: 'ngModel', - * link: function(scope, element, attrs, ngModel) { - * ngModel.$parsers.push(function(val) { - * return parseInt(val, 10); - * }); - * ngModel.$formatters.push(function(val) { - * return '' + val; - * }); - * } - * }; - * }); - * </file> - * <file name="protractor.js" type="protractor"> - * it('should initialize to model', function() { - * var select = element(by.css('select')); - * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); - * }); - * </file> - * </example> - * - */ -var selectDirective = function() { - - return { - restrict: 'E', - require: ['select', '?ngModel'], - controller: SelectController, - link: function(scope, element, attr, ctrls) { - - // if ngModel is not defined, we don't need to do anything - var ngModelCtrl = ctrls[1]; - if (!ngModelCtrl) return; - - var selectCtrl = ctrls[0]; - - selectCtrl.ngModelCtrl = ngModelCtrl; - - // We delegate rendering to the `writeValue` method, which can be changed - // if the select can have multiple selected values or if the options are being - // generated by `ngOptions` - ngModelCtrl.$render = function() { - selectCtrl.writeValue(ngModelCtrl.$viewValue); - }; - - // When the selected item(s) changes we delegate getting the value of the select control - // to the `readValue` method, which can be changed if the select can have multiple - // selected values or if the options are being generated by `ngOptions` - element.on('change', function() { - scope.$apply(function() { - ngModelCtrl.$setViewValue(selectCtrl.readValue()); - }); - }); - - // If the select allows multiple values then we need to modify how we read and write - // values from and to the control; also what it means for the value to be empty and - // we have to add an extra watch since ngModel doesn't work well with arrays - it - // doesn't trigger rendering if only an item in the array changes. - if (attr.multiple) { - - // Read value now needs to check each option to see if it is selected - selectCtrl.readValue = function readMultipleValue() { - var array = []; - forEach(element.find('option'), function(option) { - if (option.selected) { - array.push(option.value); - } - }); - return array; - }; - - // Write value now needs to set the selected property of each matching option - selectCtrl.writeValue = function writeMultipleValue(value) { - var items = new HashMap(value); - forEach(element.find('option'), function(option) { - option.selected = isDefined(items.get(option.value)); - }); - }; - - // we have to do it on each watch since ngModel watches reference, but - // we need to work of an array, so we need to see if anything was inserted/removed - var lastView, lastViewRef = NaN; - scope.$watch(function selectMultipleWatch() { - if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { - lastView = shallowCopy(ngModelCtrl.$viewValue); - ngModelCtrl.$render(); - } - lastViewRef = ngModelCtrl.$viewValue; - }); - - // If we are a multiple select then value is now a collection - // so the meaning of $isEmpty changes - ngModelCtrl.$isEmpty = function(value) { - return !value || value.length === 0; - }; - - } - } - }; -}; - - -// The option directive is purely designed to communicate the existence (or lack of) -// of dynamically created (and destroyed) option elements to their containing select -// directive via its controller. -var optionDirective = ['$interpolate', function($interpolate) { - - function chromeHack(optionElement) { - // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 - // Adding an <option selected="selected"> element to a <select required="required"> should - // automatically select the new element - if (optionElement[0].hasAttribute('selected')) { - optionElement[0].selected = true; - } - } - - return { - restrict: 'E', - priority: 100, - compile: function(element, attr) { - - if (isDefined(attr.value)) { - // If the value attribute is defined, check if it contains an interpolation - var valueInterpolated = $interpolate(attr.value, true); - } else { - // If the value attribute is not defined then we fall back to the - // text content of the option element, which may be interpolated - var interpolateFn = $interpolate(element.text(), true); - if (!interpolateFn) { - attr.$set('value', element.text()); - } - } - - return function(scope, element, attr) { - - // This is an optimization over using ^^ since we don't want to have to search - // all the way to the root of the DOM for every single option element - var selectCtrlName = '$selectController', - parent = element.parent(), - selectCtrl = parent.data(selectCtrlName) || - parent.parent().data(selectCtrlName); // in case we are in optgroup - - function addOption(optionValue) { - selectCtrl.addOption(optionValue, element); - selectCtrl.ngModelCtrl.$render(); - chromeHack(element); - } - - // Only update trigger option updates if this is an option within a `select` - // that also has `ngModel` attached - if (selectCtrl && selectCtrl.ngModelCtrl) { - - if (valueInterpolated) { - // The value attribute is interpolated - var oldVal; - attr.$observe('value', function valueAttributeObserveAction(newVal) { - if (isDefined(oldVal)) { - selectCtrl.removeOption(oldVal); - } - oldVal = newVal; - addOption(newVal); - }); - } else if (interpolateFn) { - // The text content is interpolated - scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { - attr.$set('value', newVal); - if (oldVal !== newVal) { - selectCtrl.removeOption(oldVal); - } - addOption(newVal); - }); - } else { - // The value attribute is static - addOption(attr.value); - } - - element.on('$destroy', function() { - selectCtrl.removeOption(attr.value); - selectCtrl.ngModelCtrl.$render(); - }); - } - }; - } - }; -}]; - -var styleDirective = valueFn({ - restrict: 'E', - terminal: false -}); - -var requiredDirective = function() { - return { - restrict: 'A', - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - attr.required = true; // force truthy in case we are on non input element - - ctrl.$validators.required = function(modelValue, viewValue) { - return !attr.required || !ctrl.$isEmpty(viewValue); - }; - - attr.$observe('required', function() { - ctrl.$validate(); - }); - } - }; -}; - - -var patternDirective = function() { - return { - restrict: 'A', - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - - var regexp, patternExp = attr.ngPattern || attr.pattern; - attr.$observe('pattern', function(regex) { - if (isString(regex) && regex.length > 0) { - regex = new RegExp('^' + regex + '$'); - } - - if (regex && !regex.test) { - throw minErr('ngPattern')('noregexp', - 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, - regex, startingTag(elm)); - } - - regexp = regex || undefined; - ctrl.$validate(); - }); - - ctrl.$validators.pattern = function(modelValue, viewValue) { - // HTML5 pattern constraint validates the input value, so we validate the viewValue - return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); - }; - } - }; -}; - - -var maxlengthDirective = function() { - return { - restrict: 'A', - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - - var maxlength = -1; - attr.$observe('maxlength', function(value) { - var intVal = toInt(value); - maxlength = isNaN(intVal) ? -1 : intVal; - ctrl.$validate(); - }); - ctrl.$validators.maxlength = function(modelValue, viewValue) { - return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); - }; - } - }; -}; - -var minlengthDirective = function() { - return { - restrict: 'A', - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - - var minlength = 0; - attr.$observe('minlength', function(value) { - minlength = toInt(value) || 0; - ctrl.$validate(); - }); - ctrl.$validators.minlength = function(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; - }; - } - }; -}; - -if (window.angular.bootstrap) { - //AngularJS is already loaded, so we can return here... - console.log('WARNING: Tried to load angular more than once.'); - return; -} - -//try to bind to jquery now so that one can write jqLite(document).ready() -//but we will rebind on bootstrap again. -bindJQuery(); - -publishExternalAPI(angular); - -angular.module("ngLocale", [], ["$provide", function($provide) { -var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; -function getDecimals(n) { - n = n + ''; - var i = n.indexOf('.'); - return (i == -1) ? 0 : n.length - i - 1; -} - -function getVF(n, opt_precision) { - var v = opt_precision; - - if (undefined === v) { - v = Math.min(getDecimals(n), 3); - } - - var base = Math.pow(10, v); - var f = ((n * base) | 0) % base; - return {v: v, f: f}; -} - -$provide.value("$locale", { - "DATETIME_FORMATS": { - "AMPMS": [ - "AM", - "PM" - ], - "DAY": [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday" - ], - "ERANAMES": [ - "Before Christ", - "Anno Domini" - ], - "ERAS": [ - "BC", - "AD" - ], - "FIRSTDAYOFWEEK": 6, - "MONTH": [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December" - ], - "SHORTDAY": [ - "Sun", - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat" - ], - "SHORTMONTH": [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec" - ], - "WEEKENDRANGE": [ - 5, - 6 - ], - "fullDate": "EEEE, MMMM d, y", - "longDate": "MMMM d, y", - "medium": "MMM d, y h:mm:ss a", - "mediumDate": "MMM d, y", - "mediumTime": "h:mm:ss a", - "short": "M/d/yy h:mm a", - "shortDate": "M/d/yy", - "shortTime": "h:mm a" - }, - "NUMBER_FORMATS": { - "CURRENCY_SYM": "$", - "DECIMAL_SEP": ".", - "GROUP_SEP": ",", - "PATTERNS": [ - { - "gSize": 3, - "lgSize": 3, - "maxFrac": 3, - "minFrac": 0, - "minInt": 1, - "negPre": "-", - "negSuf": "", - "posPre": "", - "posSuf": "" - }, - { - "gSize": 3, - "lgSize": 3, - "maxFrac": 2, - "minFrac": 2, - "minInt": 1, - "negPre": "-\u00a4", - "negSuf": "", - "posPre": "\u00a4", - "posSuf": "" - } - ] - }, - "id": "en-us", - "pluralCat": function(n, opt_precision) { var i = n | 0; var vf = getVF(n, opt_precision); if (i == 1 && vf.v == 0) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;} -}); -}]); - - jqLite(document).ready(function() { - angularInit(document, bootstrap); - }); - -})(window, document); - -!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>'); \ No newline at end of file diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.min.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.min.js index 272101ec7cb..b10e3e97769 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.min.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.min.js @@ -1,294 +1,314 @@ /* - AngularJS v1.4.7 - (c) 2010-2015 Google, Inc. http://angularjs.org + AngularJS v1.5.5 + (c) 2010-2016 Google, Inc. http://angularjs.org License: MIT */ -(function(Q,X,w){'use strict';function I(b){return function(){var a=arguments[0],c;c="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.4.7/"+(b?b+"/":"")+a;for(a=1;a<arguments.length;a++){c=c+(1==a?"?":"&")+"p"+(a-1)+"=";var d=encodeURIComponent,e;e=arguments[a];e="function"==typeof e?e.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof e?"undefined":"string"!=typeof e?JSON.stringify(e):e;c+=d(e)}return Error(c)}}function Da(b){if(null==b||Za(b))return!1;var a="length"in Object(b)&&b.length; -return b.nodeType===pa&&a?!0:G(b)||J(b)||0===a||"number"===typeof a&&0<a&&a-1 in b}function m(b,a,c){var d,e;if(b)if(x(b))for(d in b)"prototype"==d||"length"==d||"name"==d||b.hasOwnProperty&&!b.hasOwnProperty(d)||a.call(c,b[d],d,b);else if(J(b)||Da(b)){var f="object"!==typeof b;d=0;for(e=b.length;d<e;d++)(f||d in b)&&a.call(c,b[d],d,b)}else if(b.forEach&&b.forEach!==m)b.forEach(a,c,b);else if(mc(b))for(d in b)a.call(c,b[d],d,b);else if("function"===typeof b.hasOwnProperty)for(d in b)b.hasOwnProperty(d)&& -a.call(c,b[d],d,b);else for(d in b)ta.call(b,d)&&a.call(c,b[d],d,b);return b}function nc(b,a,c){for(var d=Object.keys(b).sort(),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function oc(b){return function(a,c){b(c,a)}}function Ud(){return++nb}function pc(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function Mb(b,a,c){for(var d=b.$$hashKey,e=0,f=a.length;e<f;++e){var h=a[e];if(C(h)||x(h))for(var g=Object.keys(h),l=0,k=g.length;l<k;l++){var n=g[l],p=h[n];c&&C(p)?ea(p)?b[n]=new Date(p.valueOf()):Oa(p)? -b[n]=new RegExp(p):(C(b[n])||(b[n]=J(p)?[]:{}),Mb(b[n],[p],!0)):b[n]=p}}pc(b,d);return b}function P(b){return Mb(b,ua.call(arguments,1),!1)}function Vd(b){return Mb(b,ua.call(arguments,1),!0)}function Y(b){return parseInt(b,10)}function Nb(b,a){return P(Object.create(b),a)}function y(){}function $a(b){return b}function qa(b){return function(){return b}}function qc(b){return x(b.toString)&&b.toString!==Object.prototype.toString}function v(b){return"undefined"===typeof b}function A(b){return"undefined"!== -typeof b}function C(b){return null!==b&&"object"===typeof b}function mc(b){return null!==b&&"object"===typeof b&&!rc(b)}function G(b){return"string"===typeof b}function V(b){return"number"===typeof b}function ea(b){return"[object Date]"===va.call(b)}function x(b){return"function"===typeof b}function Oa(b){return"[object RegExp]"===va.call(b)}function Za(b){return b&&b.window===b}function ab(b){return b&&b.$evalAsync&&b.$watch}function bb(b){return"boolean"===typeof b}function sc(b){return!(!b||!(b.nodeName|| -b.prop&&b.attr&&b.find))}function Wd(b){var a={};b=b.split(",");var c;for(c=0;c<b.length;c++)a[b[c]]=!0;return a}function wa(b){return F(b.nodeName||b[0]&&b[0].nodeName)}function cb(b,a){var c=b.indexOf(a);0<=c&&b.splice(c,1);return c}function ha(b,a,c,d){if(Za(b)||ab(b))throw Ea("cpws");if(tc.test(va.call(a)))throw Ea("cpta");if(a){if(b===a)throw Ea("cpi");c=c||[];d=d||[];C(b)&&(c.push(b),d.push(a));var e;if(J(b))for(e=a.length=0;e<b.length;e++)a.push(ha(b[e],null,c,d));else{var f=a.$$hashKey;J(a)? -a.length=0:m(a,function(b,c){delete a[c]});if(mc(b))for(e in b)a[e]=ha(b[e],null,c,d);else if(b&&"function"===typeof b.hasOwnProperty)for(e in b)b.hasOwnProperty(e)&&(a[e]=ha(b[e],null,c,d));else for(e in b)ta.call(b,e)&&(a[e]=ha(b[e],null,c,d));pc(a,f)}}else if(a=b,C(b)){if(c&&-1!==(f=c.indexOf(b)))return d[f];if(J(b))return ha(b,[],c,d);if(tc.test(va.call(b)))a=new b.constructor(b);else if(ea(b))a=new Date(b.getTime());else if(Oa(b))a=new RegExp(b.source,b.toString().match(/[^\/]*$/)[0]),a.lastIndex= -b.lastIndex;else if(x(b.cloneNode))a=b.cloneNode(!0);else return e=Object.create(rc(b)),ha(b,e,c,d);d&&(c.push(b),d.push(a))}return a}function ja(b,a){if(J(b)){a=a||[];for(var c=0,d=b.length;c<d;c++)a[c]=b[c]}else if(C(b))for(c in a=a||{},b)if("$"!==c.charAt(0)||"$"!==c.charAt(1))a[c]=b[c];return a||b}function ka(b,a){if(b===a)return!0;if(null===b||null===a)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&"object"==c)if(J(b)){if(!J(a))return!1;if((c=b.length)==a.length){for(d=0;d< -c;d++)if(!ka(b[d],a[d]))return!1;return!0}}else{if(ea(b))return ea(a)?ka(b.getTime(),a.getTime()):!1;if(Oa(b))return Oa(a)?b.toString()==a.toString():!1;if(ab(b)||ab(a)||Za(b)||Za(a)||J(a)||ea(a)||Oa(a))return!1;c=fa();for(d in b)if("$"!==d.charAt(0)&&!x(b[d])){if(!ka(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!(d in c)&&"$"!==d.charAt(0)&&A(a[d])&&!x(a[d]))return!1;return!0}return!1}function db(b,a,c){return b.concat(ua.call(a,c))}function uc(b,a){var c=2<arguments.length?ua.call(arguments,2):[]; -return!x(a)||a instanceof RegExp?a:c.length?function(){return arguments.length?a.apply(b,db(c,arguments,0)):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}}function Xd(b,a){var c=a;"string"===typeof b&&"$"===b.charAt(0)&&"$"===b.charAt(1)?c=w:Za(a)?c="$WINDOW":a&&X===a?c="$DOCUMENT":ab(a)&&(c="$SCOPE");return c}function eb(b,a){if("undefined"===typeof b)return w;V(a)||(a=a?2:null);return JSON.stringify(b,Xd,a)}function vc(b){return G(b)?JSON.parse(b):b}function wc(b, -a){var c=Date.parse("Jan 01, 1970 00:00:00 "+b)/6E4;return isNaN(c)?a:c}function Ob(b,a,c){c=c?-1:1;var d=wc(a,b.getTimezoneOffset());a=b;b=c*(d-b.getTimezoneOffset());a=new Date(a.getTime());a.setMinutes(a.getMinutes()+b);return a}function xa(b){b=B(b).clone();try{b.empty()}catch(a){}var c=B("<div>").append(b).html();try{return b[0].nodeType===Pa?F(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+F(b)})}catch(d){return F(c)}}function xc(b){try{return decodeURIComponent(b)}catch(a){}} -function yc(b){var a={};m((b||"").split("&"),function(b){var d,e,f;b&&(e=b=b.replace(/\+/g,"%20"),d=b.indexOf("="),-1!==d&&(e=b.substring(0,d),f=b.substring(d+1)),e=xc(e),A(e)&&(f=A(f)?xc(f):!0,ta.call(a,e)?J(a[e])?a[e].push(f):a[e]=[a[e],f]:a[e]=f))});return a}function Pb(b){var a=[];m(b,function(b,d){J(b)?m(b,function(b){a.push(la(d,!0)+(!0===b?"":"="+la(b,!0)))}):a.push(la(d,!0)+(!0===b?"":"="+la(b,!0)))});return a.length?a.join("&"):""}function ob(b){return la(b,!0).replace(/%26/gi,"&").replace(/%3D/gi, -"=").replace(/%2B/gi,"+")}function la(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,a?"%20":"+")}function Yd(b,a){var c,d,e=Qa.length;for(d=0;d<e;++d)if(c=Qa[d]+a,G(c=b.getAttribute(c)))return c;return null}function Zd(b,a){var c,d,e={};m(Qa,function(a){a+="app";!c&&b.hasAttribute&&b.hasAttribute(a)&&(c=b,d=b.getAttribute(a))});m(Qa,function(a){a+="app";var e;!c&&(e=b.querySelector("["+a.replace(":", -"\\:")+"]"))&&(c=e,d=e.getAttribute(a))});c&&(e.strictDi=null!==Yd(c,"strict-di"),a(c,d?[d]:[],e))}function zc(b,a,c){C(c)||(c={});c=P({strictDi:!1},c);var d=function(){b=B(b);if(b.injector()){var d=b[0]===X?"document":xa(b);throw Ea("btstrpd",d.replace(/</,"<").replace(/>/,">"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);c.debugInfoEnabled&&a.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);a.unshift("ng");d=fb(a,c.strictDi);d.invoke(["$rootScope", -"$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return d},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;Q&&e.test(Q.name)&&(c.debugInfoEnabled=!0,Q.name=Q.name.replace(e,""));if(Q&&!f.test(Q.name))return d();Q.name=Q.name.replace(f,"");da.resumeBootstrap=function(b){m(b,function(b){a.push(b)});return d()};x(da.resumeDeferredBootstrap)&&da.resumeDeferredBootstrap()}function $d(){Q.name="NG_ENABLE_DEBUG_INFO!"+Q.name;Q.location.reload()} -function ae(b){b=da.element(b).injector();if(!b)throw Ea("test");return b.get("$$testability")}function Ac(b,a){a=a||"_";return b.replace(be,function(b,d){return(d?a:"")+b.toLowerCase()})}function ce(){var b;if(!Bc){var a=pb();(ra=v(a)?Q.jQuery:a?Q[a]:w)&&ra.fn.on?(B=ra,P(ra.fn,{scope:Ra.scope,isolateScope:Ra.isolateScope,controller:Ra.controller,injector:Ra.injector,inheritedData:Ra.inheritedData}),b=ra.cleanData,ra.cleanData=function(a){var d;if(Qb)Qb=!1;else for(var e=0,f;null!=(f=a[e]);e++)(d= -ra._data(f,"events"))&&d.$destroy&&ra(f).triggerHandler("$destroy");b(a)}):B=R;da.element=B;Bc=!0}}function qb(b,a,c){if(!b)throw Ea("areq",a||"?",c||"required");return b}function Sa(b,a,c){c&&J(b)&&(b=b[b.length-1]);qb(x(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function Ta(b,a){if("hasOwnProperty"===b)throw Ea("badname",a);}function Cc(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,h=0;h<f;h++)d=a[h],b&&(b=(e=b)[d]);return!c&& -x(b)?uc(e,b):b}function rb(b){for(var a=b[0],c=b[b.length-1],d,e=1;a!==c&&(a=a.nextSibling);e++)if(d||b[e]!==a)d||(d=B(ua.call(b,0,e))),d.push(a);return d||b}function fa(){return Object.create(null)}function de(b){function a(a,b,c){return a[b]||(a[b]=c())}var c=I("$injector"),d=I("ng");b=a(b,"angular",Object);b.$$minErr=b.$$minErr||I;return a(b,"module",function(){var b={};return function(f,h,g){if("hasOwnProperty"===f)throw d("badname","module");h&&b.hasOwnProperty(f)&&(b[f]=null);return a(b,f,function(){function a(b, -c,e,f){f||(f=d);return function(){f[e||"push"]([b,c,arguments]);return E}}function b(a,c){return function(b,e){e&&x(e)&&(e.$$moduleName=f);d.push([a,c,arguments]);return E}}if(!h)throw c("nomod",f);var d=[],e=[],r=[],t=a("$injector","invoke","push",e),E={_invokeQueue:d,_configBlocks:e,_runBlocks:r,requires:h,name:f,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),decorator:b("$provide", -"decorator"),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),config:t,run:function(a){r.push(a);return this}};g&&t(g);return E})}})}function ee(b){P(b,{bootstrap:zc,copy:ha,extend:P,merge:Vd,equals:ka,element:B,forEach:m,injector:fb,noop:y,bind:uc,toJson:eb,fromJson:vc,identity:$a,isUndefined:v,isDefined:A,isString:G,isFunction:x,isObject:C,isNumber:V,isElement:sc,isArray:J, -version:fe,isDate:ea,lowercase:F,uppercase:sb,callbacks:{counter:0},getTestability:ae,$$minErr:I,$$csp:Fa,reloadWithDebugInfo:$d});Rb=de(Q);Rb("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:ge});a.provider("$compile",Dc).directive({a:he,input:Ec,textarea:Ec,form:ie,script:je,select:ke,style:le,option:me,ngBind:ne,ngBindHtml:oe,ngBindTemplate:pe,ngClass:qe,ngClassEven:re,ngClassOdd:se,ngCloak:te,ngController:ue,ngForm:ve,ngHide:we,ngIf:xe,ngInclude:ye,ngInit:ze,ngNonBindable:Ae, -ngPluralize:Be,ngRepeat:Ce,ngShow:De,ngStyle:Ee,ngSwitch:Fe,ngSwitchWhen:Ge,ngSwitchDefault:He,ngOptions:Ie,ngTransclude:Je,ngModel:Ke,ngList:Le,ngChange:Me,pattern:Fc,ngPattern:Fc,required:Gc,ngRequired:Gc,minlength:Hc,ngMinlength:Hc,maxlength:Ic,ngMaxlength:Ic,ngValue:Ne,ngModelOptions:Oe}).directive({ngInclude:Pe}).directive(tb).directive(Jc);a.provider({$anchorScroll:Qe,$animate:Re,$animateCss:Se,$$animateQueue:Te,$$AnimateRunner:Ue,$browser:Ve,$cacheFactory:We,$controller:Xe,$document:Ye,$exceptionHandler:Ze, -$filter:Kc,$$forceReflow:$e,$interpolate:af,$interval:bf,$http:cf,$httpParamSerializer:df,$httpParamSerializerJQLike:ef,$httpBackend:ff,$xhrFactory:gf,$location:hf,$log:jf,$parse:kf,$rootScope:lf,$q:mf,$$q:nf,$sce:of,$sceDelegate:pf,$sniffer:qf,$templateCache:rf,$templateRequest:sf,$$testability:tf,$timeout:uf,$window:vf,$$rAF:wf,$$jqLite:xf,$$HashMap:yf,$$cookieReader:zf})}])}function gb(b){return b.replace(Af,function(a,b,d,e){return e?d.toUpperCase():d}).replace(Bf,"Moz$1")}function Lc(b){b=b.nodeType; -return b===pa||!b||9===b}function Mc(b,a){var c,d,e=a.createDocumentFragment(),f=[];if(Sb.test(b)){c=c||e.appendChild(a.createElement("div"));d=(Cf.exec(b)||["",""])[1].toLowerCase();d=ma[d]||ma._default;c.innerHTML=d[1]+b.replace(Df,"<$1></$2>")+d[2];for(d=d[0];d--;)c=c.lastChild;f=db(f,c.childNodes);c=e.firstChild;c.textContent=""}else f.push(a.createTextNode(b));e.textContent="";e.innerHTML="";m(f,function(a){e.appendChild(a)});return e}function R(b){if(b instanceof R)return b;var a;G(b)&&(b=T(b), -a=!0);if(!(this instanceof R)){if(a&&"<"!=b.charAt(0))throw Tb("nosel");return new R(b)}if(a){a=X;var c;b=(c=Ef.exec(b))?[a.createElement(c[1])]:(c=Mc(b,a))?c.childNodes:[]}Nc(this,b)}function Ub(b){return b.cloneNode(!0)}function ub(b,a){a||vb(b);if(b.querySelectorAll)for(var c=b.querySelectorAll("*"),d=0,e=c.length;d<e;d++)vb(c[d])}function Oc(b,a,c,d){if(A(d))throw Tb("offargs");var e=(d=wb(b))&&d.events,f=d&&d.handle;if(f)if(a)m(a.split(" "),function(a){if(A(c)){var d=e[a];cb(d||[],c);if(d&&0< -d.length)return}b.removeEventListener(a,f,!1);delete e[a]});else for(a in e)"$destroy"!==a&&b.removeEventListener(a,f,!1),delete e[a]}function vb(b,a){var c=b.ng339,d=c&&hb[c];d&&(a?delete d.data[a]:(d.handle&&(d.events.$destroy&&d.handle({},"$destroy"),Oc(b)),delete hb[c],b.ng339=w))}function wb(b,a){var c=b.ng339,c=c&&hb[c];a&&!c&&(b.ng339=c=++Ff,c=hb[c]={events:{},data:{},handle:w});return c}function Vb(b,a,c){if(Lc(b)){var d=A(c),e=!d&&a&&!C(a),f=!a;b=(b=wb(b,!e))&&b.data;if(d)b[a]=c;else{if(f)return b; -if(e)return b&&b[a];P(b,a)}}}function xb(b,a){return b.getAttribute?-1<(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" "):!1}function yb(b,a){a&&b.setAttribute&&m(a.split(" "),function(a){b.setAttribute("class",T((" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+T(a)+" "," ")))})}function zb(b,a){if(a&&b.setAttribute){var c=(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");m(a.split(" "),function(a){a=T(a);-1===c.indexOf(" "+a+" ")&& -(c+=a+" ")});b.setAttribute("class",T(c))}}function Nc(b,a){if(a)if(a.nodeType)b[b.length++]=a;else{var c=a.length;if("number"===typeof c&&a.window!==a){if(c)for(var d=0;d<c;d++)b[b.length++]=a[d]}else b[b.length++]=a}}function Pc(b,a){return Ab(b,"$"+(a||"ngController")+"Controller")}function Ab(b,a,c){9==b.nodeType&&(b=b.documentElement);for(a=J(a)?a:[a];b;){for(var d=0,e=a.length;d<e;d++)if(A(c=B.data(b,a[d])))return c;b=b.parentNode||11===b.nodeType&&b.host}}function Qc(b){for(ub(b,!0);b.firstChild;)b.removeChild(b.firstChild)} -function Wb(b,a){a||ub(b);var c=b.parentNode;c&&c.removeChild(b)}function Gf(b,a){a=a||Q;if("complete"===a.document.readyState)a.setTimeout(b);else B(a).on("load",b)}function Rc(b,a){var c=Bb[a.toLowerCase()];return c&&Sc[wa(b)]&&c}function Hf(b,a){var c=function(c,e){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=a[e||c.type],h=f?f.length:0;if(h){if(v(c.immediatePropagationStopped)){var g=c.stopImmediatePropagation;c.stopImmediatePropagation=function(){c.immediatePropagationStopped= -!0;c.stopPropagation&&c.stopPropagation();g&&g.call(c)}}c.isImmediatePropagationStopped=function(){return!0===c.immediatePropagationStopped};1<h&&(f=ja(f));for(var l=0;l<h;l++)c.isImmediatePropagationStopped()||f[l].call(b,c)}};c.elem=b;return c}function xf(){this.$get=function(){return P(R,{hasClass:function(b,a){b.attr&&(b=b[0]);return xb(b,a)},addClass:function(b,a){b.attr&&(b=b[0]);return zb(b,a)},removeClass:function(b,a){b.attr&&(b=b[0]);return yb(b,a)}})}}function Ga(b,a){var c=b&&b.$$hashKey; -if(c)return"function"===typeof c&&(c=b.$$hashKey()),c;c=typeof b;return c="function"==c||"object"==c&&null!==b?b.$$hashKey=c+":"+(a||Ud)():c+":"+b}function Ua(b,a){if(a){var c=0;this.nextUid=function(){return++c}}m(b,this.put,this)}function If(b){return(b=b.toString().replace(Tc,"").match(Uc))?"function("+(b[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function fb(b,a){function c(a){return function(b,c){if(C(b))m(b,oc(a));else return a(b,c)}}function d(a,b){Ta(a,"service");if(x(b)||J(b))b=r.instantiate(b); -if(!b.$get)throw Ha("pget",a);return p[a+"Provider"]=b}function e(a,b){return function(){var c=E.invoke(b,this);if(v(c))throw Ha("undef",a);return c}}function f(a,b,c){return d(a,{$get:!1!==c?e(a,b):b})}function h(a){qb(v(a)||J(a),"modulesToLoad","not an array");var b=[],c;m(a,function(a){function d(a){var b,c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=r.get(e[0]);f[e[1]].apply(f,e[2])}}if(!n.get(a)){n.put(a,!0);try{G(a)?(c=Rb(a),b=b.concat(h(c.requires)).concat(c._runBlocks),d(c._invokeQueue),d(c._configBlocks)): -x(a)?b.push(r.invoke(a)):J(a)?b.push(r.invoke(a)):Sa(a,"module")}catch(e){throw J(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1==e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),Ha("modulerr",a,e.stack||e.message||e);}}});return b}function g(b,c){function d(a,e){if(b.hasOwnProperty(a)){if(b[a]===l)throw Ha("cdep",a+" <- "+k.join(" <- "));return b[a]}try{return k.unshift(a),b[a]=l,b[a]=c(a,e)}catch(f){throw b[a]===l&&delete b[a],f;}finally{k.shift()}}function e(b,c,f,g){"string"===typeof f&&(g= -f,f=null);var h=[],k=fb.$$annotate(b,a,g),l,r,p;r=0;for(l=k.length;r<l;r++){p=k[r];if("string"!==typeof p)throw Ha("itkn",p);h.push(f&&f.hasOwnProperty(p)?f[p]:d(p,g))}J(b)&&(b=b[l]);return b.apply(c,h)}return{invoke:e,instantiate:function(a,b,c){var d=Object.create((J(a)?a[a.length-1]:a).prototype||null);a=e(a,d,b,c);return C(a)||x(a)?a:d},get:d,annotate:fb.$$annotate,has:function(a){return p.hasOwnProperty(a+"Provider")||b.hasOwnProperty(a)}}}a=!0===a;var l={},k=[],n=new Ua([],!0),p={$provide:{provider:c(d), -factory:c(f),service:c(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:c(function(a,b){return f(a,qa(b),!1)}),constant:c(function(a,b){Ta(a,"constant");p[a]=b;t[a]=b}),decorator:function(a,b){var c=r.get(a+"Provider"),d=c.$get;c.$get=function(){var a=E.invoke(d,c);return E.invoke(b,null,{$delegate:a})}}}},r=p.$injector=g(p,function(a,b){da.isString(b)&&k.push(b);throw Ha("unpr",k.join(" <- "));}),t={},E=t.$injector=g(t,function(a,b){var c=r.get(a+"Provider",b); -return E.invoke(c.$get,c,w,a)});m(h(b),function(a){a&&E.invoke(a)});return E}function Qe(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===wa(a))return b=a,!0});return b}function f(b){if(b){b.scrollIntoView();var c;c=h.yOffset;x(c)?c=c():sc(c)?(c=c[0],c="fixed"!==a.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):V(c)||(c=0);c&&(b=b.getBoundingClientRect().top, -a.scrollBy(0,b-c))}else a.scrollTo(0,0)}function h(a){a=G(a)?a:c.hash();var b;a?(b=g.getElementById(a))?f(b):(b=e(g.getElementsByName(a)))?f(b):"top"===a&&f(null):f(null)}var g=a.document;b&&d.$watch(function(){return c.hash()},function(a,b){a===b&&""===a||Gf(function(){d.$evalAsync(h)})});return h}]}function ib(b,a){if(!b&&!a)return"";if(!b)return a;if(!a)return b;J(b)&&(b=b.join(" "));J(a)&&(a=a.join(" "));return b+" "+a}function Jf(b){G(b)&&(b=b.split(" "));var a=fa();m(b,function(b){b.length&& -(a[b]=!0)});return a}function Ia(b){return C(b)?b:{}}function Kf(b,a,c,d){function e(a){try{a.apply(null,ua.call(arguments,1))}finally{if(E--,0===E)for(;K.length;)try{K.pop()()}catch(b){c.error(b)}}}function f(){ia=null;h();g()}function h(){a:{try{u=n.state;break a}catch(a){}u=void 0}u=v(u)?null:u;ka(u,L)&&(u=L);L=u}function g(){if(z!==l.url()||q!==u)z=l.url(),q=u,m(O,function(a){a(l.url(),u)})}var l=this,k=b.location,n=b.history,p=b.setTimeout,r=b.clearTimeout,t={};l.isMock=!1;var E=0,K=[];l.$$completeOutstandingRequest= -e;l.$$incOutstandingRequestCount=function(){E++};l.notifyWhenNoOutstandingRequests=function(a){0===E?a():K.push(a)};var u,q,z=k.href,N=a.find("base"),ia=null;h();q=u;l.url=function(a,c,e){v(e)&&(e=null);k!==b.location&&(k=b.location);n!==b.history&&(n=b.history);if(a){var f=q===e;if(z===a&&(!d.history||f))return l;var g=z&&Ja(z)===Ja(a);z=a;q=e;if(!d.history||g&&f){if(!g||ia)ia=a;c?k.replace(a):g?(c=k,e=a.indexOf("#"),e=-1===e?"":a.substr(e),c.hash=e):k.href=a;k.href!==a&&(ia=a)}else n[c?"replaceState": -"pushState"](e,"",a),h(),q=u;return l}return ia||k.href.replace(/%27/g,"'")};l.state=function(){return u};var O=[],H=!1,L=null;l.onUrlChange=function(a){if(!H){if(d.history)B(b).on("popstate",f);B(b).on("hashchange",f);H=!0}O.push(a);return a};l.$$applicationDestroyed=function(){B(b).off("hashchange popstate",f)};l.$$checkUrlChange=g;l.baseHref=function(){var a=N.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};l.defer=function(a,b){var c;E++;c=p(function(){delete t[c];e(a)},b||0); -t[c]=!0;return c};l.defer.cancel=function(a){return t[a]?(delete t[a],r(a),e(y),!0):!1}}function Ve(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new Kf(b,d,a,c)}]}function We(){this.$get=function(){function b(b,d){function e(a){a!=p&&(r?r==a&&(r=a.n):r=a,f(a.n,a.p),f(a,p),p=a,p.n=null)}function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(b in a)throw I("$cacheFactory")("iid",b);var h=0,g=P({},d,{id:b}),l={},k=d&&d.capacity||Number.MAX_VALUE,n={},p=null,r=null;return a[b]= -{put:function(a,b){if(!v(b)){if(k<Number.MAX_VALUE){var c=n[a]||(n[a]={key:a});e(c)}a in l||h++;l[a]=b;h>k&&this.remove(r.key);return b}},get:function(a){if(k<Number.MAX_VALUE){var b=n[a];if(!b)return;e(b)}return l[a]},remove:function(a){if(k<Number.MAX_VALUE){var b=n[a];if(!b)return;b==p&&(p=b.p);b==r&&(r=b.n);f(b.n,b.p);delete n[a]}delete l[a];h--},removeAll:function(){l={};h=0;n={};p=r=null},destroy:function(){n=g=l=null;delete a[b]},info:function(){return P({},g,{size:h})}}}var a={};b.info=function(){var b= -{};m(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function rf(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Dc(b,a){function c(a,b,c){var d=/^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/,e={};m(a,function(a,f){var g=a.match(d);if(!g)throw ga("iscp",b,f,a,c?"controller bindings definition":"isolate scope definition");e[f]={mode:g[1][0],collection:"*"===g[2],optional:"?"===g[3],attrName:g[4]||f}});return e}function d(a){var b=a.charAt(0);if(!b|| -b!==F(b))throw ga("baddir",a);if(a!==a.trim())throw ga("baddir",a);}var e={},f=/^\s*directive\:\s*([\w\-]+)\s+(.*)$/,h=/(([\w\-]+)(?:\:([^;]+))?;?)/,g=Wd("ngSrc,ngSrcset,src,srcset"),l=/^(?:(\^\^?)?(\?)?(\^\^?)?)?/,k=/^(on[a-z]+|formaction)$/;this.directive=function r(a,f){Ta(a,"directive");G(a)?(d(a),qb(f,"directiveFactory"),e.hasOwnProperty(a)||(e[a]=[],b.factory(a+"Directive",["$injector","$exceptionHandler",function(b,d){var f=[];m(e[a],function(e,g){try{var h=b.invoke(e);x(h)?h={compile:qa(h)}: -!h.compile&&h.link&&(h.compile=qa(h.link));h.priority=h.priority||0;h.index=g;h.name=h.name||a;h.require=h.require||h.controller&&h.name;h.restrict=h.restrict||"EA";var k=h,l=h,r=h.name,n={isolateScope:null,bindToController:null};C(l.scope)&&(!0===l.bindToController?(n.bindToController=c(l.scope,r,!0),n.isolateScope={}):n.isolateScope=c(l.scope,r,!1));C(l.bindToController)&&(n.bindToController=c(l.bindToController,r,!0));if(C(n.bindToController)){var S=l.controller,E=l.controllerAs;if(!S)throw ga("noctrl", -r);var ca;a:if(E&&G(E))ca=E;else{if(G(S)){var m=Vc.exec(S);if(m){ca=m[3];break a}}ca=void 0}if(!ca)throw ga("noident",r);}var s=k.$$bindings=n;C(s.isolateScope)&&(h.$$isolateBindings=s.isolateScope);h.$$moduleName=e.$$moduleName;f.push(h)}catch(w){d(w)}});return f}])),e[a].push(f)):m(a,oc(r));return this};this.aHrefSanitizationWhitelist=function(b){return A(b)?(a.aHrefSanitizationWhitelist(b),this):a.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(b){return A(b)?(a.imgSrcSanitizationWhitelist(b), -this):a.imgSrcSanitizationWhitelist()};var n=!0;this.debugInfoEnabled=function(a){return A(a)?(n=a,this):n};this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$document","$sce","$animate","$$sanitizeUri",function(a,b,c,d,u,q,z,N,ia,O,H){function L(a,b){try{a.addClass(b)}catch(c){}}function W(a,b,c,d,e){a instanceof B||(a=B(a));m(a,function(b,c){b.nodeType==Pa&&b.nodeValue.match(/\S+/)&&(a[c]=B(b).wrap("<span></span>").parent()[0])});var f= -S(a,b,a,c,d,e);W.$$addScopeClass(a);var g=null;return function(b,c,d){qb(b,"scope");d=d||{};var e=d.parentBoundTranscludeFn,h=d.transcludeControllers;d=d.futureParentElement;e&&e.$$boundTransclude&&(e=e.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==wa(d)&&d.toString().match(/SVG/)?"svg":"html":"html");d="html"!==g?B(Xb(g,B("<div>").append(a).html())):c?Ra.clone.call(a):a;if(h)for(var k in h)d.data("$"+k+"Controller",h[k].instance);W.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,e);return d}}function S(a, -b,c,d,e,f){function g(a,c,d,e){var f,k,l,r,n,t,O;if(q)for(O=Array(c.length),r=0;r<h.length;r+=3)f=h[r],O[f]=c[f];else O=c;r=0;for(n=h.length;r<n;)if(k=O[h[r++]],c=h[r++],f=h[r++],c){if(c.scope){if(l=a.$new(),W.$$addScopeInfo(B(k),l),t=c.$$destroyBindings)c.$$destroyBindings=null,l.$on("$destroyed",t)}else l=a;t=c.transcludeOnThisElement?ba(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?ba(a,b):null;c(f,l,k,d,t,c)}else f&&f(a,k.childNodes,w,e)}for(var h=[],k,l,r,n,q,t=0;t<a.length;t++){k=new Z; -l=ca(a[t],[],k,0===t?d:w,e);(f=l.length?D(l,a[t],k,b,c,null,[],[],f):null)&&f.scope&&W.$$addScopeClass(k.$$element);k=f&&f.terminal||!(r=a[t].childNodes)||!r.length?null:S(r,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||k)h.push(t,f,k),n=!0,q=q||f;f=null}return n?g:null}function ba(a,b,c){return function(d,e,f,g,h){d||(d=a.$new(!1,h),d.$$transcluded=!0);return b(d,e,{parentBoundTranscludeFn:c,transcludeControllers:f,futureParentElement:g})}}function ca(a,b,c,d,e){var g= -c.$attr,k;switch(a.nodeType){case pa:na(b,ya(wa(a)),"E",d,e);for(var l,r,n,q=a.attributes,t=0,O=q&&q.length;t<O;t++){var K=!1,H=!1;l=q[t];k=l.name;r=T(l.value);l=ya(k);if(n=ja.test(l))k=k.replace(Wc,"").substr(8).replace(/_(.)/g,function(a,b){return b.toUpperCase()});var S=l.replace(/(Start|End)$/,"");I(S)&&l===S+"Start"&&(K=k,H=k.substr(0,k.length-5)+"end",k=k.substr(0,k.length-6));l=ya(k.toLowerCase());g[l]=k;if(n||!c.hasOwnProperty(l))c[l]=r,Rc(a,l)&&(c[l]=!0);V(a,b,r,l,n);na(b,l,"A",d,e,K,H)}a= -a.className;C(a)&&(a=a.animVal);if(G(a)&&""!==a)for(;k=h.exec(a);)l=ya(k[2]),na(b,l,"C",d,e)&&(c[l]=T(k[3])),a=a.substr(k.index+k[0].length);break;case Pa:if(11===Wa)for(;a.parentNode&&a.nextSibling&&a.nextSibling.nodeType===Pa;)a.nodeValue+=a.nextSibling.nodeValue,a.parentNode.removeChild(a.nextSibling);Ka(b,a.nodeValue);break;case 8:try{if(k=f.exec(a.nodeValue))l=ya(k[1]),na(b,l,"M",d,e)&&(c[l]=T(k[2]))}catch(E){}}b.sort(M);return b}function za(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ga("uterdir", -b,c);a.nodeType==pa&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return B(d)}function s(a,b,c){return function(d,e,f,g,h){e=za(e[0],b,c);return a(d,e,f,g,h)}}function D(a,b,d,e,f,g,h,k,r){function n(a,b,c,d){if(a){c&&(a=s(a,c,d));a.require=D.require;a.directiveName=y;if(u===D||D.$$isolateScope)a=$(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=s(b,c,d));b.require=D.require;b.directiveName=y;if(u===D||D.$$isolateScope)b=$(b,{isolateScope:!0});k.push(b)}} -function t(a,b,c,d){var e;if(G(b)){var f=b.match(l);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&e.instance;e||(d="$"+b+"Controller",e=g?c.inheritedData(d):c.data(d));if(!e&&!f)throw ga("ctreq",b,a);}else if(J(b))for(e=[],g=0,f=b.length;g<f;g++)e[g]=t(a,b[g],c,d);return e||null}function O(a,b,c,d,e,f){var g=fa(),h;for(h in d){var k=d[h],l={$scope:k===u||k.$$isolateScope?e:f,$element:a,$attrs:b,$transclude:c},r=k.controller;"@"==r&&(r=b[k.name]);l=q(r, -l,!0,k.controllerAs);g[k.name]=l;ia||a.data("$"+k.name+"Controller",l.instance)}return g}function K(a,c,e,f,g,l){function r(a,b,c){var d;ab(a)||(c=b,b=a,a=w);ia&&(d=ca);c||(c=ia?N.parent():N);return g(a,b,d,c,za)}var n,q,H,E,ca,z,N;b===e?(f=d,N=d.$$element):(N=B(e),f=new Z(N,d));u&&(E=c.$new(!0));g&&(z=r,z.$$boundTransclude=g);ba&&(ca=O(N,f,z,ba,E,c));u&&(W.$$addScopeInfo(N,E,!0,!(L&&(L===u||L===u.$$originalDirective))),W.$$addScopeClass(N,!0),E.$$isolateBindings=u.$$isolateBindings,Y(c,f,E,E.$$isolateBindings, -u,E));if(ca){var Va=u||S,m;Va&&ca[Va.name]&&(q=Va.$$bindings.bindToController,(H=ca[Va.name])&&H.identifier&&q&&(m=H,l.$$destroyBindings=Y(c,f,H.instance,q,Va)));for(n in ca){H=ca[n];var D=H();D!==H.instance&&(H.instance=D,N.data("$"+n+"Controller",D),H===m&&(l.$$destroyBindings(),l.$$destroyBindings=Y(c,f,D,q,Va)))}}n=0;for(l=h.length;n<l;n++)q=h[n],aa(q,q.isolateScope?E:c,N,f,q.require&&t(q.directiveName,q.require,N,ca),z);var za=c;u&&(u.template||null===u.templateUrl)&&(za=E);a&&a(za,e.childNodes, -w,g);for(n=k.length-1;0<=n;n--)q=k[n],aa(q,q.isolateScope?E:c,N,f,q.require&&t(q.directiveName,q.require,N,ca),z)}r=r||{};for(var H=-Number.MAX_VALUE,S=r.newScopeDirective,ba=r.controllerDirectives,u=r.newIsolateScopeDirective,L=r.templateDirective,z=r.nonTlbTranscludeDirective,N=!1,m=!1,ia=r.hasElementTranscludeDirective,v=d.$$element=B(b),D,y,M,Ka=e,na,I=0,F=a.length;I<F;I++){D=a[I];var P=D.$$start,R=D.$$end;P&&(v=za(b,P,R));M=w;if(H>D.priority)break;if(M=D.scope)D.templateUrl||(C(M)?(Q("new/isolated scope", -u||S,D,v),u=D):Q("new/isolated scope",u,D,v)),S=S||D;y=D.name;!D.templateUrl&&D.controller&&(M=D.controller,ba=ba||fa(),Q("'"+y+"' controller",ba[y],D,v),ba[y]=D);if(M=D.transclude)N=!0,D.$$tlb||(Q("transclusion",z,D,v),z=D),"element"==M?(ia=!0,H=D.priority,M=v,v=d.$$element=B(X.createComment(" "+y+": "+d[y]+" ")),b=v[0],U(f,ua.call(M,0),b),Ka=W(M,e,H,g&&g.name,{nonTlbTranscludeDirective:z})):(M=B(Ub(b)).contents(),v.empty(),Ka=W(M,e));if(D.template)if(m=!0,Q("template",L,D,v),L=D,M=x(D.template)? -D.template(v,d):D.template,M=ha(M),D.replace){g=D;M=Sb.test(M)?Xc(Xb(D.templateNamespace,T(M))):[];b=M[0];if(1!=M.length||b.nodeType!==pa)throw ga("tplrt",y,"");U(f,v,b);F={$attr:{}};M=ca(b,[],F);var Lf=a.splice(I+1,a.length-(I+1));u&&A(M);a=a.concat(M).concat(Lf);Yc(d,F);F=a.length}else v.html(M);if(D.templateUrl)m=!0,Q("template",L,D,v),L=D,D.replace&&(g=D),K=Mf(a.splice(I,a.length-I),v,d,f,N&&Ka,h,k,{controllerDirectives:ba,newScopeDirective:S!==D&&S,newIsolateScopeDirective:u,templateDirective:L, -nonTlbTranscludeDirective:z}),F=a.length;else if(D.compile)try{na=D.compile(v,d,Ka),x(na)?n(null,na,P,R):na&&n(na.pre,na.post,P,R)}catch(V){c(V,xa(v))}D.terminal&&(K.terminal=!0,H=Math.max(H,D.priority))}K.scope=S&&!0===S.scope;K.transcludeOnThisElement=N;K.templateOnThisElement=m;K.transclude=Ka;r.hasElementTranscludeDirective=ia;return K}function A(a){for(var b=0,c=a.length;b<c;b++)a[b]=Nb(a[b],{$$isolateScope:!0})}function na(b,d,f,g,h,k,l){if(d===h)return null;h=null;if(e.hasOwnProperty(d)){var n; -d=a.get(d+"Directive");for(var q=0,t=d.length;q<t;q++)try{n=d[q],(v(g)||g>n.priority)&&-1!=n.restrict.indexOf(f)&&(k&&(n=Nb(n,{$$start:k,$$end:l})),b.push(n),h=n)}catch(H){c(H)}}return h}function I(b){if(e.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,f=c.length;d<f;d++)if(b=c[d],b.multiElement)return!0;return!1}function Yc(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b,function(b,f){"class"== -f?(L(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):"style"==f?(e.attr("style",e.attr("style")+";"+b),a.style=(a.style?a.style+";":"")+b):"$"==f.charAt(0)||a.hasOwnProperty(f)||(a[f]=b,d[f]=c[f])})}function Mf(a,b,c,e,f,g,h,k){var l=[],r,n,q=b[0],t=a.shift(),H=Nb(t,{templateUrl:null,transclude:null,replace:null,$$originalDirective:t}),O=x(t.templateUrl)?t.templateUrl(b,c):t.templateUrl,E=t.templateNamespace;b.empty();d(O).then(function(d){var K,u;d=ha(d);if(t.replace){d=Sb.test(d)?Xc(Xb(E,T(d))): -[];K=d[0];if(1!=d.length||K.nodeType!==pa)throw ga("tplrt",t.name,O);d={$attr:{}};U(e,b,K);var z=ca(K,[],d);C(t.scope)&&A(z);a=z.concat(a);Yc(c,d)}else K=q,b.html(d);a.unshift(H);r=D(a,K,c,f,b,t,g,h,k);m(e,function(a,c){a==K&&(e[c]=b[0])});for(n=S(b[0].childNodes,f);l.length;){d=l.shift();u=l.shift();var N=l.shift(),W=l.shift(),z=b[0];if(!d.$$destroyed){if(u!==q){var za=u.className;k.hasElementTranscludeDirective&&t.replace||(z=Ub(K));U(N,B(u),z);L(B(z),za)}u=r.transcludeOnThisElement?ba(d,r.transclude, -W):W;r(n,d,z,e,u,r)}}l=null});return function(a,b,c,d,e){a=e;b.$$destroyed||(l?l.push(b,c,d,a):(r.transcludeOnThisElement&&(a=ba(b,r.transclude,e)),r(n,b,c,d,a,r)))}}function M(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function Q(a,b,c,d){function e(a){return a?" (module: "+a+")":""}if(b)throw ga("multidir",b.name,e(b.$$moduleName),c.name,e(c.$$moduleName),a,xa(d));}function Ka(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a= -a.parent();var b=!!a.length;b&&W.$$addBindingClass(a);return function(a,c){var e=c.parent();b||W.$$addBindingClass(e);W.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function Xb(a,b){a=F(a||"html");switch(a){case "svg":case "math":var c=X.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;default:return b}}function R(a,b){if("srcdoc"==b)return ia.HTML;var c=wa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b|| -"ngSrc"==b))return ia.RESOURCE_URL}function V(a,c,d,e,f){var h=R(a,e);f=g[e]||f;var l=b(d,!0,h,f);if(l){if("multiple"===e&&"select"===wa(a))throw ga("selmulti",xa(a));c.push({priority:100,compile:function(){return{pre:function(a,c,g){c=g.$$observers||(g.$$observers=fa());if(k.test(e))throw ga("nodomevents");var r=g[e];r!==d&&(l=r&&b(r,!0,h,f),d=r);l&&(g[e]=l(a),(c[e]||(c[e]=[])).$$inter=!0,(g.$$observers&&g.$$observers[e].$$scope||a).$watch(l,function(a,b){"class"===e&&a!=b?g.$updateClass(a,b):g.$set(e, -a)}))}}}})}}function U(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g<h;g++)if(a[g]==d){a[g++]=c;h=g+e-1;for(var k=a.length;g<k;g++,h++)h<k?a[g]=a[h]:delete a[g];a.length-=e-1;a.context===d&&(a.context=c);break}f&&f.replaceChild(c,d);a=X.createDocumentFragment();a.appendChild(d);B.hasData(d)&&(B(c).data(B(d).data()),ra?(Qb=!0,ra.cleanData([d])):delete B.cache[d[B.expando]]);d=1;for(e=b.length;d<e;d++)f=b[d],B(f).remove(),a.appendChild(f),delete b[d];b[0]=c;b.length=1}function $(a, -b){return P(function(){return a.apply(null,arguments)},a,b)}function aa(a,b,d,e,f,g){try{a(b,d,e,f,g)}catch(h){c(h,xa(d))}}function Y(a,c,d,e,f,g){var h;m(e,function(e,g){var k=e.attrName,l=e.optional,r,n,q,K;switch(e.mode){case "@":l||ta.call(c,k)||(d[g]=c[k]=void 0);c.$observe(k,function(a){G(a)&&(d[g]=a)});c.$$observers[k].$$scope=a;G(c[k])&&(d[g]=b(c[k])(a));break;case "=":if(!ta.call(c,k)){if(l)break;c[k]=void 0}if(l&&!c[k])break;n=u(c[k]);K=n.literal?ka:function(a,b){return a===b||a!==a&&b!== -b};q=n.assign||function(){r=d[g]=n(a);throw ga("nonassign",c[k],f.name);};r=d[g]=n(a);l=function(b){K(b,d[g])||(K(b,r)?q(a,b=d[g]):d[g]=b);return r=b};l.$stateful=!0;l=e.collection?a.$watchCollection(c[k],l):a.$watch(u(c[k],l),null,n.literal);h=h||[];h.push(l);break;case "&":n=c.hasOwnProperty(k)?u(c[k]):y;if(n===y&&l)break;d[g]=function(b){return n(a,b)}}});e=h?function(){for(var a=0,b=h.length;a<b;++a)h[a]()}:y;return g&&e!==y?(g.$on("$destroy",e),y):e}var Z=function(a,b){if(b){var c=Object.keys(b), -d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr={};this.$$element=a};Z.prototype={$normalize:ya,$addClass:function(a){a&&0<a.length&&O.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&O.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=Zc(a,b);c&&c.length&&O.addClass(this.$$element,c);(c=Zc(b,a))&&c.length&&O.removeClass(this.$$element,c)},$set:function(a,b,d,e){var f=Rc(this.$$element[0],a),g=$c[a],h=a;f?(this.$$element.prop(a,b),e=f):g&&(this[g]= -b,h=g);this[a]=b;e?this.$attr[a]=e:(e=this.$attr[a])||(this.$attr[a]=e=Ac(a,"-"));f=wa(this.$$element);if("a"===f&&"href"===a||"img"===f&&"src"===a)this[a]=b=H(b,"src"===a);else if("img"===f&&"srcset"===a){for(var f="",g=T(b),k=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,k=/\s/.test(g)?k:/(,)/,g=g.split(k),k=Math.floor(g.length/2),l=0;l<k;l++)var r=2*l,f=f+H(T(g[r]),!0),f=f+(" "+T(g[r+1]));g=T(g[2*l]).split(/\s/);f+=H(T(g[0]),!0);2===g.length&&(f+=" "+T(g[1]));this[a]=b=f}!1!==d&&(null===b||v(b)?this.$$element.removeAttr(e): -this.$$element.attr(e,b));(a=this.$$observers)&&m(a[h],function(a){try{a(b)}catch(d){c(d)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers=fa()),e=d[a]||(d[a]=[]);e.push(b);z.$evalAsync(function(){e.$$inter||!c.hasOwnProperty(a)||v(c[a])||b(c[a])});return function(){cb(e,b)}}};var da=b.startSymbol(),ea=b.endSymbol(),ha="{{"==da||"}}"==ea?$a:function(a){return a.replace(/\{\{/g,da).replace(/}}/g,ea)},ja=/^ngAttr[A-Z]/;W.$$addBindingInfo=n?function(a,b){var c=a.data("$binding")|| -[];J(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:y;W.$$addBindingClass=n?function(a){L(a,"ng-binding")}:y;W.$$addScopeInfo=n?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:y;W.$$addScopeClass=n?function(a,b){L(a,b?"ng-isolate-scope":"ng-scope")}:y;return W}]}function ya(b){return gb(b.replace(Wc,""))}function Zc(b,a){var c="",d=b.split(/\s+/),e=a.split(/\s+/),f=0;a:for(;f<d.length;f++){for(var h=d[f],g=0;g<e.length;g++)if(h==e[g])continue a;c+=(0<c.length? -" ":"")+h}return c}function Xc(b){b=B(b);var a=b.length;if(1>=a)return b;for(;a--;)8===b[a].nodeType&&Nf.call(b,a,1);return b}function Xe(){var b={},a=!1;this.register=function(a,d){Ta(a,"controller");C(a)?P(b,a):b[a]=d};this.allowGlobals=function(){a=!0};this.$get=["$injector","$window",function(c,d){function e(a,b,c,d){if(!a||!C(a.$scope))throw I("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,h,g,l){var k,n,p;g=!0===g;l&&G(l)&&(p=l);if(G(f)){l=f.match(Vc);if(!l)throw Of("ctrlfmt",f); -n=l[1];p=p||l[3];f=b.hasOwnProperty(n)?b[n]:Cc(h.$scope,n,!0)||(a?Cc(d,n,!0):w);Sa(f,n,!0)}if(g)return g=(J(f)?f[f.length-1]:f).prototype,k=Object.create(g||null),p&&e(h,p,k,n||f.name),P(function(){var a=c.invoke(f,k,h,n);a!==k&&(C(a)||x(a))&&(k=a,p&&e(h,p,k,n||f.name));return k},{instance:k,identifier:p});k=c.instantiate(f,h,n);p&&e(h,p,k,n||f.name);return k}}]}function Ye(){this.$get=["$window",function(b){return B(b.document)}]}function Ze(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b, -arguments)}}]}function Yb(b){return C(b)?ea(b)?b.toISOString():eb(b):b}function df(){this.$get=function(){return function(b){if(!b)return"";var a=[];nc(b,function(b,d){null===b||v(b)||(J(b)?m(b,function(b,c){a.push(la(d)+"="+la(Yb(b)))}):a.push(la(d)+"="+la(Yb(b))))});return a.join("&")}}}function ef(){this.$get=function(){return function(b){function a(b,e,f){null===b||v(b)||(J(b)?m(b,function(b,c){a(b,e+"["+(C(b)?c:"")+"]")}):C(b)&&!ea(b)?nc(b,function(b,c){a(b,e+(f?"":"[")+c+(f?"":"]"))}):c.push(la(e)+ -"="+la(Yb(b))))}if(!b)return"";var c=[];a(b,"",!0);return c.join("&")}}}function Zb(b,a){if(G(b)){var c=b.replace(Pf,"").trim();if(c){var d=a("Content-Type");(d=d&&0===d.indexOf(ad))||(d=(d=c.match(Qf))&&Rf[d[0]].test(c));d&&(b=vc(c))}}return b}function bd(b){var a=fa(),c;G(b)?m(b.split("\n"),function(b){c=b.indexOf(":");var e=F(T(b.substr(0,c)));b=T(b.substr(c+1));e&&(a[e]=a[e]?a[e]+", "+b:b)}):C(b)&&m(b,function(b,c){var f=F(c),h=T(b);f&&(a[f]=a[f]?a[f]+", "+h:h)});return a}function cd(b){var a; -return function(c){a||(a=bd(b));return c?(c=a[F(c)],void 0===c&&(c=null),c):a}}function dd(b,a,c,d){if(x(d))return d(b,a,c);m(d,function(d){b=d(b,a,c)});return b}function cf(){var b=this.defaults={transformResponse:[Zb],transformRequest:[function(a){return C(a)&&"[object File]"!==va.call(a)&&"[object Blob]"!==va.call(a)&&"[object FormData]"!==va.call(a)?eb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ja($b),put:ja($b),patch:ja($b)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN", -paramSerializer:"$httpParamSerializer"},a=!1;this.useApplyAsync=function(b){return A(b)?(a=!!b,this):a};var c=!0;this.useLegacyPromiseExtensions=function(a){return A(a)?(c=!!a,this):c};var d=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector",function(e,f,h,g,l,k){function n(a){function d(a){var b=P({},a);b.data=a.data?dd(a.data,a.headers,a.status,f.transformResponse):a.data;a=a.status;return 200<=a&&300>a?b:l.reject(b)}function e(a,b){var c, -d={};m(a,function(a,e){x(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}if(!da.isObject(a))throw I("$http")("badreq",a);var f=P({method:"get",transformRequest:b.transformRequest,transformResponse:b.transformResponse,paramSerializer:b.paramSerializer},a);f.headers=function(a){var c=b.headers,d=P({},a.headers),f,g,h,c=P({},c.common,c[F(a.method)]);a:for(f in c){g=F(f);for(h in d)if(F(h)===g)continue a;d[f]=c[f]}return e(d,ja(a))}(a);f.method=sb(f.method);f.paramSerializer=G(f.paramSerializer)?k.get(f.paramSerializer): -f.paramSerializer;var g=[function(a){var c=a.headers,e=dd(a.data,cd(c),w,a.transformRequest);v(e)&&m(c,function(a,b){"content-type"===F(b)&&delete c[b]});v(a.withCredentials)&&!v(b.withCredentials)&&(a.withCredentials=b.withCredentials);return p(a,e).then(d,d)},w],h=l.when(f);for(m(E,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;){a=g.shift();var r=g.shift(),h=h.then(a,r)}c?(h.success=function(a){Sa(a, -"fn");h.then(function(b){a(b.data,b.status,b.headers,f)});return h},h.error=function(a){Sa(a,"fn");h.then(null,function(b){a(b.data,b.status,b.headers,f)});return h}):(h.success=ed("success"),h.error=ed("error"));return h}function p(c,d){function h(b,c,d,e){function f(){k(c,b,d,e)}L&&(200<=b&&300>b?L.put(ba,[b,c,bd(d),e]):L.remove(ba));a?g.$applyAsync(f):(f(),g.$$phase||g.$apply())}function k(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?O.resolve:O.reject)({data:a,status:b,headers:cd(d),config:c,statusText:e})} -function p(a){k(a.data,a.status,ja(a.headers()),a.statusText)}function E(){var a=n.pendingRequests.indexOf(c);-1!==a&&n.pendingRequests.splice(a,1)}var O=l.defer(),H=O.promise,L,m,S=c.headers,ba=r(c.url,c.paramSerializer(c.params));n.pendingRequests.push(c);H.then(E,E);!c.cache&&!b.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(L=C(c.cache)?c.cache:C(b.cache)?b.cache:t);L&&(m=L.get(ba),A(m)?m&&x(m.then)?m.then(p,p):J(m)?k(m[1],m[0],ja(m[2]),m[3]):k(m,200,{},"OK"):L.put(ba,H));v(m)&&((m= -fd(c.url)?f()[c.xsrfCookieName||b.xsrfCookieName]:w)&&(S[c.xsrfHeaderName||b.xsrfHeaderName]=m),e(c.method,ba,d,h,S,c.timeout,c.withCredentials,c.responseType));return H}function r(a,b){0<b.length&&(a+=(-1==a.indexOf("?")?"?":"&")+b);return a}var t=h("$http");b.paramSerializer=G(b.paramSerializer)?k.get(b.paramSerializer):b.paramSerializer;var E=[];m(d,function(a){E.unshift(G(a)?k.get(a):k.invoke(a))});n.pendingRequests=[];(function(a){m(arguments,function(a){n[a]=function(b,c){return n(P({},c||{}, -{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){m(arguments,function(a){n[a]=function(b,c,d){return n(P({},d||{},{method:a,url:b,data:c}))}})})("post","put","patch");n.defaults=b;return n}]}function gf(){this.$get=function(){return function(){return new Q.XMLHttpRequest}}}function ff(){this.$get=["$browser","$window","$document","$xhrFactory",function(b,a,c,d){return Sf(b,d,b.defer,a.angular.callbacks,c[0])}]}function Sf(b,a,c,d,e){function f(a,b,c){var f=e.createElement("script"), -n=null;f.type="text/javascript";f.src=a;f.async=!0;n=function(a){f.removeEventListener("load",n,!1);f.removeEventListener("error",n,!1);e.body.removeChild(f);f=null;var h=-1,t="unknown";a&&("load"!==a.type||d[b].called||(a={type:"error"}),t=a.type,h="error"===a.type?404:200);c&&c(h,t)};f.addEventListener("load",n,!1);f.addEventListener("error",n,!1);e.body.appendChild(f);return n}return function(e,g,l,k,n,p,r,t){function E(){q&&q();z&&z.abort()}function K(a,d,e,f,g){A(s)&&c.cancel(s);q=z=null;a(d, -e,f,g);b.$$completeOutstandingRequest(y)}b.$$incOutstandingRequestCount();g=g||b.url();if("jsonp"==F(e)){var u="_"+(d.counter++).toString(36);d[u]=function(a){d[u].data=a;d[u].called=!0};var q=f(g.replace("JSON_CALLBACK","angular.callbacks."+u),u,function(a,b){K(k,a,d[u].data,"",b);d[u]=y})}else{var z=a(e,g);z.open(e,g,!0);m(n,function(a,b){A(a)&&z.setRequestHeader(b,a)});z.onload=function(){var a=z.statusText||"",b="response"in z?z.response:z.responseText,c=1223===z.status?204:z.status;0===c&&(c= -b?200:"file"==Aa(g).protocol?404:0);K(k,c,b,z.getAllResponseHeaders(),a)};e=function(){K(k,-1,null,null,"")};z.onerror=e;z.onabort=e;r&&(z.withCredentials=!0);if(t)try{z.responseType=t}catch(N){if("json"!==t)throw N;}z.send(v(l)?null:l)}if(0<p)var s=c(E,p);else p&&x(p.then)&&p.then(E)}}function af(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler","$sce",function(c,d,e){function f(a){return"\\\\\\"+ -a}function h(c){return c.replace(n,b).replace(p,a)}function g(f,g,n,p){function u(a){try{var b=a;a=n?e.getTrusted(n,b):e.valueOf(b);var c;if(p&&!A(a))c=a;else if(null==a)c="";else{switch(typeof a){case "string":break;case "number":a=""+a;break;default:a=eb(a)}c=a}return c}catch(g){d(La.interr(f,g))}}p=!!p;for(var q,m,N=0,s=[],O=[],H=f.length,L=[],W=[];N<H;)if(-1!=(q=f.indexOf(b,N))&&-1!=(m=f.indexOf(a,q+l)))N!==q&&L.push(h(f.substring(N,q))),N=f.substring(q+l,m),s.push(N),O.push(c(N,u)),N=m+k,W.push(L.length), -L.push("");else{N!==H&&L.push(h(f.substring(N)));break}n&&1<L.length&&La.throwNoconcat(f);if(!g||s.length){var S=function(a){for(var b=0,c=s.length;b<c;b++){if(p&&v(a[b]))return;L[W[b]]=a[b]}return L.join("")};return P(function(a){var b=0,c=s.length,e=Array(c);try{for(;b<c;b++)e[b]=O[b](a);return S(e)}catch(g){d(La.interr(f,g))}},{exp:f,expressions:s,$$watchDelegate:function(a,b){var c;return a.$watchGroup(O,function(d,e){var f=S(d);x(b)&&b.call(this,f,d!==e?c:f,a);c=f})}})}}var l=b.length,k=a.length, -n=new RegExp(b.replace(/./g,f),"g"),p=new RegExp(a.replace(/./g,f),"g");g.startSymbol=function(){return b};g.endSymbol=function(){return a};return g}]}function bf(){this.$get=["$rootScope","$window","$q","$$q",function(b,a,c,d){function e(e,g,l,k){var n=4<arguments.length,p=n?ua.call(arguments,4):[],r=a.setInterval,t=a.clearInterval,E=0,K=A(k)&&!k,u=(K?d:c).defer(),q=u.promise;l=A(l)?l:0;q.then(null,null,n?function(){e.apply(null,p)}:e);q.$$intervalId=r(function(){u.notify(E++);0<l&&E>=l&&(u.resolve(E), -t(q.$$intervalId),delete f[q.$$intervalId]);K||b.$apply()},g);f[q.$$intervalId]=u;return q}var f={};e.cancel=function(b){return b&&b.$$intervalId in f?(f[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId),delete f[b.$$intervalId],!0):!1};return e}]}function ac(b){b=b.split("/");for(var a=b.length;a--;)b[a]=ob(b[a]);return b.join("/")}function gd(b,a){var c=Aa(b);a.$$protocol=c.protocol;a.$$host=c.hostname;a.$$port=Y(c.port)||Tf[c.protocol]||null}function hd(b,a){var c="/"!==b.charAt(0); -c&&(b="/"+b);var d=Aa(b);a.$$path=decodeURIComponent(c&&"/"===d.pathname.charAt(0)?d.pathname.substring(1):d.pathname);a.$$search=yc(d.search);a.$$hash=decodeURIComponent(d.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function sa(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ja(b){var a=b.indexOf("#");return-1==a?b:b.substr(0,a)}function Cb(b){return b.replace(/(#.+)|#$/,"$1")}function bc(b,a,c){this.$$html5=!0;c=c||"";gd(b,this);this.$$parse=function(b){var c=sa(a, -b);if(!G(c))throw Db("ipthprfx",b,a);hd(c,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var b=Pb(this.$$search),c=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=ac(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=a+this.$$url.substr(1)};this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,h;A(f=sa(b,d))?(h=f,h=A(f=sa(c,f))?a+(sa("/",f)||f):b+h):A(f=sa(a,d))?h=a+f:a==d+"/"&&(h=a);h&&this.$$parse(h);return!!h}}function cc(b,a,c){gd(b,this); -this.$$parse=function(d){var e=sa(b,d)||sa(a,d),f;v(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",v(e)&&(b=d,this.replace())):(f=sa(c,e),v(f)&&(f=e));hd(f,this);d=this.$$path;var e=b,h=/^\/[A-Z]:(\/.*)/;0===f.indexOf(e)&&(f=f.replace(e,""));h.exec(f)||(d=(f=h.exec(d))?f[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var a=Pb(this.$$search),e=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=ac(this.$$path)+(a?"?"+a:"")+e;this.$$absUrl=b+(this.$$url?c+this.$$url:"")};this.$$parseLinkUrl= -function(a,c){return Ja(b)==Ja(a)?(this.$$parse(a),!0):!1}}function id(b,a,c){this.$$html5=!0;cc.apply(this,arguments);this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,h;b==Ja(d)?f=d:(h=sa(a,d))?f=b+c+h:a===d+"/"&&(f=a);f&&this.$$parse(f);return!!f};this.$$compose=function(){var a=Pb(this.$$search),e=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=ac(this.$$path)+(a?"?"+a:"")+e;this.$$absUrl=b+c+this.$$url}}function Eb(b){return function(){return this[b]}}function jd(b, -a){return function(c){if(v(c))return this[b];this[b]=a(c);this.$$compose();return this}}function hf(){var b="",a={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(a){return A(a)?(b=a,this):b};this.html5Mode=function(b){return bb(b)?(a.enabled=b,this):C(b)?(bb(b.enabled)&&(a.enabled=b.enabled),bb(b.requireBase)&&(a.requireBase=b.requireBase),bb(b.rewriteLinks)&&(a.rewriteLinks=b.rewriteLinks),this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(c, -d,e,f,h){function g(a,b,c){var e=k.url(),f=k.$$state;try{d.url(a,b,c),k.$$state=d.state()}catch(g){throw k.url(e),k.$$state=f,g;}}function l(a,b){c.$broadcast("$locationChangeSuccess",k.absUrl(),a,k.$$state,b)}var k,n;n=d.baseHref();var p=d.url(),r;if(a.enabled){if(!n&&a.requireBase)throw Db("nobase");r=p.substring(0,p.indexOf("/",p.indexOf("//")+2))+(n||"/");n=e.history?bc:id}else r=Ja(p),n=cc;var t=r.substr(0,Ja(r).lastIndexOf("/")+1);k=new n(r,t,"#"+b);k.$$parseLinkUrl(p,p);k.$$state=d.state(); -var E=/^\s*(javascript|mailto):/i;f.on("click",function(b){if(a.rewriteLinks&&!b.ctrlKey&&!b.metaKey&&!b.shiftKey&&2!=b.which&&2!=b.button){for(var e=B(b.target);"a"!==wa(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var g=e.prop("href"),l=e.attr("href")||e.attr("xlink:href");C(g)&&"[object SVGAnimatedString]"===g.toString()&&(g=Aa(g.animVal).href);E.test(g)||!g||e.attr("target")||b.isDefaultPrevented()||!k.$$parseLinkUrl(g,l)||(b.preventDefault(),k.absUrl()!=d.url()&&(c.$apply(),h.angular["ff-684208-preventDefault"]= -!0))}});Cb(k.absUrl())!=Cb(p)&&d.url(k.absUrl(),!0);var K=!0;d.onUrlChange(function(a,b){v(sa(t,a))?h.location.href=a:(c.$evalAsync(function(){var d=k.absUrl(),e=k.$$state,f;k.$$parse(a);k.$$state=b;f=c.$broadcast("$locationChangeStart",a,d,b,e).defaultPrevented;k.absUrl()===a&&(f?(k.$$parse(d),k.$$state=e,g(d,!1,e)):(K=!1,l(d,e)))}),c.$$phase||c.$digest())});c.$watch(function(){var a=Cb(d.url()),b=Cb(k.absUrl()),f=d.state(),h=k.$$replace,r=a!==b||k.$$html5&&e.history&&f!==k.$$state;if(K||r)K=!1, -c.$evalAsync(function(){var b=k.absUrl(),d=c.$broadcast("$locationChangeStart",b,a,k.$$state,f).defaultPrevented;k.absUrl()===b&&(d?(k.$$parse(a),k.$$state=f):(r&&g(b,h,f===k.$$state?null:k.$$state),l(a,f)))});k.$$replace=!1});return k}]}function jf(){var b=!0,a=this;this.debugEnabled=function(a){return A(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a= -a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||y;a=!1;try{a=!!e.apply}catch(l){}return a?function(){var a=[];m(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function Xa(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"=== -b||"__proto__"===b)throw Z("isecfld",a);return b}function kd(b,a){b+="";if(!G(b))throw Z("iseccst",a);return b}function Ba(b,a){if(b){if(b.constructor===b)throw Z("isecfn",a);if(b.window===b)throw Z("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw Z("isecdom",a);if(b===Object)throw Z("isecobj",a);}return b}function ld(b,a){if(b){if(b.constructor===b)throw Z("isecfn",a);if(b===Uf||b===Vf||b===Wf)throw Z("isecff",a);}}function md(b,a){if(b&&(b===(0).constructor||b===(!1).constructor|| -b==="".constructor||b==={}.constructor||b===[].constructor||b===Function.constructor))throw Z("isecaf",a);}function Xf(b,a){return"undefined"!==typeof b?b:a}function nd(b,a){return"undefined"===typeof b?a:"undefined"===typeof a?b:b+a}function U(b,a){var c,d;switch(b.type){case s.Program:c=!0;m(b.body,function(b){U(b.expression,a);c=c&&b.expression.constant});b.constant=c;break;case s.Literal:b.constant=!0;b.toWatch=[];break;case s.UnaryExpression:U(b.argument,a);b.constant=b.argument.constant;b.toWatch= -b.argument.toWatch;break;case s.BinaryExpression:U(b.left,a);U(b.right,a);b.constant=b.left.constant&&b.right.constant;b.toWatch=b.left.toWatch.concat(b.right.toWatch);break;case s.LogicalExpression:U(b.left,a);U(b.right,a);b.constant=b.left.constant&&b.right.constant;b.toWatch=b.constant?[]:[b];break;case s.ConditionalExpression:U(b.test,a);U(b.alternate,a);U(b.consequent,a);b.constant=b.test.constant&&b.alternate.constant&&b.consequent.constant;b.toWatch=b.constant?[]:[b];break;case s.Identifier:b.constant= -!1;b.toWatch=[b];break;case s.MemberExpression:U(b.object,a);b.computed&&U(b.property,a);b.constant=b.object.constant&&(!b.computed||b.property.constant);b.toWatch=[b];break;case s.CallExpression:c=b.filter?!a(b.callee.name).$stateful:!1;d=[];m(b.arguments,function(b){U(b,a);c=c&&b.constant;b.constant||d.push.apply(d,b.toWatch)});b.constant=c;b.toWatch=b.filter&&!a(b.callee.name).$stateful?d:[b];break;case s.AssignmentExpression:U(b.left,a);U(b.right,a);b.constant=b.left.constant&&b.right.constant; -b.toWatch=[b];break;case s.ArrayExpression:c=!0;d=[];m(b.elements,function(b){U(b,a);c=c&&b.constant;b.constant||d.push.apply(d,b.toWatch)});b.constant=c;b.toWatch=d;break;case s.ObjectExpression:c=!0;d=[];m(b.properties,function(b){U(b.value,a);c=c&&b.value.constant;b.value.constant||d.push.apply(d,b.value.toWatch)});b.constant=c;b.toWatch=d;break;case s.ThisExpression:b.constant=!1,b.toWatch=[]}}function od(b){if(1==b.length){b=b[0].expression;var a=b.toWatch;return 1!==a.length?a:a[0]!==b?a:w}} -function pd(b){return b.type===s.Identifier||b.type===s.MemberExpression}function qd(b){if(1===b.body.length&&pd(b.body[0].expression))return{type:s.AssignmentExpression,left:b.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function rd(b){return 0===b.body.length||1===b.body.length&&(b.body[0].expression.type===s.Literal||b.body[0].expression.type===s.ArrayExpression||b.body[0].expression.type===s.ObjectExpression)}function sd(b,a){this.astBuilder=b;this.$filter=a}function td(b, -a){this.astBuilder=b;this.$filter=a}function Fb(b){return"constructor"==b}function dc(b){return x(b.valueOf)?b.valueOf():Yf.call(b)}function kf(){var b=fa(),a=fa();this.$get=["$filter",function(c){function d(a,b){return null==a||null==b?a===b:"object"===typeof a&&(a=dc(a),"object"===typeof a)?!1:a===b||a!==a&&b!==b}function e(a,b,c,e,f){var g=e.inputs,h;if(1===g.length){var k=d,g=g[0];return a.$watch(function(a){var b=g(a);d(b,k)||(h=e(a,w,w,[b]),k=b&&dc(b));return h},b,c,f)}for(var l=[],n=[],p=0, -m=g.length;p<m;p++)l[p]=d,n[p]=null;return a.$watch(function(a){for(var b=!1,c=0,f=g.length;c<f;c++){var k=g[c](a);if(b||(b=!d(k,l[c])))n[c]=k,l[c]=k&&dc(k)}b&&(h=e(a,w,w,n));return h},b,c,f)}function f(a,b,c,d){var e,f;return e=a.$watch(function(a){return d(a)},function(a,c,d){f=a;x(b)&&b.apply(this,arguments);A(a)&&d.$$postDigest(function(){A(f)&&e()})},c)}function h(a,b,c,d){function e(a){var b=!0;m(a,function(a){A(a)||(b=!1)});return b}var f,g;return f=a.$watch(function(a){return d(a)},function(a, -c,d){g=a;x(b)&&b.call(this,a,c,d);e(a)&&d.$$postDigest(function(){e(g)&&f()})},c)}function g(a,b,c,d){var e;return e=a.$watch(function(a){return d(a)},function(a,c,d){x(b)&&b.apply(this,arguments);e()},c)}function l(a,b){if(!b)return a;var c=a.$$watchDelegate,c=c!==h&&c!==f?function(c,d,e,f){e=a(c,d,e,f);return b(e,c,d)}:function(c,d,e,f){e=a(c,d,e,f);c=b(e,c,d);return A(e)?c:e};a.$$watchDelegate&&a.$$watchDelegate!==e?c.$$watchDelegate=a.$$watchDelegate:b.$stateful||(c.$$watchDelegate=e,c.inputs= -a.inputs?a.inputs:[a]);return c}var k=Fa().noUnsafeEval,n={csp:k,expensiveChecks:!1},p={csp:k,expensiveChecks:!0};return function(d,k,E){var m,u,q;switch(typeof d){case "string":q=d=d.trim();var s=E?a:b;m=s[q];m||(":"===d.charAt(0)&&":"===d.charAt(1)&&(u=!0,d=d.substring(2)),E=E?p:n,m=new ec(E),m=(new fc(m,c,E)).parse(d),m.constant?m.$$watchDelegate=g:u?m.$$watchDelegate=m.literal?h:f:m.inputs&&(m.$$watchDelegate=e),s[q]=m);return l(m,k);case "function":return l(d,k);default:return y}}}]}function mf(){this.$get= -["$rootScope","$exceptionHandler",function(b,a){return ud(function(a){b.$evalAsync(a)},a)}]}function nf(){this.$get=["$browser","$exceptionHandler",function(b,a){return ud(function(a){b.defer(a)},a)}]}function ud(b,a){function c(a,b,c){function d(b){return function(c){e||(e=!0,b.call(a,c))}}var e=!1;return[d(b),d(c)]}function d(){this.$$state={status:0}}function e(a,b){return function(c){b.call(a,c)}}function f(c){!c.processScheduled&&c.pending&&(c.processScheduled=!0,b(function(){var b,d,e;e=c.pending; -c.processScheduled=!1;c.pending=w;for(var f=0,g=e.length;f<g;++f){d=e[f][0];b=e[f][c.status];try{x(b)?d.resolve(b(c.value)):1===c.status?d.resolve(c.value):d.reject(c.value)}catch(h){d.reject(h),a(h)}}}))}function h(){this.promise=new d;this.resolve=e(this,this.resolve);this.reject=e(this,this.reject);this.notify=e(this,this.notify)}var g=I("$q",TypeError);P(d.prototype,{then:function(a,b,c){if(v(a)&&v(b)&&v(c))return this;var d=new h;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d, -a,b,c]);0<this.$$state.status&&f(this.$$state);return d.promise},"catch":function(a){return this.then(null,a)},"finally":function(a,b){return this.then(function(b){return k(b,!0,a)},function(b){return k(b,!1,a)},b)}});P(h.prototype,{resolve:function(a){this.promise.$$state.status||(a===this.promise?this.$$reject(g("qcycle",a)):this.$$resolve(a))},$$resolve:function(b){var d,e;e=c(this,this.$$resolve,this.$$reject);try{if(C(b)||x(b))d=b&&b.then;x(d)?(this.promise.$$state.status=-1,d.call(b,e[0],e[1], -this.notify)):(this.promise.$$state.value=b,this.promise.$$state.status=1,f(this.promise.$$state))}catch(g){e[1](g),a(g)}},reject:function(a){this.promise.$$state.status||this.$$reject(a)},$$reject:function(a){this.promise.$$state.value=a;this.promise.$$state.status=2;f(this.promise.$$state)},notify:function(c){var d=this.promise.$$state.pending;0>=this.promise.$$state.status&&d&&d.length&&b(function(){for(var b,e,f=0,g=d.length;f<g;f++){e=d[f][0];b=d[f][3];try{e.notify(x(b)?b(c):c)}catch(h){a(h)}}})}}); -var l=function(a,b){var c=new h;b?c.resolve(a):c.reject(a);return c.promise},k=function(a,b,c){var d=null;try{x(c)&&(d=c())}catch(e){return l(e,!1)}return d&&x(d.then)?d.then(function(){return l(a,b)},function(a){return l(a,!1)}):l(a,b)},n=function(a,b,c,d){var e=new h;e.resolve(a);return e.promise.then(b,c,d)},p=function t(a){if(!x(a))throw g("norslvr",a);if(!(this instanceof t))return new t(a);var b=new h;a(function(a){b.resolve(a)},function(a){b.reject(a)});return b.promise};p.defer=function(){return new h}; -p.reject=function(a){var b=new h;b.reject(a);return b.promise};p.when=n;p.resolve=n;p.all=function(a){var b=new h,c=0,d=J(a)?[]:{};m(a,function(a,e){c++;n(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise};return p}function wf(){this.$get=["$window","$timeout",function(b,a){var c=b.requestAnimationFrame||b.webkitRequestAnimationFrame,d=b.cancelAnimationFrame||b.webkitCancelAnimationFrame||b.webkitCancelRequestAnimationFrame, -e=!!c,f=e?function(a){var b=c(a);return function(){d(b)}}:function(b){var c=a(b,16.66,!1);return function(){a.cancel(c)}};f.supported=e;return f}]}function lf(){function b(a){function b(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$id=++nb;this.$$ChildScope=null}b.prototype=a;return b}var a=10,c=I("$rootScope"),d=null,e=null;this.digestTtl=function(b){arguments.length&&(a=b);return a};this.$get= -["$injector","$exceptionHandler","$parse","$browser",function(f,h,g,l){function k(a){a.currentScope.$$destroyed=!0}function n(){this.$id=++nb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this.$root=this;this.$$destroyed=!1;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$$isolateBindings=null}function p(a){if(q.$$phase)throw c("inprog",q.$$phase);q.$$phase=a}function r(a,b){do a.$$watchersCount+=b;while(a= -a.$parent)}function t(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function E(){}function s(){for(;w.length;)try{w.shift()()}catch(a){h(a)}e=null}function u(){null===e&&(e=l.defer(function(){q.$apply(s)}))}n.prototype={constructor:n,$new:function(a,c){var d;c=c||this;a?(d=new n,d.$root=this.$root):(this.$$ChildScope||(this.$$ChildScope=b(this)),d=new this.$$ChildScope);d.$parent=c;d.$$prevSibling=c.$$childTail;c.$$childHead?(c.$$childTail.$$nextSibling= -d,c.$$childTail=d):c.$$childHead=c.$$childTail=d;(a||c!=this)&&d.$on("$destroy",k);return d},$watch:function(a,b,c,e){var f=g(a);if(f.$$watchDelegate)return f.$$watchDelegate(this,b,c,f,a);var h=this,k=h.$$watchers,l={fn:b,last:E,get:f,exp:e||a,eq:!!c};d=null;x(b)||(l.fn=y);k||(k=h.$$watchers=[]);k.unshift(l);r(this,1);return function(){0<=cb(k,l)&&r(h,-1);d=null}},$watchGroup:function(a,b){function c(){h=!1;k?(k=!1,b(e,e,g)):b(e,d,g)}var d=Array(a.length),e=Array(a.length),f=[],g=this,h=!1,k=!0; -if(!a.length){var l=!0;g.$evalAsync(function(){l&&b(e,e,g)});return function(){l=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=a;d[0]=c;b(e,a===c?e:d,f)});m(a,function(a,b){var k=g.$watch(a,function(a,f){e[b]=a;d[b]=f;h||(h=!0,g.$evalAsync(c))});f.push(k)});return function(){for(;f.length;)f.shift()()}},$watchCollection:function(a,b){function c(a){e=a;var b,d,g,h;if(!v(e)){if(C(e))if(Da(e))for(f!==p&&(f=p,t=f.length=0,l++),a=e.length,t!==a&&(l++,f.length=t=a),b=0;b<a;b++)h=f[b], -g=e[b],d=h!==h&&g!==g,d||h===g||(l++,f[b]=g);else{f!==r&&(f=r={},t=0,l++);a=0;for(b in e)ta.call(e,b)&&(a++,g=e[b],h=f[b],b in f?(d=h!==h&&g!==g,d||h===g||(l++,f[b]=g)):(t++,f[b]=g,l++));if(t>a)for(b in l++,f)ta.call(e,b)||(t--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1<b.length,l=0,n=g(a,c),p=[],r={},q=!0,t=0;return this.$watch(n,function(){q?(q=!1,b(e,e,d)):b(e,h,d);if(k)if(C(e))if(Da(e)){h=Array(e.length);for(var a=0;a<e.length;a++)h[a]=e[a]}else for(a in h= -{},e)ta.call(e,a)&&(h[a]=e[a]);else h=e})},$digest:function(){var b,f,g,k,n,r,t=a,m,u=[],D,v;p("$digest");l.$$checkUrlChange();this===q&&null!==e&&(l.defer.cancel(e),s());d=null;do{r=!1;for(m=this;z.length;){try{v=z.shift(),v.scope.$eval(v.expression,v.locals)}catch(w){h(w)}d=null}a:do{if(k=m.$$watchers)for(n=k.length;n--;)try{if(b=k[n])if((f=b.get(m))!==(g=b.last)&&!(b.eq?ka(f,g):"number"===typeof f&&"number"===typeof g&&isNaN(f)&&isNaN(g)))r=!0,d=b,b.last=b.eq?ha(f,null):f,b.fn(f,g===E?f:g,m),5> -t&&(D=4-t,u[D]||(u[D]=[]),u[D].push({msg:x(b.exp)?"fn: "+(b.exp.name||b.exp.toString()):b.exp,newVal:f,oldVal:g}));else if(b===d){r=!1;break a}}catch(y){h(y)}if(!(k=m.$$watchersCount&&m.$$childHead||m!==this&&m.$$nextSibling))for(;m!==this&&!(k=m.$$nextSibling);)m=m.$parent}while(m=k);if((r||z.length)&&!t--)throw q.$$phase=null,c("infdig",a,u);}while(r||z.length);for(q.$$phase=null;N.length;)try{N.shift()()}catch(A){h(A)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy"); -this.$$destroyed=!0;this===q&&l.$$applicationDestroyed();r(this,-this.$$watchersCount);for(var b in this.$$listenerCount)t(this,this.$$listenerCount[b],b);a&&a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=y;this.$on= -this.$watch=this.$watchGroup=function(){return y};this.$$listeners={};this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=this.$$watchers=null}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){q.$$phase||z.length||l.defer(function(){z.length&&q.$digest()});z.push({scope:this,expression:a,locals:b})},$$postDigest:function(a){N.push(a)},$apply:function(a){try{p("$apply");try{return this.$eval(a)}finally{q.$$phase=null}}catch(b){h(b)}finally{try{q.$digest()}catch(c){throw h(c), -c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&w.push(b);u()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,t(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,f=!1,g={name:a,targetScope:e,stopPropagation:function(){f=!0},preventDefault:function(){g.defaultPrevented=!0},defaultPrevented:!1}, -k=db([g],arguments,1),l,n;do{d=e.$$listeners[a]||c;g.currentScope=e;l=0;for(n=d.length;l<n;l++)if(d[l])try{d[l].apply(null,k)}catch(p){h(p)}else d.splice(l,1),l--,n--;if(f)return g.currentScope=null,g;e=e.$parent}while(e);g.currentScope=null;return g},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var f=db([e],arguments,1),g,k;c=d;){e.currentScope=c;d=c.$$listeners[a]|| -[];g=0;for(k=d.length;g<k;g++)if(d[g])try{d[g].apply(null,f)}catch(l){h(l)}else d.splice(g,1),g--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var q=new n,z=q.$$asyncQueue=[],N=q.$$postDigestQueue=[],w=q.$$applyAsyncQueue=[];return q}]}function ge(){var b=/^\s*(https?|ftp|mailto|tel|file):/,a=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationWhitelist=function(a){return A(a)? -(b=a,this):b};this.imgSrcSanitizationWhitelist=function(b){return A(b)?(a=b,this):a};this.$get=function(){return function(c,d){var e=d?a:b,f;f=Aa(c).href;return""===f||f.match(e)?c:"unsafe:"+f}}}function Zf(b){if("self"===b)return b;if(G(b)){if(-1<b.indexOf("***"))throw Ca("iwcard",b);b=vd(b).replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return new RegExp("^"+b+"$")}if(Oa(b))return new RegExp("^"+b.source+"$");throw Ca("imatcher");}function wd(b){var a=[];A(b)&&m(b,function(b){a.push(Zf(b))}); -return a}function pf(){this.SCE_CONTEXTS=oa;var b=["self"],a=[];this.resourceUrlWhitelist=function(a){arguments.length&&(b=wd(a));return b};this.resourceUrlBlacklist=function(b){arguments.length&&(a=wd(b));return a};this.$get=["$injector",function(c){function d(a,b){return"self"===a?fd(b):!!a.exec(b.href)}function e(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()}; -return b}var f=function(a){throw Ca("unsafe");};c.has("$sanitize")&&(f=c.get("$sanitize"));var h=e(),g={};g[oa.HTML]=e(h);g[oa.CSS]=e(h);g[oa.URL]=e(h);g[oa.JS]=e(h);g[oa.RESOURCE_URL]=e(g[oa.URL]);return{trustAs:function(a,b){var c=g.hasOwnProperty(a)?g[a]:null;if(!c)throw Ca("icontext",a,b);if(null===b||v(b)||""===b)return b;if("string"!==typeof b)throw Ca("itype",a);return new c(b)},getTrusted:function(c,e){if(null===e||v(e)||""===e)return e;var h=g.hasOwnProperty(c)?g[c]:null;if(h&&e instanceof -h)return e.$$unwrapTrustedValue();if(c===oa.RESOURCE_URL){var h=Aa(e.toString()),p,r,t=!1;p=0;for(r=b.length;p<r;p++)if(d(b[p],h)){t=!0;break}if(t)for(p=0,r=a.length;p<r;p++)if(d(a[p],h)){t=!1;break}if(t)return e;throw Ca("insecurl",e.toString());}if(c===oa.HTML)return f(e);throw Ca("unsafe");},valueOf:function(a){return a instanceof h?a.$$unwrapTrustedValue():a}}}]}function of(){var b=!0;this.enabled=function(a){arguments.length&&(b=!!a);return b};this.$get=["$parse","$sceDelegate",function(a,c){if(b&& -8>Wa)throw Ca("iequirks");var d=ja(oa);d.isEnabled=function(){return b};d.trustAs=c.trustAs;d.getTrusted=c.getTrusted;d.valueOf=c.valueOf;b||(d.trustAs=d.getTrusted=function(a,b){return b},d.valueOf=$a);d.parseAs=function(b,c){var e=a(c);return e.literal&&e.constant?e:a(c,function(a){return d.getTrusted(b,a)})};var e=d.parseAs,f=d.getTrusted,h=d.trustAs;m(oa,function(a,b){var c=F(b);d[gb("parse_as_"+c)]=function(b){return e(a,b)};d[gb("get_trusted_"+c)]=function(b){return f(a,b)};d[gb("trust_as_"+ -c)]=function(b){return h(a,b)}});return d}]}function qf(){this.$get=["$window","$document",function(b,a){var c={},d=Y((/android (\d+)/.exec(F((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},h,g=/^(Moz|webkit|ms)(?=[A-Z])/,l=f.body&&f.body.style,k=!1,n=!1;if(l){for(var p in l)if(k=g.exec(p)){h=k[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in l&&"webkit");k=!!("transition"in l||h+"Transition"in l);n=!!("animation"in l||h+"Animation"in -l);!d||k&&n||(k=G(l.webkitTransition),n=G(l.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hasEvent:function(a){if("input"===a&&11>=Wa)return!1;if(v(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:Fa(),vendorPrefix:h,transitions:k,animations:n,android:d}}]}function sf(){this.$get=["$templateCache","$http","$q","$sce",function(b,a,c,d){function e(f,h){e.totalPendingRequests++;G(f)&&b.get(f)||(f=d.getTrustedResourceUrl(f));var g=a.defaults&&a.defaults.transformResponse; -J(g)?g=g.filter(function(a){return a!==Zb}):g===Zb&&(g=null);return a.get(f,{cache:b,transformResponse:g})["finally"](function(){e.totalPendingRequests--}).then(function(a){b.put(f,a.data);return a.data},function(a){if(!h)throw ga("tpload",f,a.status,a.statusText);return c.reject(a)})}e.totalPendingRequests=0;return e}]}function tf(){this.$get=["$rootScope","$browser","$location",function(b,a,c){return{findBindings:function(a,b,c){a=a.getElementsByClassName("ng-binding");var h=[];m(a,function(a){var d= -da.element(a).data("$binding");d&&m(d,function(d){c?(new RegExp("(^|\\s)"+vd(b)+"(\\s|\\||$)")).test(d)&&h.push(a):-1!=d.indexOf(b)&&h.push(a)})});return h},findModels:function(a,b,c){for(var h=["ng-","data-ng-","ng\\:"],g=0;g<h.length;++g){var l=a.querySelectorAll("["+h[g]+"model"+(c?"=":"*=")+'"'+b+'"]');if(l.length)return l}},getLocation:function(){return c.url()},setLocation:function(a){a!==c.url()&&(c.url(a),b.$digest())},whenStable:function(b){a.notifyWhenNoOutstandingRequests(b)}}}]}function uf(){this.$get= -["$rootScope","$browser","$q","$$q","$exceptionHandler",function(b,a,c,d,e){function f(f,l,k){x(f)||(k=l,l=f,f=y);var n=ua.call(arguments,3),p=A(k)&&!k,r=(p?d:c).defer(),t=r.promise,m;m=a.defer(function(){try{r.resolve(f.apply(null,n))}catch(a){r.reject(a),e(a)}finally{delete h[t.$$timeoutId]}p||b.$apply()},l);t.$$timeoutId=m;h[m]=r;return t}var h={};f.cancel=function(b){return b&&b.$$timeoutId in h?(h[b.$$timeoutId].reject("canceled"),delete h[b.$$timeoutId],a.defer.cancel(b.$$timeoutId)):!1};return f}]} -function Aa(b){Wa&&($.setAttribute("href",b),b=$.href);$.setAttribute("href",b);return{href:$.href,protocol:$.protocol?$.protocol.replace(/:$/,""):"",host:$.host,search:$.search?$.search.replace(/^\?/,""):"",hash:$.hash?$.hash.replace(/^#/,""):"",hostname:$.hostname,port:$.port,pathname:"/"===$.pathname.charAt(0)?$.pathname:"/"+$.pathname}}function fd(b){b=G(b)?Aa(b):b;return b.protocol===xd.protocol&&b.host===xd.host}function vf(){this.$get=qa(Q)}function yd(b){function a(a){try{return decodeURIComponent(a)}catch(b){return a}} -var c=b[0]||{},d={},e="";return function(){var b,h,g,l,k;b=c.cookie||"";if(b!==e)for(e=b,b=e.split("; "),d={},g=0;g<b.length;g++)h=b[g],l=h.indexOf("="),0<l&&(k=a(h.substring(0,l)),v(d[k])&&(d[k]=a(h.substring(l+1))));return d}}function zf(){this.$get=yd}function Kc(b){function a(c,d){if(C(c)){var e={};m(c,function(b,c){e[c]=a(c,b)});return e}return b.factory(c+"Filter",d)}this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+"Filter")}}];a("currency",zd);a("date",Ad); -a("filter",$f);a("json",ag);a("limitTo",bg);a("lowercase",cg);a("number",Bd);a("orderBy",Cd);a("uppercase",dg)}function $f(){return function(b,a,c){if(!Da(b)){if(null==b)return b;throw I("filter")("notarray",b);}var d;switch(gc(a)){case "function":break;case "boolean":case "null":case "number":case "string":d=!0;case "object":a=eg(a,c,d);break;default:return b}return Array.prototype.filter.call(b,a)}}function eg(b,a,c){var d=C(b)&&"$"in b;!0===a?a=ka:x(a)||(a=function(a,b){if(v(a))return!1;if(null=== -a||null===b)return a===b;if(C(b)||C(a)&&!qc(a))return!1;a=F(""+a);b=F(""+b);return-1!==a.indexOf(b)});return function(e){return d&&!C(e)?Ma(e,b.$,a,!1):Ma(e,b,a,c)}}function Ma(b,a,c,d,e){var f=gc(b),h=gc(a);if("string"===h&&"!"===a.charAt(0))return!Ma(b,a.substring(1),c,d);if(J(b))return b.some(function(b){return Ma(b,a,c,d)});switch(f){case "object":var g;if(d){for(g in b)if("$"!==g.charAt(0)&&Ma(b[g],a,c,!0))return!0;return e?!1:Ma(b,a,c,!1)}if("object"===h){for(g in a)if(e=a[g],!x(e)&&!v(e)&& -(f="$"===g,!Ma(f?b:b[g],e,c,f,f)))return!1;return!0}return c(b,a);case "function":return!1;default:return c(b,a)}}function gc(b){return null===b?"null":typeof b}function zd(b){var a=b.NUMBER_FORMATS;return function(b,d,e){v(d)&&(d=a.CURRENCY_SYM);v(e)&&(e=a.PATTERNS[1].maxFrac);return null==b?b:Dd(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,e).replace(/\u00A4/g,d)}}function Bd(b){var a=b.NUMBER_FORMATS;return function(b,d){return null==b?b:Dd(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Dd(b, -a,c,d,e){if(C(b))return"";var f=0>b;b=Math.abs(b);var h=Infinity===b;if(!h&&!isFinite(b))return"";var g=b+"",l="",k=!1,n=[];h&&(l="\u221e");if(!h&&-1!==g.indexOf("e")){var p=g.match(/([\d\.]+)e(-?)(\d+)/);p&&"-"==p[2]&&p[3]>e+1?b=0:(l=g,k=!0)}if(h||k)0<e&&1>b&&(l=b.toFixed(e),b=parseFloat(l),l=l.replace(hc,d));else{h=(g.split(hc)[1]||"").length;v(e)&&(e=Math.min(Math.max(a.minFrac,h),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);var h=(""+b).split(hc),g=h[0],h=h[1]||"",p=0, -r=a.lgSize,t=a.gSize;if(g.length>=r+t)for(p=g.length-r,k=0;k<p;k++)0===(p-k)%t&&0!==k&&(l+=c),l+=g.charAt(k);for(k=p;k<g.length;k++)0===(g.length-k)%r&&0!==k&&(l+=c),l+=g.charAt(k);for(;h.length<e;)h+="0";e&&"0"!==e&&(l+=d+h.substr(0,e))}0===b&&(f=!1);n.push(f?a.negPre:a.posPre,l,f?a.negSuf:a.posSuf);return n.join("")}function Gb(b,a,c){var d="";0>b&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function aa(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();if(0< -c||e>-c)e+=c;0===e&&-12==c&&(e=12);return Gb(e,a,d)}}function Hb(b,a){return function(c,d){var e=c["get"+b](),f=sb(a?"SHORT"+b:b);return d[f][e]}}function Ed(b){var a=(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function Fd(b){return function(a){var c=Ed(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+(4-a.getDay()))-+c;a=1+Math.round(a/6048E5);return Gb(a,b)}}function ic(b,a){return 0>=b.getFullYear()?a.ERAS[0]:a.ERAS[1]}function Ad(b){function a(a){var b;if(b= -a.match(c)){a=new Date(0);var f=0,h=0,g=b[8]?a.setUTCFullYear:a.setFullYear,l=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Y(b[9]+b[10]),h=Y(b[9]+b[11]));g.call(a,Y(b[1]),Y(b[2])-1,Y(b[3]));f=Y(b[4]||0)-f;h=Y(b[5]||0)-h;g=Y(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));l.call(a,f,h,g,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e,f){var h="",g=[],l,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;G(c)&&(c= -fg.test(c)?Y(c):a(c));V(c)&&(c=new Date(c));if(!ea(c)||!isFinite(c.getTime()))return c;for(;e;)(k=gg.exec(e))?(g=db(g,k,1),e=g.pop()):(g.push(e),e=null);var n=c.getTimezoneOffset();f&&(n=wc(f,c.getTimezoneOffset()),c=Ob(c,f,!0));m(g,function(a){l=hg[a];h+=l?l(c,b.DATETIME_FORMATS,n):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return h}}function ag(){return function(b,a){v(a)&&(a=2);return eb(b,a)}}function bg(){return function(b,a,c){a=Infinity===Math.abs(Number(a))?Number(a):Y(a);if(isNaN(a))return b; -V(b)&&(b=b.toString());if(!J(b)&&!G(b))return b;c=!c||isNaN(c)?0:Y(c);c=0>c&&c>=-b.length?b.length+c:c;return 0<=a?b.slice(c,c+a):0===c?b.slice(a,b.length):b.slice(Math.max(0,c+a),c)}}function Cd(b){function a(a,c){c=c?-1:1;return a.map(function(a){var d=1,g=$a;if(x(a))g=a;else if(G(a)){if("+"==a.charAt(0)||"-"==a.charAt(0))d="-"==a.charAt(0)?-1:1,a=a.substring(1);if(""!==a&&(g=b(a),g.constant))var l=g(),g=function(a){return a[l]}}return{get:g,descending:d*c}})}function c(a){switch(typeof a){case "number":case "boolean":case "string":return!0; -default:return!1}}return function(b,e,f){if(!Da(b))return b;J(e)||(e=[e]);0===e.length&&(e=["+"]);var h=a(e,f);h.push({get:function(){return{}},descending:f?-1:1});b=Array.prototype.map.call(b,function(a,b){return{value:a,predicateValues:h.map(function(d){var e=d.get(a);d=typeof e;if(null===e)d="string",e="null";else if("string"===d)e=e.toLowerCase();else if("object"===d)a:{if("function"===typeof e.valueOf&&(e=e.valueOf(),c(e)))break a;if(qc(e)&&(e=e.toString(),c(e)))break a;e=b}return{value:e,type:d}})}}); -b.sort(function(a,b){for(var c=0,d=0,e=h.length;d<e;++d){var c=a.predicateValues[d],f=b.predicateValues[d],t=0;c.type===f.type?c.value!==f.value&&(t=c.value<f.value?-1:1):t=c.type<f.type?-1:1;if(c=t*h[d].descending)break}return c});return b=b.map(function(a){return a.value})}}function Na(b){x(b)&&(b={link:b});b.restrict=b.restrict||"AC";return qa(b)}function Gd(b,a,c,d,e){var f=this,h=[];f.$error={};f.$$success={};f.$pending=w;f.$name=e(a.name||a.ngForm||"")(c);f.$dirty=!1;f.$pristine=!0;f.$valid= -!0;f.$invalid=!1;f.$submitted=!1;f.$$parentForm=Ib;f.$rollbackViewValue=function(){m(h,function(a){a.$rollbackViewValue()})};f.$commitViewValue=function(){m(h,function(a){a.$commitViewValue()})};f.$addControl=function(a){Ta(a.$name,"input");h.push(a);a.$name&&(f[a.$name]=a);a.$$parentForm=f};f.$$renameControl=function(a,b){var c=a.$name;f[c]===a&&delete f[c];f[b]=a;a.$name=b};f.$removeControl=function(a){a.$name&&f[a.$name]===a&&delete f[a.$name];m(f.$pending,function(b,c){f.$setValidity(c,null,a)}); -m(f.$error,function(b,c){f.$setValidity(c,null,a)});m(f.$$success,function(b,c){f.$setValidity(c,null,a)});cb(h,a);a.$$parentForm=Ib};Hd({ctrl:this,$element:b,set:function(a,b,c){var d=a[b];d?-1===d.indexOf(c)&&d.push(c):a[b]=[c]},unset:function(a,b,c){var d=a[b];d&&(cb(d,c),0===d.length&&delete a[b])},$animate:d});f.$setDirty=function(){d.removeClass(b,Ya);d.addClass(b,Jb);f.$dirty=!0;f.$pristine=!1;f.$$parentForm.$setDirty()};f.$setPristine=function(){d.setClass(b,Ya,Jb+" ng-submitted");f.$dirty= -!1;f.$pristine=!0;f.$submitted=!1;m(h,function(a){a.$setPristine()})};f.$setUntouched=function(){m(h,function(a){a.$setUntouched()})};f.$setSubmitted=function(){d.addClass(b,"ng-submitted");f.$submitted=!0;f.$$parentForm.$setSubmitted()}}function jc(b){b.$formatters.push(function(a){return b.$isEmpty(a)?a:a.toString()})}function jb(b,a,c,d,e,f){var h=F(a[0].type);if(!e.android){var g=!1;a.on("compositionstart",function(a){g=!0});a.on("compositionend",function(){g=!1;l()})}var l=function(b){k&&(f.defer.cancel(k), -k=null);if(!g){var e=a.val();b=b&&b.type;"password"===h||c.ngTrim&&"false"===c.ngTrim||(e=T(e));(d.$viewValue!==e||""===e&&d.$$hasNativeValidators)&&d.$setViewValue(e,b)}};if(e.hasEvent("input"))a.on("input",l);else{var k,n=function(a,b,c){k||(k=f.defer(function(){k=null;b&&b.value===c||l(a)}))};a.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b||n(a,this,this.value)});if(e.hasEvent("paste"))a.on("paste cut",n)}a.on("change",l);d.$render=function(){var b=d.$isEmpty(d.$viewValue)? -"":d.$viewValue;a.val()!==b&&a.val(b)}}function Kb(b,a){return function(c,d){var e,f;if(ea(c))return c;if(G(c)){'"'==c.charAt(0)&&'"'==c.charAt(c.length-1)&&(c=c.substring(1,c.length-1));if(ig.test(c))return new Date(c);b.lastIndex=0;if(e=b.exec(c))return e.shift(),f=d?{yyyy:d.getFullYear(),MM:d.getMonth()+1,dd:d.getDate(),HH:d.getHours(),mm:d.getMinutes(),ss:d.getSeconds(),sss:d.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},m(e,function(b,c){c<a.length&&(f[a[c]]=+b)}),new Date(f.yyyy, -f.MM-1,f.dd,f.HH,f.mm,f.ss||0,1E3*f.sss||0)}return NaN}}function kb(b,a,c,d){return function(e,f,h,g,l,k,n){function p(a){return a&&!(a.getTime&&a.getTime()!==a.getTime())}function r(a){return A(a)&&!ea(a)?c(a)||w:a}Id(e,f,h,g);jb(e,f,h,g,l,k);var t=g&&g.$options&&g.$options.timezone,m;g.$$parserName=b;g.$parsers.push(function(b){return g.$isEmpty(b)?null:a.test(b)?(b=c(b,m),t&&(b=Ob(b,t)),b):w});g.$formatters.push(function(a){if(a&&!ea(a))throw lb("datefmt",a);if(p(a))return(m=a)&&t&&(m=Ob(m,t,!0)), -n("date")(a,d,t);m=null;return""});if(A(h.min)||h.ngMin){var s;g.$validators.min=function(a){return!p(a)||v(s)||c(a)>=s};h.$observe("min",function(a){s=r(a);g.$validate()})}if(A(h.max)||h.ngMax){var u;g.$validators.max=function(a){return!p(a)||v(u)||c(a)<=u};h.$observe("max",function(a){u=r(a);g.$validate()})}}}function Id(b,a,c,d){(d.$$hasNativeValidators=C(a[0].validity))&&d.$parsers.push(function(b){var c=a.prop("validity")||{};return c.badInput&&!c.typeMismatch?w:b})}function Jd(b,a,c,d,e){if(A(d)){b= -b(d);if(!b.constant)throw lb("constexpr",c,d);return b(a)}return e}function kc(b,a){b="ngClass"+b;return["$animate",function(c){function d(a,b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],n=0;n<b.length;n++)if(e==b[n])continue a;c.push(e)}return c}function e(a){var b=[];return J(a)?(m(a,function(a){b=b.concat(e(a))}),b):G(a)?a.split(" "):C(a)?(m(a,function(a,c){a&&(b=b.concat(c.split(" ")))}),b):a}return{restrict:"AC",link:function(f,h,g){function l(a,b){var c=h.data("$classCounts")||fa(), -d=[];m(a,function(a){if(0<b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)});h.data("$classCounts",c);return d.join(" ")}function k(b){if(!0===a||f.$index%2===a){var k=e(b||[]);if(!n){var m=l(k,1);g.$addClass(m)}else if(!ka(b,n)){var s=e(n),m=d(k,s),k=d(s,k),m=l(m,1),k=l(k,-1);m&&m.length&&c.addClass(h,m);k&&k.length&&c.removeClass(h,k)}}n=ja(b)}var n;f.$watch(g[b],k,!0);g.$observe("class",function(a){k(f.$eval(g[b]))});"ngClass"!==b&&f.$watch("$index",function(c,d){var h=c&1;if(h!==(d&1)){var k= -e(f.$eval(g[b]));h===a?(h=l(k,1),g.$addClass(h)):(h=l(k,-1),g.$removeClass(h))}})}}}]}function Hd(b){function a(a,b){b&&!f[a]?(l.addClass(e,a),f[a]=!0):!b&&f[a]&&(l.removeClass(e,a),f[a]=!1)}function c(b,c){b=b?"-"+Ac(b,"-"):"";a(mb+b,!0===c);a(Kd+b,!1===c)}var d=b.ctrl,e=b.$element,f={},h=b.set,g=b.unset,l=b.$animate;f[Kd]=!(f[mb]=e.hasClass(mb));d.$setValidity=function(b,e,f){v(e)?(d.$pending||(d.$pending={}),h(d.$pending,b,f)):(d.$pending&&g(d.$pending,b,f),Ld(d.$pending)&&(d.$pending=w));bb(e)? -e?(g(d.$error,b,f),h(d.$$success,b,f)):(h(d.$error,b,f),g(d.$$success,b,f)):(g(d.$error,b,f),g(d.$$success,b,f));d.$pending?(a(Md,!0),d.$valid=d.$invalid=w,c("",null)):(a(Md,!1),d.$valid=Ld(d.$error),d.$invalid=!d.$valid,c("",d.$valid));e=d.$pending&&d.$pending[b]?w:d.$error[b]?!1:d.$$success[b]?!0:null;c(b,e);d.$$parentForm.$setValidity(b,e,d)}}function Ld(b){if(b)for(var a in b)if(b.hasOwnProperty(a))return!1;return!0}var jg=/^\/(.+)\/([a-z]*)$/,F=function(b){return G(b)?b.toLowerCase():b},ta=Object.prototype.hasOwnProperty, -sb=function(b){return G(b)?b.toUpperCase():b},Wa,B,ra,ua=[].slice,Nf=[].splice,kg=[].push,va=Object.prototype.toString,rc=Object.getPrototypeOf,Ea=I("ng"),da=Q.angular||(Q.angular={}),Rb,nb=0;Wa=X.documentMode;y.$inject=[];$a.$inject=[];var J=Array.isArray,tc=/^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/,T=function(b){return G(b)?b.trim():b},vd=function(b){return b.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")},Fa=function(){if(!A(Fa.rules)){var b= -X.querySelector("[ng-csp]")||X.querySelector("[data-ng-csp]");if(b){var a=b.getAttribute("ng-csp")||b.getAttribute("data-ng-csp");Fa.rules={noUnsafeEval:!a||-1!==a.indexOf("no-unsafe-eval"),noInlineStyle:!a||-1!==a.indexOf("no-inline-style")}}else{b=Fa;try{new Function(""),a=!1}catch(c){a=!0}b.rules={noUnsafeEval:a,noInlineStyle:!1}}}return Fa.rules},pb=function(){if(A(pb.name_))return pb.name_;var b,a,c=Qa.length,d,e;for(a=0;a<c;++a)if(d=Qa[a],b=X.querySelector("["+d.replace(":","\\:")+"jq]")){e= -b.getAttribute(d+"jq");break}return pb.name_=e},Qa=["ng-","data-ng-","ng:","x-ng-"],be=/[A-Z]/g,Bc=!1,Qb,pa=1,Pa=3,fe={full:"1.4.7",major:1,minor:4,dot:7,codeName:"dark-luminescence"};R.expando="ng339";var hb=R.cache={},Ff=1;R._data=function(b){return this.cache[b[this.expando]]||{}};var Af=/([\:\-\_]+(.))/g,Bf=/^moz([A-Z])/,lg={mouseleave:"mouseout",mouseenter:"mouseover"},Tb=I("jqLite"),Ef=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,Sb=/<|&#?\w+;/,Cf=/<([\w:-]+)/,Df=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi, -ma={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ma.optgroup=ma.option;ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead;ma.th=ma.td;var Ra=R.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===X.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),R(Q).on("load",a))}, -toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?B(this[b]):B(this[this.length+b])},length:0,push:kg,sort:[].sort,splice:[].splice},Bb={};m("multiple selected checked disabled readOnly required open".split(" "),function(b){Bb[F(b)]=b});var Sc={};m("input select option textarea button form details".split(" "),function(b){Sc[b]=!0});var $c={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"}; -m({data:Vb,removeData:vb,hasData:function(b){for(var a in hb[b.ng339])return!0;return!1}},function(b,a){R[a]=b});m({data:Vb,inheritedData:Ab,scope:function(b){return B.data(b,"$scope")||Ab(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return B.data(b,"$isolateScope")||B.data(b,"$isolateScopeNoTemplate")},controller:Pc,injector:function(b){return Ab(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:xb,css:function(b,a,c){a=gb(a);if(A(c))b.style[a]=c;else return b.style[a]}, -attr:function(b,a,c){var d=b.nodeType;if(d!==Pa&&2!==d&&8!==d)if(d=F(a),Bb[d])if(A(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||y).specified?d:w;else if(A(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?w:b},prop:function(b,a,c){if(A(c))b[a]=c;else return b[a]},text:function(){function b(a,b){if(v(b)){var d=a.nodeType;return d===pa||d===Pa?a.textContent:""}a.textContent=b}b.$dv="";return b}(), -val:function(b,a){if(v(a)){if(b.multiple&&"select"===wa(b)){var c=[];m(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(v(a))return b.innerHTML;ub(b,!0);b.innerHTML=a},empty:Qc},function(b,a){R.prototype[a]=function(a,d){var e,f,h=this.length;if(b!==Qc&&v(2==b.length&&b!==xb&&b!==Pc?a:d)){if(C(a)){for(e=0;e<h;e++)if(b===Vb)b(this[e],a);else for(f in a)b(this[e],f,a[f]);return this}e=b.$dv;h=v(e)?Math.min(h,1):h; -for(f=0;f<h;f++){var g=b(this[f],a,d);e=e?e+g:g}return e}for(e=0;e<h;e++)b(this[e],a,d);return this}});m({removeData:vb,on:function a(c,d,e,f){if(A(f))throw Tb("onargs");if(Lc(c)){var h=wb(c,!0);f=h.events;var g=h.handle;g||(g=h.handle=Hf(c,f));for(var h=0<=d.indexOf(" ")?d.split(" "):[d],l=h.length;l--;){d=h[l];var k=f[d];k||(f[d]=[],"mouseenter"===d||"mouseleave"===d?a(c,lg[d],function(a){var c=a.relatedTarget;c&&(c===this||this.contains(c))||g(a,d)}):"$destroy"!==d&&c.addEventListener(d,g,!1), -k=f[d]);k.push(e)}}},off:Oc,one:function(a,c,d){a=B(a);a.on(c,function f(){a.off(c,d);a.off(c,f)});a.on(c,d)},replaceWith:function(a,c){var d,e=a.parentNode;ub(a);m(new R(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];m(a.childNodes,function(a){a.nodeType===pa&&c.push(a)});return c},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,c){var d=a.nodeType;if(d===pa||11===d){c=new R(c);for(var d=0,e=c.length;d< -e;d++)a.appendChild(c[d])}},prepend:function(a,c){if(a.nodeType===pa){var d=a.firstChild;m(new R(c),function(c){a.insertBefore(c,d)})}},wrap:function(a,c){c=B(c).eq(0).clone()[0];var d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:Wb,detach:function(a){Wb(a,!0)},after:function(a,c){var d=a,e=a.parentNode;c=new R(c);for(var f=0,h=c.length;f<h;f++){var g=c[f];e.insertBefore(g,d.nextSibling);d=g}},addClass:zb,removeClass:yb,toggleClass:function(a,c,d){c&&m(c.split(" "),function(c){var f= -d;v(f)&&(f=!xb(a,c));(f?zb:yb)(a,c)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},find:function(a,c){return a.getElementsByTagName?a.getElementsByTagName(c):[]},clone:Ub,triggerHandler:function(a,c,d){var e,f,h=c.type||c,g=wb(a);if(g=(g=g&&g.events)&&g[h])e={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopImmediatePropagation:function(){this.immediatePropagationStopped= -!0},isImmediatePropagationStopped:function(){return!0===this.immediatePropagationStopped},stopPropagation:y,type:h,target:a},c.type&&(e=P(e,c)),c=ja(g),f=d?[e].concat(d):[e],m(c,function(c){e.isImmediatePropagationStopped()||c.apply(a,f)})}},function(a,c){R.prototype[c]=function(c,e,f){for(var h,g=0,l=this.length;g<l;g++)v(h)?(h=a(this[g],c,e,f),A(h)&&(h=B(h))):Nc(h,a(this[g],c,e,f));return A(h)?h:this};R.prototype.bind=R.prototype.on;R.prototype.unbind=R.prototype.off});Ua.prototype={put:function(a, -c){this[Ga(a,this.nextUid)]=c},get:function(a){return this[Ga(a,this.nextUid)]},remove:function(a){var c=this[a=Ga(a,this.nextUid)];delete this[a];return c}};var yf=[function(){this.$get=[function(){return Ua}]}],Uc=/^[^\(]*\(\s*([^\)]*)\)/m,mg=/,/,ng=/^\s*(_?)(\S+?)\1\s*$/,Tc=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ha=I("$injector");fb.$$annotate=function(a,c,d){var e;if("function"===typeof a){if(!(e=a.$inject)){e=[];if(a.length){if(c)throw G(d)&&d||(d=a.name||If(a)),Ha("strictdi",d);c=a.toString().replace(Tc, -"");c=c.match(Uc);m(c[1].split(mg),function(a){a.replace(ng,function(a,c,d){e.push(d)})})}a.$inject=e}}else J(a)?(c=a.length-1,Sa(a[c],"fn"),e=a.slice(0,c)):Sa(a,"fn",!0);return e};var Nd=I("$animate"),Ue=function(){this.$get=["$q","$$rAF",function(a,c){function d(){}d.all=y;d.chain=y;d.prototype={end:y,cancel:y,resume:y,pause:y,complete:y,then:function(d,f){return a(function(a){c(function(){a()})}).then(d,f)}};return d}]},Te=function(){var a=new Ua,c=[];this.$get=["$$AnimateRunner","$rootScope", -function(d,e){function f(a,c,d){var e=!1;c&&(c=G(c)?c.split(" "):J(c)?c:[],m(c,function(c){c&&(e=!0,a[c]=d)}));return e}function h(){m(c,function(c){var d=a.get(c);if(d){var e=Jf(c.attr("class")),f="",h="";m(d,function(a,c){a!==!!e[c]&&(a?f+=(f.length?" ":"")+c:h+=(h.length?" ":"")+c)});m(c,function(a){f&&zb(a,f);h&&yb(a,h)});a.remove(c)}});c.length=0}return{enabled:y,on:y,off:y,pin:y,push:function(g,l,k,n){n&&n();k=k||{};k.from&&g.css(k.from);k.to&&g.css(k.to);if(k.addClass||k.removeClass)if(l=k.addClass, -n=k.removeClass,k=a.get(g)||{},l=f(k,l,!0),n=f(k,n,!1),l||n)a.put(g,k),c.push(g),1===c.length&&e.$$postDigest(h);return new d}}}]},Re=["$provide",function(a){var c=this;this.$$registeredAnimations=Object.create(null);this.register=function(d,e){if(d&&"."!==d.charAt(0))throw Nd("notcsel",d);var f=d+"-animation";c.$$registeredAnimations[d.substr(1)]=f;a.factory(f,e)};this.classNameFilter=function(a){if(1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw Nd("nongcls", -"ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function c(a,d,e){if(e){var l;a:{for(l=0;l<e.length;l++){var k=e[l];if(1===k.nodeType){l=k;break a}}l=void 0}!l||l.parentNode||l.previousElementSibling||(e=null)}e?e.after(a):d.prepend(a)}return{on:a.on,off:a.off,pin:a.pin,enabled:a.enabled,cancel:function(a){a.end&&a.end()},enter:function(f,h,g,l){h=h&&B(h);g=g&&B(g);h=h||g.parent();c(f,h,g);return a.push(f,"enter",Ia(l))},move:function(f,h,g,l){h=h&&B(h);g=g&&B(g); -h=h||g.parent();c(f,h,g);return a.push(f,"move",Ia(l))},leave:function(c,e){return a.push(c,"leave",Ia(e),function(){c.remove()})},addClass:function(c,e,g){g=Ia(g);g.addClass=ib(g.addclass,e);return a.push(c,"addClass",g)},removeClass:function(c,e,g){g=Ia(g);g.removeClass=ib(g.removeClass,e);return a.push(c,"removeClass",g)},setClass:function(c,e,g,l){l=Ia(l);l.addClass=ib(l.addClass,e);l.removeClass=ib(l.removeClass,g);return a.push(c,"setClass",l)},animate:function(c,e,g,l,k){k=Ia(k);k.from=k.from? -P(k.from,e):e;k.to=k.to?P(k.to,g):g;k.tempClasses=ib(k.tempClasses,l||"ng-inline-animate");return a.push(c,"animate",k)}}}]}],Se=function(){this.$get=["$$rAF","$q",function(a,c){var d=function(){};d.prototype={done:function(a){this.defer&&this.defer[!0===a?"reject":"resolve"]()},end:function(){this.done()},cancel:function(){this.done(!0)},getPromise:function(){this.defer||(this.defer=c.defer());return this.defer.promise},then:function(a,c){return this.getPromise().then(a,c)},"catch":function(a){return this.getPromise()["catch"](a)}, -"finally":function(a){return this.getPromise()["finally"](a)}};return function(c,f){function h(){a(function(){f.addClass&&(c.addClass(f.addClass),f.addClass=null);f.removeClass&&(c.removeClass(f.removeClass),f.removeClass=null);f.to&&(c.css(f.to),f.to=null);g||l.done();g=!0});return l}f.cleanupStyles&&(f.from=f.to=null);f.from&&(c.css(f.from),f.from=null);var g,l=new d;return{start:h,end:h}}}]},ga=I("$compile");Dc.$inject=["$provide","$$sanitizeUriProvider"];var Wc=/^((?:x|data)[\:\-_])/i,Of=I("$controller"), -Vc=/^(\S+)(\s+as\s+(\w+))?$/,$e=function(){this.$get=["$document",function(a){return function(c){c?!c.nodeType&&c instanceof B&&(c=c[0]):c=a[0].body;return c.offsetWidth+1}}]},ad="application/json",$b={"Content-Type":ad+";charset=utf-8"},Qf=/^\[|^\{(?!\{)/,Rf={"[":/]$/,"{":/}$/},Pf=/^\)\]\}',?\n/,og=I("$http"),ed=function(a){return function(){throw og("legacy",a);}},La=da.$interpolateMinErr=I("$interpolate");La.throwNoconcat=function(a){throw La("noconcat",a);};La.interr=function(a,c){return La("interr", -a,c.toString())};var pg=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,Tf={http:80,https:443,ftp:21},Db=I("$location"),qg={$$html5:!1,$$replace:!1,absUrl:Eb("$$absUrl"),url:function(a){if(v(a))return this.$$url;var c=pg.exec(a);(c[1]||""===a)&&this.path(decodeURIComponent(c[1]));(c[2]||c[1]||""===a)&&this.search(c[3]||"");this.hash(c[5]||"");return this},protocol:Eb("$$protocol"),host:Eb("$$host"),port:Eb("$$port"),path:jd("$$path",function(a){a=null!==a?a.toString():"";return"/"==a.charAt(0)?a:"/"+a}),search:function(a, -c){switch(arguments.length){case 0:return this.$$search;case 1:if(G(a)||V(a))a=a.toString(),this.$$search=yc(a);else if(C(a))a=ha(a,{}),m(a,function(c,e){null==c&&delete a[e]}),this.$$search=a;else throw Db("isrcharg");break;default:v(c)||null===c?delete this.$$search[a]:this.$$search[a]=c}this.$$compose();return this},hash:jd("$$hash",function(a){return null!==a?a.toString():""}),replace:function(){this.$$replace=!0;return this}};m([id,cc,bc],function(a){a.prototype=Object.create(qg);a.prototype.state= -function(c){if(!arguments.length)return this.$$state;if(a!==bc||!this.$$html5)throw Db("nostate");this.$$state=v(c)?null:c;return this}});var Z=I("$parse"),Uf=Function.prototype.call,Vf=Function.prototype.apply,Wf=Function.prototype.bind,Lb=fa();m("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(a){Lb[a]=!0});var rg={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},ec=function(a){this.options=a};ec.prototype={constructor:ec,lex:function(a){this.text=a;this.index=0;for(this.tokens= -[];this.index<this.text.length;)if(a=this.text.charAt(this.index),'"'===a||"'"===a)this.readString(a);else if(this.isNumber(a)||"."===a&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdent(a))this.readIdent();else if(this.is(a,"(){}[].,;:?"))this.tokens.push({index:this.index,text:a}),this.index++;else if(this.isWhitespace(a))this.index++;else{var c=a+this.peek(),d=c+this.peek(2),e=Lb[c],f=Lb[d];Lb[a]||e||f?(a=f?d:e?c:a,this.tokens.push({index:this.index,text:a,operator:!0}),this.index+= -a.length):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a,c){return-1!==c.indexOf(a)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"=== -a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=A(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw Z("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index<this.text.length;){var d=F(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=this.peek();if("e"==d&&this.isExpOperator(e))a+=d;else if(this.isExpOperator(d)&&e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)|| -e&&this.isNumber(e)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:c,text:a,constant:!0,value:Number(a)})},readIdent:function(){for(var a=this.index;this.index<this.text.length;){var c=this.text.charAt(this.index);if(!this.isIdent(c)&&!this.isNumber(c))break;this.index++}this.tokens.push({index:a,text:this.text.slice(a,this.index),identifier:!0})},readString:function(a){var c=this.index;this.index++;for(var d="",e=a,f=!1;this.index<this.text.length;){var h= -this.text.charAt(this.index),e=e+h;if(f)"u"===h?(f=this.text.substring(this.index+1,this.index+5),f.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+f+"]"),this.index+=4,d+=String.fromCharCode(parseInt(f,16))):d+=rg[h]||h,f=!1;else if("\\"===h)f=!0;else{if(h===a){this.index++;this.tokens.push({index:c,text:e,constant:!0,value:d});return}d+=h}this.index++}this.throwError("Unterminated quote",c)}};var s=function(a,c){this.lexer=a;this.options=c};s.Program="Program";s.ExpressionStatement= -"ExpressionStatement";s.AssignmentExpression="AssignmentExpression";s.ConditionalExpression="ConditionalExpression";s.LogicalExpression="LogicalExpression";s.BinaryExpression="BinaryExpression";s.UnaryExpression="UnaryExpression";s.CallExpression="CallExpression";s.MemberExpression="MemberExpression";s.Identifier="Identifier";s.Literal="Literal";s.ArrayExpression="ArrayExpression";s.Property="Property";s.ObjectExpression="ObjectExpression";s.ThisExpression="ThisExpression";s.NGValueParameter="NGValueParameter"; -s.prototype={ast:function(a){this.text=a;this.tokens=this.lexer.lex(a);a=this.program();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);return a},program:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.expressionStatement()),!this.expect(";"))return{type:s.Program,body:a}},expressionStatement:function(){return{type:s.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var a=this.expression();this.expect("|");)a= -this.filter(a);return a},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary();this.expect("=")&&(a={type:s.AssignmentExpression,left:a,right:this.assignment(),operator:"="});return a},ternary:function(){var a=this.logicalOR(),c,d;return this.expect("?")&&(c=this.expression(),this.consume(":"))?(d=this.expression(),{type:s.ConditionalExpression,test:a,alternate:c,consequent:d}):a},logicalOR:function(){for(var a=this.logicalAND();this.expect("||");)a={type:s.LogicalExpression, -operator:"||",left:a,right:this.logicalAND()};return a},logicalAND:function(){for(var a=this.equality();this.expect("&&");)a={type:s.LogicalExpression,operator:"&&",left:a,right:this.equality()};return a},equality:function(){for(var a=this.relational(),c;c=this.expect("==","!=","===","!==");)a={type:s.BinaryExpression,operator:c.text,left:a,right:this.relational()};return a},relational:function(){for(var a=this.additive(),c;c=this.expect("<",">","<=",">=");)a={type:s.BinaryExpression,operator:c.text, -left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a={type:s.BinaryExpression,operator:c.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:c.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()}, -primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.constants.hasOwnProperty(this.peek().text)?a=ha(this.constants[this.consume().text]):this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var c;c=this.expect("(","[",".");)"("===c.text?(a={type:s.CallExpression,callee:a,arguments:this.parseArguments()}, -this.consume(")")):"["===c.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===c.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var c={type:s.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return c},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.expression()); -while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:s.ArrayExpression,elements:a}},object:function(){var a=[],c;if("}"!==this.peekToken().text){do{if(this.peek("}"))break; -c={type:s.Property,kind:"init"};this.peek().constant?c.key=this.constant():this.peek().identifier?c.key=this.identifier():this.throwError("invalid key",this.peek());this.consume(":");c.value=this.expression();a.push(c)}while(this.expect(","))}this.consume("}");return{type:s.ObjectExpression,properties:a}},throwError:function(a,c){throw Z("syntax",c.text,a,c.index+1,this.text,this.text.substring(c.index));},consume:function(a){if(0===this.tokens.length)throw Z("ueoe",this.text);var c=this.expect(a); -c||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return c},peekToken:function(){if(0===this.tokens.length)throw Z("ueoe",this.text);return this.tokens[0]},peek:function(a,c,d,e){return this.peekAhead(0,a,c,d,e)},peekAhead:function(a,c,d,e,f){if(this.tokens.length>a){a=this.tokens[a];var h=a.text;if(h===c||h===d||h===e||h===f||!(c||d||e||f))return a}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},constants:{"true":{type:s.Literal,value:!0}, -"false":{type:s.Literal,value:!1},"null":{type:s.Literal,value:null},undefined:{type:s.Literal,value:w},"this":{type:s.ThisExpression}}};sd.prototype={compile:function(a,c){var d=this,e=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:c,fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};U(e,d.$filter);var f="",h;this.stage="assign";if(h=qd(e))this.state.computing="assign",f=this.nextId(),this.recurse(h,f),this.return_(f),f="fn.assign="+this.generateFunction("assign", -"s,v,l");h=od(e.body);d.stage="inputs";m(h,function(a,c){var e="fn"+c;d.state[e]={vars:[],body:[],own:{}};d.state.computing=e;var f=d.nextId();d.recurse(a,f);d.return_(f);d.state.inputs.push(e);a.watchId=c});this.state.computing="fn";this.stage="main";this.recurse(e);f='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+f+this.watchFns()+"return fn;";f=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","getStringValue", -"ensureSafeAssignContext","ifDefined","plus","text",f))(this.$filter,Xa,Ba,ld,kd,md,Xf,nd,a);this.state=this.stage=w;f.literal=rd(e);f.constant=e.constant;return f},USE:"use",STRICT:"strict",watchFns:function(){var a=[],c=this.state.inputs,d=this;m(c,function(c){a.push("var "+c+"="+d.generateFunction(c,"s"))});c.length&&a.push("fn.inputs=["+c.join(",")+"];");return a.join("")},generateFunction:function(a,c){return"function("+c+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a= -[],c=this;m(this.state.filters,function(d,e){a.push(d+"=$filter("+c.escape(e)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,c,d,e,f,h){var g,l,k=this,n,p;e=e||y;if(!h&&A(a.watchId))c=c||this.nextId(),this.if_("i",this.lazyAssign(c,this.computedMember("i",a.watchId)),this.lazyRecurse(a,c,d,e,f,!0));else switch(a.type){case s.Program:m(a.body, -function(c,d){k.recurse(c.expression,w,w,function(a){l=a});d!==a.body.length-1?k.current().body.push(l,";"):k.return_(l)});break;case s.Literal:p=this.escape(a.value);this.assign(c,p);e(p);break;case s.UnaryExpression:this.recurse(a.argument,w,w,function(a){l=a});p=a.operator+"("+this.ifDefined(l,0)+")";this.assign(c,p);e(p);break;case s.BinaryExpression:this.recurse(a.left,w,w,function(a){g=a});this.recurse(a.right,w,w,function(a){l=a});p="+"===a.operator?this.plus(g,l):"-"===a.operator?this.ifDefined(g, -0)+a.operator+this.ifDefined(l,0):"("+g+")"+a.operator+"("+l+")";this.assign(c,p);e(p);break;case s.LogicalExpression:c=c||this.nextId();k.recurse(a.left,c);k.if_("&&"===a.operator?c:k.not(c),k.lazyRecurse(a.right,c));e(c);break;case s.ConditionalExpression:c=c||this.nextId();k.recurse(a.test,c);k.if_(c,k.lazyRecurse(a.alternate,c),k.lazyRecurse(a.consequent,c));e(c);break;case s.Identifier:c=c||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l", -a.name)+"?l:s"),d.computed=!1,d.name=a.name);Xa(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){f&&1!==f&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(c,k.nonComputedMember("s",a.name))})},c&&k.lazyAssign(c,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Fb(a.name))&&k.addEnsureSafeObject(c);e(c);break;case s.MemberExpression:g=d&&(d.context=this.nextId())|| -this.nextId();c=c||this.nextId();k.recurse(a.object,g,w,function(){k.if_(k.notNull(g),function(){if(a.computed)l=k.nextId(),k.recurse(a.property,l),k.getStringValue(l),k.addEnsureSafeMemberName(l),f&&1!==f&&k.if_(k.not(k.computedMember(g,l)),k.lazyAssign(k.computedMember(g,l),"{}")),p=k.ensureSafeObject(k.computedMember(g,l)),k.assign(c,p),d&&(d.computed=!0,d.name=l);else{Xa(a.property.name);f&&1!==f&&k.if_(k.not(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name), -"{}"));p=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Fb(a.property.name))p=k.ensureSafeObject(p);k.assign(c,p);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(c,"undefined")});e(c)},!!f);break;case s.CallExpression:c=c||this.nextId();a.filter?(l=k.filter(a.callee.name),n=[],m(a.arguments,function(a){var c=k.nextId();k.recurse(a,c);n.push(c)}),p=l+"("+n.join(",")+")",k.assign(c,p),e(c)):(l=k.nextId(),g={},n=[],k.recurse(a.callee,l,g,function(){k.if_(k.notNull(l), -function(){k.addEnsureSafeFunction(l);m(a.arguments,function(a){k.recurse(a,k.nextId(),w,function(a){n.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),p=k.member(g.context,g.name,g.computed)+"("+n.join(",")+")"):p=l+"("+n.join(",")+")";p=k.ensureSafeObject(p);k.assign(c,p)},function(){k.assign(c,"undefined")});e(c)}));break;case s.AssignmentExpression:l=this.nextId();g={};if(!pd(a.left))throw Z("lval");this.recurse(a.left,w,g,function(){k.if_(k.notNull(g.context), -function(){k.recurse(a.right,l);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);p=k.member(g.context,g.name,g.computed)+a.operator+l;k.assign(c,p);e(c||p)})},1);break;case s.ArrayExpression:n=[];m(a.elements,function(a){k.recurse(a,k.nextId(),w,function(a){n.push(a)})});p="["+n.join(",")+"]";this.assign(c,p);e(p);break;case s.ObjectExpression:n=[];m(a.properties,function(a){k.recurse(a.value,k.nextId(),w,function(c){n.push(k.escape(a.key.type=== -s.Identifier?a.key.name:""+a.key.value)+":"+c)})});p="{"+n.join(",")+"}";this.assign(c,p);e(p);break;case s.ThisExpression:this.assign(c,"s");e("s");break;case s.NGValueParameter:this.assign(c,"v"),e("v")}},getHasOwnProperty:function(a,c){var d=a+"."+c,e=this.current().own;e.hasOwnProperty(d)||(e[d]=this.nextId(!1,a+"&&("+this.escape(c)+" in "+a+")"));return e[d]},assign:function(a,c){if(a)return this.current().body.push(a,"=",c,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]= -this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,c){return"ifDefined("+a+","+this.escape(c)+")"},plus:function(a,c){return"plus("+a+","+c+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,c,d){if(!0===a)c();else{var e=this.current().body;e.push("if(",a,"){");c();e.push("}");d&&(e.push("else{"),d(),e.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,c){return a+"."+c},computedMember:function(a, -c){return a+"["+c+"]"},member:function(a,c,d){return d?this.computedMember(a,c):this.nonComputedMember(a,c)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),";")},addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a),";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+ -a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+",text)")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+a+",text)"},lazyRecurse:function(a,c,d,e,f,h){var g=this;return function(){g.recurse(a,c,d,e,f,h)}},lazyAssign:function(a,c){var d=this;return function(){d.assign(a,c)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g, -stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(G(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(V(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===typeof a)return"undefined";throw Z("esc");},nextId:function(a,c){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(c?"="+c:""));return d},current:function(){return this.state[this.state.computing]}}; -td.prototype={compile:function(a,c){var d=this,e=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=c;U(e,d.$filter);var f,h;if(f=qd(e))h=this.recurse(f);f=od(e.body);var g;f&&(g=[],m(f,function(a,c){var e=d.recurse(a);a.input=e;g.push(e);a.watchId=c}));var l=[];m(e.body,function(a){l.push(d.recurse(a.expression))});f=0===e.body.length?function(){}:1===e.body.length?l[0]:function(a,c){var d;m(l,function(e){d=e(a,c)});return d};h&&(f.assign=function(a,c,d){return h(a,d,c)});g&&(f.inputs= -g);f.literal=rd(e);f.constant=e.constant;return f},recurse:function(a,c,d){var e,f,h=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case s.Literal:return this.value(a.value,c);case s.UnaryExpression:return f=this.recurse(a.argument),this["unary"+a.operator](f,c);case s.BinaryExpression:return e=this.recurse(a.left),f=this.recurse(a.right),this["binary"+a.operator](e,f,c);case s.LogicalExpression:return e=this.recurse(a.left),f=this.recurse(a.right),this["binary"+a.operator](e, -f,c);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),c);case s.Identifier:return Xa(a.name,h.expression),h.identifier(a.name,h.expensiveChecks||Fb(a.name),c,d,h.expression);case s.MemberExpression:return e=this.recurse(a.object,!1,!!d),a.computed||(Xa(a.property.name,h.expression),f=a.property.name),a.computed&&(f=this.recurse(a.property)),a.computed?this.computedMember(e,f,c,d,h.expression):this.nonComputedMember(e,f, -h.expensiveChecks,c,d,h.expression);case s.CallExpression:return g=[],m(a.arguments,function(a){g.push(h.recurse(a))}),a.filter&&(f=this.$filter(a.callee.name)),a.filter||(f=this.recurse(a.callee,!0)),a.filter?function(a,d,e,h){for(var r=[],m=0;m<g.length;++m)r.push(g[m](a,d,e,h));a=f.apply(w,r,h);return c?{context:w,name:w,value:a}:a}:function(a,d,e,p){var r=f(a,d,e,p),m;if(null!=r.value){Ba(r.context,h.expression);ld(r.value,h.expression);m=[];for(var s=0;s<g.length;++s)m.push(Ba(g[s](a,d,e,p), -h.expression));m=Ba(r.value.apply(r.context,m),h.expression)}return c?{value:m}:m};case s.AssignmentExpression:return e=this.recurse(a.left,!0,1),f=this.recurse(a.right),function(a,d,g,p){var r=e(a,d,g,p);a=f(a,d,g,p);Ba(r.value,h.expression);md(r.context);r.context[r.name]=a;return c?{value:a}:a};case s.ArrayExpression:return g=[],m(a.elements,function(a){g.push(h.recurse(a))}),function(a,d,e,f){for(var h=[],m=0;m<g.length;++m)h.push(g[m](a,d,e,f));return c?{value:h}:h};case s.ObjectExpression:return g= -[],m(a.properties,function(a){g.push({key:a.key.type===s.Identifier?a.key.name:""+a.key.value,value:h.recurse(a.value)})}),function(a,d,e,f){for(var h={},m=0;m<g.length;++m)h[g[m].key]=g[m].value(a,d,e,f);return c?{value:h}:h};case s.ThisExpression:return function(a){return c?{value:a}:a};case s.NGValueParameter:return function(a,d,e,f){return c?{value:e}:e}}},"unary+":function(a,c){return function(d,e,f,h){d=a(d,e,f,h);d=A(d)?+d:0;return c?{value:d}:d}},"unary-":function(a,c){return function(d,e, -f,h){d=a(d,e,f,h);d=A(d)?-d:0;return c?{value:d}:d}},"unary!":function(a,c){return function(d,e,f,h){d=!a(d,e,f,h);return c?{value:d}:d}},"binary+":function(a,c,d){return function(e,f,h,g){var l=a(e,f,h,g);e=c(e,f,h,g);l=nd(l,e);return d?{value:l}:l}},"binary-":function(a,c,d){return function(e,f,h,g){var l=a(e,f,h,g);e=c(e,f,h,g);l=(A(l)?l:0)-(A(e)?e:0);return d?{value:l}:l}},"binary*":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)*c(e,f,h,g);return d?{value:e}:e}},"binary/":function(a,c, -d){return function(e,f,h,g){e=a(e,f,h,g)/c(e,f,h,g);return d?{value:e}:e}},"binary%":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)%c(e,f,h,g);return d?{value:e}:e}},"binary===":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)===c(e,f,h,g);return d?{value:e}:e}},"binary!==":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)!==c(e,f,h,g);return d?{value:e}:e}},"binary==":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)==c(e,f,h,g);return d?{value:e}:e}},"binary!=":function(a,c, -d){return function(e,f,h,g){e=a(e,f,h,g)!=c(e,f,h,g);return d?{value:e}:e}},"binary<":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)<c(e,f,h,g);return d?{value:e}:e}},"binary>":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)>c(e,f,h,g);return d?{value:e}:e}},"binary<=":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)<=c(e,f,h,g);return d?{value:e}:e}},"binary>=":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)>=c(e,f,h,g);return d?{value:e}:e}},"binary&&":function(a,c,d){return function(e, -f,h,g){e=a(e,f,h,g)&&c(e,f,h,g);return d?{value:e}:e}},"binary||":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)||c(e,f,h,g);return d?{value:e}:e}},"ternary?:":function(a,c,d,e){return function(f,h,g,l){f=a(f,h,g,l)?c(f,h,g,l):d(f,h,g,l);return e?{value:f}:f}},value:function(a,c){return function(){return c?{context:w,name:w,value:a}:a}},identifier:function(a,c,d,e,f){return function(h,g,l,k){h=g&&a in g?g:h;e&&1!==e&&h&&!h[a]&&(h[a]={});g=h?h[a]:w;c&&Ba(g,f);return d?{context:h,name:a,value:g}: -g}},computedMember:function(a,c,d,e,f){return function(h,g,l,k){var n=a(h,g,l,k),p,m;null!=n&&(p=c(h,g,l,k),p=kd(p),Xa(p,f),e&&1!==e&&n&&!n[p]&&(n[p]={}),m=n[p],Ba(m,f));return d?{context:n,name:p,value:m}:m}},nonComputedMember:function(a,c,d,e,f,h){return function(g,l,k,n){g=a(g,l,k,n);f&&1!==f&&g&&!g[c]&&(g[c]={});l=null!=g?g[c]:w;(d||Fb(c))&&Ba(l,h);return e?{context:g,name:c,value:l}:l}},inputs:function(a,c){return function(d,e,f,h){return h?h[c]:a(d,e,f)}}};var fc=function(a,c,d){this.lexer= -a;this.$filter=c;this.options=d;this.ast=new s(this.lexer);this.astCompiler=d.csp?new td(this.ast,c):new sd(this.ast,c)};fc.prototype={constructor:fc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};fa();fa();var Yf=Object.prototype.valueOf,Ca=I("$sce"),oa={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},ga=I("$compile"),$=X.createElement("a"),xd=Aa(Q.location.href);yd.$inject=["$document"];Kc.$inject=["$provide"];zd.$inject=["$locale"];Bd.$inject= -["$locale"];var hc=".",hg={yyyy:aa("FullYear",4),yy:aa("FullYear",2,0,!0),y:aa("FullYear",1),MMMM:Hb("Month"),MMM:Hb("Month",!0),MM:aa("Month",2,1),M:aa("Month",1,1),dd:aa("Date",2),d:aa("Date",1),HH:aa("Hours",2),H:aa("Hours",1),hh:aa("Hours",2,-12),h:aa("Hours",1,-12),mm:aa("Minutes",2),m:aa("Minutes",1),ss:aa("Seconds",2),s:aa("Seconds",1),sss:aa("Milliseconds",3),EEEE:Hb("Day"),EEE:Hb("Day",!0),a:function(a,c){return 12>a.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a,c,d){a=-1*d;return a=(0<= -a?"+":"")+(Gb(Math[0<a?"floor":"ceil"](a/60),2)+Gb(Math.abs(a%60),2))},ww:Fd(2),w:Fd(1),G:ic,GG:ic,GGG:ic,GGGG:function(a,c){return 0>=a.getFullYear()?c.ERANAMES[0]:c.ERANAMES[1]}},gg=/((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,fg=/^\-?\d+$/;Ad.$inject=["$locale"];var cg=qa(F),dg=qa(sb);Cd.$inject=["$parse"];var he=qa({restrict:"E",compile:function(a,c){if(!c.href&&!c.xlinkHref)return function(a,c){if("a"===c[0].nodeName.toLowerCase()){var f="[object SVGAnimatedString]"=== -va.call(c.prop("href"))?"xlink:href":"href";c.on("click",function(a){c.attr(f)||a.preventDefault()})}}}}),tb={};m(Bb,function(a,c){function d(a,d,f){a.$watch(f[e],function(a){f.$set(c,!!a)})}if("multiple"!=a){var e=ya("ng-"+c),f=d;"checked"===a&&(f=function(a,c,f){f.ngModel!==f[e]&&d(a,c,f)});tb[e]=function(){return{restrict:"A",priority:100,link:f}}}});m($c,function(a,c){tb[c]=function(){return{priority:100,link:function(a,e,f){if("ngPattern"===c&&"/"==f.ngPattern.charAt(0)&&(e=f.ngPattern.match(jg))){f.$set("ngPattern", -new RegExp(e[1],e[2]));return}a.$watch(f[c],function(a){f.$set(c,a)})}}}});m(["src","srcset","href"],function(a){var c=ya("ng-"+a);tb[c]=function(){return{priority:99,link:function(d,e,f){var h=a,g=a;"href"===a&&"[object SVGAnimatedString]"===va.call(e.prop("href"))&&(g="xlinkHref",f.$attr[g]="xlink:href",h=null);f.$observe(c,function(c){c?(f.$set(g,c),Wa&&h&&e.prop(h,f[g])):"href"===a&&f.$set(g,null)})}}}});var Ib={$addControl:y,$$renameControl:function(a,c){a.$name=c},$removeControl:y,$setValidity:y, -$setDirty:y,$setPristine:y,$setSubmitted:y};Gd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Od=function(a){return["$timeout","$parse",function(c,d){function e(a){return""===a?d('this[""]').assign:d(a).assign||y}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Gd,compile:function(d,h){d.addClass(Ya).addClass(mb);var g=h.name?"name":a&&h.ngForm?"ngForm":!1;return{pre:function(a,d,f,h){var m=h[0];if(!("action"in f)){var t=function(c){a.$apply(function(){m.$commitViewValue(); -m.$setSubmitted()});c.preventDefault()};d[0].addEventListener("submit",t,!1);d.on("$destroy",function(){c(function(){d[0].removeEventListener("submit",t,!1)},0,!1)})}(h[1]||m.$$parentForm).$addControl(m);var s=g?e(m.$name):y;g&&(s(a,m),f.$observe(g,function(c){m.$name!==c&&(s(a,w),m.$$parentForm.$$renameControl(m,c),s=e(m.$name),s(a,m))}));d.on("$destroy",function(){m.$$parentForm.$removeControl(m);s(a,w);P(m,Ib)})}}}}}]},ie=Od(),ve=Od(!0),ig=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/, -sg=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,tg=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,ug=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Pd=/^(\d{4})-(\d{2})-(\d{2})$/,Qd=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,lc=/^(\d{4})-W(\d\d)$/,Rd=/^(\d{4})-(\d\d)$/,Sd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Td={text:function(a,c,d,e,f,h){jb(a,c,d,e,f,h);jc(e)},date:kb("date", -Pd,Kb(Pd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":kb("datetimelocal",Qd,Kb(Qd,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:kb("time",Sd,Kb(Sd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:kb("week",lc,function(a,c){if(ea(a))return a;if(G(a)){lc.lastIndex=0;var d=lc.exec(a);if(d){var e=+d[1],f=+d[2],h=d=0,g=0,l=0,k=Ed(e),f=7*(f-1);c&&(d=c.getHours(),h=c.getMinutes(),g=c.getSeconds(),l=c.getMilliseconds());return new Date(e,0,k.getDate()+f,d,h,g,l)}}return NaN},"yyyy-Www"), -month:kb("month",Rd,Kb(Rd,["yyyy","MM"]),"yyyy-MM"),number:function(a,c,d,e,f,h){Id(a,c,d,e);jb(a,c,d,e,f,h);e.$$parserName="number";e.$parsers.push(function(a){return e.$isEmpty(a)?null:ug.test(a)?parseFloat(a):w});e.$formatters.push(function(a){if(!e.$isEmpty(a)){if(!V(a))throw lb("numfmt",a);a=a.toString()}return a});if(A(d.min)||d.ngMin){var g;e.$validators.min=function(a){return e.$isEmpty(a)||v(g)||a>=g};d.$observe("min",function(a){A(a)&&!V(a)&&(a=parseFloat(a,10));g=V(a)&&!isNaN(a)?a:w;e.$validate()})}if(A(d.max)|| -d.ngMax){var l;e.$validators.max=function(a){return e.$isEmpty(a)||v(l)||a<=l};d.$observe("max",function(a){A(a)&&!V(a)&&(a=parseFloat(a,10));l=V(a)&&!isNaN(a)?a:w;e.$validate()})}},url:function(a,c,d,e,f,h){jb(a,c,d,e,f,h);jc(e);e.$$parserName="url";e.$validators.url=function(a,c){var d=a||c;return e.$isEmpty(d)||sg.test(d)}},email:function(a,c,d,e,f,h){jb(a,c,d,e,f,h);jc(e);e.$$parserName="email";e.$validators.email=function(a,c){var d=a||c;return e.$isEmpty(d)||tg.test(d)}},radio:function(a,c, -d,e){v(d.name)&&c.attr("name",++nb);c.on("click",function(a){c[0].checked&&e.$setViewValue(d.value,a&&a.type)});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e,f,h,g,l){var k=Jd(l,a,"ngTrueValue",d.ngTrueValue,!0),n=Jd(l,a,"ngFalseValue",d.ngFalseValue,!1);c.on("click",function(a){e.$setViewValue(c[0].checked,a&&a.type)});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return!1===a};e.$formatters.push(function(a){return ka(a, -k)});e.$parsers.push(function(a){return a?k:n})},hidden:y,button:y,submit:y,reset:y,file:y},Ec=["$browser","$sniffer","$filter","$parse",function(a,c,d,e){return{restrict:"E",require:["?ngModel"],link:{pre:function(f,h,g,l){l[0]&&(Td[F(g.type)]||Td.text)(f,h,g,l[0],c,a,d,e)}}}}],vg=/^(true|false|\d+)$/,Ne=function(){return{restrict:"A",priority:100,compile:function(a,c){return vg.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value", -a)})}}}},ne=["$compile",function(a){return{restrict:"AC",compile:function(c){a.$$addBindingClass(c);return function(c,e,f){a.$$addBindingInfo(e,f.ngBind);e=e[0];c.$watch(f.ngBind,function(a){e.textContent=v(a)?"":a})}}}}],pe=["$interpolate","$compile",function(a,c){return{compile:function(d){c.$$addBindingClass(d);return function(d,f,h){d=a(f.attr(h.$attr.ngBindTemplate));c.$$addBindingInfo(f,d.expressions);f=f[0];h.$observe("ngBindTemplate",function(a){f.textContent=v(a)?"":a})}}}}],oe=["$sce","$parse", -"$compile",function(a,c,d){return{restrict:"A",compile:function(e,f){var h=c(f.ngBindHtml),g=c(f.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(e);return function(c,e,f){d.$$addBindingInfo(e,f.ngBindHtml);c.$watch(g,function(){e.html(a.getTrustedHtml(h(c))||"")})}}}}],Me=qa({restrict:"A",require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),qe=kc("",!0),se=kc("Odd",0),re=kc("Even",1),te=Na({compile:function(a,c){c.$set("ngCloak", -w);a.removeClass("ng-cloak")}}),ue=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Jc={},wg={blur:!0,focus:!0};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=ya("ng-"+a);Jc[c]=["$parse","$rootScope",function(d,e){return{restrict:"A",compile:function(f,h){var g=d(h[c],null,!0);return function(c,d){d.on(a,function(d){var f=function(){g(c,{$event:d})}; -wg[a]&&e.$$phase?c.$evalAsync(f):c.$apply(f)})}}}}]});var xe=["$animate",function(a){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,h){var g,l,k;c.$watch(e.ngIf,function(c){c?l||h(function(c,f){l=f;c[c.length++]=X.createComment(" end ngIf: "+e.ngIf+" ");g={clone:c};a.enter(c,d.parent(),d)}):(k&&(k.remove(),k=null),l&&(l.$destroy(),l=null),g&&(k=rb(g.clone),a.leave(k).then(function(){k=null}),g=null))})}}}],ye=["$templateRequest","$anchorScroll", -"$animate",function(a,c,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:da.noop,compile:function(e,f){var h=f.ngInclude||f.src,g=f.onload||"",l=f.autoscroll;return function(e,f,m,r,t){var s=0,v,u,q,z=function(){u&&(u.remove(),u=null);v&&(v.$destroy(),v=null);q&&(d.leave(q).then(function(){u=null}),u=q,q=null)};e.$watch(h,function(h){var m=function(){!A(l)||l&&!e.$eval(l)||c()},p=++s;h?(a(h,!0).then(function(a){if(p===s){var c=e.$new();r.template=a;a=t(c,function(a){z(); -d.enter(a,null,f).then(m)});v=c;q=a;v.$emit("$includeContentLoaded",h);e.$eval(g)}},function(){p===s&&(z(),e.$emit("$includeContentError",h))}),e.$emit("$includeContentRequested",h)):(z(),r.template=null)})}}}}],Pe=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,f){/SVG/.test(d[0].toString())?(d.empty(),a(Mc(f.template,X).childNodes)(c,function(a){d.append(a)},{futureParentElement:d})):(d.html(f.template),a(d.contents())(c))}}}],ze=Na({priority:450, -compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Le=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,c,d,e){var f=c.attr(d.$attr.ngList)||", ",h="false"!==d.ngTrim,g=h?T(f):f;e.$parsers.push(function(a){if(!v(a)){var c=[];a&&m(a.split(g),function(a){a&&c.push(h?T(a):a)});return c}});e.$formatters.push(function(a){return J(a)?a.join(f):w});e.$isEmpty=function(a){return!a||!a.length}}}},mb="ng-valid",Kd="ng-invalid",Ya="ng-pristine",Jb="ng-dirty",Md= -"ng-pending",lb=I("ngModel"),xg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,c,d,e,f,h,g,l,k,n){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=w;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending= -w;this.$name=n(d.name||"",!1)(a);this.$$parentForm=Ib;var p=f(d.ngModel),r=p.assign,t=p,s=r,K=null,u,q=this;this.$$setOptions=function(a){if((q.$options=a)&&a.getterSetter){var c=f(d.ngModel+"()"),g=f(d.ngModel+"($$$p)");t=function(a){var d=p(a);x(d)&&(d=c(a));return d};s=function(a,c){x(p(a))?g(a,{$$$p:q.$modelValue}):r(a,q.$modelValue)}}else if(!p.assign)throw lb("nonassign",d.ngModel,xa(e));};this.$render=y;this.$isEmpty=function(a){return v(a)||""===a||null===a||a!==a};var z=0;Hd({ctrl:this,$element:e, -set:function(a,c){a[c]=!0},unset:function(a,c){delete a[c]},$animate:h});this.$setPristine=function(){q.$dirty=!1;q.$pristine=!0;h.removeClass(e,Jb);h.addClass(e,Ya)};this.$setDirty=function(){q.$dirty=!0;q.$pristine=!1;h.removeClass(e,Ya);h.addClass(e,Jb);q.$$parentForm.$setDirty()};this.$setUntouched=function(){q.$touched=!1;q.$untouched=!0;h.setClass(e,"ng-untouched","ng-touched")};this.$setTouched=function(){q.$touched=!0;q.$untouched=!1;h.setClass(e,"ng-touched","ng-untouched")};this.$rollbackViewValue= -function(){g.cancel(K);q.$viewValue=q.$$lastCommittedViewValue;q.$render()};this.$validate=function(){if(!V(q.$modelValue)||!isNaN(q.$modelValue)){var a=q.$$rawModelValue,c=q.$valid,d=q.$modelValue,e=q.$options&&q.$options.allowInvalid;q.$$runValidators(a,q.$$lastCommittedViewValue,function(f){e||c===f||(q.$modelValue=f?a:w,q.$modelValue!==d&&q.$$writeModelToScope())})}};this.$$runValidators=function(a,c,d){function e(){var d=!0;m(q.$validators,function(e,f){var h=e(a,c);d=d&&h;g(f,h)});return d? -!0:(m(q.$asyncValidators,function(a,c){g(c,null)}),!1)}function f(){var d=[],e=!0;m(q.$asyncValidators,function(f,h){var k=f(a,c);if(!k||!x(k.then))throw lb("$asyncValidators",k);g(h,w);d.push(k.then(function(){g(h,!0)},function(a){e=!1;g(h,!1)}))});d.length?k.all(d).then(function(){h(e)},y):h(!0)}function g(a,c){l===z&&q.$setValidity(a,c)}function h(a){l===z&&d(a)}z++;var l=z;(function(){var a=q.$$parserName||"parse";if(v(u))g(a,null);else return u||(m(q.$validators,function(a,c){g(c,null)}),m(q.$asyncValidators, -function(a,c){g(c,null)})),g(a,u),u;return!0})()?e()?f():h(!1):h(!1)};this.$commitViewValue=function(){var a=q.$viewValue;g.cancel(K);if(q.$$lastCommittedViewValue!==a||""===a&&q.$$hasNativeValidators)q.$$lastCommittedViewValue=a,q.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var c=q.$$lastCommittedViewValue;if(u=v(c)?w:!0)for(var d=0;d<q.$parsers.length;d++)if(c=q.$parsers[d](c),v(c)){u=!1;break}V(q.$modelValue)&&isNaN(q.$modelValue)&&(q.$modelValue=t(a)); -var e=q.$modelValue,f=q.$options&&q.$options.allowInvalid;q.$$rawModelValue=c;f&&(q.$modelValue=c,q.$modelValue!==e&&q.$$writeModelToScope());q.$$runValidators(c,q.$$lastCommittedViewValue,function(a){f||(q.$modelValue=a?c:w,q.$modelValue!==e&&q.$$writeModelToScope())})};this.$$writeModelToScope=function(){s(a,q.$modelValue);m(q.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};this.$setViewValue=function(a,c){q.$viewValue=a;q.$options&&!q.$options.updateOnDefault||q.$$debounceViewValueCommit(c)}; -this.$$debounceViewValueCommit=function(c){var d=0,e=q.$options;e&&A(e.debounce)&&(e=e.debounce,V(e)?d=e:V(e[c])?d=e[c]:V(e["default"])&&(d=e["default"]));g.cancel(K);d?K=g(function(){q.$commitViewValue()},d):l.$$phase?q.$commitViewValue():a.$apply(function(){q.$commitViewValue()})};a.$watch(function(){var c=t(a);if(c!==q.$modelValue&&(q.$modelValue===q.$modelValue||c===c)){q.$modelValue=q.$$rawModelValue=c;u=w;for(var d=q.$formatters,e=d.length,f=c;e--;)f=d[e](f);q.$viewValue!==f&&(q.$viewValue= -q.$$lastCommittedViewValue=f,q.$render(),q.$$runValidators(c,f,y))}return c})}],Ke=["$rootScope",function(a){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:xg,priority:1,compile:function(c){c.addClass(Ya).addClass("ng-untouched").addClass(mb);return{pre:function(a,c,f,h){var g=h[0];c=h[1]||g.$$parentForm;g.$$setOptions(h[2]&&h[2].$options);c.$addControl(g);f.$observe("name",function(a){g.$name!==a&&g.$$parentForm.$$renameControl(g,a)});a.$on("$destroy",function(){g.$$parentForm.$removeControl(g)})}, -post:function(c,e,f,h){var g=h[0];if(g.$options&&g.$options.updateOn)e.on(g.$options.updateOn,function(a){g.$$debounceViewValueCommit(a&&a.type)});e.on("blur",function(e){g.$touched||(a.$$phase?c.$evalAsync(g.$setTouched):c.$apply(g.$setTouched))})}}}}}],yg=/(\s+|^)default(\s+|$)/,Oe=function(){return{restrict:"A",controller:["$scope","$attrs",function(a,c){var d=this;this.$options=ha(a.$eval(c.ngModelOptions));A(this.$options.updateOn)?(this.$options.updateOnDefault=!1,this.$options.updateOn=T(this.$options.updateOn.replace(yg, -function(){d.$options.updateOnDefault=!0;return" "}))):this.$options.updateOnDefault=!0}]}},Ae=Na({terminal:!0,priority:1E3}),zg=I("ngOptions"),Ag=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,Ie=["$compile","$parse",function(a,c){function d(a,d,e){function f(a,c,d,e,g){this.selectValue=a;this.viewValue=c;this.label= -d;this.group=e;this.disabled=g}function n(a){var c;if(!s&&Da(a))c=a;else{c=[];for(var d in a)a.hasOwnProperty(d)&&"$"!==d.charAt(0)&&c.push(d)}return c}var m=a.match(Ag);if(!m)throw zg("iexp",a,xa(d));var r=m[5]||m[7],s=m[6];a=/ as /.test(m[0])&&m[1];var v=m[9];d=c(m[2]?m[1]:r);var w=a&&c(a)||d,u=v&&c(v),q=v?function(a,c){return u(e,c)}:function(a){return Ga(a)},z=function(a,c){return q(a,x(a,c))},y=c(m[2]||m[1]),A=c(m[3]||""),O=c(m[4]||""),H=c(m[8]),B={},x=s?function(a,c){B[s]=c;B[r]=a;return B}: -function(a){B[r]=a;return B};return{trackBy:v,getTrackByValue:z,getWatchables:c(H,function(a){var c=[];a=a||[];for(var d=n(a),f=d.length,g=0;g<f;g++){var h=a===d?g:d[g],k=x(a[h],h),h=q(a[h],k);c.push(h);if(m[2]||m[1])h=y(e,k),c.push(h);m[4]&&(k=O(e,k),c.push(k))}return c}),getOptions:function(){for(var a=[],c={},d=H(e)||[],g=n(d),h=g.length,m=0;m<h;m++){var p=d===g?m:g[m],r=x(d[p],p),s=w(e,r),p=q(s,r),t=y(e,r),u=A(e,r),r=O(e,r),s=new f(p,s,t,u,r);a.push(s);c[p]=s}return{items:a,selectValueMap:c,getOptionFromViewValue:function(a){return c[z(a)]}, -getViewValueFromOption:function(a){return v?da.copy(a.viewValue):a.viewValue}}}}}var e=X.createElement("option"),f=X.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","?ngModel"],link:function(c,g,l,k){function n(a,c){a.element=c;c.disabled=a.disabled;a.label!==c.label&&(c.label=a.label,c.textContent=a.label);a.value!==c.value&&(c.value=a.selectValue)}function p(a,c,d,e){c&&F(c.nodeName)===d?d=c:(d=e.cloneNode(!1),c?a.insertBefore(d,c):a.appendChild(d));return d}function r(a){for(var c;a;)c= -a.nextSibling,Wb(a),a=c}function s(a){var c=q&&q[0],d=H&&H[0];if(c||d)for(;a&&(a===c||a===d||c&&8===c.nodeType);)a=a.nextSibling;return a}function v(){var a=x&&u.readValue();x=C.getOptions();var c={},d=g[0].firstChild;O&&g.prepend(q);d=s(d);x.items.forEach(function(a){var h,k;a.group?(h=c[a.group],h||(h=p(g[0],d,"optgroup",f),d=h.nextSibling,h.label=a.group,h=c[a.group]={groupElement:h,currentOptionElement:h.firstChild}),k=p(h.groupElement,h.currentOptionElement,"option",e),n(a,k),h.currentOptionElement= -k.nextSibling):(k=p(g[0],d,"option",e),n(a,k),d=k.nextSibling)});Object.keys(c).forEach(function(a){r(c[a].currentOptionElement)});r(d);w.$render();if(!w.$isEmpty(a)){var h=u.readValue();(C.trackBy?ka(a,h):a===h)||(w.$setViewValue(h),w.$render())}}var w=k[1];if(w){var u=k[0];k=l.multiple;for(var q,z=0,y=g.children(),A=y.length;z<A;z++)if(""===y[z].value){q=y.eq(z);break}var O=!!q,H=B(e.cloneNode(!1));H.val("?");var x,C=d(l.ngOptions,g,c);k?(w.$isEmpty=function(a){return!a||0===a.length},u.writeValue= -function(a){x.items.forEach(function(a){a.element.selected=!1});a&&a.forEach(function(a){(a=x.getOptionFromViewValue(a))&&!a.disabled&&(a.element.selected=!0)})},u.readValue=function(){var a=g.val()||[],c=[];m(a,function(a){(a=x.selectValueMap[a])&&!a.disabled&&c.push(x.getViewValueFromOption(a))});return c},C.trackBy&&c.$watchCollection(function(){if(J(w.$viewValue))return w.$viewValue.map(function(a){return C.getTrackByValue(a)})},function(){w.$render()})):(u.writeValue=function(a){var c=x.getOptionFromViewValue(a); -c&&!c.disabled?g[0].value!==c.selectValue&&(H.remove(),O||q.remove(),g[0].value=c.selectValue,c.element.selected=!0,c.element.setAttribute("selected","selected")):null===a||O?(H.remove(),O||g.prepend(q),g.val(""),q.prop("selected",!0),q.attr("selected",!0)):(O||q.remove(),g.prepend(H),g.val("?"),H.prop("selected",!0),H.attr("selected",!0))},u.readValue=function(){var a=x.selectValueMap[g.val()];return a&&!a.disabled?(O||q.remove(),H.remove(),x.getViewValueFromOption(a)):null},C.trackBy&&c.$watch(function(){return C.getTrackByValue(w.$viewValue)}, -function(){w.$render()}));O?(q.remove(),a(q)(c),q.removeClass("ng-scope")):q=B(e.cloneNode(!1));v();c.$watchCollection(C.getWatchables,v)}}}}],Be=["$locale","$interpolate","$log",function(a,c,d){var e=/{}/g,f=/^when(Minus)?(.+)$/;return{link:function(h,g,l){function k(a){g.text(a||"")}var n=l.count,p=l.$attr.when&&g.attr(l.$attr.when),r=l.offset||0,s=h.$eval(p)||{},w={},A=c.startSymbol(),u=c.endSymbol(),q=A+n+"-"+r+u,z=da.noop,x;m(l,function(a,c){var d=f.exec(c);d&&(d=(d[1]?"-":"")+F(d[2]),s[d]=g.attr(l.$attr[c]))}); -m(s,function(a,d){w[d]=c(a.replace(e,q))});h.$watch(n,function(c){var e=parseFloat(c),f=isNaN(e);f||e in s||(e=a.pluralCat(e-r));e===x||f&&V(x)&&isNaN(x)||(z(),f=w[e],v(f)?(null!=c&&d.debug("ngPluralize: no rule defined for '"+e+"' in "+p),z=y,k()):z=h.$watch(f,k),x=e)})}}}],Ce=["$parse","$animate",function(a,c){var d=I("ngRepeat"),e=function(a,c,d,e,k,m,p){a[d]=e;k&&(a[k]=m);a.$index=c;a.$first=0===c;a.$last=c===p-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(c&1))};return{restrict:"A", -multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(f,h){var g=h.ngRepeat,l=X.createComment(" end ngRepeat: "+g+" "),k=g.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!k)throw d("iexp",g);var n=k[1],p=k[2],r=k[3],s=k[4],k=n.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);if(!k)throw d("iidexp",n);var v=k[3]||k[1],y=k[2];if(r&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(r)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(r)))throw d("badident", -r);var u,q,z,A,x={$id:Ga};s?u=a(s):(z=function(a,c){return Ga(c)},A=function(a){return a});return function(a,f,h,k,n){u&&(q=function(c,d,e){y&&(x[y]=c);x[v]=d;x.$index=e;return u(a,x)});var s=fa();a.$watchCollection(p,function(h){var k,p,t=f[0],u,x=fa(),C,G,J,M,I,F,L;r&&(a[r]=h);if(Da(h))I=h,p=q||z;else for(L in p=q||A,I=[],h)ta.call(h,L)&&"$"!==L.charAt(0)&&I.push(L);C=I.length;L=Array(C);for(k=0;k<C;k++)if(G=h===I?k:I[k],J=h[G],M=p(G,J,k),s[M])F=s[M],delete s[M],x[M]=F,L[k]=F;else{if(x[M])throw m(L, -function(a){a&&a.scope&&(s[a.id]=a)}),d("dupes",g,M,J);L[k]={id:M,scope:w,clone:w};x[M]=!0}for(u in s){F=s[u];M=rb(F.clone);c.leave(M);if(M[0].parentNode)for(k=0,p=M.length;k<p;k++)M[k].$$NG_REMOVED=!0;F.scope.$destroy()}for(k=0;k<C;k++)if(G=h===I?k:I[k],J=h[G],F=L[k],F.scope){u=t;do u=u.nextSibling;while(u&&u.$$NG_REMOVED);F.clone[0]!=u&&c.move(rb(F.clone),null,B(t));t=F.clone[F.clone.length-1];e(F.scope,k,v,J,y,G,C)}else n(function(a,d){F.scope=d;var f=l.cloneNode(!1);a[a.length++]=f;c.enter(a, -null,B(t));t=f;F.clone=a;x[F.id]=F;e(F.scope,k,v,J,y,G,C)});s=x})}}}}],De=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(c,d,e){c.$watch(e.ngShow,function(c){a[c?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],we=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(c,d,e){c.$watch(e.ngHide,function(c){a[c?"addClass":"removeClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],Ee=Na(function(a,c,d){a.$watch(d.ngStyle, -function(a,d){d&&a!==d&&m(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),Fe=["$animate",function(a){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(c,d,e,f){var h=[],g=[],l=[],k=[],n=function(a,c){return function(){a.splice(c,1)}};c.$watch(e.ngSwitch||e.on,function(c){var d,e;d=0;for(e=l.length;d<e;++d)a.cancel(l[d]);d=l.length=0;for(e=k.length;d<e;++d){var s=rb(g[d].clone);k[d].$destroy();(l[d]=a.leave(s)).then(n(l,d))}g.length=0;k.length=0;(h=f.cases["!"+ -c]||f.cases["?"])&&m(h,function(c){c.transclude(function(d,e){k.push(e);var f=c.element;d[d.length++]=X.createComment(" end ngSwitchWhen: ");g.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],Ge=Na({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,c,d,e,f){e.cases["!"+d.ngSwitchWhen]=e.cases["!"+d.ngSwitchWhen]||[];e.cases["!"+d.ngSwitchWhen].push({transclude:f,element:c})}}),He=Na({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a, -c,d,e,f){e.cases["?"]=e.cases["?"]||[];e.cases["?"].push({transclude:f,element:c})}}),Je=Na({restrict:"EAC",link:function(a,c,d,e,f){if(!f)throw I("ngTransclude")("orphan",xa(c));f(function(a){c.empty();c.append(a)})}}),je=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){"text/ng-template"==d.type&&a.put(d.id,c[0].text)}}}],Bg={$setViewValue:y,$render:y},Cg=["$element","$scope","$attrs",function(a,c,d){var e=this,f=new Ua;e.ngModelCtrl=Bg;e.unknownOption=B(X.createElement("option")); -e.renderUnknownOption=function(c){c="? "+Ga(c)+" ?";e.unknownOption.val(c);a.prepend(e.unknownOption);a.val(c)};c.$on("$destroy",function(){e.renderUnknownOption=y});e.removeUnknownOption=function(){e.unknownOption.parent()&&e.unknownOption.remove()};e.readValue=function(){e.removeUnknownOption();return a.val()};e.writeValue=function(c){e.hasOption(c)?(e.removeUnknownOption(),a.val(c),""===c&&e.emptyOption.prop("selected",!0)):null==c&&e.emptyOption?(e.removeUnknownOption(),a.val("")):e.renderUnknownOption(c)}; -e.addOption=function(a,c){Ta(a,'"option value"');""===a&&(e.emptyOption=c);var d=f.get(a)||0;f.put(a,d+1)};e.removeOption=function(a){var c=f.get(a);c&&(1===c?(f.remove(a),""===a&&(e.emptyOption=w)):f.put(a,c-1))};e.hasOption=function(a){return!!f.get(a)}}],ke=function(){return{restrict:"E",require:["select","?ngModel"],controller:Cg,link:function(a,c,d,e){var f=e[1];if(f){var h=e[0];h.ngModelCtrl=f;f.$render=function(){h.writeValue(f.$viewValue)};c.on("change",function(){a.$apply(function(){f.$setViewValue(h.readValue())})}); -if(d.multiple){h.readValue=function(){var a=[];m(c.find("option"),function(c){c.selected&&a.push(c.value)});return a};h.writeValue=function(a){var d=new Ua(a);m(c.find("option"),function(a){a.selected=A(d.get(a.value))})};var g,l=NaN;a.$watch(function(){l!==f.$viewValue||ka(g,f.$viewValue)||(g=ja(f.$viewValue),f.$render());l=f.$viewValue});f.$isEmpty=function(a){return!a||0===a.length}}}}}},me=["$interpolate",function(a){return{restrict:"E",priority:100,compile:function(c,d){if(A(d.value))var e=a(d.value, -!0);else{var f=a(c.text(),!0);f||d.$set("value",c.text())}return function(a,c,d){function k(a){p.addOption(a,c);p.ngModelCtrl.$render();c[0].hasAttribute("selected")&&(c[0].selected=!0)}var m=c.parent(),p=m.data("$selectController")||m.parent().data("$selectController");if(p&&p.ngModelCtrl){if(e){var r;d.$observe("value",function(a){A(r)&&p.removeOption(r);r=a;k(a)})}else f?a.$watch(f,function(a,c){d.$set("value",a);c!==a&&p.removeOption(c);k(a)}):k(d.value);c.on("$destroy",function(){p.removeOption(d.value); -p.ngModelCtrl.$render()})}}}}}],le=qa({restrict:"E",terminal:!1}),Gc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){e&&(d.required=!0,e.$validators.required=function(a,c){return!d.required||!e.$isEmpty(c)},d.$observe("required",function(){e.$validate()}))}}},Fc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f,h=d.ngPattern||d.pattern;d.$observe("pattern",function(a){G(a)&&0<a.length&&(a=new RegExp("^"+a+"$"));if(a&&!a.test)throw I("ngPattern")("noregexp", -h,a,xa(c));f=a||w;e.$validate()});e.$validators.pattern=function(a,c){return e.$isEmpty(c)||v(f)||f.test(c)}}}}},Ic=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=-1;d.$observe("maxlength",function(a){a=Y(a);f=isNaN(a)?-1:a;e.$validate()});e.$validators.maxlength=function(a,c){return 0>f||e.$isEmpty(c)||c.length<=f}}}}},Hc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("minlength",function(a){f=Y(a)||0;e.$validate()}); -e.$validators.minlength=function(a,c){return e.$isEmpty(c)||c.length>=f}}}}};Q.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(ce(),ee(da),da.module("ngLocale",[],["$provide",function(a){function c(a){a+="";var c=a.indexOf(".");return-1==c?0:a.length-c-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "), -SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3, -maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",pluralCat:function(a,e){var f=a|0,h=e;w===h&&(h=Math.min(c(a),3));Math.pow(10,h);return 1==f&&0==h?"one":"other"}})}]),B(X).ready(function(){Zd(X,zc)}))})(window,document);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>'); +(function(v){'use strict';function O(a){return function(){var b=arguments[0],d;d="["+(a?a+":":"")+b+"] http://errors.angularjs.org/1.5.5/"+(a?a+"/":"")+b;for(b=1;b<arguments.length;b++){d=d+(1==b?"?":"&")+"p"+(b-1)+"=";var c=encodeURIComponent,e;e=arguments[b];e="function"==typeof e?e.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof e?"undefined":"string"!=typeof e?JSON.stringify(e):e;d+=c(e)}return Error(d)}}function ya(a){if(null==a||Va(a))return!1;if(K(a)||F(a)||B&&a instanceof B)return!0; +var b="length"in Object(a)&&a.length;return Q(b)&&(0<=b&&(b-1 in a||a instanceof Array)||"function"==typeof a.item)}function q(a,b,d){var c,e;if(a)if(E(a))for(c in a)"prototype"==c||"length"==c||"name"==c||a.hasOwnProperty&&!a.hasOwnProperty(c)||b.call(d,a[c],c,a);else if(K(a)||ya(a)){var f="object"!==typeof a;c=0;for(e=a.length;c<e;c++)(f||c in a)&&b.call(d,a[c],c,a)}else if(a.forEach&&a.forEach!==q)a.forEach(b,d,a);else if(oc(a))for(c in a)b.call(d,a[c],c,a);else if("function"===typeof a.hasOwnProperty)for(c in a)a.hasOwnProperty(c)&& +b.call(d,a[c],c,a);else for(c in a)ua.call(a,c)&&b.call(d,a[c],c,a);return a}function pc(a,b,d){for(var c=Object.keys(a).sort(),e=0;e<c.length;e++)b.call(d,a[c[e]],c[e]);return c}function qc(a){return function(b,d){a(d,b)}}function Xd(){return++nb}function Nb(a,b,d){for(var c=a.$$hashKey,e=0,f=b.length;e<f;++e){var g=b[e];if(G(g)||E(g))for(var h=Object.keys(g),k=0,l=h.length;k<l;k++){var n=h[k],m=g[n];d&&G(m)?fa(m)?a[n]=new Date(m.valueOf()):Wa(m)?a[n]=new RegExp(m):m.nodeName?a[n]=m.cloneNode(!0): +Ob(m)?a[n]=m.clone():(G(a[n])||(a[n]=K(m)?[]:{}),Nb(a[n],[m],!0)):a[n]=m}}c?a.$$hashKey=c:delete a.$$hashKey;return a}function R(a){return Nb(a,za.call(arguments,1),!1)}function Yd(a){return Nb(a,za.call(arguments,1),!0)}function X(a){return parseInt(a,10)}function Pb(a,b){return R(Object.create(a),b)}function C(){}function Xa(a){return a}function da(a){return function(){return a}}function rc(a){return E(a.toString)&&a.toString!==ma}function y(a){return"undefined"===typeof a}function x(a){return"undefined"!== +typeof a}function G(a){return null!==a&&"object"===typeof a}function oc(a){return null!==a&&"object"===typeof a&&!sc(a)}function F(a){return"string"===typeof a}function Q(a){return"number"===typeof a}function fa(a){return"[object Date]"===ma.call(a)}function E(a){return"function"===typeof a}function Wa(a){return"[object RegExp]"===ma.call(a)}function Va(a){return a&&a.window===a}function Ya(a){return a&&a.$evalAsync&&a.$watch}function Da(a){return"boolean"===typeof a}function Zd(a){return a&&Q(a.length)&& +$d.test(ma.call(a))}function Ob(a){return!(!a||!(a.nodeName||a.prop&&a.attr&&a.find))}function ae(a){var b={};a=a.split(",");var d;for(d=0;d<a.length;d++)b[a[d]]=!0;return b}function va(a){return P(a.nodeName||a[0]&&a[0].nodeName)}function Za(a,b){var d=a.indexOf(b);0<=d&&a.splice(d,1);return d}function qa(a,b){function d(a,b){var d=b.$$hashKey,e;if(K(a)){e=0;for(var f=a.length;e<f;e++)b.push(c(a[e]))}else if(oc(a))for(e in a)b[e]=c(a[e]);else if(a&&"function"===typeof a.hasOwnProperty)for(e in a)a.hasOwnProperty(e)&& +(b[e]=c(a[e]));else for(e in a)ua.call(a,e)&&(b[e]=c(a[e]));d?b.$$hashKey=d:delete b.$$hashKey;return b}function c(a){if(!G(a))return a;var b=f.indexOf(a);if(-1!==b)return g[b];if(Va(a)||Ya(a))throw Aa("cpws");var b=!1,c=e(a);void 0===c&&(c=K(a)?[]:Object.create(sc(a)),b=!0);f.push(a);g.push(c);return b?d(a,c):c}function e(a){switch(ma.call(a)){case "[object Int8Array]":case "[object Int16Array]":case "[object Int32Array]":case "[object Float32Array]":case "[object Float64Array]":case "[object Uint8Array]":case "[object Uint8ClampedArray]":case "[object Uint16Array]":case "[object Uint32Array]":return new a.constructor(c(a.buffer)); +case "[object ArrayBuffer]":if(!a.slice){var b=new ArrayBuffer(a.byteLength);(new Uint8Array(b)).set(new Uint8Array(a));return b}return a.slice(0);case "[object Boolean]":case "[object Number]":case "[object String]":case "[object Date]":return new a.constructor(a.valueOf());case "[object RegExp]":return b=new RegExp(a.source,a.toString().match(/[^\/]*$/)[0]),b.lastIndex=a.lastIndex,b;case "[object Blob]":return new a.constructor([a],{type:a.type})}if(E(a.cloneNode))return a.cloneNode(!0)}var f=[], +g=[];if(b){if(Zd(b)||"[object ArrayBuffer]"===ma.call(b))throw Aa("cpta");if(a===b)throw Aa("cpi");K(b)?b.length=0:q(b,function(a,d){"$$hashKey"!==d&&delete b[d]});f.push(a);g.push(b);return d(a,b)}return c(a)}function ha(a,b){if(K(a)){b=b||[];for(var d=0,c=a.length;d<c;d++)b[d]=a[d]}else if(G(a))for(d in b=b||{},a)if("$"!==d.charAt(0)||"$"!==d.charAt(1))b[d]=a[d];return b||a}function pa(a,b){if(a===b)return!0;if(null===a||null===b)return!1;if(a!==a&&b!==b)return!0;var d=typeof a,c;if(d==typeof b&& +"object"==d)if(K(a)){if(!K(b))return!1;if((d=a.length)==b.length){for(c=0;c<d;c++)if(!pa(a[c],b[c]))return!1;return!0}}else{if(fa(a))return fa(b)?pa(a.getTime(),b.getTime()):!1;if(Wa(a))return Wa(b)?a.toString()==b.toString():!1;if(Ya(a)||Ya(b)||Va(a)||Va(b)||K(b)||fa(b)||Wa(b))return!1;d=T();for(c in a)if("$"!==c.charAt(0)&&!E(a[c])){if(!pa(a[c],b[c]))return!1;d[c]=!0}for(c in b)if(!(c in d)&&"$"!==c.charAt(0)&&x(b[c])&&!E(b[c]))return!1;return!0}return!1}function $a(a,b,d){return a.concat(za.call(b, +d))}function tc(a,b){var d=2<arguments.length?za.call(arguments,2):[];return!E(b)||b instanceof RegExp?b:d.length?function(){return arguments.length?b.apply(a,$a(d,arguments,0)):b.apply(a,d)}:function(){return arguments.length?b.apply(a,arguments):b.call(a)}}function be(a,b){var d=b;"string"===typeof a&&"$"===a.charAt(0)&&"$"===a.charAt(1)?d=void 0:Va(b)?d="$WINDOW":b&&v.document===b?d="$DOCUMENT":Ya(b)&&(d="$SCOPE");return d}function ab(a,b){if(!y(a))return Q(b)||(b=b?2:null),JSON.stringify(a,be, +b)}function uc(a){return F(a)?JSON.parse(a):a}function vc(a,b){a=a.replace(ce,"");var d=Date.parse("Jan 01, 1970 00:00:00 "+a)/6E4;return isNaN(d)?b:d}function Qb(a,b,d){d=d?-1:1;var c=a.getTimezoneOffset();b=vc(b,c);d*=b-c;a=new Date(a.getTime());a.setMinutes(a.getMinutes()+d);return a}function wa(a){a=B(a).clone();try{a.empty()}catch(b){}var d=B("<div>").append(a).html();try{return a[0].nodeType===Ma?P(d):d.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+P(b)})}catch(c){return P(d)}} +function wc(a){try{return decodeURIComponent(a)}catch(b){}}function xc(a){var b={};q((a||"").split("&"),function(a){var c,e,f;a&&(e=a=a.replace(/\+/g,"%20"),c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=wc(e),x(e)&&(f=x(f)?wc(f):!0,ua.call(b,e)?K(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Rb(a){var b=[];q(a,function(a,c){K(a)?q(a,function(a){b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))}):b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))});return b.length?b.join("&"):""} +function ob(a){return ja(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ja(a,b){return encodeURIComponent(a).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function de(a,b){var d,c,e=Na.length;for(c=0;c<e;++c)if(d=Na[c]+b,F(d=a.getAttribute(d)))return d;return null}function ee(a,b){var d,c,e={};q(Na,function(b){b+="app";!d&&a.hasAttribute&&a.hasAttribute(b)&&(d=a,c=a.getAttribute(b))}); +q(Na,function(b){b+="app";var e;!d&&(e=a.querySelector("["+b.replace(":","\\:")+"]"))&&(d=e,c=e.getAttribute(b))});d&&(e.strictDi=null!==de(d,"strict-di"),b(d,c?[c]:[],e))}function yc(a,b,d){G(d)||(d={});d=R({strictDi:!1},d);var c=function(){a=B(a);if(a.injector()){var c=a[0]===v.document?"document":wa(a);throw Aa("btstrpd",c.replace(/</,"<").replace(/>/,">"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]); +b.unshift("ng");c=bb(b,d.strictDi);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;v&&e.test(v.name)&&(d.debugInfoEnabled=!0,v.name=v.name.replace(e,""));if(v&&!f.test(v.name))return c();v.name=v.name.replace(f,"");ea.resumeBootstrap=function(a){q(a,function(a){b.push(a)});return c()};E(ea.resumeDeferredBootstrap)&&ea.resumeDeferredBootstrap()}function fe(){v.name= +"NG_ENABLE_DEBUG_INFO!"+v.name;v.location.reload()}function ge(a){a=ea.element(a).injector();if(!a)throw Aa("test");return a.get("$$testability")}function zc(a,b){b=b||"_";return a.replace(he,function(a,c){return(c?b:"")+a.toLowerCase()})}function ie(){var a;if(!Ac){var b=pb();(Z=y(b)?v.jQuery:b?v[b]:void 0)&&Z.fn.on?(B=Z,R(Z.fn,{scope:Oa.scope,isolateScope:Oa.isolateScope,controller:Oa.controller,injector:Oa.injector,inheritedData:Oa.inheritedData}),a=Z.cleanData,Z.cleanData=function(b){for(var c, +e=0,f;null!=(f=b[e]);e++)(c=Z._data(f,"events"))&&c.$destroy&&Z(f).triggerHandler("$destroy");a(b)}):B=U;ea.element=B;Ac=!0}}function qb(a,b,d){if(!a)throw Aa("areq",b||"?",d||"required");return a}function Pa(a,b,d){d&&K(a)&&(a=a[a.length-1]);qb(E(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Qa(a,b){if("hasOwnProperty"===a)throw Aa("badname",b);}function Bc(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g<f;g++)c= +b[g],a&&(a=(e=a)[c]);return!d&&E(a)?tc(e,a):a}function rb(a){for(var b=a[0],d=a[a.length-1],c,e=1;b!==d&&(b=b.nextSibling);e++)if(c||a[e]!==b)c||(c=B(za.call(a,0,e))),c.push(b);return c||a}function T(){return Object.create(null)}function je(a){function b(a,b,c){return a[b]||(a[b]=c())}var d=O("$injector"),c=O("ng");a=b(a,"angular",Object);a.$$minErr=a.$$minErr||O;return b(a,"module",function(){var a={};return function(f,g,h){if("hasOwnProperty"===f)throw c("badname","module");g&&a.hasOwnProperty(f)&& +(a[f]=null);return b(a,f,function(){function a(b,d,e,f){f||(f=c);return function(){f[e||"push"]([b,d,arguments]);return M}}function b(a,d){return function(b,e){e&&E(e)&&(e.$$moduleName=f);c.push([a,d,arguments]);return M}}if(!g)throw d("nomod",f);var c=[],e=[],r=[],N=a("$injector","invoke","push",e),M={_invokeQueue:c,_configBlocks:e,_runBlocks:r,requires:g,name:f,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:a("$provide","value"),constant:a("$provide", +"constant","unshift"),decorator:b("$provide","decorator"),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),component:b("$compileProvider","component"),config:N,run:function(a){r.push(a);return this}};h&&N(h);return M})}})}function ke(a){R(a,{bootstrap:yc,copy:qa,extend:R,merge:Yd,equals:pa,element:B,forEach:q,injector:bb,noop:C,bind:tc,toJson:ab,fromJson:uc,identity:Xa,isUndefined:y, +isDefined:x,isString:F,isFunction:E,isObject:G,isNumber:Q,isElement:Ob,isArray:K,version:le,isDate:fa,lowercase:P,uppercase:sb,callbacks:{counter:0},getTestability:ge,$$minErr:O,$$csp:Ea,reloadWithDebugInfo:fe});Sb=je(v);Sb("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:me});a.provider("$compile",Cc).directive({a:ne,input:Dc,textarea:Dc,form:oe,script:pe,select:qe,style:re,option:se,ngBind:te,ngBindHtml:ue,ngBindTemplate:ve,ngClass:we,ngClassEven:xe,ngClassOdd:ye,ngCloak:ze,ngController:Ae, +ngForm:Be,ngHide:Ce,ngIf:De,ngInclude:Ee,ngInit:Fe,ngNonBindable:Ge,ngPluralize:He,ngRepeat:Ie,ngShow:Je,ngStyle:Ke,ngSwitch:Le,ngSwitchWhen:Me,ngSwitchDefault:Ne,ngOptions:Oe,ngTransclude:Pe,ngModel:Qe,ngList:Re,ngChange:Se,pattern:Ec,ngPattern:Ec,required:Fc,ngRequired:Fc,minlength:Gc,ngMinlength:Gc,maxlength:Hc,ngMaxlength:Hc,ngValue:Te,ngModelOptions:Ue}).directive({ngInclude:Ve}).directive(tb).directive(Ic);a.provider({$anchorScroll:We,$animate:Xe,$animateCss:Ye,$$animateJs:Ze,$$animateQueue:$e, +$$AnimateRunner:af,$$animateAsyncRun:bf,$browser:cf,$cacheFactory:df,$controller:ef,$document:ff,$exceptionHandler:gf,$filter:Jc,$$forceReflow:hf,$interpolate:jf,$interval:kf,$http:lf,$httpParamSerializer:mf,$httpParamSerializerJQLike:nf,$httpBackend:of,$xhrFactory:pf,$location:qf,$log:rf,$parse:sf,$rootScope:tf,$q:uf,$$q:vf,$sce:wf,$sceDelegate:xf,$sniffer:yf,$templateCache:zf,$templateRequest:Af,$$testability:Bf,$timeout:Cf,$window:Df,$$rAF:Ef,$$jqLite:Ff,$$HashMap:Gf,$$cookieReader:Hf})}])}function cb(a){return a.replace(If, +function(a,d,c,e){return e?c.toUpperCase():c}).replace(Jf,"Moz$1")}function Kc(a){a=a.nodeType;return 1===a||!a||9===a}function Lc(a,b){var d,c,e=b.createDocumentFragment(),f=[];if(Tb.test(a)){d=d||e.appendChild(b.createElement("div"));c=(Kf.exec(a)||["",""])[1].toLowerCase();c=ia[c]||ia._default;d.innerHTML=c[1]+a.replace(Lf,"<$1></$2>")+c[2];for(c=c[0];c--;)d=d.lastChild;f=$a(f,d.childNodes);d=e.firstChild;d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";q(f,function(a){e.appendChild(a)}); +return e}function Mc(a,b){var d=a.parentNode;d&&d.replaceChild(b,a);b.appendChild(a)}function U(a){if(a instanceof U)return a;var b;F(a)&&(a=V(a),b=!0);if(!(this instanceof U)){if(b&&"<"!=a.charAt(0))throw Ub("nosel");return new U(a)}if(b){b=v.document;var d;a=(d=Mf.exec(a))?[b.createElement(d[1])]:(d=Lc(a,b))?d.childNodes:[]}Nc(this,a)}function Vb(a){return a.cloneNode(!0)}function ub(a,b){b||db(a);if(a.querySelectorAll)for(var d=a.querySelectorAll("*"),c=0,e=d.length;c<e;c++)db(d[c])}function Oc(a, +b,d,c){if(x(c))throw Ub("offargs");var e=(c=vb(a))&&c.events,f=c&&c.handle;if(f)if(b){var g=function(b){var c=e[b];x(d)&&Za(c||[],d);x(d)&&c&&0<c.length||(a.removeEventListener(b,f,!1),delete e[b])};q(b.split(" "),function(a){g(a);wb[a]&&g(wb[a])})}else for(b in e)"$destroy"!==b&&a.removeEventListener(b,f,!1),delete e[b]}function db(a,b){var d=a.ng339,c=d&&eb[d];c&&(b?delete c.data[b]:(c.handle&&(c.events.$destroy&&c.handle({},"$destroy"),Oc(a)),delete eb[d],a.ng339=void 0))}function vb(a,b){var d= +a.ng339,d=d&&eb[d];b&&!d&&(a.ng339=d=++Nf,d=eb[d]={events:{},data:{},handle:void 0});return d}function Wb(a,b,d){if(Kc(a)){var c=x(d),e=!c&&b&&!G(b),f=!b;a=(a=vb(a,!e))&&a.data;if(c)a[b]=d;else{if(f)return a;if(e)return a&&a[b];R(a,b)}}}function xb(a,b){return a.getAttribute?-1<(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+b+" "):!1}function yb(a,b){b&&a.setAttribute&&q(b.split(" "),function(b){a.setAttribute("class",V((" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g, +" ").replace(" "+V(b)+" "," ")))})}function zb(a,b){if(b&&a.setAttribute){var d=(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");q(b.split(" "),function(a){a=V(a);-1===d.indexOf(" "+a+" ")&&(d+=a+" ")});a.setAttribute("class",V(d))}}function Nc(a,b){if(b)if(b.nodeType)a[a.length++]=b;else{var d=b.length;if("number"===typeof d&&b.window!==b){if(d)for(var c=0;c<d;c++)a[a.length++]=b[c]}else a[a.length++]=b}}function Pc(a,b){return Ab(a,"$"+(b||"ngController")+"Controller")}function Ab(a, +b,d){9==a.nodeType&&(a=a.documentElement);for(b=K(b)?b:[b];a;){for(var c=0,e=b.length;c<e;c++)if(x(d=B.data(a,b[c])))return d;a=a.parentNode||11===a.nodeType&&a.host}}function Qc(a){for(ub(a,!0);a.firstChild;)a.removeChild(a.firstChild)}function Bb(a,b){b||ub(a);var d=a.parentNode;d&&d.removeChild(a)}function Of(a,b){b=b||v;if("complete"===b.document.readyState)b.setTimeout(a);else B(b).on("load",a)}function Rc(a,b){var d=Cb[b.toLowerCase()];return d&&Sc[va(a)]&&d}function Pf(a,b){var d=function(c, +d){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=b[d||c.type],g=f?f.length:0;if(g){if(y(c.immediatePropagationStopped)){var h=c.stopImmediatePropagation;c.stopImmediatePropagation=function(){c.immediatePropagationStopped=!0;c.stopPropagation&&c.stopPropagation();h&&h.call(c)}}c.isImmediatePropagationStopped=function(){return!0===c.immediatePropagationStopped};var k=f.specialHandlerWrapper||Qf;1<g&&(f=ha(f));for(var l=0;l<g;l++)c.isImmediatePropagationStopped()||k(a,c,f[l])}};d.elem= +a;return d}function Qf(a,b,d){d.call(a,b)}function Rf(a,b,d){var c=b.relatedTarget;c&&(c===a||Sf.call(a,c))||d.call(a,b)}function Ff(){this.$get=function(){return R(U,{hasClass:function(a,b){a.attr&&(a=a[0]);return xb(a,b)},addClass:function(a,b){a.attr&&(a=a[0]);return zb(a,b)},removeClass:function(a,b){a.attr&&(a=a[0]);return yb(a,b)}})}}function Fa(a,b){var d=a&&a.$$hashKey;if(d)return"function"===typeof d&&(d=a.$$hashKey()),d;d=typeof a;return d="function"==d||"object"==d&&null!==a?a.$$hashKey= +d+":"+(b||Xd)():d+":"+a}function Ra(a,b){if(b){var d=0;this.nextUid=function(){return++d}}q(a,this.put,this)}function Tc(a){a=Function.prototype.toString.call(a).replace(Tf,"");return a.match(Uf)||a.match(Vf)}function Wf(a){return(a=Tc(a))?"function("+(a[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function bb(a,b){function d(a){return function(b,c){if(G(b))q(b,qc(a));else return a(b,c)}}function c(a,b){Qa(a,"service");if(E(b)||K(b))b=r.instantiate(b);if(!b.$get)throw Ga("pget",a);return m[a+"Provider"]= +b}function e(a,b){return function(){var c=w.invoke(b,this);if(y(c))throw Ga("undef",a);return c}}function f(a,b,d){return c(a,{$get:!1!==d?e(a,b):b})}function g(a){qb(y(a)||K(a),"modulesToLoad","not an array");var b=[],c;q(a,function(a){function d(a){var b,c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=r.get(e[0]);f[e[1]].apply(f,e[2])}}if(!n.get(a)){n.put(a,!0);try{F(a)?(c=Sb(a),b=b.concat(g(c.requires)).concat(c._runBlocks),d(c._invokeQueue),d(c._configBlocks)):E(a)?b.push(r.invoke(a)):K(a)?b.push(r.invoke(a)): +Pa(a,"module")}catch(e){throw K(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1==e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),Ga("modulerr",a,e.stack||e.message||e);}}});return b}function h(a,c){function d(b,e){if(a.hasOwnProperty(b)){if(a[b]===k)throw Ga("cdep",b+" <- "+l.join(" <- "));return a[b]}try{return l.unshift(b),a[b]=k,a[b]=c(b,e)}catch(f){throw a[b]===k&&delete a[b],f;}finally{l.shift()}}function e(a,c,f){var g=[];a=bb.$$annotate(a,b,f);for(var h=0,k=a.length;h<k;h++){var l=a[h]; +if("string"!==typeof l)throw Ga("itkn",l);g.push(c&&c.hasOwnProperty(l)?c[l]:d(l,f))}return g}return{invoke:function(a,b,c,d){"string"===typeof c&&(d=c,c=null);c=e(a,c,d);K(a)&&(a=a[a.length-1]);d=11>=Ca?!1:"function"===typeof a&&/^(?:class\s|constructor\()/.test(Function.prototype.toString.call(a));return d?(c.unshift(null),new (Function.prototype.bind.apply(a,c))):a.apply(b,c)},instantiate:function(a,b,c){var d=K(a)?a[a.length-1]:a;a=e(a,b,c);a.unshift(null);return new (Function.prototype.bind.apply(d, +a))},get:d,annotate:bb.$$annotate,has:function(b){return m.hasOwnProperty(b+"Provider")||a.hasOwnProperty(b)}}}b=!0===b;var k={},l=[],n=new Ra([],!0),m={$provide:{provider:d(c),factory:d(f),service:d(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:d(function(a,b){return f(a,da(b),!1)}),constant:d(function(a,b){Qa(a,"constant");m[a]=b;N[a]=b}),decorator:function(a,b){var c=r.get(a+"Provider"),d=c.$get;c.$get=function(){var a=w.invoke(d,c);return w.invoke(b,null, +{$delegate:a})}}}},r=m.$injector=h(m,function(a,b){ea.isString(b)&&l.push(b);throw Ga("unpr",l.join(" <- "));}),N={},M=h(N,function(a,b){var c=r.get(a+"Provider",b);return w.invoke(c.$get,c,void 0,a)}),w=M;m.$injectorProvider={$get:da(M)};var p=g(a),w=M.get("$injector");w.strictDi=b;q(p,function(a){a&&w.invoke(a)});return w}function We(){var a=!0;this.disableAutoScrolling=function(){a=!1};this.$get=["$window","$location","$rootScope",function(b,d,c){function e(a){var b=null;Array.prototype.some.call(a, +function(a){if("a"===va(a))return b=a,!0});return b}function f(a){if(a){a.scrollIntoView();var c;c=g.yOffset;E(c)?c=c():Ob(c)?(c=c[0],c="fixed"!==b.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):Q(c)||(c=0);c&&(a=a.getBoundingClientRect().top,b.scrollBy(0,a-c))}else b.scrollTo(0,0)}function g(a){a=F(a)?a:d.hash();var b;a?(b=h.getElementById(a))?f(b):(b=e(h.getElementsByName(a)))?f(b):"top"===a&&f(null):f(null)}var h=b.document;a&&c.$watch(function(){return d.hash()},function(a,b){a=== +b&&""===a||Of(function(){c.$evalAsync(g)})});return g}]}function fb(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;K(a)&&(a=a.join(" "));K(b)&&(b=b.join(" "));return a+" "+b}function Xf(a){F(a)&&(a=a.split(" "));var b=T();q(a,function(a){a.length&&(b[a]=!0)});return b}function Ha(a){return G(a)?a:{}}function Yf(a,b,d,c){function e(a){try{a.apply(null,za.call(arguments,1))}finally{if(M--,0===M)for(;w.length;)try{w.pop()()}catch(b){d.error(b)}}}function f(){u=null;g();h()}function g(){p=I(); +p=y(p)?null:p;pa(p,L)&&(p=L);L=p}function h(){if(t!==k.url()||H!==p)t=k.url(),H=p,q(J,function(a){a(k.url(),p)})}var k=this,l=a.location,n=a.history,m=a.setTimeout,r=a.clearTimeout,N={};k.isMock=!1;var M=0,w=[];k.$$completeOutstandingRequest=e;k.$$incOutstandingRequestCount=function(){M++};k.notifyWhenNoOutstandingRequests=function(a){0===M?a():w.push(a)};var p,H,t=l.href,z=b.find("base"),u=null,I=c.history?function(){try{return n.state}catch(a){}}:C;g();H=p;k.url=function(b,d,e){y(e)&&(e=null);l!== +a.location&&(l=a.location);n!==a.history&&(n=a.history);if(b){var f=H===e;if(t===b&&(!c.history||f))return k;var h=t&&Ia(t)===Ia(b);t=b;H=e;if(!c.history||h&&f){if(!h||u)u=b;d?l.replace(b):h?(d=l,e=b.indexOf("#"),e=-1===e?"":b.substr(e),d.hash=e):l.href=b;l.href!==b&&(u=b)}else n[d?"replaceState":"pushState"](e,"",b),g(),H=p;return k}return u||l.href.replace(/%27/g,"'")};k.state=function(){return p};var J=[],D=!1,L=null;k.onUrlChange=function(b){if(!D){if(c.history)B(a).on("popstate",f);B(a).on("hashchange", +f);D=!0}J.push(b);return b};k.$$applicationDestroyed=function(){B(a).off("hashchange popstate",f)};k.$$checkUrlChange=h;k.baseHref=function(){var a=z.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};k.defer=function(a,b){var c;M++;c=m(function(){delete N[c];e(a)},b||0);N[c]=!0;return c};k.defer.cancel=function(a){return N[a]?(delete N[a],r(a),e(C),!0):!1}}function cf(){this.$get=["$window","$log","$sniffer","$document",function(a,b,d,c){return new Yf(a,c,b,d)}]}function df(){this.$get= +function(){function a(a,c){function e(a){a!=m&&(r?r==a&&(r=a.n):r=a,f(a.n,a.p),f(a,m),m=a,m.n=null)}function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(a in b)throw O("$cacheFactory")("iid",a);var g=0,h=R({},c,{id:a}),k=T(),l=c&&c.capacity||Number.MAX_VALUE,n=T(),m=null,r=null;return b[a]={put:function(a,b){if(!y(b)){if(l<Number.MAX_VALUE){var c=n[a]||(n[a]={key:a});e(c)}a in k||g++;k[a]=b;g>l&&this.remove(r.key);return b}},get:function(a){if(l<Number.MAX_VALUE){var b=n[a];if(!b)return;e(b)}return k[a]}, +remove:function(a){if(l<Number.MAX_VALUE){var b=n[a];if(!b)return;b==m&&(m=b.p);b==r&&(r=b.n);f(b.n,b.p);delete n[a]}a in k&&(delete k[a],g--)},removeAll:function(){k=T();g=0;n=T();m=r=null},destroy:function(){n=h=k=null;delete b[a]},info:function(){return R({},h,{size:g})}}}var b={};a.info=function(){var a={};q(b,function(b,e){a[e]=b.info()});return a};a.get=function(a){return b[a]};return a}}function zf(){this.$get=["$cacheFactory",function(a){return a("templates")}]}function Cc(a,b){function d(a, +b,c){var d=/^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/,e=T();q(a,function(a,f){if(a in n)e[f]=n[a];else{var g=a.match(d);if(!g)throw ga("iscp",b,f,a,c?"controller bindings definition":"isolate scope definition");e[f]={mode:g[1][0],collection:"*"===g[2],optional:"?"===g[3],attrName:g[4]||f};g[4]&&(n[a]=e[f])}});return e}function c(a){var b=a.charAt(0);if(!b||b!==P(b))throw ga("baddir",a);if(a!==a.trim())throw ga("baddir",a);}var e={},f=/^\s*directive\:\s*([\w\-]+)\s+(.*)$/,g=/(([\w\-]+)(?:\:([^;]+))?;?)/, +h=ae("ngSrc,ngSrcset,src,srcset"),k=/^(?:(\^\^?)?(\?)?(\^\^?)?)?/,l=/^(on[a-z]+|formaction)$/,n=T();this.directive=function M(b,d){Qa(b,"directive");F(b)?(c(b),qb(d,"directiveFactory"),e.hasOwnProperty(b)||(e[b]=[],a.factory(b+"Directive",["$injector","$exceptionHandler",function(a,c){var d=[];q(e[b],function(e,f){try{var g=a.invoke(e);E(g)?g={compile:da(g)}:!g.compile&&g.link&&(g.compile=da(g.link));g.priority=g.priority||0;g.index=f;g.name=g.name||b;g.require=g.require||g.controller&&g.name;g.restrict= +g.restrict||"EA";g.$$moduleName=e.$$moduleName;d.push(g)}catch(h){c(h)}});return d}])),e[b].push(d)):q(b,qc(M));return this};this.component=function(a,b){function c(a){function e(b){return E(b)||K(b)?function(c,d){return a.invoke(b,this,{$element:c,$attrs:d})}:b}var f=b.template||b.templateUrl?b.template:"",g={controller:d,controllerAs:Uc(b.controller)||b.controllerAs||"$ctrl",template:e(f),templateUrl:e(b.templateUrl),transclude:b.transclude,scope:{},bindToController:b.bindings||{},restrict:"E", +require:b.require};q(b,function(a,b){"$"===b.charAt(0)&&(g[b]=a)});return g}var d=b.controller||function(){};q(b,function(a,b){"$"===b.charAt(0)&&(c[b]=a,E(d)&&(d[b]=a))});c.$inject=["$injector"];return this.directive(a,c)};this.aHrefSanitizationWhitelist=function(a){return x(a)?(b.aHrefSanitizationWhitelist(a),this):b.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(a){return x(a)?(b.imgSrcSanitizationWhitelist(a),this):b.imgSrcSanitizationWhitelist()};var m=!0;this.debugInfoEnabled= +function(a){return x(a)?(m=a,this):m};var r=10;this.onChangesTtl=function(a){return arguments.length?(r=a,this):r};this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$sce","$animate","$$sanitizeUri",function(a,b,c,n,t,z,u,I,J,D){function L(){try{if(!--qa)throw Z=void 0,ga("infchng",r);u.$apply(function(){for(var a=0,b=Z.length;a<b;++a)Z[a]();Z=void 0})}finally{qa++}}function S(a,b){if(b){var c=Object.keys(b),d,e,f;d=0;for(e=c.length;d< +e;d++)f=c[d],this[f]=b[f]}else this.$attr={};this.$$element=a}function $(a,b,c){na.innerHTML="<span "+b+">";b=na.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function A(a,b){try{a.addClass(b)}catch(c){}}function ba(a,b,c,d,e){a instanceof B||(a=B(a));for(var f=/\S+/,g=0,h=a.length;g<h;g++){var k=a[g];k.nodeType===Ma&&k.nodeValue.match(f)&&Mc(k,a[g]=v.document.createElement("span"))}var l=s(a,b,a,c,d,e);ba.$$addScopeClass(a);var m=null;return function(b, +c,d){qb(b,"scope");e&&e.needsNewScope&&(b=b.$parent.$new());d=d||{};var f=d.parentBoundTranscludeFn,g=d.transcludeControllers;d=d.futureParentElement;f&&f.$$boundTransclude&&(f=f.$$boundTransclude);m||(m=(d=d&&d[0])?"foreignobject"!==va(d)&&ma.call(d).match(/SVG/)?"svg":"html":"html");d="html"!==m?B(ca(m,B("<div>").append(a).html())):c?Oa.clone.call(a):a;if(g)for(var h in g)d.data("$"+h+"Controller",g[h].instance);ba.$$addScopeInfo(d,b);c&&c(d,b);l&&l(b,d,d,f);return d}}function s(a,b,c,d,e,f){function g(a, +c,d,e){var f,k,l,m,n,t,p;if(r)for(p=Array(c.length),m=0;m<h.length;m+=3)f=h[m],p[f]=c[f];else p=c;m=0;for(n=h.length;m<n;)k=p[h[m++]],c=h[m++],f=h[m++],c?(c.scope?(l=a.$new(),ba.$$addScopeInfo(B(k),l)):l=a,t=c.transcludeOnThisElement?ka(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?ka(a,b):null,c(f,l,k,d,t)):f&&f(a,k.childNodes,void 0,e)}for(var h=[],k,l,m,n,r,t=0;t<a.length;t++){k=new S;l=x(a[t],[],k,0===t?d:void 0,e);(f=l.length?Ba(l,a[t],k,b,c,null,[],[],f):null)&&f.scope&&ba.$$addScopeClass(k.$$element); +k=f&&f.terminal||!(m=a[t].childNodes)||!m.length?null:s(m,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||k)h.push(t,f,k),n=!0,r=r||f;f=null}return n?g:null}function ka(a,b,c){function d(e,f,g,h,k){e||(e=a.$new(!1,k),e.$$transcluded=!0);return b(e,f,{parentBoundTranscludeFn:c,transcludeControllers:g,futureParentElement:h})}var e=d.$$slots=T(),f;for(f in b.$$slots)e[f]=b.$$slots[f]?ka(a,b.$$slots[f],c):null;return d}function x(a,b,c,d,e){var h=c.$attr,k;switch(a.nodeType){case 1:la(b, +xa(va(a)),"E",d,e);for(var l,m,n,t=a.attributes,r=0,p=t&&t.length;r<p;r++){var I=!1,D=!1;l=t[r];k=l.name;m=V(l.value);l=xa(k);if(n=ya.test(l))k=k.replace(Vc,"").substr(8).replace(/_(.)/g,function(a,b){return b.toUpperCase()});(l=l.match(Aa))&&Q(l[1])&&(I=k,D=k.substr(0,k.length-5)+"end",k=k.substr(0,k.length-6));l=xa(k.toLowerCase());h[l]=k;if(n||!c.hasOwnProperty(l))c[l]=m,Rc(a,l)&&(c[l]=!0);fa(a,b,m,l,n);la(b,l,"A",d,e,I,D)}a=a.className;G(a)&&(a=a.animVal);if(F(a)&&""!==a)for(;k=g.exec(a);)l=xa(k[2]), +la(b,l,"C",d,e)&&(c[l]=V(k[3])),a=a.substr(k.index+k[0].length);break;case Ma:if(11===Ca)for(;a.parentNode&&a.nextSibling&&a.nextSibling.nodeType===Ma;)a.nodeValue+=a.nextSibling.nodeValue,a.parentNode.removeChild(a.nextSibling);X(b,a.nodeValue);break;case 8:try{if(k=f.exec(a.nodeValue))l=xa(k[1]),la(b,l,"M",d,e)&&(c[l]=V(k[2]))}catch(J){}}b.sort(Y);return b}function Wc(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ga("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&& +e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return B(d)}function Xc(a,b,c){return function(d,e,f,g,h){e=Wc(e[0],b,c);return a(d,e,f,g,h)}}function Yb(a,b,c,d,e,f){var g;return a?ba(b,c,d,e,f):function(){g||(g=ba(b,c,d,e,f),b=c=f=null);return g.apply(this,arguments)}}function Ba(a,b,d,e,f,g,h,k,l){function m(a,b,c,d){if(a){c&&(a=Xc(a,c,d));a.require=A.require;a.directiveName=M;if(D===A||A.$$isolateScope)a=ha(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=Xc(b,c,d)); +b.require=A.require;b.directiveName=M;if(D===A||A.$$isolateScope)b=ha(b,{isolateScope:!0});k.push(b)}}function n(a,c,e,f,g){function l(a,b,c,d){var e;Ya(a)||(d=c,c=b,b=a,a=void 0);H&&(e=u);c||(c=H?z.parent():z);if(d){var f=g.$$slots[d];if(f)return f(a,b,e,c,$);if(y(f))throw ga("noslot",d,wa(z));}else return g(a,b,e,c,$)}var m,t,p,A,w,u,L,z;b===e?(f=d,z=d.$$element):(z=B(e),f=new S(z,d));w=c;D?A=c.$new(!0):r&&(w=c.$parent);g&&(L=l,L.$$boundTransclude=g,L.isSlotFilled=function(a){return!!g.$$slots[a]}); +I&&(u=O(z,f,L,I,A,c,D));D&&(ba.$$addScopeInfo(z,A,!0,!(J&&(J===D||J===D.$$originalDirective))),ba.$$addScopeClass(z,!0),A.$$isolateBindings=D.$$isolateBindings,t=ia(c,f,A,A.$$isolateBindings,D),t.removeWatches&&A.$on("$destroy",t.removeWatches));for(m in u){t=I[m];p=u[m];var Xb=t.$$bindings.bindToController;p.bindingInfo=p.identifier&&Xb?ia(w,f,p.instance,Xb,t):{};var M=p();M!==p.instance&&(p.instance=M,z.data("$"+t.name+"Controller",M),p.bindingInfo.removeWatches&&p.bindingInfo.removeWatches(),p.bindingInfo= +ia(w,f,p.instance,Xb,t))}q(I,function(a,b){var c=a.require;a.bindToController&&!K(c)&&G(c)&&R(u[b].instance,gb(b,c,z,u))});q(u,function(a){var b=a.instance;E(b.$onChanges)&&b.$onChanges(a.bindingInfo.initialChanges);E(b.$onInit)&&b.$onInit();E(b.$onDestroy)&&w.$on("$destroy",function(){b.$onDestroy()})});m=0;for(t=h.length;m<t;m++)p=h[m],ja(p,p.isolateScope?A:c,z,f,p.require&&gb(p.directiveName,p.require,z,u),L);var $=c;D&&(D.template||null===D.templateUrl)&&($=A);a&&a($,e.childNodes,void 0,g);for(m= +k.length-1;0<=m;m--)p=k[m],ja(p,p.isolateScope?A:c,z,f,p.require&&gb(p.directiveName,p.require,z,u),L);q(u,function(a){a=a.instance;E(a.$postLink)&&a.$postLink()})}l=l||{};for(var t=-Number.MAX_VALUE,r=l.newScopeDirective,I=l.controllerDirectives,D=l.newIsolateScopeDirective,J=l.templateDirective,w=l.nonTlbTranscludeDirective,u=!1,L=!1,H=l.hasElementTranscludeDirective,z=d.$$element=B(b),A,M,$,s=e,Sa,ka=!1,C=!1,v,F=0,Ba=a.length;F<Ba;F++){A=a[F];var P=A.$$start,Q=A.$$end;P&&(z=Wc(b,P,Q));$=void 0; +if(t>A.priority)break;if(v=A.scope)A.templateUrl||(G(v)?(W("new/isolated scope",D||r,A,z),D=A):W("new/isolated scope",D,A,z)),r=r||A;M=A.name;if(!ka&&(A.replace&&(A.templateUrl||A.template)||A.transclude&&!A.$$tlb)){for(v=F+1;ka=a[v++];)if(ka.transclude&&!ka.$$tlb||ka.replace&&(ka.templateUrl||ka.template)){C=!0;break}ka=!0}!A.templateUrl&&A.controller&&(v=A.controller,I=I||T(),W("'"+M+"' controller",I[M],A,z),I[M]=A);if(v=A.transclude)if(u=!0,A.$$tlb||(W("transclusion",w,A,z),w=A),"element"==v)H= +!0,t=A.priority,$=z,z=d.$$element=B(ba.$$createComment(M,d[M])),b=z[0],da(f,za.call($,0),b),$[0].$$parentNode=$[0].parentNode,s=Yb(C,$,e,t,g&&g.name,{nonTlbTranscludeDirective:w});else{var la=T();$=B(Vb(b)).contents();if(G(v)){$=[];var Y=T(),X=T();q(v,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Y[a]=b;la[b]=null;X[b]=c});q(z.contents(),function(a){var b=Y[xa(va(a))];b?(X[b]=!0,la[b]=la[b]||[],la[b].push(a)):$.push(a)});q(X,function(a,b){if(!a)throw ga("reqslot",b);});for(var Z in la)la[Z]&& +(la[Z]=Yb(C,la[Z],e))}z.empty();s=Yb(C,$,e,void 0,void 0,{needsNewScope:A.$$isolateScope||A.$$newScope});s.$$slots=la}if(A.template)if(L=!0,W("template",J,A,z),J=A,v=E(A.template)?A.template(z,d):A.template,v=ta(v),A.replace){g=A;$=Tb.test(v)?Yc(ca(A.templateNamespace,V(v))):[];b=$[0];if(1!=$.length||1!==b.nodeType)throw ga("tplrt",M,"");da(f,z,b);Ba={$attr:{}};v=x(b,[],Ba);var ea=a.splice(F+1,a.length-(F+1));(D||r)&&Zc(v,D,r);a=a.concat(v).concat(ea);U(d,Ba);Ba=a.length}else z.html(v);if(A.templateUrl)L= +!0,W("template",J,A,z),J=A,A.replace&&(g=A),n=aa(a.splice(F,a.length-F),z,d,f,u&&s,h,k,{controllerDirectives:I,newScopeDirective:r!==A&&r,newIsolateScopeDirective:D,templateDirective:J,nonTlbTranscludeDirective:w}),Ba=a.length;else if(A.compile)try{Sa=A.compile(z,d,s),E(Sa)?m(null,Sa,P,Q):Sa&&m(Sa.pre,Sa.post,P,Q)}catch(fa){c(fa,wa(z))}A.terminal&&(n.terminal=!0,t=Math.max(t,A.priority))}n.scope=r&&!0===r.scope;n.transcludeOnThisElement=u;n.templateOnThisElement=L;n.transclude=s;l.hasElementTranscludeDirective= +H;return n}function gb(a,b,c,d){var e;if(F(b)){var f=b.match(k);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&e.instance;if(!e){var h="$"+b+"Controller";e=g?c.inheritedData(h):c.data(h)}if(!e&&!f)throw ga("ctreq",b,a);}else if(K(b))for(e=[],g=0,f=b.length;g<f;g++)e[g]=gb(a,b[g],c,d);else G(b)&&(e={},q(b,function(b,f){e[f]=gb(a,b,c,d)}));return e||null}function O(a,b,c,d,e,f,g){var h=T(),k;for(k in d){var l=d[k],m={$scope:l===g||l.$$isolateScope?e:f, +$element:a,$attrs:b,$transclude:c},n=l.controller;"@"==n&&(n=b[l.name]);m=z(n,m,!0,l.controllerAs);h[l.name]=m;a.data("$"+l.name+"Controller",m.instance)}return h}function Zc(a,b,c){for(var d=0,e=a.length;d<e;d++)a[d]=Pb(a[d],{$$isolateScope:b,$$newScope:c})}function la(b,f,g,h,k,l,m){if(f===k)return null;k=null;if(e.hasOwnProperty(f)){var n;f=a.get(f+"Directive");for(var t=0,r=f.length;t<r;t++)try{if(n=f[t],(y(h)||h>n.priority)&&-1!=n.restrict.indexOf(g)){l&&(n=Pb(n,{$$start:l,$$end:m}));if(!n.$$bindings){var I= +n,D=n,A=n.name,J={isolateScope:null,bindToController:null};G(D.scope)&&(!0===D.bindToController?(J.bindToController=d(D.scope,A,!0),J.isolateScope={}):J.isolateScope=d(D.scope,A,!1));G(D.bindToController)&&(J.bindToController=d(D.bindToController,A,!0));if(G(J.bindToController)){var w=D.controller,z=D.controllerAs;if(!w)throw ga("noctrl",A);if(!Uc(w,z))throw ga("noident",A);}var u=I.$$bindings=J;G(u.isolateScope)&&(n.$$isolateBindings=u.isolateScope)}b.push(n);k=n}}catch(L){c(L)}}return k}function Q(b){if(e.hasOwnProperty(b))for(var c= +a.get(b+"Directive"),d=0,f=c.length;d<f;d++)if(b=c[d],b.multiElement)return!0;return!1}function U(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;q(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});q(b,function(b,f){"class"==f?(A(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):"style"==f?(e.attr("style",e.attr("style")+";"+b),a.style=(a.style?a.style+";":"")+b):"$"==f.charAt(0)||a.hasOwnProperty(f)||(a[f]=b,d[f]=c[f])})}function aa(a,b,c,d,e,f, +g,h){var k=[],l,m,t=b[0],p=a.shift(),r=Pb(p,{templateUrl:null,transclude:null,replace:null,$$originalDirective:p}),I=E(p.templateUrl)?p.templateUrl(b,c):p.templateUrl,D=p.templateNamespace;b.empty();n(I).then(function(n){var J,w;n=ta(n);if(p.replace){n=Tb.test(n)?Yc(ca(D,V(n))):[];J=n[0];if(1!=n.length||1!==J.nodeType)throw ga("tplrt",p.name,I);n={$attr:{}};da(d,b,J);var z=x(J,[],n);G(p.scope)&&Zc(z,!0);a=z.concat(a);U(c,n)}else J=t,b.html(n);a.unshift(r);l=Ba(a,J,c,e,b,p,f,g,h);q(d,function(a,c){a== +J&&(d[c]=b[0])});for(m=s(b[0].childNodes,e);k.length;){n=k.shift();w=k.shift();var u=k.shift(),L=k.shift(),z=b[0];if(!n.$$destroyed){if(w!==t){var S=w.className;h.hasElementTranscludeDirective&&p.replace||(z=Vb(J));da(u,B(w),z);A(B(z),S)}w=l.transcludeOnThisElement?ka(n,l.transclude,L):L;l(m,n,z,d,w)}}k=null});return function(a,b,c,d,e){a=e;b.$$destroyed||(k?k.push(b,c,d,a):(l.transcludeOnThisElement&&(a=ka(b,l.transclude,e)),l(m,b,c,d,a)))}}function Y(a,b){var c=b.priority-a.priority;return 0!== +c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function W(a,b,c,d){function e(a){return a?" (module: "+a+")":""}if(b)throw ga("multidir",b.name,e(b.$$moduleName),c.name,e(c.$$moduleName),a,wa(d));}function X(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=a.parent();var b=!!a.length;b&&ba.$$addBindingClass(a);return function(a,c){var e=c.parent();b||ba.$$addBindingClass(e);ba.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function ca(a,b){a= +P(a||"html");switch(a){case "svg":case "math":var c=v.document.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;default:return b}}function ea(a,b){if("srcdoc"==b)return I.HTML;var c=va(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return I.RESOURCE_URL}function fa(a,c,d,e,f){var g=ea(a,e);f=h[e]||f;var k=b(d,!0,g,f);if(k){if("multiple"===e&&"select"===va(a))throw ga("selmulti",wa(a));c.push({priority:100,compile:function(){return{pre:function(a, +c,h){c=h.$$observers||(h.$$observers=T());if(l.test(e))throw ga("nodomevents");var m=h[e];m!==d&&(k=m&&b(m,!0,g,f),d=m);k&&(h[e]=k(a),(c[e]||(c[e]=[])).$$inter=!0,(h.$$observers&&h.$$observers[e].$$scope||a).$watch(k,function(a,b){"class"===e&&a!=b?h.$updateClass(a,b):h.$set(e,a)}))}}}})}}function da(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g<h;g++)if(a[g]==d){a[g++]=c;h=g+e-1;for(var k=a.length;g<k;g++,h++)h<k?a[g]=a[h]:delete a[g];a.length-=e-1;a.context===d&&(a.context= +c);break}f&&f.replaceChild(c,d);a=v.document.createDocumentFragment();for(g=0;g<e;g++)a.appendChild(b[g]);B.hasData(d)&&(B.data(c,B.data(d)),B(d).off("$destroy"));B.cleanData(a.querySelectorAll("*"));for(g=1;g<e;g++)delete b[g];b[0]=c;b.length=1}function ha(a,b){return R(function(){return a.apply(null,arguments)},a,b)}function ja(a,b,d,e,f,g){try{a(b,d,e,f,g)}catch(h){c(h,wa(d))}}function ia(a,c,d,e,f){function g(b,c,e){E(d.$onChanges)&&c!==e&&(Z||(a.$$postDigest(L),Z=[]),m||(m={},Z.push(h)),m[b]&& +(e=m[b].previousValue),m[b]=new Db(e,c))}function h(){d.$onChanges(m);m=void 0}var k=[],l={},m;q(e,function(e,h){var m=e.attrName,n=e.optional,p,r,I,D;switch(e.mode){case "@":n||ua.call(c,m)||(d[h]=c[m]=void 0);c.$observe(m,function(a){if(F(a)||Da(a))g(h,a,d[h]),d[h]=a});c.$$observers[m].$$scope=a;p=c[m];F(p)?d[h]=b(p)(a):Da(p)&&(d[h]=p);l[h]=new Db(Zb,d[h]);break;case "=":if(!ua.call(c,m)){if(n)break;c[m]=void 0}if(n&&!c[m])break;r=t(c[m]);D=r.literal?pa:function(a,b){return a===b||a!==a&&b!==b}; +I=r.assign||function(){p=d[h]=r(a);throw ga("nonassign",c[m],m,f.name);};p=d[h]=r(a);n=function(b){D(b,d[h])||(D(b,p)?I(a,b=d[h]):d[h]=b);return p=b};n.$stateful=!0;n=e.collection?a.$watchCollection(c[m],n):a.$watch(t(c[m],n),null,r.literal);k.push(n);break;case "<":if(!ua.call(c,m)){if(n)break;c[m]=void 0}if(n&&!c[m])break;r=t(c[m]);d[h]=r(a);l[h]=new Db(Zb,d[h]);n=a.$watch(r,function(a,b){a===b&&(b=d[h]);g(h,a,b);d[h]=a},r.literal);k.push(n);break;case "&":r=c.hasOwnProperty(m)?t(c[m]):C;if(r=== +C&&n)break;d[h]=function(b){return r(a,b)}}});return{initialChanges:l,removeWatches:k.length&&function(){for(var a=0,b=k.length;a<b;++a)k[a]()}}}var oa=/^\w/,na=v.document.createElement("div"),qa=r,Z;S.prototype={$normalize:xa,$addClass:function(a){a&&0<a.length&&J.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&J.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=$c(a,b);c&&c.length&&J.addClass(this.$$element,c);(c=$c(b,a))&&c.length&&J.removeClass(this.$$element, +c)},$set:function(a,b,d,e){var f=Rc(this.$$element[0],a),g=ad[a],h=a;f?(this.$$element.prop(a,b),e=f):g&&(this[g]=b,h=g);this[a]=b;e?this.$attr[a]=e:(e=this.$attr[a])||(this.$attr[a]=e=zc(a,"-"));f=va(this.$$element);if("a"===f&&("href"===a||"xlinkHref"===a)||"img"===f&&"src"===a)this[a]=b=D(b,"src"===a);else if("img"===f&&"srcset"===a){for(var f="",g=V(b),k=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,k=/\s/.test(g)?k:/(,)/,g=g.split(k),k=Math.floor(g.length/2),l=0;l<k;l++)var m=2*l,f=f+D(V(g[m]),!0),f= +f+(" "+V(g[m+1]));g=V(g[2*l]).split(/\s/);f+=D(V(g[0]),!0);2===g.length&&(f+=" "+V(g[1]));this[a]=b=f}!1!==d&&(null===b||y(b)?this.$$element.removeAttr(e):oa.test(e)?this.$$element.attr(e,b):$(this.$$element[0],e,b));(a=this.$$observers)&&q(a[h],function(a){try{a(b)}catch(d){c(d)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers=T()),e=d[a]||(d[a]=[]);e.push(b);u.$evalAsync(function(){e.$$inter||!c.hasOwnProperty(a)||y(c[a])||b(c[a])});return function(){Za(e,b)}}};var ra=b.startSymbol(), +sa=b.endSymbol(),ta="{{"==ra&&"}}"==sa?Xa:function(a){return a.replace(/\{\{/g,ra).replace(/}}/g,sa)},ya=/^ngAttr[A-Z]/,Aa=/^(.+)Start$/;ba.$$addBindingInfo=m?function(a,b){var c=a.data("$binding")||[];K(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:C;ba.$$addBindingClass=m?function(a){A(a,"ng-binding")}:C;ba.$$addScopeInfo=m?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:C;ba.$$addScopeClass=m?function(a,b){A(a,b?"ng-isolate-scope":"ng-scope")}:C;ba.$$createComment= +function(a,b){var c="";m&&(c=" "+(a||"")+": "+(b||"")+" ");return v.document.createComment(c)};return ba}]}function Db(a,b){this.previousValue=a;this.currentValue=b}function xa(a){return cb(a.replace(Vc,""))}function $c(a,b){var d="",c=a.split(/\s+/),e=b.split(/\s+/),f=0;a:for(;f<c.length;f++){for(var g=c[f],h=0;h<e.length;h++)if(g==e[h])continue a;d+=(0<d.length?" ":"")+g}return d}function Yc(a){a=B(a);var b=a.length;if(1>=b)return a;for(;b--;)8===a[b].nodeType&&Zf.call(a,b,1);return a}function Uc(a, +b){if(b&&F(b))return b;if(F(a)){var d=bd.exec(a);if(d)return d[3]}}function ef(){var a={},b=!1;this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,c){Qa(b,"controller");G(b)?R(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector","$window",function(d,c){function e(a,b,c,d){if(!a||!G(a.$scope))throw O("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,h,k){var l,n,m;h=!0===h;k&&F(k)&&(m=k);if(F(f)){k=f.match(bd);if(!k)throw $f("ctrlfmt",f);n=k[1];m= +m||k[3];f=a.hasOwnProperty(n)?a[n]:Bc(g.$scope,n,!0)||(b?Bc(c,n,!0):void 0);Pa(f,n,!0)}if(h)return h=(K(f)?f[f.length-1]:f).prototype,l=Object.create(h||null),m&&e(g,m,l,n||f.name),R(function(){var a=d.invoke(f,l,g,n);a!==l&&(G(a)||E(a))&&(l=a,m&&e(g,m,l,n||f.name));return l},{instance:l,identifier:m});l=d.instantiate(f,g,n);m&&e(g,m,l,n||f.name);return l}}]}function ff(){this.$get=["$window",function(a){return B(a.document)}]}function gf(){this.$get=["$log",function(a){return function(b,d){a.error.apply(a, +arguments)}}]}function $b(a){return G(a)?fa(a)?a.toISOString():ab(a):a}function mf(){this.$get=function(){return function(a){if(!a)return"";var b=[];pc(a,function(a,c){null===a||y(a)||(K(a)?q(a,function(a){b.push(ja(c)+"="+ja($b(a)))}):b.push(ja(c)+"="+ja($b(a))))});return b.join("&")}}}function nf(){this.$get=function(){return function(a){function b(a,e,f){null===a||y(a)||(K(a)?q(a,function(a,c){b(a,e+"["+(G(a)?c:"")+"]")}):G(a)&&!fa(a)?pc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):d.push(ja(e)+ +"="+ja($b(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function ac(a,b){if(F(a)){var d=a.replace(ag,"").trim();if(d){var c=b("Content-Type");(c=c&&0===c.indexOf(cd))||(c=(c=d.match(bg))&&cg[c[0]].test(d));c&&(a=uc(d))}}return a}function dd(a){var b=T(),d;F(a)?q(a.split("\n"),function(a){d=a.indexOf(":");var e=P(V(a.substr(0,d)));a=V(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):G(a)&&q(a,function(a,d){var f=P(d),g=V(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function ed(a){var b; +return function(d){b||(b=dd(a));return d?(d=b[P(d)],void 0===d&&(d=null),d):b}}function fd(a,b,d,c){if(E(c))return c(a,b,d);q(c,function(c){a=c(a,b,d)});return a}function lf(){var a=this.defaults={transformResponse:[ac],transformRequest:[function(a){return G(a)&&"[object File]"!==ma.call(a)&&"[object Blob]"!==ma.call(a)&&"[object FormData]"!==ma.call(a)?ab(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ha(bc),put:ha(bc),patch:ha(bc)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN", +paramSerializer:"$httpParamSerializer"},b=!1;this.useApplyAsync=function(a){return x(a)?(b=!!a,this):b};var d=!0;this.useLegacyPromiseExtensions=function(a){return x(a)?(d=!!a,this):d};var c=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector",function(e,f,g,h,k,l){function n(b){function c(a){var b=R({},a);b.data=fd(a.data,a.headers,a.status,f.transformResponse);a=a.status;return 200<=a&&300>a?b:k.reject(b)}function e(a,b){var c,d={};q(a,function(a, +e){E(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}if(!G(b))throw O("$http")("badreq",b);if(!F(b.url))throw O("$http")("badreq",b.url);var f=R({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer},b);f.headers=function(b){var c=a.headers,d=R({},b.headers),f,g,h,c=R({},c.common,c[P(b.method)]);a:for(f in c){g=P(f);for(h in d)if(P(h)===g)continue a;d[f]=c[f]}return e(d,ha(b))}(b);f.method=sb(f.method);f.paramSerializer=F(f.paramSerializer)? +l.get(f.paramSerializer):f.paramSerializer;var g=[function(b){var d=b.headers,e=fd(b.data,ed(d),void 0,b.transformRequest);y(e)&&q(d,function(a,b){"content-type"===P(b)&&delete d[b]});y(b.withCredentials)&&!y(a.withCredentials)&&(b.withCredentials=a.withCredentials);return m(b,e).then(c,c)},void 0],h=k.when(f);for(q(M,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;){b=g.shift();var n=g.shift(), +h=h.then(b,n)}d?(h.success=function(a){Pa(a,"fn");h.then(function(b){a(b.data,b.status,b.headers,f)});return h},h.error=function(a){Pa(a,"fn");h.then(null,function(b){a(b.data,b.status,b.headers,f)});return h}):(h.success=gd("success"),h.error=gd("error"));return h}function m(c,d){function g(a){if(a){var c={};q(a,function(a,d){c[d]=function(c){function d(){a(c)}b?h.$applyAsync(d):h.$$phase?d():h.$apply(d)}});return c}}function l(a,c,d,e){function f(){m(c,a,d,e)}L&&(200<=a&&300>a?L.put(A,[a,c,dd(d), +e]):L.remove(A));b?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function m(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?J.resolve:J.reject)({data:a,status:b,headers:ed(d),config:c,statusText:e})}function u(a){m(a.data,a.status,ha(a.headers()),a.statusText)}function I(){var a=n.pendingRequests.indexOf(c);-1!==a&&n.pendingRequests.splice(a,1)}var J=k.defer(),D=J.promise,L,S,M=c.headers,A=r(c.url,c.paramSerializer(c.params));n.pendingRequests.push(c);D.then(I,I);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&& +"JSONP"!==c.method||(L=G(c.cache)?c.cache:G(a.cache)?a.cache:N);L&&(S=L.get(A),x(S)?S&&E(S.then)?S.then(u,u):K(S)?m(S[1],S[0],ha(S[2]),S[3]):m(S,200,{},"OK"):L.put(A,D));y(S)&&((S=hd(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]:void 0)&&(M[c.xsrfHeaderName||a.xsrfHeaderName]=S),e(c.method,A,d,l,M,c.timeout,c.withCredentials,c.responseType,g(c.eventHandlers),g(c.uploadEventHandlers)));return D}function r(a,b){0<b.length&&(a+=(-1==a.indexOf("?")?"?":"&")+b);return a}var N=g("$http");a.paramSerializer= +F(a.paramSerializer)?l.get(a.paramSerializer):a.paramSerializer;var M=[];q(c,function(a){M.unshift(F(a)?l.get(a):l.invoke(a))});n.pendingRequests=[];(function(a){q(arguments,function(a){n[a]=function(b,c){return n(R({},c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){q(arguments,function(a){n[a]=function(b,c,d){return n(R({},d||{},{method:a,url:b,data:c}))}})})("post","put","patch");n.defaults=a;return n}]}function pf(){this.$get=function(){return function(){return new v.XMLHttpRequest}}} +function of(){this.$get=["$browser","$window","$document","$xhrFactory",function(a,b,d,c){return dg(a,c,a.defer,b.angular.callbacks,d[0])}]}function dg(a,b,d,c,e){function f(a,b,d){var f=e.createElement("script"),n=null;f.type="text/javascript";f.src=a;f.async=!0;n=function(a){f.removeEventListener("load",n,!1);f.removeEventListener("error",n,!1);e.body.removeChild(f);f=null;var g=-1,N="unknown";a&&("load"!==a.type||c[b].called||(a={type:"error"}),N=a.type,g="error"===a.type?404:200);d&&d(g,N)};f.addEventListener("load", +n,!1);f.addEventListener("error",n,!1);e.body.appendChild(f);return n}return function(e,h,k,l,n,m,r,N,M,w){function p(){z&&z();u&&u.abort()}function H(b,c,e,f,g){x(J)&&d.cancel(J);z=u=null;b(c,e,f,g);a.$$completeOutstandingRequest(C)}a.$$incOutstandingRequestCount();h=h||a.url();if("jsonp"==P(e)){var t="_"+(c.counter++).toString(36);c[t]=function(a){c[t].data=a;c[t].called=!0};var z=f(h.replace("JSON_CALLBACK","angular.callbacks."+t),t,function(a,b){H(l,a,c[t].data,"",b);c[t]=C})}else{var u=b(e,h); +u.open(e,h,!0);q(n,function(a,b){x(a)&&u.setRequestHeader(b,a)});u.onload=function(){var a=u.statusText||"",b="response"in u?u.response:u.responseText,c=1223===u.status?204:u.status;0===c&&(c=b?200:"file"==ra(h).protocol?404:0);H(l,c,b,u.getAllResponseHeaders(),a)};e=function(){H(l,-1,null,null,"")};u.onerror=e;u.onabort=e;q(M,function(a,b){u.addEventListener(b,a)});q(w,function(a,b){u.upload.addEventListener(b,a)});r&&(u.withCredentials=!0);if(N)try{u.responseType=N}catch(I){if("json"!==N)throw I; +}u.send(y(k)?null:k)}if(0<m)var J=d(p,m);else m&&E(m.then)&&m.then(p)}}function jf(){var a="{{",b="}}";this.startSymbol=function(b){return b?(a=b,this):a};this.endSymbol=function(a){return a?(b=a,this):b};this.$get=["$parse","$exceptionHandler","$sce",function(d,c,e){function f(a){return"\\\\\\"+a}function g(c){return c.replace(m,a).replace(r,b)}function h(a,b,c,d){var e;return e=a.$watch(function(a){e();return d(a)},b,c)}function k(f,k,m,r){function H(a){try{var b=a;a=m?e.getTrusted(m,b):e.valueOf(b); +var d;if(r&&!x(a))d=a;else if(null==a)d="";else{switch(typeof a){case "string":break;case "number":a=""+a;break;default:a=ab(a)}d=a}return d}catch(g){c(Ja.interr(f,g))}}if(!f.length||-1===f.indexOf(a)){var t;k||(k=g(f),t=da(k),t.exp=f,t.expressions=[],t.$$watchDelegate=h);return t}r=!!r;var z,u,I=0,J=[],D=[];t=f.length;for(var L=[],S=[];I<t;)if(-1!=(z=f.indexOf(a,I))&&-1!=(u=f.indexOf(b,z+l)))I!==z&&L.push(g(f.substring(I,z))),I=f.substring(z+l,u),J.push(I),D.push(d(I,H)),I=u+n,S.push(L.length),L.push(""); +else{I!==t&&L.push(g(f.substring(I)));break}m&&1<L.length&&Ja.throwNoconcat(f);if(!k||J.length){var q=function(a){for(var b=0,c=J.length;b<c;b++){if(r&&y(a[b]))return;L[S[b]]=a[b]}return L.join("")};return R(function(a){var b=0,d=J.length,e=Array(d);try{for(;b<d;b++)e[b]=D[b](a);return q(e)}catch(g){c(Ja.interr(f,g))}},{exp:f,expressions:J,$$watchDelegate:function(a,b){var c;return a.$watchGroup(D,function(d,e){var f=q(d);E(b)&&b.call(this,f,d!==e?c:f,a);c=f})}})}}var l=a.length,n=b.length,m=new RegExp(a.replace(/./g, +f),"g"),r=new RegExp(b.replace(/./g,f),"g");k.startSymbol=function(){return a};k.endSymbol=function(){return b};return k}]}function kf(){this.$get=["$rootScope","$window","$q","$$q","$browser",function(a,b,d,c,e){function f(f,k,l,n){function m(){r?f.apply(null,N):f(p)}var r=4<arguments.length,N=r?za.call(arguments,4):[],q=b.setInterval,w=b.clearInterval,p=0,H=x(n)&&!n,t=(H?c:d).defer(),z=t.promise;l=x(l)?l:0;z.$$intervalId=q(function(){H?e.defer(m):a.$evalAsync(m);t.notify(p++);0<l&&p>=l&&(t.resolve(p), +w(z.$$intervalId),delete g[z.$$intervalId]);H||a.$apply()},k);g[z.$$intervalId]=t;return z}var g={};f.cancel=function(a){return a&&a.$$intervalId in g?(g[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId),delete g[a.$$intervalId],!0):!1};return f}]}function cc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=ob(a[b]);return a.join("/")}function id(a,b){var d=ra(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=X(d.port)||eg[d.protocol]||null}function jd(a,b){var d="/"!==a.charAt(0); +d&&(a="/"+a);var c=ra(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1):c.pathname);b.$$search=xc(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!=b.$$path.charAt(0)&&(b.$$path="/"+b.$$path)}function na(a,b){if(0===b.indexOf(a))return b.substr(a.length)}function Ia(a){var b=a.indexOf("#");return-1==b?a:a.substr(0,b)}function hb(a){return a.replace(/(#.+)|#$/,"$1")}function dc(a,b,d){this.$$html5=!0;d=d||"";id(a,this);this.$$parse=function(a){var d=na(b, +a);if(!F(d))throw Eb("ipthprfx",a,b);jd(d,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Rb(this.$$search),d=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=cc(this.$$path)+(a?"?"+a:"")+d;this.$$absUrl=b+this.$$url.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;x(f=na(a,c))?(g=f,g=x(f=na(d,f))?b+(na("/",f)||f):a+g):x(f=na(b,c))?g=b+f:b==c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function ec(a,b,d){id(a,this); +this.$$parse=function(c){var e=na(a,c)||na(b,c),f;y(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",y(e)&&(a=c,this.replace())):(f=na(d,e),y(f)&&(f=e));jd(f,this);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;0===f.indexOf(e)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=Rb(this.$$search),e=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=cc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url?d+this.$$url:"")};this.$$parseLinkUrl= +function(b,d){return Ia(a)==Ia(b)?(this.$$parse(b),!0):!1}}function kd(a,b,d){this.$$html5=!0;ec.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a==Ia(c)?f=c:(g=na(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=Rb(this.$$search),e=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=cc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+d+this.$$url}}function Fb(a){return function(){return this[a]}}function ld(a, +b){return function(d){if(y(d))return this[a];this[a]=b(d);this.$$compose();return this}}function qf(){var a="",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return x(b)?(a=b,this):a};this.html5Mode=function(a){return Da(a)?(b.enabled=a,this):G(a)?(Da(a.enabled)&&(b.enabled=a.enabled),Da(a.requireBase)&&(b.requireBase=a.requireBase),Da(a.rewriteLinks)&&(b.rewriteLinks=a.rewriteLinks),this):b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d, +c,e,f,g){function h(a,b,d){var e=l.url(),f=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(g){throw l.url(e),l.$$state=f,g;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state,b)}var l,n;n=c.baseHref();var m=c.url(),r;if(b.enabled){if(!n&&b.requireBase)throw Eb("nobase");r=m.substring(0,m.indexOf("/",m.indexOf("//")+2))+(n||"/");n=e.history?dc:kd}else r=Ia(m),n=ec;var N=r.substr(0,Ia(r).lastIndexOf("/")+1);l=new n(r,N,"#"+a);l.$$parseLinkUrl(m,m);l.$$state=c.state(); +var q=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(b.rewriteLinks&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!=a.which&&2!=a.button){for(var e=B(a.target);"a"!==va(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var h=e.prop("href"),k=e.attr("href")||e.attr("xlink:href");G(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=ra(h.animVal).href);q.test(h)||!h||e.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(h,k)||(a.preventDefault(),l.absUrl()!=c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]= +!0))}});hb(l.absUrl())!=hb(m)&&c.url(l.absUrl(),!0);var w=!0;c.onUrlChange(function(a,b){y(na(N,a))?g.location.href=a:(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,f;a=hb(a);l.$$parse(a);l.$$state=b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(f?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(w=!1,k(c,e)))}),d.$$phase||d.$digest())});d.$watch(function(){var a=hb(c.url()),b=hb(l.absUrl()),f=c.state(),g=l.$$replace,m=a!==b||l.$$html5&&e.history&&f!==l.$$state;if(w|| +m)w=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,f).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=f):(m&&h(b,g,f===l.$$state?null:l.$$state),k(a,f)))});l.$$replace=!1});return l}]}function rf(){var a=!0,b=this;this.debugEnabled=function(b){return x(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&& +(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||C;a=!1;try{a=!!e.apply}catch(k){}return a?function(){var a=[];q(arguments,function(b){a.push(c(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function Ta(a,b){if("__defineGetter__"===a||"__defineSetter__"===a||"__lookupGetter__"===a||"__lookupSetter__"=== +a||"__proto__"===a)throw ca("isecfld",b);return a}function fg(a){return a+""}function sa(a,b){if(a){if(a.constructor===a)throw ca("isecfn",b);if(a.window===a)throw ca("isecwindow",b);if(a.children&&(a.nodeName||a.prop&&a.attr&&a.find))throw ca("isecdom",b);if(a===Object)throw ca("isecobj",b);}return a}function md(a,b){if(a){if(a.constructor===a)throw ca("isecfn",b);if(a===gg||a===hg||a===ig)throw ca("isecff",b);}}function Gb(a,b){if(a&&(a===(0).constructor||a===(!1).constructor||a==="".constructor|| +a==={}.constructor||a===[].constructor||a===Function.constructor))throw ca("isecaf",b);}function jg(a,b){return"undefined"!==typeof a?a:b}function nd(a,b){return"undefined"===typeof a?b:"undefined"===typeof b?a:a+b}function aa(a,b){var d,c;switch(a.type){case s.Program:d=!0;q(a.body,function(a){aa(a.expression,b);d=d&&a.expression.constant});a.constant=d;break;case s.Literal:a.constant=!0;a.toWatch=[];break;case s.UnaryExpression:aa(a.argument,b);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch; +break;case s.BinaryExpression:aa(a.left,b);aa(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case s.LogicalExpression:aa(a.left,b);aa(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case s.ConditionalExpression:aa(a.test,b);aa(a.alternate,b);aa(a.consequent,b);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case s.Identifier:a.constant= +!1;a.toWatch=[a];break;case s.MemberExpression:aa(a.object,b);a.computed&&aa(a.property,b);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=[a];break;case s.CallExpression:d=a.filter?!b(a.callee.name).$stateful:!1;c=[];q(a.arguments,function(a){aa(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=a.filter&&!b(a.callee.name).$stateful?c:[a];break;case s.AssignmentExpression:aa(a.left,b);aa(a.right,b);a.constant=a.left.constant&&a.right.constant; +a.toWatch=[a];break;case s.ArrayExpression:d=!0;c=[];q(a.elements,function(a){aa(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=c;break;case s.ObjectExpression:d=!0;c=[];q(a.properties,function(a){aa(a.value,b);d=d&&a.value.constant;a.value.constant||c.push.apply(c,a.value.toWatch)});a.constant=d;a.toWatch=c;break;case s.ThisExpression:a.constant=!1;a.toWatch=[];break;case s.LocalsExpression:a.constant=!1,a.toWatch=[]}}function od(a){if(1==a.length){a=a[0].expression; +var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:void 0}}function pd(a){return a.type===s.Identifier||a.type===s.MemberExpression}function qd(a){if(1===a.body.length&&pd(a.body[0].expression))return{type:s.AssignmentExpression,left:a.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function rd(a){return 0===a.body.length||1===a.body.length&&(a.body[0].expression.type===s.Literal||a.body[0].expression.type===s.ArrayExpression||a.body[0].expression.type===s.ObjectExpression)}function sd(a, +b){this.astBuilder=a;this.$filter=b}function td(a,b){this.astBuilder=a;this.$filter=b}function Hb(a){return"constructor"==a}function fc(a){return E(a.valueOf)?a.valueOf():kg.call(a)}function sf(){var a=T(),b=T(),d={"true":!0,"false":!1,"null":null,undefined:void 0},c,e;this.addLiteral=function(a,b){d[a]=b};this.setIdentifierFns=function(a,b){c=a;e=b;return this};this.$get=["$filter",function(f){function g(c,d,e){var g,k,D;e=e||H;switch(typeof c){case "string":D=c=c.trim();var q=e?b:a;g=q[D];if(!g){":"=== +c.charAt(0)&&":"===c.charAt(1)&&(k=!0,c=c.substring(2));g=e?p:w;var S=new gc(g);g=(new hc(S,f,g)).parse(c);g.constant?g.$$watchDelegate=r:k?g.$$watchDelegate=g.literal?m:n:g.inputs&&(g.$$watchDelegate=l);e&&(g=h(g));q[D]=g}return N(g,d);case "function":return N(c,d);default:return N(C,d)}}function h(a){function b(c,d,e,f){var g=H;H=!0;try{return a(c,d,e,f)}finally{H=g}}if(!a)return a;b.$$watchDelegate=a.$$watchDelegate;b.assign=h(a.assign);b.constant=a.constant;b.literal=a.literal;for(var c=0;a.inputs&& +c<a.inputs.length;++c)a.inputs[c]=h(a.inputs[c]);b.inputs=a.inputs;return b}function k(a,b){return null==a||null==b?a===b:"object"===typeof a&&(a=fc(a),"object"===typeof a)?!1:a===b||a!==a&&b!==b}function l(a,b,c,d,e){var f=d.inputs,g;if(1===f.length){var h=k,f=f[0];return a.$watch(function(a){var b=f(a);k(b,h)||(g=d(a,void 0,void 0,[b]),h=b&&fc(b));return g},b,c,e)}for(var l=[],m=[],n=0,r=f.length;n<r;n++)l[n]=k,m[n]=null;return a.$watch(function(a){for(var b=!1,c=0,e=f.length;c<e;c++){var h=f[c](a); +if(b||(b=!k(h,l[c])))m[c]=h,l[c]=h&&fc(h)}b&&(g=d(a,void 0,void 0,m));return g},b,c,e)}function n(a,b,c,d){var e,f;return e=a.$watch(function(a){return d(a)},function(a,c,d){f=a;E(b)&&b.apply(this,arguments);x(a)&&d.$$postDigest(function(){x(f)&&e()})},c)}function m(a,b,c,d){function e(a){var b=!0;q(a,function(a){x(a)||(b=!1)});return b}var f,g;return f=a.$watch(function(a){return d(a)},function(a,c,d){g=a;E(b)&&b.call(this,a,c,d);e(a)&&d.$$postDigest(function(){e(g)&&f()})},c)}function r(a,b,c,d){var e; +return e=a.$watch(function(a){e();return d(a)},b,c)}function N(a,b){if(!b)return a;var c=a.$$watchDelegate,d=!1,c=c!==m&&c!==n?function(c,e,f,g){f=d&&g?g[0]:a(c,e,f,g);return b(f,c,e)}:function(c,d,e,f){e=a(c,d,e,f);c=b(e,c,d);return x(e)?c:e};a.$$watchDelegate&&a.$$watchDelegate!==l?c.$$watchDelegate=a.$$watchDelegate:b.$stateful||(c.$$watchDelegate=l,d=!a.inputs,c.inputs=a.inputs?a.inputs:[a]);return c}var M=Ea().noUnsafeEval,w={csp:M,expensiveChecks:!1,literals:qa(d),isIdentifierStart:E(c)&&c, +isIdentifierContinue:E(e)&&e},p={csp:M,expensiveChecks:!0,literals:qa(d),isIdentifierStart:E(c)&&c,isIdentifierContinue:E(e)&&e},H=!1;g.$$runningExpensiveChecks=function(){return H};return g}]}function uf(){this.$get=["$rootScope","$exceptionHandler",function(a,b){return ud(function(b){a.$evalAsync(b)},b)}]}function vf(){this.$get=["$browser","$exceptionHandler",function(a,b){return ud(function(b){a.defer(b)},b)}]}function ud(a,b){function d(){this.$$state={status:0}}function c(a,b){return function(c){b.call(a, +c)}}function e(c){!c.processScheduled&&c.pending&&(c.processScheduled=!0,a(function(){var a,d,e;e=c.pending;c.processScheduled=!1;c.pending=void 0;for(var f=0,g=e.length;f<g;++f){d=e[f][0];a=e[f][c.status];try{E(a)?d.resolve(a(c.value)):1===c.status?d.resolve(c.value):d.reject(c.value)}catch(h){d.reject(h),b(h)}}}))}function f(){this.promise=new d}var g=O("$q",TypeError);R(d.prototype,{then:function(a,b,c){if(y(a)&&y(b)&&y(c))return this;var d=new f;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d, +a,b,c]);0<this.$$state.status&&e(this.$$state);return d.promise},"catch":function(a){return this.then(null,a)},"finally":function(a,b){return this.then(function(b){return k(b,!0,a)},function(b){return k(b,!1,a)},b)}});R(f.prototype,{resolve:function(a){this.promise.$$state.status||(a===this.promise?this.$$reject(g("qcycle",a)):this.$$resolve(a))},$$resolve:function(a){function d(a){k||(k=!0,h.$$resolve(a))}function f(a){k||(k=!0,h.$$reject(a))}var g,h=this,k=!1;try{if(G(a)||E(a))g=a&&a.then;E(g)? +(this.promise.$$state.status=-1,g.call(a,d,f,c(this,this.notify))):(this.promise.$$state.value=a,this.promise.$$state.status=1,e(this.promise.$$state))}catch(l){f(l),b(l)}},reject:function(a){this.promise.$$state.status||this.$$reject(a)},$$reject:function(a){this.promise.$$state.value=a;this.promise.$$state.status=2;e(this.promise.$$state)},notify:function(c){var d=this.promise.$$state.pending;0>=this.promise.$$state.status&&d&&d.length&&a(function(){for(var a,e,f=0,g=d.length;f<g;f++){e=d[f][0]; +a=d[f][3];try{e.notify(E(a)?a(c):c)}catch(h){b(h)}}})}});var h=function(a,b){var c=new f;b?c.resolve(a):c.reject(a);return c.promise},k=function(a,b,c){var d=null;try{E(c)&&(d=c())}catch(e){return h(e,!1)}return d&&E(d.then)?d.then(function(){return h(a,b)},function(a){return h(a,!1)}):h(a,b)},l=function(a,b,c,d){var e=new f;e.resolve(a);return e.promise.then(b,c,d)},n=function(a){if(!E(a))throw g("norslvr",a);var b=new f;a(function(a){b.resolve(a)},function(a){b.reject(a)});return b.promise};n.prototype= +d.prototype;n.defer=function(){var a=new f;a.resolve=c(a,a.resolve);a.reject=c(a,a.reject);a.notify=c(a,a.notify);return a};n.reject=function(a){var b=new f;b.reject(a);return b.promise};n.when=l;n.resolve=l;n.all=function(a){var b=new f,c=0,d=K(a)?[]:{};q(a,function(a,e){c++;l(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise};return n}function Ef(){this.$get=["$window","$timeout",function(a, +b){var d=a.requestAnimationFrame||a.webkitRequestAnimationFrame,c=a.cancelAnimationFrame||a.webkitCancelAnimationFrame||a.webkitCancelRequestAnimationFrame,e=!!d,f=e?function(a){var b=d(a);return function(){c(b)}}:function(a){var c=b(a,16.66,!1);return function(){b.cancel(c)}};f.supported=e;return f}]}function tf(){function a(a){function b(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$id=++nb;this.$$ChildScope= +null}b.prototype=a;return b}var b=10,d=O("$rootScope"),c=null,e=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$exceptionHandler","$parse","$browser",function(f,g,h){function k(a){a.currentScope.$$destroyed=!0}function l(a){9===Ca&&(a.$$childHead&&l(a.$$childHead),a.$$nextSibling&&l(a.$$nextSibling));a.$parent=a.$$nextSibling=a.$$prevSibling=a.$$childHead=a.$$childTail=a.$root=a.$$watchers=null}function n(){this.$id=++nb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling= +this.$$prevSibling=this.$$childHead=this.$$childTail=null;this.$root=this;this.$$destroyed=!1;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$$isolateBindings=null}function m(a){if(H.$$phase)throw d("inprog",H.$$phase);H.$$phase=a}function r(a,b){do a.$$watchersCount+=b;while(a=a.$parent)}function N(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function s(){}function w(){for(;u.length;)try{u.shift()()}catch(a){f(a)}e= +null}function p(){null===e&&(e=h.defer(function(){H.$apply(w)}))}n.prototype={constructor:n,$new:function(b,c){var d;c=c||this;b?(d=new n,d.$root=this.$root):(this.$$ChildScope||(this.$$ChildScope=a(this)),d=new this.$$ChildScope);d.$parent=c;d.$$prevSibling=c.$$childTail;c.$$childHead?(c.$$childTail.$$nextSibling=d,c.$$childTail=d):c.$$childHead=c.$$childTail=d;(b||c!=this)&&d.$on("$destroy",k);return d},$watch:function(a,b,d,e){var f=g(a);if(f.$$watchDelegate)return f.$$watchDelegate(this,b,d,f, +a);var h=this,k=h.$$watchers,l={fn:b,last:s,get:f,exp:e||a,eq:!!d};c=null;E(b)||(l.fn=C);k||(k=h.$$watchers=[]);k.unshift(l);r(this,1);return function(){0<=Za(k,l)&&r(h,-1);c=null}},$watchGroup:function(a,b){function c(){h=!1;k?(k=!1,b(e,e,g)):b(e,d,g)}var d=Array(a.length),e=Array(a.length),f=[],g=this,h=!1,k=!0;if(!a.length){var l=!0;g.$evalAsync(function(){l&&b(e,e,g)});return function(){l=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=a;d[0]=c;b(e,a===c?e:d,f)});q(a,function(a, +b){var k=g.$watch(a,function(a,f){e[b]=a;d[b]=f;h||(h=!0,g.$evalAsync(c))});f.push(k)});return function(){for(;f.length;)f.shift()()}},$watchCollection:function(a,b){function c(a){e=a;var b,d,g,h;if(!y(e)){if(G(e))if(ya(e))for(f!==m&&(f=m,t=f.length=0,l++),a=e.length,t!==a&&(l++,f.length=t=a),b=0;b<a;b++)h=f[b],g=e[b],d=h!==h&&g!==g,d||h===g||(l++,f[b]=g);else{f!==r&&(f=r={},t=0,l++);a=0;for(b in e)ua.call(e,b)&&(a++,g=e[b],h=f[b],b in f?(d=h!==h&&g!==g,d||h===g||(l++,f[b]=g)):(t++,f[b]=g,l++));if(t> +a)for(b in l++,f)ua.call(e,b)||(t--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1<b.length,l=0,n=g(a,c),m=[],r={},p=!0,t=0;return this.$watch(n,function(){p?(p=!1,b(e,e,d)):b(e,h,d);if(k)if(G(e))if(ya(e)){h=Array(e.length);for(var a=0;a<e.length;a++)h[a]=e[a]}else for(a in h={},e)ua.call(e,a)&&(h[a]=e[a]);else h=e})},$digest:function(){var a,g,k,l,n,r,p,q,N=b,u,x=[],y,v;m("$digest");h.$$checkUrlChange();this===H&&null!==e&&(h.defer.cancel(e),w());c=null;do{q=!1; +for(u=this;t.length;){try{v=t.shift(),v.scope.$eval(v.expression,v.locals)}catch(C){f(C)}c=null}a:do{if(r=u.$$watchers)for(p=r.length;p--;)try{if(a=r[p])if(n=a.get,(g=n(u))!==(k=a.last)&&!(a.eq?pa(g,k):"number"===typeof g&&"number"===typeof k&&isNaN(g)&&isNaN(k)))q=!0,c=a,a.last=a.eq?qa(g,null):g,l=a.fn,l(g,k===s?g:k,u),5>N&&(y=4-N,x[y]||(x[y]=[]),x[y].push({msg:E(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:k}));else if(a===c){q=!1;break a}}catch(F){f(F)}if(!(r=u.$$watchersCount&& +u.$$childHead||u!==this&&u.$$nextSibling))for(;u!==this&&!(r=u.$$nextSibling);)u=u.$parent}while(u=r);if((q||t.length)&&!N--)throw H.$$phase=null,d("infdig",b,x);}while(q||t.length);for(H.$$phase=null;z.length;)try{z.shift()()}catch(B){f(B)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===H&&h.$$applicationDestroyed();r(this,-this.$$watchersCount);for(var b in this.$$listenerCount)N(this,this.$$listenerCount[b],b);a&&a.$$childHead== +this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=C;this.$on=this.$watch=this.$watchGroup=function(){return C};this.$$listeners={};this.$$nextSibling=null;l(this)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){H.$$phase|| +t.length||h.defer(function(){t.length&&H.$digest()});t.push({scope:this,expression:g(a),locals:b})},$$postDigest:function(a){z.push(a)},$apply:function(a){try{m("$apply");try{return this.$eval(a)}finally{H.$$phase=null}}catch(b){f(b)}finally{try{H.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&u.push(b);a=g(a);p()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]= +0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,N(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=$a([h],arguments,1),l,n;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(n=d.length;l<n;l++)if(d[l])try{d[l].apply(null,k)}catch(m){f(m)}else d.splice(l,1),l--,n--;if(g)return h.currentScope=null,h;e=e.$parent}while(e); +h.currentScope=null;return h},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var g=$a([e],arguments,1),h,k;c=d;){e.currentScope=c;d=c.$$listeners[a]||[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,g)}catch(l){f(l)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope= +null;return e}};var H=new n,t=H.$$asyncQueue=[],z=H.$$postDigestQueue=[],u=H.$$applyAsyncQueue=[];return H}]}function me(){var a=/^\s*(https?|ftp|mailto|tel|file):/,b=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationWhitelist=function(b){return x(b)?(a=b,this):a};this.imgSrcSanitizationWhitelist=function(a){return x(a)?(b=a,this):b};this.$get=function(){return function(d,c){var e=c?b:a,f;f=ra(d).href;return""===f||f.match(e)?d:"unsafe:"+f}}}function lg(a){if("self"===a)return a; +if(F(a)){if(-1<a.indexOf("***"))throw ta("iwcard",a);a=vd(a).replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return new RegExp("^"+a+"$")}if(Wa(a))return new RegExp("^"+a.source+"$");throw ta("imatcher");}function wd(a){var b=[];x(a)&&q(a,function(a){b.push(lg(a))});return b}function xf(){this.SCE_CONTEXTS=oa;var a=["self"],b=[];this.resourceUrlWhitelist=function(b){arguments.length&&(a=wd(b));return a};this.resourceUrlBlacklist=function(a){arguments.length&&(b=wd(a));return b};this.$get=["$injector", +function(d){function c(a,b){return"self"===a?hd(b):!!a.exec(b.href)}function e(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var f=function(a){throw ta("unsafe");};d.has("$sanitize")&&(f=d.get("$sanitize"));var g=e(),h={};h[oa.HTML]=e(g);h[oa.CSS]=e(g);h[oa.URL]=e(g);h[oa.JS]=e(g);h[oa.RESOURCE_URL]= +e(h[oa.URL]);return{trustAs:function(a,b){var c=h.hasOwnProperty(a)?h[a]:null;if(!c)throw ta("icontext",a,b);if(null===b||y(b)||""===b)return b;if("string"!==typeof b)throw ta("itype",a);return new c(b)},getTrusted:function(d,e){if(null===e||y(e)||""===e)return e;var g=h.hasOwnProperty(d)?h[d]:null;if(g&&e instanceof g)return e.$$unwrapTrustedValue();if(d===oa.RESOURCE_URL){var g=ra(e.toString()),m,r,q=!1;m=0;for(r=a.length;m<r;m++)if(c(a[m],g)){q=!0;break}if(q)for(m=0,r=b.length;m<r;m++)if(c(b[m], +g)){q=!1;break}if(q)return e;throw ta("insecurl",e.toString());}if(d===oa.HTML)return f(e);throw ta("unsafe");},valueOf:function(a){return a instanceof g?a.$$unwrapTrustedValue():a}}}]}function wf(){var a=!0;this.enabled=function(b){arguments.length&&(a=!!b);return a};this.$get=["$parse","$sceDelegate",function(b,d){if(a&&8>Ca)throw ta("iequirks");var c=ha(oa);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b}, +c.valueOf=Xa);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,f=c.getTrusted,g=c.trustAs;q(oa,function(a,b){var d=P(b);c[cb("parse_as_"+d)]=function(b){return e(a,b)};c[cb("get_trusted_"+d)]=function(b){return f(a,b)};c[cb("trust_as_"+d)]=function(b){return g(a,b)}});return c}]}function yf(){this.$get=["$window","$document",function(a,b){var d={},c=!(a.chrome&&a.chrome.app&&a.chrome.app.runtime)&&a.history&&a.history.pushState, +e=X((/android (\d+)/.exec(P((a.navigator||{}).userAgent))||[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},h,k=/^(Moz|webkit|ms)(?=[A-Z])/,l=g.body&&g.body.style,n=!1,m=!1;if(l){for(var r in l)if(n=k.exec(r)){h=n[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in l&&"webkit");n=!!("transition"in l||h+"Transition"in l);m=!!("animation"in l||h+"Animation"in l);!e||n&&m||(n=F(l.webkitTransition),m=F(l.webkitAnimation))}return{history:!(!c||4>e||f),hasEvent:function(a){if("input"=== +a&&11>=Ca)return!1;if(y(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ea(),vendorPrefix:h,transitions:n,animations:m,android:e}}]}function Af(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$templateCache","$http","$q","$sce",function(b,d,c,e){function f(g,h){f.totalPendingRequests++;F(g)&&b.get(g)||(g=e.getTrustedResourceUrl(g));var k=d.defaults&&d.defaults.transformResponse;K(k)?k=k.filter(function(a){return a!==ac}):k===ac&&(k=null);return d.get(g, +R({cache:b,transformResponse:k},a))["finally"](function(){f.totalPendingRequests--}).then(function(a){b.put(g,a.data);return a.data},function(a){if(!h)throw mg("tpload",g,a.status,a.statusText);return c.reject(a)})}f.totalPendingRequests=0;return f}]}function Bf(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];q(a,function(a){var c=ea.element(a).data("$binding");c&&q(c,function(c){d?(new RegExp("(^|\\s)"+ +vd(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!=c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],h=0;h<g.length;++h){var k=a.querySelectorAll("["+g[h]+"model"+(d?"=":"*=")+'"'+b+'"]');if(k.length)return k}},getLocation:function(){return d.url()},setLocation:function(b){b!==d.url()&&(d.url(b),a.$digest())},whenStable:function(a){b.notifyWhenNoOutstandingRequests(a)}}}]}function Cf(){this.$get=["$rootScope","$browser","$q","$$q","$exceptionHandler", +function(a,b,d,c,e){function f(f,k,l){E(f)||(l=k,k=f,f=C);var n=za.call(arguments,3),m=x(l)&&!l,r=(m?c:d).defer(),q=r.promise,s;s=b.defer(function(){try{r.resolve(f.apply(null,n))}catch(b){r.reject(b),e(b)}finally{delete g[q.$$timeoutId]}m||a.$apply()},k);q.$$timeoutId=s;g[s]=r;return q}var g={};f.cancel=function(a){return a&&a.$$timeoutId in g?(g[a.$$timeoutId].reject("canceled"),delete g[a.$$timeoutId],b.defer.cancel(a.$$timeoutId)):!1};return f}]}function ra(a){Ca&&(Y.setAttribute("href",a),a= +Y.href);Y.setAttribute("href",a);return{href:Y.href,protocol:Y.protocol?Y.protocol.replace(/:$/,""):"",host:Y.host,search:Y.search?Y.search.replace(/^\?/,""):"",hash:Y.hash?Y.hash.replace(/^#/,""):"",hostname:Y.hostname,port:Y.port,pathname:"/"===Y.pathname.charAt(0)?Y.pathname:"/"+Y.pathname}}function hd(a){a=F(a)?ra(a):a;return a.protocol===xd.protocol&&a.host===xd.host}function Df(){this.$get=da(v)}function yd(a){function b(a){try{return decodeURIComponent(a)}catch(b){return a}}var d=a[0]||{}, +c={},e="";return function(){var a,g,h,k,l;a=d.cookie||"";if(a!==e)for(e=a,a=e.split("; "),c={},h=0;h<a.length;h++)g=a[h],k=g.indexOf("="),0<k&&(l=b(g.substring(0,k)),y(c[l])&&(c[l]=b(g.substring(k+1))));return c}}function Hf(){this.$get=yd}function Jc(a){function b(d,c){if(G(d)){var e={};q(d,function(a,c){e[c]=b(c,a)});return e}return a.factory(d+"Filter",c)}this.register=b;this.$get=["$injector",function(a){return function(b){return a.get(b+"Filter")}}];b("currency",zd);b("date",Ad);b("filter",ng); +b("json",og);b("limitTo",pg);b("lowercase",qg);b("number",Bd);b("orderBy",Cd);b("uppercase",rg)}function ng(){return function(a,b,d){if(!ya(a)){if(null==a)return a;throw O("filter")("notarray",a);}var c;switch(ic(b)){case "function":break;case "boolean":case "null":case "number":case "string":c=!0;case "object":b=sg(b,d,c);break;default:return a}return Array.prototype.filter.call(a,b)}}function sg(a,b,d){var c=G(a)&&"$"in a;!0===b?b=pa:E(b)||(b=function(a,b){if(y(a))return!1;if(null===a||null===b)return a=== +b;if(G(b)||G(a)&&!rc(a))return!1;a=P(""+a);b=P(""+b);return-1!==a.indexOf(b)});return function(e){return c&&!G(e)?Ka(e,a.$,b,!1):Ka(e,a,b,d)}}function Ka(a,b,d,c,e){var f=ic(a),g=ic(b);if("string"===g&&"!"===b.charAt(0))return!Ka(a,b.substring(1),d,c);if(K(a))return a.some(function(a){return Ka(a,b,d,c)});switch(f){case "object":var h;if(c){for(h in a)if("$"!==h.charAt(0)&&Ka(a[h],b,d,!0))return!0;return e?!1:Ka(a,b,d,!1)}if("object"===g){for(h in b)if(e=b[h],!E(e)&&!y(e)&&(f="$"===h,!Ka(f?a:a[h], +e,d,f,f)))return!1;return!0}return d(a,b);case "function":return!1;default:return d(a,b)}}function ic(a){return null===a?"null":typeof a}function zd(a){var b=a.NUMBER_FORMATS;return function(a,c,e){y(c)&&(c=b.CURRENCY_SYM);y(e)&&(e=b.PATTERNS[1].maxFrac);return null==a?a:Dd(a,b.PATTERNS[1],b.GROUP_SEP,b.DECIMAL_SEP,e).replace(/\u00A4/g,c)}}function Bd(a){var b=a.NUMBER_FORMATS;return function(a,c){return null==a?a:Dd(a,b.PATTERNS[0],b.GROUP_SEP,b.DECIMAL_SEP,c)}}function tg(a){var b=0,d,c,e,f,g;-1< +(c=a.indexOf(Ed))&&(a=a.replace(Ed,""));0<(e=a.search(/e/i))?(0>c&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)==jc;e++);if(e==(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)==jc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Fd&&(d=d.splice(0,Fd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function ug(a,b,d,c){var e=a.d,f=e.length-a.i;b=y(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0<d){e.splice(Math.max(a.i,d));for(var g=d;g<e.length;g++)e[g]=0}else for(f= +Math.max(0,f),a.i=1,e.length=Math.max(1,d=b+1),e[0]=0,g=1;g<d;g++)e[g]=0;if(5<=c)if(0>d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;for(;f<Math.max(0,b);f++)e.push(0);if(b=e.reduceRight(function(a,b,c,d){b+=a;d[c]=b%10;return Math.floor(b/10)},0))e.unshift(b),a.i++}function Dd(a,b,d,c,e){if(!F(a)&&!Q(a)||isNaN(a))return"";var f=!isFinite(a),g=!1,h=Math.abs(a)+"",k="";if(f)k="\u221e";else{g=tg(h);ug(g,e,b.minFrac,b.maxFrac);k=g.d;h=g.i;e=g.e;f=[];for(g=k.reduce(function(a, +b){return a&&!b},!0);0>h;)k.unshift(0),h++;0<h?f=k.splice(h):(f=k,k=[0]);h=[];for(k.length>=b.lgSize&&h.unshift(k.splice(-b.lgSize).join(""));k.length>b.gSize;)h.unshift(k.splice(-b.gSize).join(""));k.length&&h.unshift(k.join(""));k=h.join(d);f.length&&(k+=c+f.join(""));e&&(k+="e+"+e)}return 0>a&&!g?b.negPre+k+b.negSuf:b.posPre+k+b.posSuf}function Ib(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length<b;)a=jc+a;d&&(a=a.substr(a.length-b));return e+a}function W(a,b,d,c,e){d= +d||0;return function(f){f=f["get"+a]();if(0<d||f>-d)f+=d;0===f&&-12==d&&(f=12);return Ib(f,b,c,e)}}function ib(a,b,d){return function(c,e){var f=c["get"+a](),g=sb((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Gd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Hd(a){return function(b){var d=Gd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Ib(b,a)}}function kc(a,b){return 0>=a.getFullYear()? +b.ERAS[0]:b.ERAS[1]}function Ad(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=X(b[9]+b[10]),g=X(b[9]+b[11]));h.call(a,X(b[1]),X(b[2])-1,X(b[3]));f=X(b[4]||0)-f;g=X(b[5]||0)-g;h=X(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,f,g,h,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,f){var g="",h= +[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;F(c)&&(c=vg.test(c)?X(c):b(c));Q(c)&&(c=new Date(c));if(!fa(c)||!isFinite(c.getTime()))return c;for(;d;)(l=wg.exec(d))?(h=$a(h,l,1),d=h.pop()):(h.push(d),d=null);var n=c.getTimezoneOffset();f&&(n=vc(f,n),c=Qb(c,f,!0));q(h,function(b){k=xg[b];g+=k?k(c,a.DATETIME_FORMATS,n):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function og(){return function(a,b){y(b)&&(b=2);return ab(a,b)}}function pg(){return function(a,b,d){b=Infinity=== +Math.abs(Number(b))?Number(b):X(b);if(isNaN(b))return a;Q(a)&&(a=a.toString());if(!K(a)&&!F(a))return a;d=!d||isNaN(d)?0:X(d);d=0>d?Math.max(0,a.length+d):d;return 0<=b?a.slice(d,d+b):0===d?a.slice(b,a.length):a.slice(Math.max(0,d+b),d)}}function Cd(a){function b(b,d){d=d?-1:1;return b.map(function(b){var c=1,h=Xa;if(E(b))h=b;else if(F(b)){if("+"==b.charAt(0)||"-"==b.charAt(0))c="-"==b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(h=a(b),h.constant))var k=h(),h=function(a){return a[k]}}return{get:h, +descending:c*d}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}return function(a,e,f){if(null==a)return a;if(!ya(a))throw O("orderBy")("notarray",a);K(e)||(e=[e]);0===e.length&&(e=["+"]);var g=b(e,f);g.push({get:function(){return{}},descending:f?-1:1});a=Array.prototype.map.call(a,function(a,b){return{value:a,predicateValues:g.map(function(c){var e=c.get(a);c=typeof e;if(null===e)c="string",e="null";else if("string"===c)e=e.toLowerCase();else if("object"=== +c)a:{if("function"===typeof e.valueOf&&(e=e.valueOf(),d(e)))break a;if(rc(e)&&(e=e.toString(),d(e)))break a;e=b}return{value:e,type:c}})}});a.sort(function(a,b){for(var c=0,d=0,e=g.length;d<e;++d){var c=a.predicateValues[d],f=b.predicateValues[d],q=0;c.type===f.type?c.value!==f.value&&(q=c.value<f.value?-1:1):q=c.type<f.type?-1:1;if(c=q*g[d].descending)break}return c});return a=a.map(function(a){return a.value})}}function La(a){E(a)&&(a={link:a});a.restrict=a.restrict||"AC";return da(a)}function Id(a, +b,d,c,e){var f=this,g=[];f.$error={};f.$$success={};f.$pending=void 0;f.$name=e(b.name||b.ngForm||"")(d);f.$dirty=!1;f.$pristine=!0;f.$valid=!0;f.$invalid=!1;f.$submitted=!1;f.$$parentForm=Jb;f.$rollbackViewValue=function(){q(g,function(a){a.$rollbackViewValue()})};f.$commitViewValue=function(){q(g,function(a){a.$commitViewValue()})};f.$addControl=function(a){Qa(a.$name,"input");g.push(a);a.$name&&(f[a.$name]=a);a.$$parentForm=f};f.$$renameControl=function(a,b){var c=a.$name;f[c]===a&&delete f[c]; +f[b]=a;a.$name=b};f.$removeControl=function(a){a.$name&&f[a.$name]===a&&delete f[a.$name];q(f.$pending,function(b,c){f.$setValidity(c,null,a)});q(f.$error,function(b,c){f.$setValidity(c,null,a)});q(f.$$success,function(b,c){f.$setValidity(c,null,a)});Za(g,a);a.$$parentForm=Jb};Jd({ctrl:this,$element:a,set:function(a,b,c){var d=a[b];d?-1===d.indexOf(c)&&d.push(c):a[b]=[c]},unset:function(a,b,c){var d=a[b];d&&(Za(d,c),0===d.length&&delete a[b])},$animate:c});f.$setDirty=function(){c.removeClass(a,Ua); +c.addClass(a,Kb);f.$dirty=!0;f.$pristine=!1;f.$$parentForm.$setDirty()};f.$setPristine=function(){c.setClass(a,Ua,Kb+" ng-submitted");f.$dirty=!1;f.$pristine=!0;f.$submitted=!1;q(g,function(a){a.$setPristine()})};f.$setUntouched=function(){q(g,function(a){a.$setUntouched()})};f.$setSubmitted=function(){c.addClass(a,"ng-submitted");f.$submitted=!0;f.$$parentForm.$setSubmitted()}}function lc(a){a.$formatters.push(function(b){return a.$isEmpty(b)?b:b.toString()})}function jb(a,b,d,c,e,f){var g=P(b[0].type); +if(!e.android){var h=!1;b.on("compositionstart",function(){h=!0});b.on("compositionend",function(){h=!1;l()})}var k,l=function(a){k&&(f.defer.cancel(k),k=null);if(!h){var e=b.val();a=a&&a.type;"password"===g||d.ngTrim&&"false"===d.ngTrim||(e=V(e));(c.$viewValue!==e||""===e&&c.$$hasNativeValidators)&&c.$setViewValue(e,a)}};if(e.hasEvent("input"))b.on("input",l);else{var n=function(a,b,c){k||(k=f.defer(function(){k=null;b&&b.value===c||l(a)}))};b.on("keydown",function(a){var b=a.keyCode;91===b||15< +b&&19>b||37<=b&&40>=b||n(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut",n)}b.on("change",l);if(Kd[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!k){var b=this.validity,c=b.badInput,d=b.typeMismatch;k=f.defer(function(){k=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Lb(a,b){return function(d,c){var e,f;if(fa(d))return d;if(F(d)){'"'==d.charAt(0)&& +'"'==d.charAt(d.length-1)&&(d=d.substring(1,d.length-1));if(yg.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},q(e,function(a,c){c<b.length&&(f[b[c]]=+a)}),new Date(f.yyyy,f.MM-1,f.dd,f.HH,f.mm,f.ss||0,1E3*f.sss||0)}return NaN}}function kb(a,b,d,c){return function(e,f,g,h,k,l,n){function m(a){return a&& +!(a.getTime&&a.getTime()!==a.getTime())}function r(a){return x(a)&&!fa(a)?d(a)||void 0:a}Ld(e,f,g,h);jb(e,f,g,h,k,l);var q=h&&h.$options&&h.$options.timezone,s;h.$$parserName=a;h.$parsers.push(function(a){if(h.$isEmpty(a))return null;if(b.test(a))return a=d(a,s),q&&(a=Qb(a,q)),a});h.$formatters.push(function(a){if(a&&!fa(a))throw lb("datefmt",a);if(m(a))return(s=a)&&q&&(s=Qb(s,q,!0)),n("date")(a,c,q);s=null;return""});if(x(g.min)||g.ngMin){var w;h.$validators.min=function(a){return!m(a)||y(w)||d(a)>= +w};g.$observe("min",function(a){w=r(a);h.$validate()})}if(x(g.max)||g.ngMax){var p;h.$validators.max=function(a){return!m(a)||y(p)||d(a)<=p};g.$observe("max",function(a){p=r(a);h.$validate()})}}}function Ld(a,b,d,c){(c.$$hasNativeValidators=G(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{};return c.badInput||c.typeMismatch?void 0:a})}function Md(a,b,d,c,e){if(x(c)){a=a(c);if(!a.constant)throw lb("constexpr",d,c);return a(b)}return e}function mc(a,b){a="ngClass"+a;return["$animate", +function(d){function c(a,b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],n=0;n<b.length;n++)if(e==b[n])continue a;c.push(e)}return c}function e(a){var b=[];return K(a)?(q(a,function(a){b=b.concat(e(a))}),b):F(a)?a.split(" "):G(a)?(q(a,function(a,c){a&&(b=b.concat(c.split(" ")))}),b):a}return{restrict:"AC",link:function(f,g,h){function k(a){a=l(a,1);h.$addClass(a)}function l(a,b){var c=g.data("$classCounts")||T(),d=[];q(a,function(a){if(0<b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)});g.data("$classCounts", +c);return d.join(" ")}function n(a,b){var e=c(b,a),f=c(a,b),e=l(e,1),f=l(f,-1);e&&e.length&&d.addClass(g,e);f&&f.length&&d.removeClass(g,f)}function m(a){if(!0===b||f.$index%2===b){var c=e(a||[]);if(!r)k(c);else if(!pa(a,r)){var d=e(r);n(d,c)}}r=K(a)?a.map(function(a){return ha(a)}):ha(a)}var r;f.$watch(h[a],m,!0);h.$observe("class",function(b){m(f.$eval(h[a]))});"ngClass"!==a&&f.$watch("$index",function(c,d){var g=c&1;if(g!==(d&1)){var m=e(f.$eval(h[a]));g===b?k(m):(g=l(m,-1),h.$removeClass(g))}})}}}]} +function Jd(a){function b(a,b){b&&!f[a]?(k.addClass(e,a),f[a]=!0):!b&&f[a]&&(k.removeClass(e,a),f[a]=!1)}function d(a,c){a=a?"-"+zc(a,"-"):"";b(mb+a,!0===c);b(Nd+a,!1===c)}var c=a.ctrl,e=a.$element,f={},g=a.set,h=a.unset,k=a.$animate;f[Nd]=!(f[mb]=e.hasClass(mb));c.$setValidity=function(a,e,f){y(e)?(c.$pending||(c.$pending={}),g(c.$pending,a,f)):(c.$pending&&h(c.$pending,a,f),Od(c.$pending)&&(c.$pending=void 0));Da(e)?e?(h(c.$error,a,f),g(c.$$success,a,f)):(g(c.$error,a,f),h(c.$$success,a,f)):(h(c.$error, +a,f),h(c.$$success,a,f));c.$pending?(b(Pd,!0),c.$valid=c.$invalid=void 0,d("",null)):(b(Pd,!1),c.$valid=Od(c.$error),c.$invalid=!c.$valid,d("",c.$valid));e=c.$pending&&c.$pending[a]?void 0:c.$error[a]?!1:c.$$success[a]?!0:null;d(a,e);c.$$parentForm.$setValidity(a,e,c)}}function Od(a){if(a)for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}var zg=/^\/(.+)\/([a-z]*)$/,ua=Object.prototype.hasOwnProperty,P=function(a){return F(a)?a.toLowerCase():a},sb=function(a){return F(a)?a.toUpperCase():a},Ca, +B,Z,za=[].slice,Zf=[].splice,Ag=[].push,ma=Object.prototype.toString,sc=Object.getPrototypeOf,Aa=O("ng"),ea=v.angular||(v.angular={}),Sb,nb=0;Ca=v.document.documentMode;C.$inject=[];Xa.$inject=[];var K=Array.isArray,$d=/^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/,V=function(a){return F(a)?a.trim():a},vd=function(a){return a.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")},Ea=function(){if(!x(Ea.rules)){var a=v.document.querySelector("[ng-csp]")|| +v.document.querySelector("[data-ng-csp]");if(a){var b=a.getAttribute("ng-csp")||a.getAttribute("data-ng-csp");Ea.rules={noUnsafeEval:!b||-1!==b.indexOf("no-unsafe-eval"),noInlineStyle:!b||-1!==b.indexOf("no-inline-style")}}else{a=Ea;try{new Function(""),b=!1}catch(d){b=!0}a.rules={noUnsafeEval:b,noInlineStyle:!1}}}return Ea.rules},pb=function(){if(x(pb.name_))return pb.name_;var a,b,d=Na.length,c,e;for(b=0;b<d;++b)if(c=Na[b],a=v.document.querySelector("["+c.replace(":","\\:")+"jq]")){e=a.getAttribute(c+ +"jq");break}return pb.name_=e},ce=/:/g,Na=["ng-","data-ng-","ng:","x-ng-"],he=/[A-Z]/g,Ac=!1,Ma=3,le={full:"1.5.5",major:1,minor:5,dot:5,codeName:"material-conspiration"};U.expando="ng339";var eb=U.cache={},Nf=1;U._data=function(a){return this.cache[a[this.expando]]||{}};var If=/([\:\-\_]+(.))/g,Jf=/^moz([A-Z])/,wb={mouseleave:"mouseout",mouseenter:"mouseover"},Ub=O("jqLite"),Mf=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,Tb=/<|&#?\w+;/,Kf=/<([\w:-]+)/,Lf=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi, +ia={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ia.optgroup=ia.option;ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead;ia.th=ia.td;var Sf=v.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Oa=U.prototype={ready:function(a){function b(){d||(d=!0,a())}var d=!1;"complete"=== +v.document.readyState?v.setTimeout(b):(this.on("DOMContentLoaded",b),U(v).on("load",b))},toString:function(){var a=[];q(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?B(this[a]):B(this[this.length+a])},length:0,push:Ag,sort:[].sort,splice:[].splice},Cb={};q("multiple selected checked disabled readOnly required open".split(" "),function(a){Cb[P(a)]=a});var Sc={};q("input select option textarea button form details".split(" "),function(a){Sc[a]=!0});var ad={ngMinlength:"minlength", +ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};q({data:Wb,removeData:db,hasData:function(a){for(var b in eb[a.ng339])return!0;return!1},cleanData:function(a){for(var b=0,d=a.length;b<d;b++)db(a[b])}},function(a,b){U[b]=a});q({data:Wb,inheritedData:Ab,scope:function(a){return B.data(a,"$scope")||Ab(a.parentNode||a,["$isolateScope","$scope"])},isolateScope:function(a){return B.data(a,"$isolateScope")||B.data(a,"$isolateScopeNoTemplate")},controller:Pc,injector:function(a){return Ab(a, +"$injector")},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:xb,css:function(a,b,d){b=cb(b);if(x(d))a.style[b]=d;else return a.style[b]},attr:function(a,b,d){var c=a.nodeType;if(c!==Ma&&2!==c&&8!==c)if(c=P(b),Cb[c])if(x(d))d?(a[b]=!0,a.setAttribute(b,c)):(a[b]=!1,a.removeAttribute(c));else return a[b]||(a.attributes.getNamedItem(b)||C).specified?c:void 0;else if(x(d))a.setAttribute(b,d);else if(a.getAttribute)return a=a.getAttribute(b,2),null===a?void 0:a},prop:function(a,b,d){if(x(d))a[b]= +d;else return a[b]},text:function(){function a(a,d){if(y(d)){var c=a.nodeType;return 1===c||c===Ma?a.textContent:""}a.textContent=d}a.$dv="";return a}(),val:function(a,b){if(y(b)){if(a.multiple&&"select"===va(a)){var d=[];q(a.options,function(a){a.selected&&d.push(a.value||a.text)});return 0===d.length?null:d}return a.value}a.value=b},html:function(a,b){if(y(b))return a.innerHTML;ub(a,!0);a.innerHTML=b},empty:Qc},function(a,b){U.prototype[b]=function(b,c){var e,f,g=this.length;if(a!==Qc&&y(2==a.length&& +a!==xb&&a!==Pc?b:c)){if(G(b)){for(e=0;e<g;e++)if(a===Wb)a(this[e],b);else for(f in b)a(this[e],f,b[f]);return this}e=a.$dv;g=y(e)?Math.min(g,1):g;for(f=0;f<g;f++){var h=a(this[f],b,c);e=e?e+h:h}return e}for(e=0;e<g;e++)a(this[e],b,c);return this}});q({removeData:db,on:function(a,b,d,c){if(x(c))throw Ub("onargs");if(Kc(a)){c=vb(a,!0);var e=c.events,f=c.handle;f||(f=c.handle=Pf(a,e));c=0<=b.indexOf(" ")?b.split(" "):[b];for(var g=c.length,h=function(b,c,g){var h=e[b];h||(h=e[b]=[],h.specialHandlerWrapper= +c,"$destroy"===b||g||a.addEventListener(b,f,!1));h.push(d)};g--;)b=c[g],wb[b]?(h(wb[b],Rf),h(b,void 0,!0)):h(b)}},off:Oc,one:function(a,b,d){a=B(a);a.on(b,function e(){a.off(b,d);a.off(b,e)});a.on(b,d)},replaceWith:function(a,b){var d,c=a.parentNode;ub(a);q(new U(b),function(b){d?c.insertBefore(b,d.nextSibling):c.replaceChild(b,a);d=b})},children:function(a){var b=[];q(a.childNodes,function(a){1===a.nodeType&&b.push(a)});return b},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a, +b){var d=a.nodeType;if(1===d||11===d){b=new U(b);for(var d=0,c=b.length;d<c;d++)a.appendChild(b[d])}},prepend:function(a,b){if(1===a.nodeType){var d=a.firstChild;q(new U(b),function(b){a.insertBefore(b,d)})}},wrap:function(a,b){Mc(a,B(b).eq(0).clone()[0])},remove:Bb,detach:function(a){Bb(a,!0)},after:function(a,b){var d=a,c=a.parentNode;b=new U(b);for(var e=0,f=b.length;e<f;e++){var g=b[e];c.insertBefore(g,d.nextSibling);d=g}},addClass:zb,removeClass:yb,toggleClass:function(a,b,d){b&&q(b.split(" "), +function(b){var e=d;y(e)&&(e=!xb(a,b));(e?zb:yb)(a,b)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},find:function(a,b){return a.getElementsByTagName?a.getElementsByTagName(b):[]},clone:Vb,triggerHandler:function(a,b,d){var c,e,f=b.type||b,g=vb(a);if(g=(g=g&&g.events)&&g[f])c={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopImmediatePropagation:function(){this.immediatePropagationStopped= +!0},isImmediatePropagationStopped:function(){return!0===this.immediatePropagationStopped},stopPropagation:C,type:f,target:a},b.type&&(c=R(c,b)),b=ha(g),e=d?[c].concat(d):[c],q(b,function(b){c.isImmediatePropagationStopped()||b.apply(a,e)})}},function(a,b){U.prototype[b]=function(b,c,e){for(var f,g=0,h=this.length;g<h;g++)y(f)?(f=a(this[g],b,c,e),x(f)&&(f=B(f))):Nc(f,a(this[g],b,c,e));return x(f)?f:this};U.prototype.bind=U.prototype.on;U.prototype.unbind=U.prototype.off});Ra.prototype={put:function(a, +b){this[Fa(a,this.nextUid)]=b},get:function(a){return this[Fa(a,this.nextUid)]},remove:function(a){var b=this[a=Fa(a,this.nextUid)];delete this[a];return b}};var Gf=[function(){this.$get=[function(){return Ra}]}],Uf=/^([^\(]+?)=>/,Vf=/^[^\(]*\(\s*([^\)]*)\)/m,Bg=/,/,Cg=/^\s*(_?)(\S+?)\1\s*$/,Tf=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ga=O("$injector");bb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw F(d)&&d||(d=a.name||Wf(a)),Ga("strictdi",d); +b=Tc(a);q(b[1].split(Bg),function(a){a.replace(Cg,function(a,b,d){c.push(d)})})}a.$inject=c}}else K(a)?(b=a.length-1,Pa(a[b],"fn"),c=a.slice(0,b)):Pa(a,"fn",!0);return c};var Qd=O("$animate"),Ze=function(){this.$get=C},$e=function(){var a=new Ra,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=F(b)?b.split(" "):K(b)?b:[],q(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){q(b,function(b){var c=a.get(b);if(c){var d=Xf(b.attr("class")),e="",f="";q(c, +function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});q(b,function(a){e&&zb(a,e);f&&yb(a,f)});a.remove(b)}});b.length=0}return{enabled:C,on:C,off:C,pin:C,push:function(g,h,k,l){l&&l();k=k||{};k.from&&g.css(k.from);k.to&&g.css(k.to);if(k.addClass||k.removeClass)if(h=k.addClass,l=k.removeClass,k=a.get(g)||{},h=e(k,h,!0),l=e(k,l,!1),h||l)a.put(g,k),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},Xe=["$provide",function(a){var b=this;this.$$registeredAnimations= +Object.create(null);this.register=function(d,c){if(d&&"."!==d.charAt(0))throw Qd("notcsel",d);var e=d+"-animation";b.$$registeredAnimations[d.substr(1)]=e;a.factory(e,c)};this.classNameFilter=function(a){if(1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw Qd("nongcls","ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var h;a:{for(h=0;h<d.length;h++){var k= +d[h];if(1===k.nodeType){h=k;break a}}h=void 0}!h||h.parentNode||h.previousElementSibling||(d=null)}d?d.after(a):c.prepend(a)}return{on:a.on,off:a.off,pin:a.pin,enabled:a.enabled,cancel:function(a){a.end&&a.end()},enter:function(e,f,g,h){f=f&&B(f);g=g&&B(g);f=f||g.parent();b(e,f,g);return a.push(e,"enter",Ha(h))},move:function(e,f,g,h){f=f&&B(f);g=g&&B(g);f=f||g.parent();b(e,f,g);return a.push(e,"move",Ha(h))},leave:function(b,c){return a.push(b,"leave",Ha(c),function(){b.remove()})},addClass:function(b, +c,g){g=Ha(g);g.addClass=fb(g.addclass,c);return a.push(b,"addClass",g)},removeClass:function(b,c,g){g=Ha(g);g.removeClass=fb(g.removeClass,c);return a.push(b,"removeClass",g)},setClass:function(b,c,g,h){h=Ha(h);h.addClass=fb(h.addClass,c);h.removeClass=fb(h.removeClass,g);return a.push(b,"setClass",h)},animate:function(b,c,g,h,k){k=Ha(k);k.from=k.from?R(k.from,c):c;k.to=k.to?R(k.to,g):g;k.tempClasses=fb(k.tempClasses,h||"ng-inline-animate");return a.push(b,"animate",k)}}}]}],bf=function(){this.$get= +["$$rAF",function(a){function b(b){d.push(b);1<d.length||a(function(){for(var a=0;a<d.length;a++)d[a]();d=[]})}var d=[];return function(){var a=!1;b(function(){a=!0});return function(d){a?d():b(d)}}}]},af=function(){this.$get=["$q","$sniffer","$$animateAsyncRun","$document","$timeout",function(a,b,d,c,e){function f(a){this.setHost(a);var b=d();this._doneCallbacks=[];this._tick=function(a){var d=c[0];d&&d.hidden?e(a,0,!1):b(a)};this._state=0}f.chain=function(a,b){function c(){if(d===a.length)b(!0); +else a[d](function(a){!1===a?b(!1):(d++,c())})}var d=0;c()};f.all=function(a,b){function c(f){e=e&&f;++d===a.length&&b(e)}var d=0,e=!0;q(a,function(a){a.done(c)})};f.prototype={setHost:function(a){this.host=a||{}},done:function(a){2===this._state?a():this._doneCallbacks.push(a)},progress:C,getPromise:function(){if(!this.promise){var b=this;this.promise=a(function(a,c){b.done(function(b){!1===b?c():a()})})}return this.promise},then:function(a,b){return this.getPromise().then(a,b)},"catch":function(a){return this.getPromise()["catch"](a)}, +"finally":function(a){return this.getPromise()["finally"](a)},pause:function(){this.host.pause&&this.host.pause()},resume:function(){this.host.resume&&this.host.resume()},end:function(){this.host.end&&this.host.end();this._resolve(!0)},cancel:function(){this.host.cancel&&this.host.cancel();this._resolve(!1)},complete:function(a){var b=this;0===b._state&&(b._state=1,b._tick(function(){b._resolve(a)}))},_resolve:function(a){2!==this._state&&(q(this._doneCallbacks,function(b){b(a)}),this._doneCallbacks.length= +0,this._state=2)}};return f}]},Ye=function(){this.$get=["$$rAF","$q","$$AnimateRunner",function(a,b,d){return function(b,e){function f(){a(function(){g.addClass&&(b.addClass(g.addClass),g.addClass=null);g.removeClass&&(b.removeClass(g.removeClass),g.removeClass=null);g.to&&(b.css(g.to),g.to=null);h||k.complete();h=!0});return k}var g=e||{};g.$$prepared||(g=qa(g));g.cleanupStyles&&(g.from=g.to=null);g.from&&(b.css(g.from),g.from=null);var h,k=new d;return{start:f,end:f}}}]},ga=O("$compile"),Zb=new function(){}; +Cc.$inject=["$provide","$$sanitizeUriProvider"];Db.prototype.isFirstChange=function(){return this.previousValue===Zb};var Vc=/^((?:x|data)[\:\-_])/i,$f=O("$controller"),bd=/^(\S+)(\s+as\s+([\w$]+))?$/,hf=function(){this.$get=["$document",function(a){return function(b){b?!b.nodeType&&b instanceof B&&(b=b[0]):b=a[0].body;return b.offsetWidth+1}}]},cd="application/json",bc={"Content-Type":cd+";charset=utf-8"},bg=/^\[|^\{(?!\{)/,cg={"[":/]$/,"{":/}$/},ag=/^\)\]\}',?\n/,Dg=O("$http"),gd=function(a){return function(){throw Dg("legacy", +a);}},Ja=ea.$interpolateMinErr=O("$interpolate");Ja.throwNoconcat=function(a){throw Ja("noconcat",a);};Ja.interr=function(a,b){return Ja("interr",a,b.toString())};var Eg=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,eg={http:80,https:443,ftp:21},Eb=O("$location"),Fg={$$html5:!1,$$replace:!1,absUrl:Fb("$$absUrl"),url:function(a){if(y(a))return this.$$url;var b=Eg.exec(a);(b[1]||""===a)&&this.path(decodeURIComponent(b[1]));(b[2]||b[1]||""===a)&&this.search(b[3]||"");this.hash(b[5]||"");return this},protocol:Fb("$$protocol"), +host:Fb("$$host"),port:Fb("$$port"),path:ld("$$path",function(a){a=null!==a?a.toString():"";return"/"==a.charAt(0)?a:"/"+a}),search:function(a,b){switch(arguments.length){case 0:return this.$$search;case 1:if(F(a)||Q(a))a=a.toString(),this.$$search=xc(a);else if(G(a))a=qa(a,{}),q(a,function(b,c){null==b&&delete a[c]}),this.$$search=a;else throw Eb("isrcharg");break;default:y(b)||null===b?delete this.$$search[a]:this.$$search[a]=b}this.$$compose();return this},hash:ld("$$hash",function(a){return null!== +a?a.toString():""}),replace:function(){this.$$replace=!0;return this}};q([kd,ec,dc],function(a){a.prototype=Object.create(Fg);a.prototype.state=function(b){if(!arguments.length)return this.$$state;if(a!==dc||!this.$$html5)throw Eb("nostate");this.$$state=y(b)?null:b;return this}});var ca=O("$parse"),gg=Function.prototype.call,hg=Function.prototype.apply,ig=Function.prototype.bind,Mb=T();q("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(a){Mb[a]=!0});var Gg={n:"\n",f:"\f",r:"\r", +t:"\t",v:"\v","'":"'",'"':'"'},gc=function(a){this.options=a};gc.prototype={constructor:gc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index<this.text.length;)if(a=this.text.charAt(this.index),'"'===a||"'"===a)this.readString(a);else if(this.isNumber(a)||"."===a&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdentifierStart(this.peekMultichar()))this.readIdent();else if(this.is(a,"(){}[].,;:?"))this.tokens.push({index:this.index,text:a}),this.index++;else if(this.isWhitespace(a))this.index++; +else{var b=a+this.peek(),d=b+this.peek(2),c=Mb[b],e=Mb[d];Mb[a]||c||e?(a=e?d:c?b:a,this.tokens.push({index:this.index,text:a,operator:!0}),this.index+=a.length):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a,b){return-1!==b.indexOf(a)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a|| +"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdentifierStart:function(a){return this.options.isIdentifierStart?this.options.isIdentifierStart(a,this.codePointAt(a)):this.isValidIdentifierStart(a)},isValidIdentifierStart:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isIdentifierContinue:function(a){return this.options.isIdentifierContinue?this.options.isIdentifierContinue(a,this.codePointAt(a)):this.isValidIdentifierContinue(a)},isValidIdentifierContinue:function(a,b){return this.isValidIdentifierStart(a, +b)||this.isNumber(a)},codePointAt:function(a){return 1===a.length?a.charCodeAt(0):(a.charCodeAt(0)<<10)+a.charCodeAt(1)-56613888},peekMultichar:function(){var a=this.text.charAt(this.index),b=this.peek();if(!b)return a;var d=a.charCodeAt(0),c=b.charCodeAt(0);return 55296<=d&&56319>=d&&56320<=c&&57343>=c?a+b:a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=x(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw ca("lexerr", +a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index<this.text.length;){var d=P(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var c=this.peek();if("e"==d&&this.isExpOperator(c))a+=d;else if(this.isExpOperator(d)&&c&&this.isNumber(c)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||c&&this.isNumber(c)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:b,text:a,constant:!0,value:Number(a)})}, +readIdent:function(){var a=this.index;for(this.index+=this.peekMultichar().length;this.index<this.text.length;){var b=this.peekMultichar();if(!this.isIdentifierContinue(b))break;this.index+=b.length}this.tokens.push({index:a,text:this.text.slice(a,this.index),identifier:!0})},readString:function(a){var b=this.index;this.index++;for(var d="",c=a,e=!1;this.index<this.text.length;){var f=this.text.charAt(this.index),c=c+f;if(e)"u"===f?(e=this.text.substring(this.index+1,this.index+5),e.match(/[\da-f]{4}/i)|| +this.throwError("Invalid unicode escape [\\u"+e+"]"),this.index+=4,d+=String.fromCharCode(parseInt(e,16))):d+=Gg[f]||f,e=!1;else if("\\"===f)e=!0;else{if(f===a){this.index++;this.tokens.push({index:b,text:c,constant:!0,value:d});return}d+=f}this.index++}this.throwError("Unterminated quote",b)}};var s=function(a,b){this.lexer=a;this.options=b};s.Program="Program";s.ExpressionStatement="ExpressionStatement";s.AssignmentExpression="AssignmentExpression";s.ConditionalExpression="ConditionalExpression"; +s.LogicalExpression="LogicalExpression";s.BinaryExpression="BinaryExpression";s.UnaryExpression="UnaryExpression";s.CallExpression="CallExpression";s.MemberExpression="MemberExpression";s.Identifier="Identifier";s.Literal="Literal";s.ArrayExpression="ArrayExpression";s.Property="Property";s.ObjectExpression="ObjectExpression";s.ThisExpression="ThisExpression";s.LocalsExpression="LocalsExpression";s.NGValueParameter="NGValueParameter";s.prototype={ast:function(a){this.text=a;this.tokens=this.lexer.lex(a); +a=this.program();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);return a},program:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.expressionStatement()),!this.expect(";"))return{type:s.Program,body:a}},expressionStatement:function(){return{type:s.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var a=this.expression();this.expect("|");)a=this.filter(a);return a},expression:function(){return this.assignment()}, +assignment:function(){var a=this.ternary();this.expect("=")&&(a={type:s.AssignmentExpression,left:a,right:this.assignment(),operator:"="});return a},ternary:function(){var a=this.logicalOR(),b,d;return this.expect("?")&&(b=this.expression(),this.consume(":"))?(d=this.expression(),{type:s.ConditionalExpression,test:a,alternate:b,consequent:d}):a},logicalOR:function(){for(var a=this.logicalAND();this.expect("||");)a={type:s.LogicalExpression,operator:"||",left:a,right:this.logicalAND()};return a},logicalAND:function(){for(var a= +this.equality();this.expect("&&");)a={type:s.LogicalExpression,operator:"&&",left:a,right:this.equality()};return a},equality:function(){for(var a=this.relational(),b;b=this.expect("==","!=","===","!==");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.relational()};return a},relational:function(){for(var a=this.additive(),b;b=this.expect("<",">","<=",">=");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(), +b;b=this.expect("+","-");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")): +this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.selfReferential.hasOwnProperty(this.peek().text)?a=qa(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:s.Literal,value:this.options.literals[this.consume().text]}:this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:s.CallExpression, +callee:a,arguments:this.parseArguments()},this.consume(")")):"["===b.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:s.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return b},parseArguments:function(){var a=[];if(")"!== +this.peekToken().text){do a.push(this.expression());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:s.ArrayExpression,elements:a}}, +object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;b={type:s.Property,kind:"init"};this.peek().constant?b.key=this.constant():this.peek().identifier?b.key=this.identifier():this.throwError("invalid key",this.peek());this.consume(":");b.value=this.expression();a.push(b)}while(this.expect(","))}this.consume("}");return{type:s.ObjectExpression,properties:a}},throwError:function(a,b){throw ca("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0=== +this.tokens.length)throw ca("ueoe",this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw ca("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,e){if(this.tokens.length>a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))? +(this.tokens.shift(),a):!1},selfReferential:{"this":{type:s.ThisExpression},$locals:{type:s.LocalsExpression}}};sd.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:b,fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};aa(c,d.$filter);var e="",f;this.stage="assign";if(f=qd(c))this.state.computing="assign",e=this.nextId(),this.recurse(f,e),this.return_(e),e="fn.assign="+this.generateFunction("assign","s,v,l");f=od(c.body); +d.stage="inputs";q(f,function(a,b){var c="fn"+b;d.state[c]={vars:[],body:[],own:{}};d.state.computing=c;var e=d.nextId();d.recurse(a,e);d.return_(e);d.state.inputs.push(c);a.watchId=b});this.state.computing="fn";this.stage="main";this.recurse(c);e='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+e+this.watchFns()+"return fn;";e=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","getStringValue","ensureSafeAssignContext", +"ifDefined","plus","text",e))(this.$filter,Ta,sa,md,fg,Gb,jg,nd,a);this.state=this.stage=void 0;e.literal=rd(c);e.constant=c.constant;return e},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;q(b,function(b){a.push("var "+b+"="+d.generateFunction(b,"s"))});b.length&&a.push("fn.inputs=["+b.join(",")+"];");return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;q(this.state.filters, +function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,e,f){var g,h,k=this,l,n;c=c||C;if(!f&&x(a.watchId))b=b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,e,!0));else switch(a.type){case s.Program:q(a.body,function(b,c){k.recurse(b.expression, +void 0,void 0,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case s.Literal:n=this.escape(a.value);this.assign(b,n);c(n);break;case s.UnaryExpression:this.recurse(a.argument,void 0,void 0,function(a){h=a});n=a.operator+"("+this.ifDefined(h,0)+")";this.assign(b,n);c(n);break;case s.BinaryExpression:this.recurse(a.left,void 0,void 0,function(a){g=a});this.recurse(a.right,void 0,void 0,function(a){h=a});n="+"===a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g, +0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,n);c(n);break;case s.LogicalExpression:b=b||this.nextId();k.recurse(a.left,b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case s.ConditionalExpression:b=b||this.nextId();k.recurse(a.test,b);k.if_(b,k.lazyRecurse(a.alternate,b),k.lazyRecurse(a.consequent,b));c(b);break;case s.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l", +a.name)+"?l:s"),d.computed=!1,d.name=a.name);Ta(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){e&&1!==e&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s",a.name))})},b&&k.lazyAssign(b,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Hb(a.name))&&k.addEnsureSafeObject(b);c(b);break;case s.MemberExpression:g=d&&(d.context=this.nextId())|| +this.nextId();b=b||this.nextId();k.recurse(a.object,g,void 0,function(){k.if_(k.notNull(g),function(){e&&1!==e&&k.addEnsureSafeAssignContext(g);if(a.computed)h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),k.addEnsureSafeMemberName(h),e&&1!==e&&k.if_(k.not(k.computedMember(g,h)),k.lazyAssign(k.computedMember(g,h),"{}")),n=k.ensureSafeObject(k.computedMember(g,h)),k.assign(b,n),d&&(d.computed=!0,d.name=h);else{Ta(a.property.name);e&&1!==e&&k.if_(k.not(k.nonComputedMember(g,a.property.name)), +k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}"));n=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Hb(a.property.name))n=k.ensureSafeObject(n);k.assign(b,n);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(b,"undefined")});c(b)},!!e);break;case s.CallExpression:b=b||this.nextId();a.filter?(h=k.filter(a.callee.name),l=[],q(a.arguments,function(a){var b=k.nextId();k.recurse(a,b);l.push(b)}),n=h+"("+l.join(",")+")",k.assign(b,n),c(b)):(h=k.nextId(),g={},l= +[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){k.addEnsureSafeFunction(h);q(a.arguments,function(a){k.recurse(a,k.nextId(),void 0,function(a){l.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),n=k.member(g.context,g.name,g.computed)+"("+l.join(",")+")"):n=h+"("+l.join(",")+")";n=k.ensureSafeObject(n);k.assign(b,n)},function(){k.assign(b,"undefined")});c(b)}));break;case s.AssignmentExpression:h=this.nextId();g={};if(!pd(a.left))throw ca("lval"); +this.recurse(a.left,void 0,g,function(){k.if_(k.notNull(g.context),function(){k.recurse(a.right,h);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);n=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,n);c(b||n)})},1);break;case s.ArrayExpression:l=[];q(a.elements,function(a){k.recurse(a,k.nextId(),void 0,function(a){l.push(a)})});n="["+l.join(",")+"]";this.assign(b,n);c(n);break;case s.ObjectExpression:l=[];q(a.properties,function(a){k.recurse(a.value, +k.nextId(),void 0,function(b){l.push(k.escape(a.key.type===s.Identifier?a.key.name:""+a.key.value)+":"+b)})});n="{"+l.join(",")+"}";this.assign(b,n);c(n);break;case s.ThisExpression:this.assign(b,"s");c("s");break;case s.LocalsExpression:this.assign(b,"l");c("l");break;case s.NGValueParameter:this.assign(b,"v"),c("v")}},getHasOwnProperty:function(a,b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},assign:function(a,b){if(a)return this.current().body.push(a, +"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}");d&&(c.push("else{"),d(),c.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+ +"!=null"},nonComputedMember:function(a,b){var d=/[^$_a-zA-Z0-9]/g;return/[$_a-zA-Z][$_a-zA-Z0-9]*/.test(b)?a+"."+b:a+'["'+b.replace(d,this.stringEscapeFn)+'"]'},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),";")},addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a), +";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+a+",text)"},lazyRecurse:function(a,b,d,c,e,f){var g= +this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(F(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(Q(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===typeof a)return"undefined";throw ca("esc");},nextId:function(a, +b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};td.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=b;aa(c,d.$filter);var e,f;if(e=qd(c))f=this.recurse(e);e=od(c.body);var g;e&&(g=[],q(e,function(a,b){var c=d.recurse(a);a.input=c;g.push(c);a.watchId=b}));var h=[];q(c.body,function(a){h.push(d.recurse(a.expression))});e=0===c.body.length?C:1=== +c.body.length?h[0]:function(a,b){var c;q(h,function(d){c=d(a,b)});return c};f&&(e.assign=function(a,b,c){return f(a,c,b)});g&&(e.inputs=g);e.literal=rd(c);e.constant=c.constant;return e},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case s.Literal:return this.value(a.value,b);case s.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case s.BinaryExpression:return c=this.recurse(a.left),e=this.recurse(a.right), +this["binary"+a.operator](c,e,b);case s.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case s.Identifier:return Ta(a.name,f.expression),f.identifier(a.name,f.expensiveChecks||Hb(a.name),b,d,f.expression);case s.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(Ta(a.property.name,f.expression), +e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d,f.expression):this.nonComputedMember(c,e,f.expensiveChecks,b,d,f.expression);case s.CallExpression:return g=[],q(a.arguments,function(a){g.push(f.recurse(a))}),a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var m=[],r=0;r<g.length;++r)m.push(g[r](a,c,d,f));a=e.apply(void 0,m,f);return b?{context:void 0,name:void 0,value:a}:a}:function(a, +c,d,n){var m=e(a,c,d,n),r;if(null!=m.value){sa(m.context,f.expression);md(m.value,f.expression);r=[];for(var q=0;q<g.length;++q)r.push(sa(g[q](a,c,d,n),f.expression));r=sa(m.value.apply(m.context,r),f.expression)}return b?{value:r}:r};case s.AssignmentExpression:return c=this.recurse(a.left,!0,1),e=this.recurse(a.right),function(a,d,g,n){var m=c(a,d,g,n);a=e(a,d,g,n);sa(m.value,f.expression);Gb(m.context);m.context[m.name]=a;return b?{value:a}:a};case s.ArrayExpression:return g=[],q(a.elements,function(a){g.push(f.recurse(a))}), +function(a,c,d,e){for(var f=[],r=0;r<g.length;++r)f.push(g[r](a,c,d,e));return b?{value:f}:f};case s.ObjectExpression:return g=[],q(a.properties,function(a){g.push({key:a.key.type===s.Identifier?a.key.name:""+a.key.value,value:f.recurse(a.value)})}),function(a,c,d,e){for(var f={},r=0;r<g.length;++r)f[g[r].key]=g[r].value(a,c,d,e);return b?{value:f}:f};case s.ThisExpression:return function(a){return b?{value:a}:a};case s.LocalsExpression:return function(a,c){return b?{value:c}:c};case s.NGValueParameter:return function(a, +c,d){return b?{value:d}:d}}},"unary+":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=x(d)?+d:0;return b?{value:d}:d}},"unary-":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=x(d)?-d:0;return b?{value:d}:d}},"unary!":function(a,b){return function(d,c,e,f){d=!a(d,c,e,f);return b?{value:d}:d}},"binary+":function(a,b,d){return function(c,e,f,g){var h=a(c,e,f,g);c=b(c,e,f,g);h=nd(h,c);return d?{value:h}:h}},"binary-":function(a,b,d){return function(c,e,f,g){var h=a(c,e,f,g);c=b(c,e,f,g); +h=(x(h)?h:0)-(x(c)?c:0);return d?{value:h}:h}},"binary*":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)*b(c,e,f,g);return d?{value:c}:c}},"binary/":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)/b(c,e,f,g);return d?{value:c}:c}},"binary%":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)%b(c,e,f,g);return d?{value:c}:c}},"binary===":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)===b(c,e,f,g);return d?{value:c}:c}},"binary!==":function(a,b,d){return function(c,e,f,g){c=a(c, +e,f,g)!==b(c,e,f,g);return d?{value:c}:c}},"binary==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)==b(c,e,f,g);return d?{value:c}:c}},"binary!=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!=b(c,e,f,g);return d?{value:c}:c}},"binary<":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)<b(c,e,f,g);return d?{value:c}:c}},"binary>":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f, +g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,h){e=a(e,f,g,h)?b(e,f,g,h):d(e,f,g,h);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:void 0, +name:void 0,value:a}:a}},identifier:function(a,b,d,c,e){return function(f,g,h,k){f=g&&a in g?g:f;c&&1!==c&&f&&!f[a]&&(f[a]={});g=f?f[a]:void 0;b&&sa(g,e);return d?{context:f,name:a,value:g}:g}},computedMember:function(a,b,d,c,e){return function(f,g,h,k){var l=a(f,g,h,k),n,m;null!=l&&(n=b(f,g,h,k),n+="",Ta(n,e),c&&1!==c&&(Gb(l),l&&!l[n]&&(l[n]={})),m=l[n],sa(m,e));return d?{context:l,name:n,value:m}:m}},nonComputedMember:function(a,b,d,c,e,f){return function(g,h,k,l){g=a(g,h,k,l);e&&1!==e&&(Gb(g), +g&&!g[b]&&(g[b]={}));h=null!=g?g[b]:void 0;(d||Hb(b))&&sa(h,f);return c?{context:g,name:b,value:h}:h}},inputs:function(a,b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};var hc=function(a,b,d){this.lexer=a;this.$filter=b;this.options=d;this.ast=new s(a,d);this.astCompiler=d.csp?new td(this.ast,b):new sd(this.ast,b)};hc.prototype={constructor:hc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};var kg=Object.prototype.valueOf,ta=O("$sce"),oa={HTML:"html",CSS:"css", +URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},mg=O("$compile"),Y=v.document.createElement("a"),xd=ra(v.location.href);yd.$inject=["$document"];Jc.$inject=["$provide"];var Fd=22,Ed=".",jc="0";zd.$inject=["$locale"];Bd.$inject=["$locale"];var xg={yyyy:W("FullYear",4,0,!1,!0),yy:W("FullYear",2,0,!0,!0),y:W("FullYear",1,0,!1,!0),MMMM:ib("Month"),MMM:ib("Month",!0),MM:W("Month",2,1),M:W("Month",1,1),LLLL:ib("Month",!1,!0),dd:W("Date",2),d:W("Date",1),HH:W("Hours",2),H:W("Hours",1),hh:W("Hours",2,-12), +h:W("Hours",1,-12),mm:W("Minutes",2),m:W("Minutes",1),ss:W("Seconds",2),s:W("Seconds",1),sss:W("Milliseconds",3),EEEE:ib("Day"),EEE:ib("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Ib(Math[0<a?"floor":"ceil"](a/60),2)+Ib(Math.abs(a%60),2))},ww:Hd(2),w:Hd(1),G:kc,GG:kc,GGG:kc,GGGG:function(a,b){return 0>=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},wg=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, +vg=/^\-?\d+$/;Ad.$inject=["$locale"];var qg=da(P),rg=da(sb);Cd.$inject=["$parse"];var ne=da({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===ma.call(b.prop("href"))?"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),tb={};q(Cb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!=a){var c=xa("ng-"+b),e=d;"checked"===a&&(e=function(a, +b,e){e.ngModel!==e[c]&&d(a,b,e)});tb[c]=function(){return{restrict:"A",priority:100,link:e}}}});q(ad,function(a,b){tb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"==e.ngPattern.charAt(0)&&(c=e.ngPattern.match(zg))){e.$set("ngPattern",new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});q(["src","srcset","href"],function(a){var b=xa("ng-"+a);tb[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"=== +ma.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),Ca&&f&&c.prop(f,e[g])):"href"===a&&e.$set(g,null)})}}}});var Jb={$addControl:C,$$renameControl:function(a,b){a.$name=b},$removeControl:C,$setValidity:C,$setDirty:C,$setPristine:C,$setSubmitted:C};Id.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Rd=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||C}return{name:"form", +restrict:a?"EAC":"E",require:["form","^^?form"],controller:Id,compile:function(d,f){d.addClass(Ua).addClass(mb);var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var m=f[0];if(!("action"in e)){var r=function(b){a.$apply(function(){m.$commitViewValue();m.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",r,!1);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",r,!1)},0,!1)})}(f[1]||m.$$parentForm).$addControl(m);var q=g?c(m.$name):C;g&& +(q(a,m),e.$observe(g,function(b){m.$name!==b&&(q(a,void 0),m.$$parentForm.$$renameControl(m,b),q=c(m.$name),q(a,m))}));d.on("$destroy",function(){m.$$parentForm.$removeControl(m);q(a,void 0);R(m,Jb)})}}}}}]},oe=Rd(),Be=Rd(!0),yg=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,Hg=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,Ig=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i, +Jg=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Sd=/^(\d{4,})-(\d{2})-(\d{2})$/,Td=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,nc=/^(\d{4,})-W(\d\d)$/,Ud=/^(\d{4,})-(\d\d)$/,Vd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Kd=T();q(["date","datetime-local","month","time","week"],function(a){Kd[a]=!0});var Wd={text:function(a,b,d,c,e,f){jb(a,b,d,c,e,f);lc(c)},date:kb("date",Sd,Lb(Sd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":kb("datetimelocal",Td,Lb(Td,"yyyy MM dd HH mm ss sss".split(" ")), +"yyyy-MM-ddTHH:mm:ss.sss"),time:kb("time",Vd,Lb(Vd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:kb("week",nc,function(a,b){if(fa(a))return a;if(F(a)){nc.lastIndex=0;var d=nc.exec(a);if(d){var c=+d[1],e=+d[2],f=d=0,g=0,h=0,k=Gd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds());return new Date(c,0,k.getDate()+e,d,f,g,h)}}return NaN},"yyyy-Www"),month:kb("month",Ud,Lb(Ud,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Ld(a,b,d,c);jb(a,b,d,c,e,f);c.$$parserName= +"number";c.$parsers.push(function(a){if(c.$isEmpty(a))return null;if(Jg.test(a))return parseFloat(a)});c.$formatters.push(function(a){if(!c.$isEmpty(a)){if(!Q(a))throw lb("numfmt",a);a=a.toString()}return a});if(x(d.min)||d.ngMin){var g;c.$validators.min=function(a){return c.$isEmpty(a)||y(g)||a>=g};d.$observe("min",function(a){x(a)&&!Q(a)&&(a=parseFloat(a,10));g=Q(a)&&!isNaN(a)?a:void 0;c.$validate()})}if(x(d.max)||d.ngMax){var h;c.$validators.max=function(a){return c.$isEmpty(a)||y(h)||a<=h};d.$observe("max", +function(a){x(a)&&!Q(a)&&(a=parseFloat(a,10));h=Q(a)&&!isNaN(a)?a:void 0;c.$validate()})}},url:function(a,b,d,c,e,f){jb(a,b,d,c,e,f);lc(c);c.$$parserName="url";c.$validators.url=function(a,b){var d=a||b;return c.$isEmpty(d)||Hg.test(d)}},email:function(a,b,d,c,e,f){jb(a,b,d,c,e,f);lc(c);c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||Ig.test(d)}},radio:function(a,b,d,c){y(d.name)&&b.attr("name",++nb);b.on("click",function(a){b[0].checked&&c.$setViewValue(d.value, +a&&a.type)});c.$render=function(){b[0].checked=d.value==c.$viewValue};d.$observe("value",c.$render)},checkbox:function(a,b,d,c,e,f,g,h){var k=Md(h,a,"ngTrueValue",d.ngTrueValue,!0),l=Md(h,a,"ngFalseValue",d.ngFalseValue,!1);b.on("click",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1===a};c.$formatters.push(function(a){return pa(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:C,button:C,submit:C,reset:C, +file:C},Dc=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,h){h[0]&&(Wd[P(g.type)]||Wd.text)(e,f,g,h[0],b,a,d,c)}}}}],Kg=/^(true|false|\d+)$/,Te=function(){return{restrict:"A",priority:100,compile:function(a,b){return Kg.test(b.ngValue)?function(a,b,e){e.$set("value",a.$eval(e.ngValue))}:function(a,b,e){a.$watch(e.ngValue,function(a){e.$set("value",a)})}}}},te=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b); +return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=y(a)?"":a})}}}}],ve=["$interpolate","$compile",function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=y(a)?"":a})}}}}],ue=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g= +b(e.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){c.html(a.getTrustedHtml(f(b))||"")})}}}}],Se=da({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),we=mc("",!0),ye=mc("Odd",0),xe=mc("Even",1),ze=La({compile:function(a,b){b.$set("ngCloak",void 0);a.removeClass("ng-cloak")}}),Ae=[function(){return{restrict:"A",scope:!0,controller:"@", +priority:500}}],Ic={},Lg={blur:!0,focus:!0};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var b=xa("ng-"+a);Ic[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g=d(f[b],null,!0);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};Lg[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var De=["$animate","$compile",function(a, +b){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var h,k,l;d.$watch(e.ngIf,function(d){d?k||g(function(d,f){k=f;d[d.length++]=b.$$createComment("end ngIf",e.ngIf);h={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),k&&(k.$destroy(),k=null),h&&(l=rb(h.clone),a.leave(l).then(function(){l=null}),h=null))})}}}],Ee=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0, +transclude:"element",controller:ea.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",h=e.autoscroll;return function(c,e,n,m,r){var q=0,s,w,p,y=function(){w&&(w.remove(),w=null);s&&(s.$destroy(),s=null);p&&(d.leave(p).then(function(){w=null}),w=p,p=null)};c.$watch(f,function(f){var n=function(){!x(h)||h&&!c.$eval(h)||b()},u=++q;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&u===q){var b=c.$new();m.template=a;a=r(b,function(a){y();d.enter(a,null,e).then(n)});s=b;p=a;s.$emit("$includeContentLoaded", +f);c.$eval(g)}},function(){c.$$destroyed||u!==q||(y(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(y(),m.template=null)})}}}}],Ve=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(b,d,c,e){ma.call(d[0]).match(/SVG/)?(d.empty(),a(Lc(e.template,v.document).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],Fe=La({priority:450,compile:function(){return{pre:function(a, +b,d){a.$eval(d.ngInit)}}}}),Re=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=b.attr(d.$attr.ngList)||", ",f="false"!==d.ngTrim,g=f?V(e):e;c.$parsers.push(function(a){if(!y(a)){var b=[];a&&q(a.split(g),function(a){a&&b.push(f?V(a):a)});return b}});c.$formatters.push(function(a){if(K(a))return a.join(e)});c.$isEmpty=function(a){return!a||!a.length}}}},mb="ng-valid",Nd="ng-invalid",Ua="ng-pristine",Kb="ng-dirty",Pd="ng-pending",lb=O("ngModel"),Mg=["$scope", +"$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,b,d,c,e,f,g,h,k,l){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=void 0;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=void 0;this.$name=l(d.name||"",!1)(a); +this.$$parentForm=Jb;var n=e(d.ngModel),m=n.assign,r=n,s=m,v=null,w,p=this;this.$$setOptions=function(a){if((p.$options=a)&&a.getterSetter){var b=e(d.ngModel+"()"),f=e(d.ngModel+"($$$p)");r=function(a){var c=n(a);E(c)&&(c=b(a));return c};s=function(a,b){E(n(a))?f(a,{$$$p:b}):m(a,b)}}else if(!n.assign)throw lb("nonassign",d.ngModel,wa(c));};this.$render=C;this.$isEmpty=function(a){return y(a)||""===a||null===a||a!==a};this.$$updateEmptyClasses=function(a){p.$isEmpty(a)?(f.removeClass(c,"ng-not-empty"), +f.addClass(c,"ng-empty")):(f.removeClass(c,"ng-empty"),f.addClass(c,"ng-not-empty"))};var H=0;Jd({ctrl:this,$element:c,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]},$animate:f});this.$setPristine=function(){p.$dirty=!1;p.$pristine=!0;f.removeClass(c,Kb);f.addClass(c,Ua)};this.$setDirty=function(){p.$dirty=!0;p.$pristine=!1;f.removeClass(c,Ua);f.addClass(c,Kb);p.$$parentForm.$setDirty()};this.$setUntouched=function(){p.$touched=!1;p.$untouched=!0;f.setClass(c,"ng-untouched","ng-touched")}; +this.$setTouched=function(){p.$touched=!0;p.$untouched=!1;f.setClass(c,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){g.cancel(v);p.$viewValue=p.$$lastCommittedViewValue;p.$render()};this.$validate=function(){if(!Q(p.$modelValue)||!isNaN(p.$modelValue)){var a=p.$$rawModelValue,b=p.$valid,c=p.$modelValue,d=p.$options&&p.$options.allowInvalid;p.$$runValidators(a,p.$$lastCommittedViewValue,function(e){d||b===e||(p.$modelValue=e?a:void 0,p.$modelValue!==c&&p.$$writeModelToScope())})}}; +this.$$runValidators=function(a,b,c){function d(){var c=!0;q(p.$validators,function(d,e){var g=d(a,b);c=c&&g;f(e,g)});return c?!0:(q(p.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;q(p.$asyncValidators,function(e,g){var h=e(a,b);if(!h||!E(h.then))throw lb("nopromise",h);f(g,void 0);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?k.all(c).then(function(){g(d)},C):g(!0)}function f(a,b){h===H&&p.$setValidity(a,b)}function g(a){h===H&&c(a)}H++;var h= +H;(function(){var a=p.$$parserName||"parse";if(y(w))f(a,null);else return w||(q(p.$validators,function(a,b){f(b,null)}),q(p.$asyncValidators,function(a,b){f(b,null)})),f(a,w),w;return!0})()?d()?e():g(!1):g(!1)};this.$commitViewValue=function(){var a=p.$viewValue;g.cancel(v);if(p.$$lastCommittedViewValue!==a||""===a&&p.$$hasNativeValidators)p.$$updateEmptyClasses(a),p.$$lastCommittedViewValue=a,p.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var b=p.$$lastCommittedViewValue; +if(w=y(b)?void 0:!0)for(var c=0;c<p.$parsers.length;c++)if(b=p.$parsers[c](b),y(b)){w=!1;break}Q(p.$modelValue)&&isNaN(p.$modelValue)&&(p.$modelValue=r(a));var d=p.$modelValue,e=p.$options&&p.$options.allowInvalid;p.$$rawModelValue=b;e&&(p.$modelValue=b,p.$modelValue!==d&&p.$$writeModelToScope());p.$$runValidators(b,p.$$lastCommittedViewValue,function(a){e||(p.$modelValue=a?b:void 0,p.$modelValue!==d&&p.$$writeModelToScope())})};this.$$writeModelToScope=function(){s(a,p.$modelValue);q(p.$viewChangeListeners, +function(a){try{a()}catch(c){b(c)}})};this.$setViewValue=function(a,b){p.$viewValue=a;p.$options&&!p.$options.updateOnDefault||p.$$debounceViewValueCommit(b)};this.$$debounceViewValueCommit=function(b){var c=0,d=p.$options;d&&x(d.debounce)&&(d=d.debounce,Q(d)?c=d:Q(d[b])?c=d[b]:Q(d["default"])&&(c=d["default"]));g.cancel(v);c?v=g(function(){p.$commitViewValue()},c):h.$$phase?p.$commitViewValue():a.$apply(function(){p.$commitViewValue()})};a.$watch(function(){var b=r(a);if(b!==p.$modelValue&&(p.$modelValue=== +p.$modelValue||b===b)){p.$modelValue=p.$$rawModelValue=b;w=void 0;for(var c=p.$formatters,d=c.length,e=b;d--;)e=c[d](e);p.$viewValue!==e&&(p.$$updateEmptyClasses(e),p.$viewValue=p.$$lastCommittedViewValue=e,p.$render(),p.$$runValidators(b,e,C))}return b})}],Qe=["$rootScope",function(a){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:Mg,priority:1,compile:function(b){b.addClass(Ua).addClass("ng-untouched").addClass(mb);return{pre:function(a,b,e,f){var g=f[0];b=f[1]|| +g.$$parentForm;g.$$setOptions(f[2]&&f[2].$options);b.$addControl(g);e.$observe("name",function(a){g.$name!==a&&g.$$parentForm.$$renameControl(g,a)});a.$on("$destroy",function(){g.$$parentForm.$removeControl(g)})},post:function(b,c,e,f){var g=f[0];if(g.$options&&g.$options.updateOn)c.on(g.$options.updateOn,function(a){g.$$debounceViewValueCommit(a&&a.type)});c.on("blur",function(){g.$touched||(a.$$phase?b.$evalAsync(g.$setTouched):b.$apply(g.$setTouched))})}}}}}],Ng=/(\s+|^)default(\s+|$)/,Ue=function(){return{restrict:"A", +controller:["$scope","$attrs",function(a,b){var d=this;this.$options=qa(a.$eval(b.ngModelOptions));x(this.$options.updateOn)?(this.$options.updateOnDefault=!1,this.$options.updateOn=V(this.$options.updateOn.replace(Ng,function(){d.$options.updateOnDefault=!0;return" "}))):this.$options.updateOnDefault=!0}]}},Ge=La({terminal:!0,priority:1E3}),Og=O("ngOptions"),Pg=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/, +Oe=["$compile","$document","$parse",function(a,b,d){function c(a,b,c){function e(a,b,c,d,f){this.selectValue=a;this.viewValue=b;this.label=c;this.group=d;this.disabled=f}function f(a){var b;if(!q&&ya(a))b=a;else{b=[];for(var c in a)a.hasOwnProperty(c)&&"$"!==c.charAt(0)&&b.push(c)}return b}var m=a.match(Pg);if(!m)throw Og("iexp",a,wa(b));var r=m[5]||m[7],q=m[6];a=/ as /.test(m[0])&&m[1];var s=m[9];b=d(m[2]?m[1]:r);var w=a&&d(a)||b,p=s&&d(s),v=s?function(a,b){return p(c,b)}:function(a){return Fa(a)}, +t=function(a,b){return v(a,L(a,b))},z=d(m[2]||m[1]),u=d(m[3]||""),y=d(m[4]||""),x=d(m[8]),D={},L=q?function(a,b){D[q]=b;D[r]=a;return D}:function(a){D[r]=a;return D};return{trackBy:s,getTrackByValue:t,getWatchables:d(x,function(a){var b=[];a=a||[];for(var d=f(a),e=d.length,g=0;g<e;g++){var h=a===d?g:d[g],l=a[h],h=L(l,h),l=v(l,h);b.push(l);if(m[2]||m[1])l=z(c,h),b.push(l);m[4]&&(h=y(c,h),b.push(h))}return b}),getOptions:function(){for(var a=[],b={},d=x(c)||[],g=f(d),h=g.length,m=0;m<h;m++){var p=d=== +g?m:g[m],q=L(d[p],p),r=w(c,q),p=v(r,q),D=z(c,q),N=u(c,q),q=y(c,q),r=new e(p,r,D,N,q);a.push(r);b[p]=r}return{items:a,selectValueMap:b,getOptionFromViewValue:function(a){return b[t(a)]},getViewValueFromOption:function(a){return s?ea.copy(a.viewValue):a.viewValue}}}}}var e=v.document.createElement("option"),f=v.document.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","ngModel"],link:{pre:function(a,b,c,d){d[0].registerOption=C},post:function(d,h,k,l){function n(a,b){a.element= +b;b.disabled=a.disabled;a.label!==b.label&&(b.label=a.label,b.textContent=a.label);a.value!==b.value&&(b.value=a.selectValue)}function m(){var a=u&&r.readValue();if(u)for(var b=u.items.length-1;0<=b;b--){var c=u.items[b];c.group?Bb(c.element.parentNode):Bb(c.element)}u=I.getOptions();var d={};t&&h.prepend(w);u.items.forEach(function(a){var b;if(x(a.group)){b=d[a.group];b||(b=f.cloneNode(!1),E.appendChild(b),b.label=a.group,d[a.group]=b);var c=e.cloneNode(!1)}else b=E,c=e.cloneNode(!1);b.appendChild(c); +n(a,c)});h[0].appendChild(E);s.$render();s.$isEmpty(a)||(b=r.readValue(),(I.trackBy||v?pa(a,b):a===b)||(s.$setViewValue(b),s.$render()))}var r=l[0],s=l[1],v=k.multiple,w;l=0;for(var p=h.children(),y=p.length;l<y;l++)if(""===p[l].value){w=p.eq(l);break}var t=!!w,z=B(e.cloneNode(!1));z.val("?");var u,I=c(k.ngOptions,h,d),E=b[0].createDocumentFragment();v?(s.$isEmpty=function(a){return!a||0===a.length},r.writeValue=function(a){u.items.forEach(function(a){a.element.selected=!1});a&&a.forEach(function(a){if(a= +u.getOptionFromViewValue(a))a.element.selected=!0})},r.readValue=function(){var a=h.val()||[],b=[];q(a,function(a){(a=u.selectValueMap[a])&&!a.disabled&&b.push(u.getViewValueFromOption(a))});return b},I.trackBy&&d.$watchCollection(function(){if(K(s.$viewValue))return s.$viewValue.map(function(a){return I.getTrackByValue(a)})},function(){s.$render()})):(r.writeValue=function(a){var b=u.getOptionFromViewValue(a);b?(h[0].value!==b.selectValue&&(z.remove(),t||w.remove(),h[0].value=b.selectValue,b.element.selected= +!0),b.element.setAttribute("selected","selected")):null===a||t?(z.remove(),t||h.prepend(w),h.val(""),w.prop("selected",!0),w.attr("selected",!0)):(t||w.remove(),h.prepend(z),h.val("?"),z.prop("selected",!0),z.attr("selected",!0))},r.readValue=function(){var a=u.selectValueMap[h.val()];return a&&!a.disabled?(t||w.remove(),z.remove(),u.getViewValueFromOption(a)):null},I.trackBy&&d.$watch(function(){return I.getTrackByValue(s.$viewValue)},function(){s.$render()}));t?(w.remove(),a(w)(d),w.removeClass("ng-scope")): +w=B(e.cloneNode(!1));h.empty();m();d.$watchCollection(I.getWatchables,m)}}}}],He=["$locale","$interpolate","$log",function(a,b,d){var c=/{}/g,e=/^when(Minus)?(.+)$/;return{link:function(f,g,h){function k(a){g.text(a||"")}var l=h.count,n=h.$attr.when&&g.attr(h.$attr.when),m=h.offset||0,r=f.$eval(n)||{},s={},v=b.startSymbol(),w=b.endSymbol(),p=v+l+"-"+m+w,x=ea.noop,t;q(h,function(a,b){var c=e.exec(b);c&&(c=(c[1]?"-":"")+P(c[2]),r[c]=g.attr(h.$attr[b]))});q(r,function(a,d){s[d]=b(a.replace(c,p))});f.$watch(l, +function(b){var c=parseFloat(b),e=isNaN(c);e||c in r||(c=a.pluralCat(c-m));c===t||e&&Q(t)&&isNaN(t)||(x(),e=s[c],y(e)?(null!=b&&d.debug("ngPluralize: no rule defined for '"+c+"' in "+n),x=C,k()):x=f.$watch(e,k),t=c)})}}}],Ie=["$parse","$animate","$compile",function(a,b,d){var c=O("ngRepeat"),e=function(a,b,c,d,e,n,m){a[c]=d;e&&(a[e]=n);a.$index=b;a.$first=0===b;a.$last=b===m-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(b&1))};return{restrict:"A",multiElement:!0,transclude:"element",priority:1E3, +terminal:!0,$$tlb:!0,compile:function(f,g){var h=g.ngRepeat,k=d.$$createComment("end ngRepeat",h),l=h.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!l)throw c("iexp",h);var n=l[1],m=l[2],r=l[3],s=l[4],l=n.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);if(!l)throw c("iidexp",n);var v=l[3]||l[1],w=l[2];if(r&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(r)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(r)))throw c("badident", +r);var p,y,t,z,u={$id:Fa};s?p=a(s):(t=function(a,b){return Fa(b)},z=function(a){return a});return function(a,d,f,g,l){p&&(y=function(b,c,d){w&&(u[w]=b);u[v]=c;u.$index=d;return p(a,u)});var n=T();a.$watchCollection(m,function(f){var g,m,p=d[0],s,u=T(),x,D,E,C,F,B,G;r&&(a[r]=f);if(ya(f))F=f,m=y||t;else for(G in m=y||z,F=[],f)ua.call(f,G)&&"$"!==G.charAt(0)&&F.push(G);x=F.length;G=Array(x);for(g=0;g<x;g++)if(D=f===F?g:F[g],E=f[D],C=m(D,E,g),n[C])B=n[C],delete n[C],u[C]=B,G[g]=B;else{if(u[C])throw q(G, +function(a){a&&a.scope&&(n[a.id]=a)}),c("dupes",h,C,E);G[g]={id:C,scope:void 0,clone:void 0};u[C]=!0}for(s in n){B=n[s];C=rb(B.clone);b.leave(C);if(C[0].parentNode)for(g=0,m=C.length;g<m;g++)C[g].$$NG_REMOVED=!0;B.scope.$destroy()}for(g=0;g<x;g++)if(D=f===F?g:F[g],E=f[D],B=G[g],B.scope){s=p;do s=s.nextSibling;while(s&&s.$$NG_REMOVED);B.clone[0]!=s&&b.move(rb(B.clone),null,p);p=B.clone[B.clone.length-1];e(B.scope,g,v,E,w,D,x)}else l(function(a,c){B.scope=c;var d=k.cloneNode(!1);a[a.length++]=d;b.enter(a, +null,p);p=d;B.clone=a;u[B.id]=B;e(B.scope,g,v,E,w,D,x)});n=u})}}}}],Je=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngShow,function(b){a[b?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],Ce=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngHide,function(b){a[b?"addClass":"removeClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],Ke=La(function(a,b,d){a.$watch(d.ngStyle,function(a, +d){d&&a!==d&&q(d,function(a,c){b.css(c,"")});a&&b.css(a)},!0)}),Le=["$animate","$compile",function(a,b){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(d,c,e,f){var g=[],h=[],k=[],l=[],n=function(a,b){return function(){a.splice(b,1)}};d.$watch(e.ngSwitch||e.on,function(c){var d,e;d=0;for(e=k.length;d<e;++d)a.cancel(k[d]);d=k.length=0;for(e=l.length;d<e;++d){var s=rb(h[d].clone);l[d].$destroy();(k[d]=a.leave(s)).then(n(k,d))}h.length=0;l.length=0;(g=f.cases["!"+ +c]||f.cases["?"])&&q(g,function(c){c.transclude(function(d,e){l.push(e);var f=c.element;d[d.length++]=b.$$createComment("end ngSwitchWhen");h.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],Me=La({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["!"+d.ngSwitchWhen]=c.cases["!"+d.ngSwitchWhen]||[];c.cases["!"+d.ngSwitchWhen].push({transclude:e,element:b})}}),Ne=La({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a, +b,d,c,e){c.cases["?"]=c.cases["?"]||[];c.cases["?"].push({transclude:e,element:b})}}),Qg=O("ngTransclude"),Pe=La({restrict:"EAC",link:function(a,b,d,c,e){d.ngTransclude===d.$attr.ngTransclude&&(d.ngTransclude="");if(!e)throw Qg("orphan",wa(b));e(function(a){a.length&&(b.empty(),b.append(a))},null,d.ngTransclude||d.ngTranscludeSlot)}}),pe=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(b,d){"text/ng-template"==d.type&&a.put(d.id,b[0].text)}}}],Rg={$setViewValue:C,$render:C}, +Sg=["$element","$scope",function(a,b){var d=this,c=new Ra;d.ngModelCtrl=Rg;d.unknownOption=B(v.document.createElement("option"));d.renderUnknownOption=function(b){b="? "+Fa(b)+" ?";d.unknownOption.val(b);a.prepend(d.unknownOption);a.val(b)};b.$on("$destroy",function(){d.renderUnknownOption=C});d.removeUnknownOption=function(){d.unknownOption.parent()&&d.unknownOption.remove()};d.readValue=function(){d.removeUnknownOption();return a.val()};d.writeValue=function(b){d.hasOption(b)?(d.removeUnknownOption(), +a.val(b),""===b&&d.emptyOption.prop("selected",!0)):null==b&&d.emptyOption?(d.removeUnknownOption(),a.val("")):d.renderUnknownOption(b)};d.addOption=function(a,b){if(8!==b[0].nodeType){Qa(a,'"option value"');""===a&&(d.emptyOption=b);var g=c.get(a)||0;c.put(a,g+1);d.ngModelCtrl.$render();b[0].hasAttribute("selected")&&(b[0].selected=!0)}};d.removeOption=function(a){var b=c.get(a);b&&(1===b?(c.remove(a),""===a&&(d.emptyOption=void 0)):c.put(a,b-1))};d.hasOption=function(a){return!!c.get(a)};d.registerOption= +function(a,b,c,h,k){if(h){var l;c.$observe("value",function(a){x(l)&&d.removeOption(l);l=a;d.addOption(a,b)})}else k?a.$watch(k,function(a,e){c.$set("value",a);e!==a&&d.removeOption(e);d.addOption(a,b)}):d.addOption(c.value,b);b.on("$destroy",function(){d.removeOption(c.value);d.ngModelCtrl.$render()})}}],qe=function(){return{restrict:"E",require:["select","?ngModel"],controller:Sg,priority:1,link:{pre:function(a,b,d,c){var e=c[1];if(e){var f=c[0];f.ngModelCtrl=e;b.on("change",function(){a.$apply(function(){e.$setViewValue(f.readValue())})}); +if(d.multiple){f.readValue=function(){var a=[];q(b.find("option"),function(b){b.selected&&a.push(b.value)});return a};f.writeValue=function(a){var c=new Ra(a);q(b.find("option"),function(a){a.selected=x(c.get(a.value))})};var g,h=NaN;a.$watch(function(){h!==e.$viewValue||pa(g,e.$viewValue)||(g=ha(e.$viewValue),e.$render());h=e.$viewValue});e.$isEmpty=function(a){return!a||0===a.length}}}},post:function(a,b,d,c){var e=c[1];if(e){var f=c[0];e.$render=function(){f.writeValue(e.$viewValue)}}}}}},se=["$interpolate", +function(a){return{restrict:"E",priority:100,compile:function(b,d){if(x(d.value))var c=a(d.value,!0);else{var e=a(b.text(),!0);e||d.$set("value",b.text())}return function(a,b,d){var k=b.parent();(k=k.data("$selectController")||k.parent().data("$selectController"))&&k.registerOption(a,b,d,c,e)}}}}],re=da({restrict:"E",terminal:!1}),Fc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){c&&(d.required=!0,c.$validators.required=function(a,b){return!d.required||!c.$isEmpty(b)},d.$observe("required", +function(){c.$validate()}))}}},Ec=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e,f=d.ngPattern||d.pattern;d.$observe("pattern",function(a){F(a)&&0<a.length&&(a=new RegExp("^"+a+"$"));if(a&&!a.test)throw O("ngPattern")("noregexp",f,a,wa(b));e=a||void 0;c.$validate()});c.$validators.pattern=function(a,b){return c.$isEmpty(b)||y(e)||e.test(b)}}}}},Hc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=-1;d.$observe("maxlength",function(a){a= +X(a);e=isNaN(a)?-1:a;c.$validate()});c.$validators.maxlength=function(a,b){return 0>e||c.$isEmpty(b)||b.length<=e}}}}},Gc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=X(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};v.angular.bootstrap?v.console&&console.log("WARNING: Tried to load angular more than once."):(ie(),ke(ea),ea.module("ngLocale",[],["$provide",function(a){function b(a){a+= +"";var b=a.indexOf(".");return-1==b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "), +WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a, +c){var e=a|0,f=c;void 0===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),B(v.document).ready(function(){ee(v.document,yc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>'); //# sourceMappingURL=angular.min.js.map diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.min.js.map b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.min.js.map deleted file mode 100644 index a57c997f942..00000000000 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular/angular.min.js.map +++ /dev/null @@ -1,8 +0,0 @@ -{ -"version":3, -"file":"angular.min.js", -"lineCount":293, -"mappings":"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", -"sources":["angular.js"], -"names":["window","document","undefined","minErr","isArrayLike","obj","isWindow","length","Object","nodeType","NODE_TYPE_ELEMENT","isString","isArray","forEach","iterator","context","key","isFunction","hasOwnProperty","call","isPrimitive","isBlankObject","forEachSorted","keys","sort","i","reverseParams","iteratorFn","value","nextUid","uid","setHashKey","h","$$hashKey","baseExtend","dst","objs","deep","ii","isObject","j","jj","src","isDate","Date","valueOf","isRegExp","RegExp","extend","slice","arguments","merge","toInt","str","parseInt","inherit","parent","extra","create","noop","identity","$","valueFn","hasCustomToString","toString","prototype","isUndefined","isDefined","getPrototypeOf","isNumber","isScope","$evalAsync","$watch","isBoolean","isElement","node","nodeName","prop","attr","find","makeMap","items","split","nodeName_","element","lowercase","arrayRemove","array","index","indexOf","splice","copy","source","destination","stackSource","stackDest","ngMinErr","TYPED_ARRAY_REGEXP","test","push","constructor","getTime","match","lastIndex","cloneNode","emptyObject","shallowCopy","charAt","equals","o1","o2","t1","t2","keySet","createMap","concat","array1","array2","bind","self","fn","curryArgs","startIndex","apply","toJsonReplacer","val","toJson","pretty","JSON","stringify","fromJson","json","parse","timezoneToOffset","timezone","fallback","requestedTimezoneOffset","isNaN","convertTimezoneToLocal","date","reverse","timezoneOffset","getTimezoneOffset","setMinutes","getMinutes","minutes","startingTag","jqLite","clone","empty","e","elemHtml","append","html","NODE_TYPE_TEXT","replace","tryDecodeURIComponent","decodeURIComponent","parseKeyValue","keyValue","splitPoint","substring","toKeyValue","parts","arrayValue","encodeUriQuery","join","encodeUriSegment","pctEncodeSpaces","encodeURIComponent","getNgAttribute","ngAttr","ngAttrPrefixes","getAttribute","angularInit","bootstrap","appElement","module","config","prefix","name","hasAttribute","candidate","querySelector","strictDi","modules","defaultConfig","doBootstrap","injector","tag","unshift","$provide","debugInfoEnabled","$compileProvider","createInjector","invoke","bootstrapApply","scope","compile","$apply","data","NG_ENABLE_DEBUG_INFO","NG_DEFER_BOOTSTRAP","angular","resumeBootstrap","angular.resumeBootstrap","extraModules","resumeDeferredBootstrap","reloadWithDebugInfo","location","reload","getTestability","rootElement","get","snake_case","separator","SNAKE_CASE_REGEXP","letter","pos","toLowerCase","bindJQuery","originalCleanData","bindJQueryFired","jqName","jq","jQuery","on","JQLitePrototype","isolateScope","controller","inheritedData","cleanData","jQuery.cleanData","elems","events","skipDestroyOnNextJQueryCleanData","elem","_data","$destroy","triggerHandler","JQLite","assertArg","arg","reason","assertArgFn","acceptArrayAnnotation","assertNotHasOwnProperty","getter","path","bindFnToScope","lastInstance","len","getBlockNodes","nodes","endNode","blockNodes","nextSibling","setupModuleLoader","ensure","factory","$injectorMinErr","$$minErr","requires","configFn","invokeLater","provider","method","insertMethod","queue","invokeQueue","moduleInstance","invokeLaterAndSetModuleName","recipeName","factoryFunction","$$moduleName","configBlocks","runBlocks","_invokeQueue","_configBlocks","_runBlocks","service","constant","decorator","animation","filter","directive","run","block","publishExternalAPI","version","uppercase","counter","csp","angularModule","ngModule","$$sanitizeUri","$$SanitizeUriProvider","$CompileProvider","a","htmlAnchorDirective","input","inputDirective","textarea","form","formDirective","script","scriptDirective","select","selectDirective","style","styleDirective","option","optionDirective","ngBind","ngBindDirective","ngBindHtml","ngBindHtmlDirective","ngBindTemplate","ngBindTemplateDirective","ngClass","ngClassDirective","ngClassEven","ngClassEvenDirective","ngClassOdd","ngClassOddDirective","ngCloak","ngCloakDirective","ngController","ngControllerDirective","ngForm","ngFormDirective","ngHide","ngHideDirective","ngIf","ngIfDirective","ngInclude","ngIncludeDirective","ngInit","ngInitDirective","ngNonBindable","ngNonBindableDirective","ngPluralize","ngPluralizeDirective","ngRepeat","ngRepeatDirective","ngShow","ngShowDirective","ngStyle","ngStyleDirective","ngSwitch","ngSwitchDirective","ngSwitchWhen","ngSwitchWhenDirective","ngSwitchDefault","ngSwitchDefaultDirective","ngOptions","ngOptionsDirective","ngTransclude","ngTranscludeDirective","ngModel","ngModelDirective","ngList","ngListDirective","ngChange","ngChangeDirective","pattern","patternDirective","ngPattern","required","requiredDirective","ngRequired","minlength","minlengthDirective","ngMinlength","maxlength","maxlengthDirective","ngMaxlength","ngValue","ngValueDirective","ngModelOptions","ngModelOptionsDirective","ngIncludeFillContentDirective","ngAttributeAliasDirectives","ngEventDirectives","$anchorScroll","$AnchorScrollProvider","$animate","$AnimateProvider","$animateCss","$CoreAnimateCssProvider","$$animateQueue","$$CoreAnimateQueueProvider","$$AnimateRunner","$$CoreAnimateRunnerProvider","$browser","$BrowserProvider","$cacheFactory","$CacheFactoryProvider","$controller","$ControllerProvider","$document","$DocumentProvider","$exceptionHandler","$ExceptionHandlerProvider","$filter","$FilterProvider","$$forceReflow","$$ForceReflowProvider","$interpolate","$InterpolateProvider","$interval","$IntervalProvider","$http","$HttpProvider","$httpParamSerializer","$HttpParamSerializerProvider","$httpParamSerializerJQLike","$HttpParamSerializerJQLikeProvider","$httpBackend","$HttpBackendProvider","$xhrFactory","$xhrFactoryProvider","$location","$LocationProvider","$log","$LogProvider","$parse","$ParseProvider","$rootScope","$RootScopeProvider","$q","$QProvider","$$q","$$QProvider","$sce","$SceProvider","$sceDelegate","$SceDelegateProvider","$sniffer","$SnifferProvider","$templateCache","$TemplateCacheProvider","$templateRequest","$TemplateRequestProvider","$$testability","$$TestabilityProvider","$timeout","$TimeoutProvider","$window","$WindowProvider","$$rAF","$$RAFProvider","$$jqLite","$$jqLiteProvider","$$HashMap","$$HashMapProvider","$$cookieReader","$$CookieReaderProvider","camelCase","SPECIAL_CHARS_REGEXP","_","offset","toUpperCase","MOZ_HACK_REGEXP","jqLiteAcceptsData","NODE_TYPE_DOCUMENT","jqLiteBuildFragment","tmp","fragment","createDocumentFragment","HTML_REGEXP","appendChild","createElement","TAG_NAME_REGEXP","exec","wrap","wrapMap","_default","innerHTML","XHTML_TAG_REGEXP","lastChild","childNodes","firstChild","textContent","createTextNode","argIsString","trim","jqLiteMinErr","parsed","SINGLE_TAG_REGEXP","jqLiteAddNodes","jqLiteClone","jqLiteDealoc","onlyDescendants","jqLiteRemoveData","querySelectorAll","descendants","l","jqLiteOff","type","unsupported","expandoStore","jqLiteExpandoStore","handle","listenerFns","removeEventListener","expandoId","ng339","jqCache","createIfNecessary","jqId","jqLiteData","isSimpleSetter","isSimpleGetter","massGetter","jqLiteHasClass","selector","jqLiteRemoveClass","cssClasses","setAttribute","cssClass","jqLiteAddClass","existingClasses","root","elements","jqLiteController","jqLiteInheritedData","documentElement","names","parentNode","NODE_TYPE_DOCUMENT_FRAGMENT","host","jqLiteEmpty","removeChild","jqLiteRemove","keepData","jqLiteDocumentLoaded","action","win","readyState","setTimeout","getBooleanAttrName","booleanAttr","BOOLEAN_ATTR","BOOLEAN_ELEMENTS","createEventHandler","eventHandler","event","isDefaultPrevented","event.isDefaultPrevented","defaultPrevented","eventFns","eventFnsLength","immediatePropagationStopped","originalStopImmediatePropagation","stopImmediatePropagation","event.stopImmediatePropagation","stopPropagation","isImmediatePropagationStopped","event.isImmediatePropagationStopped","$get","this.$get","hasClass","classes","addClass","removeClass","hashKey","nextUidFn","objType","HashMap","isolatedUid","this.nextUid","put","anonFn","args","fnText","STRIP_COMMENTS","FN_ARGS","modulesToLoad","supportObject","delegate","provider_","providerInjector","instantiate","providerCache","providerSuffix","enforceReturnValue","enforcedReturnValue","result","instanceInjector","factoryFn","enforce","loadModules","moduleFn","runInvokeQueue","invokeArgs","loadedModules","message","stack","createInternalInjector","cache","getService","serviceName","caller","INSTANTIATING","err","shift","locals","$inject","$$annotate","Type","instance","returnedValue","annotate","has","$injector","instanceCache","decorFn","origProvider","orig$get","origProvider.$get","origInstance","$delegate","autoScrollingEnabled","disableAutoScrolling","this.disableAutoScrolling","getFirstAnchor","list","Array","some","scrollTo","scrollIntoView","scroll","yOffset","getComputedStyle","position","getBoundingClientRect","bottom","elemTop","top","scrollBy","hash","elm","getElementById","getElementsByName","autoScrollWatch","autoScrollWatchAction","newVal","oldVal","mergeClasses","b","splitClasses","klass","prepareAnimateOptions","options","Browser","completeOutstandingRequest","outstandingRequestCount","outstandingRequestCallbacks","pop","error","cacheStateAndFireUrlChange","pendingLocation","cacheState","fireUrlChange","history","state","cachedState","lastCachedState","lastBrowserUrl","url","lastHistoryState","urlChangeListeners","listener","clearTimeout","pendingDeferIds","isMock","$$completeOutstandingRequest","$$incOutstandingRequestCount","self.$$incOutstandingRequestCount","notifyWhenNoOutstandingRequests","self.notifyWhenNoOutstandingRequests","callback","href","baseElement","self.url","sameState","sameBase","stripHash","substr","self.state","urlChangeInit","onUrlChange","self.onUrlChange","$$applicationDestroyed","self.$$applicationDestroyed","off","$$checkUrlChange","baseHref","self.baseHref","defer","self.defer","delay","timeoutId","cancel","self.defer.cancel","deferId","cacheFactory","cacheId","refresh","entry","freshEnd","staleEnd","n","link","p","nextEntry","prevEntry","caches","size","stats","id","capacity","Number","MAX_VALUE","lruHash","lruEntry","remove","removeAll","destroy","info","cacheFactory.info","cacheFactory.get","$$sanitizeUriProvider","parseIsolateBindings","directiveName","isController","LOCAL_REGEXP","bindings","definition","scopeName","$compileMinErr","mode","collection","optional","attrName","assertValidDirectiveName","hasDirectives","COMMENT_DIRECTIVE_REGEXP","CLASS_DIRECTIVE_REGEXP","ALL_OR_NOTHING_ATTRS","REQUIRE_PREFIX_REGEXP","EVENT_HANDLER_ATTR_REGEXP","this.directive","registerDirective","directiveFactory","Suffix","directives","priority","require","restrict","bindToController","controllerAs","CNTRL_REG","$$bindings","$$isolateBindings","aHrefSanitizationWhitelist","this.aHrefSanitizationWhitelist","regexp","imgSrcSanitizationWhitelist","this.imgSrcSanitizationWhitelist","this.debugInfoEnabled","enabled","safeAddClass","$element","className","$compileNodes","transcludeFn","maxPriority","ignoreDirective","previousCompileContext","nodeValue","compositeLinkFn","compileNodes","$$addScopeClass","namespace","publicLinkFn","cloneConnectFn","parentBoundTranscludeFn","transcludeControllers","futureParentElement","$$boundTransclude","$linkNode","wrapTemplate","controllerName","$$addScopeInfo","nodeList","$rootElement","childLinkFn","childScope","childBoundTranscludeFn","stableNodeList","nodeLinkFnFound","linkFns","idx","nodeLinkFn","destroyBindings","$new","$$destroyBindings","$on","transcludeOnThisElement","createBoundTranscludeFn","transclude","templateOnThisElement","attrs","linkFnFound","Attributes","collectDirectives","applyDirectivesToNode","$$element","terminal","previousBoundTranscludeFn","boundTranscludeFn","transcludedScope","cloneFn","controllers","containingScope","$$transcluded","attrsMap","$attr","addDirective","directiveNormalize","isNgAttr","nAttrs","attributes","attrStartName","attrEndName","ngAttrName","NG_ATTR_BINDING","PREFIX_REGEXP","directiveNName","directiveIsMultiElement","nName","addAttrInterpolateDirective","animVal","msie","addTextInterpolateDirective","NODE_TYPE_COMMENT","byPriority","groupScan","attrStart","attrEnd","depth","groupElementsLinkFnWrapper","linkFn","compileNode","templateAttrs","jqCollection","originalReplaceDirective","preLinkFns","postLinkFns","addLinkFns","pre","post","newIsolateScopeDirective","$$isolateScope","cloneAndAnnotateFn","getControllers","elementControllers","inheritType","dataName","setupControllers","controllerDirectives","controllerKey","$scope","$attrs","$transclude","controllerInstance","hasElementTranscludeDirective","linkNode","thisLinkFn","controllersBoundTransclude","cloneAttachFn","scopeToChild","templateDirective","$$originalDirective","initializeDirectiveBindings","scopeDirective","newScopeDirective","controllerForBindings","identifier","controllerResult","invokeLinkFn","template","templateUrl","terminalPriority","nonTlbTranscludeDirective","hasTranscludeDirective","hasTemplate","$compileNode","$template","childTranscludeFn","$$start","$$end","directiveValue","assertNoDuplicate","$$tlb","createComment","replaceWith","replaceDirective","contents","denormalizeTemplate","removeComments","templateNamespace","newTemplateAttrs","templateDirectives","unprocessedDirectives","markDirectivesAsIsolate","mergeTemplateAttributes","compileTemplateUrl","Math","max","tDirectives","startAttrName","endAttrName","multiElement","srcAttr","dstAttr","$set","tAttrs","linkQueue","afterTemplateNodeLinkFn","afterTemplateChildLinkFn","beforeTemplateCompileNode","origAsyncDirective","derivedSyncDirective","then","content","tempTemplateAttrs","beforeTemplateLinkNode","linkRootElement","$$destroyed","oldClasses","delayedNodeLinkFn","ignoreChildLinkFn","diff","what","previousDirective","wrapModuleNameIfDefined","moduleName","text","interpolateFn","textInterpolateCompileFn","templateNode","templateNodeParent","hasCompileParent","$$addBindingClass","textInterpolateLinkFn","$$addBindingInfo","expressions","interpolateFnWatchAction","wrapper","getTrustedContext","attrNormalizedName","HTML","RESOURCE_URL","allOrNothing","trustedContext","attrInterpolatePreLinkFn","$$observers","newValue","$$inter","$$scope","oldValue","$updateClass","elementsToRemove","newNode","firstElementToRemove","removeCount","j2","replaceChild","hasData","expando","k","kk","annotation","newScope","onNewScopeDestroyed","lastValue","parentGet","parentSet","compare","$observe","literal","assign","parentValueWatch","parentValue","$stateful","unwatch","$watchCollection","attributesToCopy","$normalize","$addClass","classVal","$removeClass","newClasses","toAdd","tokenDifference","toRemove","writeAttr","booleanKey","aliasedKey","ALIASED_ATTR","observer","trimmedSrcset","srcPattern","rawUris","nbrUrisWith2parts","floor","innerIdx","lastTuple","removeAttr","listeners","startSymbol","endSymbol","binding","isolated","noTemplate","str1","str2","values","tokens1","tokens2","token","jqNodes","globals","register","this.register","allowGlobals","this.allowGlobals","addIdentifier","expression","later","ident","$controllerMinErr","controllerPrototype","exception","cause","serializeValue","v","toISOString","ngParamSerializer","params","jQueryLikeParamSerializer","serialize","toSerialize","topLevel","defaultHttpResponseTransform","headers","tempData","JSON_PROTECTION_PREFIX","contentType","jsonStart","JSON_START","JSON_ENDS","parseHeaders","line","headerVal","headerKey","headersGetter","headersObj","transformData","status","fns","defaults","transformResponse","transformRequest","d","common","CONTENT_TYPE_APPLICATION_JSON","patch","xsrfCookieName","xsrfHeaderName","paramSerializer","useApplyAsync","this.useApplyAsync","useLegacyPromise","useLegacyPromiseExtensions","this.useLegacyPromiseExtensions","interceptorFactories","interceptors","requestConfig","response","resp","reject","executeHeaderFns","headerContent","processedHeaders","headerFn","header","mergeHeaders","defHeaders","reqHeaders","defHeaderName","lowercaseDefHeaderName","reqHeaderName","chain","serverRequest","reqData","withCredentials","sendReq","promise","when","reversedInterceptors","interceptor","request","requestError","responseError","thenFn","rejectFn","success","promise.success","promise.error","$httpMinErrLegacyFn","done","headersString","statusText","resolveHttpPromise","resolvePromise","$applyAsync","$$phase","deferred","resolve","resolvePromiseWithResult","removePendingReq","pendingRequests","cachedResp","buildUrl","defaultCache","xsrfValue","urlIsSameOrigin","timeout","responseType","serializedParams","interceptorFactory","createShortMethods","createShortMethodsWithData","createXhr","XMLHttpRequest","createHttpBackend","callbacks","$browserDefer","rawDocument","jsonpReq","callbackId","async","body","called","addEventListener","timeoutRequest","jsonpDone","xhr","abort","completeRequest","open","setRequestHeader","onload","xhr.onload","responseText","urlResolve","protocol","getAllResponseHeaders","onerror","onabort","send","this.startSymbol","this.endSymbol","escape","ch","unescapeText","escapedStartRegexp","escapedEndRegexp","mustHaveExpression","parseStringifyInterceptor","getTrusted","$interpolateMinErr","interr","endIndex","parseFns","textLength","expressionPositions","startSymbolLength","exp","endSymbolLength","throwNoconcat","compute","interpolationFn","$$watchDelegate","$watchGroup","interpolateFnWatcher","oldValues","currValue","$interpolate.startSymbol","$interpolate.endSymbol","interval","count","invokeApply","hasParams","setInterval","clearInterval","iteration","skipApply","$$intervalId","tick","notify","intervals","interval.cancel","encodePath","segments","parseAbsoluteUrl","absoluteUrl","locationObj","parsedUrl","$$protocol","$$host","hostname","$$port","port","DEFAULT_PORTS","parseAppUrl","relativeUrl","prefixed","$$path","pathname","$$search","search","$$hash","beginsWith","begin","whole","trimEmptyHash","LocationHtml5Url","appBase","appBaseNoFile","basePrefix","$$html5","$$parse","this.$$parse","pathUrl","$locationMinErr","$$compose","this.$$compose","$$url","$$absUrl","$$parseLinkUrl","this.$$parseLinkUrl","relHref","appUrl","prevAppUrl","rewrittenUrl","LocationHashbangUrl","hashPrefix","withoutBaseUrl","withoutHashUrl","windowsFilePathExp","base","firstPathSegmentMatch","LocationHashbangInHtml5Url","locationGetter","property","locationGetterSetter","preprocess","html5Mode","requireBase","rewriteLinks","this.hashPrefix","this.html5Mode","setBrowserUrlWithFallback","oldUrl","oldState","$$state","afterLocationChange","$broadcast","absUrl","LocationMode","initialUrl","lastIndexOf","IGNORE_URI_REGEXP","ctrlKey","metaKey","shiftKey","which","button","target","absHref","preventDefault","initializing","newUrl","newState","$digest","$locationWatch","currentReplace","$$replace","urlOrStateChanged","debug","debugEnabled","this.debugEnabled","flag","formatError","Error","sourceURL","consoleLog","console","logFn","log","hasApply","arg1","arg2","warn","ensureSafeMemberName","fullExpression","$parseMinErr","getStringValue","ensureSafeObject","children","ensureSafeFunction","CALL","APPLY","BIND","ensureSafeAssignContext","Function","ifDefined","plusFn","r","findConstantAndWatchExpressions","ast","allConstants","argsToWatch","AST","Program","expr","Literal","toWatch","UnaryExpression","argument","BinaryExpression","left","right","LogicalExpression","ConditionalExpression","alternate","consequent","Identifier","MemberExpression","object","computed","CallExpression","callee","AssignmentExpression","ArrayExpression","ObjectExpression","properties","ThisExpression","getInputs","lastExpression","isAssignable","assignableAST","NGValueParameter","operator","isLiteral","ASTCompiler","astBuilder","ASTInterpreter","isPossiblyDangerousMemberName","getValueOf","objectValueOf","cacheDefault","cacheExpensive","expressionInputDirtyCheck","oldValueOfValue","inputsWatchDelegate","objectEquality","parsedExpression","prettyPrintExpression","inputExpressions","inputs","lastResult","oldInputValueOf","expressionInputWatch","newInputValue","oldInputValueOfValues","oldInputValues","expressionInputsWatch","changed","oneTimeWatchDelegate","oneTimeWatch","oneTimeListener","old","$$postDigest","oneTimeLiteralWatchDelegate","isAllDefined","allDefined","constantWatchDelegate","constantWatch","constantListener","addInterceptor","interceptorFn","watchDelegate","regularInterceptedExpression","oneTimeInterceptedExpression","noUnsafeEval","$parseOptions","expensiveChecks","$parseOptionsExpensive","oneTime","cacheKey","parseOptions","lexer","Lexer","parser","Parser","qFactory","nextTick","exceptionHandler","callOnce","resolveFn","Promise","simpleBind","scheduleProcessQueue","processScheduled","pending","Deferred","$qMinErr","TypeError","onFulfilled","onRejected","progressBack","catch","finally","handleCallback","$$reject","$$resolve","progress","makePromise","resolved","isResolved","callbackOutput","errback","$Q","Q","resolver","all","promises","results","requestAnimationFrame","webkitRequestAnimationFrame","cancelAnimationFrame","webkitCancelAnimationFrame","webkitCancelRequestAnimationFrame","rafSupported","raf","timer","supported","createChildScopeClass","ChildScope","$$watchers","$$nextSibling","$$childHead","$$childTail","$$listeners","$$listenerCount","$$watchersCount","$id","$$ChildScope","TTL","$rootScopeMinErr","lastDirtyWatch","applyAsyncId","digestTtl","this.digestTtl","destroyChildScope","$event","currentScope","Scope","$parent","$$prevSibling","$root","beginPhase","phase","incrementWatchersCount","current","decrementListenerCount","initWatchVal","flushApplyAsync","applyAsyncQueue","scheduleApplyAsync","isolate","child","watchExp","watcher","last","eq","deregisterWatch","watchExpressions","watchGroupAction","changeReactionScheduled","firstRun","newValues","deregisterFns","shouldCall","deregisterWatchGroup","unwatchFn","watchGroupSubAction","$watchCollectionInterceptor","_value","bothNaN","newItem","oldItem","internalArray","oldLength","changeDetected","newLength","internalObject","veryOldValue","trackVeryOldValue","changeDetector","initRun","$watchCollectionAction","watch","watchers","dirty","ttl","watchLog","logIdx","asyncTask","asyncQueue","$eval","msg","next","postDigestQueue","eventName","this.$watchGroup","$applyAsyncExpression","namedListeners","indexOfListener","$emit","targetScope","listenerArgs","$$asyncQueue","$$postDigestQueue","$$applyAsyncQueue","sanitizeUri","uri","isImage","regex","normalizedVal","adjustMatcher","matcher","$sceMinErr","escapeForRegexp","adjustMatchers","matchers","adjustedMatchers","SCE_CONTEXTS","resourceUrlWhitelist","resourceUrlBlacklist","this.resourceUrlWhitelist","this.resourceUrlBlacklist","matchUrl","generateHolderType","Base","holderType","trustedValue","$$unwrapTrustedValue","this.$$unwrapTrustedValue","holderType.prototype.valueOf","holderType.prototype.toString","htmlSanitizer","trustedValueHolderBase","byType","CSS","URL","JS","trustAs","Constructor","maybeTrusted","allowed","this.enabled","sce","isEnabled","sce.isEnabled","sce.getTrusted","parseAs","sce.parseAs","enumValue","lName","eventSupport","android","userAgent","navigator","boxee","vendorPrefix","vendorRegex","bodyStyle","transitions","animations","webkitTransition","webkitAnimation","pushState","hasEvent","divElm","handleRequestFn","tpl","ignoreRequestError","totalPendingRequests","getTrustedResourceUrl","transformer","httpOptions","handleError","testability","testability.findBindings","opt_exactMatch","getElementsByClassName","matches","dataBinding","bindingName","testability.findModels","prefixes","attributeEquals","testability.getLocation","testability.setLocation","testability.whenStable","deferreds","$$timeoutId","timeout.cancel","urlParsingNode","requestUrl","originUrl","$$CookieReader","safeDecodeURIComponent","lastCookies","lastCookieString","cookieArray","cookie","currentCookieString","filters","suffix","currencyFilter","dateFilter","filterFilter","jsonFilter","limitToFilter","lowercaseFilter","numberFilter","orderByFilter","uppercaseFilter","comparator","matchAgainstAnyProp","getTypeForFilter","expressionType","predicateFn","createPredicateFn","shouldMatchPrimitives","actual","expected","item","deepCompare","dontMatchWholeObject","actualType","expectedType","expectedVal","matchAnyProperty","actualVal","$locale","formats","NUMBER_FORMATS","amount","currencySymbol","fractionSize","CURRENCY_SYM","PATTERNS","maxFrac","formatNumber","GROUP_SEP","DECIMAL_SEP","number","groupSep","decimalSep","isNegative","abs","isInfinity","Infinity","isFinite","numStr","formatedText","hasExponent","toFixed","parseFloat","fractionLen","min","minFrac","round","fraction","lgroup","lgSize","group","gSize","negPre","posPre","negSuf","posSuf","padNumber","num","digits","neg","dateGetter","dateStrGetter","shortForm","getFirstThursdayOfYear","year","dayOfWeekOnFirst","getDay","weekGetter","firstThurs","getFullYear","thisThurs","getMonth","getDate","eraGetter","ERAS","jsonStringToDate","string","R_ISO8601_STR","tzHour","tzMin","dateSetter","setUTCFullYear","setFullYear","timeSetter","setUTCHours","setHours","m","s","ms","format","DATETIME_FORMATS","NUMBER_STRING","DATE_FORMATS_SPLIT","dateTimezoneOffset","DATE_FORMATS","spacing","limit","processPredicates","sortPredicate","reverseOrder","map","predicate","descending","predicates","compareValues","getComparisonObject","predicateValues","doComparison","v1","v2","ngDirective","FormController","controls","$error","$$success","$pending","$name","$dirty","$pristine","$valid","$invalid","$submitted","$$parentForm","nullFormCtrl","$rollbackViewValue","form.$rollbackViewValue","control","$commitViewValue","form.$commitViewValue","$addControl","form.$addControl","$$renameControl","form.$$renameControl","newName","oldName","$removeControl","form.$removeControl","$setValidity","addSetValidityMethod","ctrl","set","unset","$setDirty","form.$setDirty","PRISTINE_CLASS","DIRTY_CLASS","$setPristine","form.$setPristine","setClass","SUBMITTED_CLASS","$setUntouched","form.$setUntouched","$setSubmitted","form.$setSubmitted","stringBasedInputType","$formatters","$isEmpty","baseInputType","composing","ev","ngTrim","$viewValue","$$hasNativeValidators","$setViewValue","deferListener","origValue","keyCode","$render","ctrl.$render","createDateParser","mapping","iso","ISO_DATE_REGEXP","yyyy","MM","dd","HH","getHours","mm","ss","getSeconds","sss","getMilliseconds","part","NaN","createDateInputType","parseDate","dynamicDateInputType","isValidDate","parseObservedDateValue","badInputChecker","$options","previousDate","$$parserName","$parsers","parsedDate","ngModelMinErr","ngMin","minVal","$validators","ctrl.$validators.min","$validate","ngMax","maxVal","ctrl.$validators.max","validity","VALIDITY_STATE_PROPERTY","badInput","typeMismatch","parseConstantExpr","parseFn","classDirective","arrayDifference","arrayClasses","digestClassCounts","classCounts","classesToUpdate","ngClassWatchAction","$index","old$index","mod","cachedToggleClass","switchValue","classCache","toggleValidationCss","validationErrorKey","isValid","VALID_CLASS","INVALID_CLASS","setValidity","isObjectEmpty","PENDING_CLASS","combinedState","REGEX_STRING_REGEXP","documentMode","rules","ngCspElement","ngCspAttribute","noInlineStyle","name_","el","full","major","minor","dot","codeName","JQLite._data","MOUSE_EVENT_MAP","mouseleave","mouseenter","optgroup","tbody","tfoot","colgroup","caption","thead","th","td","ready","trigger","fired","removeData","jqLiteHasData","removeAttribute","css","NODE_TYPE_ATTRIBUTE","lowercasedName","specified","getNamedItem","ret","getText","$dv","multiple","selected","nodeCount","jqLiteOn","types","related","relatedTarget","contains","one","onFn","replaceNode","insertBefore","contentDocument","prepend","wrapNode","detach","after","newElement","toggleClass","condition","classCondition","nextElementSibling","getElementsByTagName","extraParameters","dummyEvent","handlerArgs","eventFnsCopy","arg3","unbind","FN_ARG_SPLIT","FN_ARG","argDecl","underscore","$animateMinErr","AnimateRunner","end","resume","pause","complete","pass","fail","postDigestElements","updateData","handleCSSClassChanges","existing","pin","domOperation","from","to","classesAdded","add","classesRemoved","$$registeredAnimations","classNameFilter","this.classNameFilter","$$classNameFilter","reservedRegex","NG_ANIMATE_CLASSNAME","domInsert","parentElement","afterElement","afterNode","ELEMENT_NODE","previousElementSibling","runner","enter","move","leave","addclass","animate","tempClasses","RAFPromise","getPromise","f1","f2","closed","cleanupStyles","start","domNode","offsetWidth","APPLICATION_JSON","$httpMinErr","$interpolateMinErr.throwNoconcat","$interpolateMinErr.interr","PATH_MATCH","locationPrototype","paramValue","Location","Location.prototype.state","OPERATORS","ESCAPE","lex","tokens","readString","peek","readNumber","isIdent","readIdent","is","isWhitespace","ch2","ch3","op2","op3","op1","throwError","chars","isExpOperator","colStr","peekCh","quote","rawString","hex","String","fromCharCode","rep","ExpressionStatement","Property","program","expressionStatement","expect","filterChain","assignment","ternary","logicalOR","consume","logicalAND","equality","relational","additive","multiplicative","unary","primary","arrayDeclaration","constants","parseArguments","baseExpression","peekToken","kind","e1","e2","e3","e4","peekAhead","t","nextId","vars","own","assignable","stage","computing","recurse","return_","generateFunction","fnKey","intoId","watchId","fnString","USE","STRICT","filterPrefix","watchFns","varsPrefix","section","nameId","recursionFn","skipWatchIdCheck","if_","lazyAssign","computedMember","lazyRecurse","plus","not","getHasOwnProperty","nonComputedMember","addEnsureSafeObject","notNull","addEnsureSafeMemberName","addEnsureSafeFunction","member","addEnsureSafeAssignContext","filterName","defaultValue","stringEscapeRegex","stringEscapeFn","c","charCodeAt","skip","init","fn.assign","rhs","lhs","unary+","unary-","unary!","binary+","binary-","binary*","binary/","binary%","binary===","binary!==","binary==","binary!=","binary<","binary>","binary<=","binary>=","binary&&","binary||","ternary?:","astCompiler","yy","y","MMMM","MMM","M","H","hh","EEEE","EEE","ampmGetter","AMPMS","Z","timeZoneGetter","zone","paddedZone","ww","w","G","GG","GGG","GGGG","longEraGetter","ERANAMES","xlinkHref","propName","defaultLinkFn","normalized","ngBooleanAttrWatchAction","htmlAttr","ngAttrAliasWatchAction","nullFormRenameControl","formDirectiveFactory","isNgForm","getSetter","ngFormCompile","formElement","nameAttr","ngFormPreLink","ctrls","handleFormSubmission","setter","URL_REGEXP","EMAIL_REGEXP","NUMBER_REGEXP","DATE_REGEXP","DATETIMELOCAL_REGEXP","WEEK_REGEXP","MONTH_REGEXP","TIME_REGEXP","inputType","textInputType","weekParser","isoWeek","existingDate","week","hours","seconds","milliseconds","addDays","numberInputType","urlInputType","ctrl.$validators.url","modelValue","viewValue","emailInputType","email","ctrl.$validators.email","radioInputType","checked","checkboxInputType","trueValue","ngTrueValue","falseValue","ngFalseValue","ctrl.$isEmpty","CONSTANT_VALUE_REGEXP","tplAttr","ngValueConstantLink","ngValueLink","valueWatchAction","$compile","ngBindCompile","templateElement","ngBindLink","ngBindWatchAction","ngBindTemplateCompile","ngBindTemplateLink","ngBindHtmlCompile","tElement","ngBindHtmlGetter","ngBindHtmlWatch","ngBindHtmlLink","ngBindHtmlWatchAction","getTrustedHtml","$viewChangeListeners","forceAsyncEvents","ngEventHandler","previousElements","ngIfWatchAction","srcExp","onloadExp","autoScrollExp","autoscroll","changeCounter","previousElement","currentElement","cleanupLastIncludeContent","ngIncludeWatchAction","afterAnimation","thisChangeId","namespaceAdaptedClone","trimValues","NgModelController","$modelValue","$$rawModelValue","$asyncValidators","$untouched","$touched","parsedNgModel","parsedNgModelAssign","ngModelGet","ngModelSet","pendingDebounce","parserValid","$$setOptions","this.$$setOptions","getterSetter","invokeModelGetter","invokeModelSetter","$$$p","this.$isEmpty","currentValidationRunId","this.$setPristine","this.$setDirty","this.$setUntouched","UNTOUCHED_CLASS","TOUCHED_CLASS","$setTouched","this.$setTouched","this.$rollbackViewValue","$$lastCommittedViewValue","this.$validate","prevValid","prevModelValue","allowInvalid","$$runValidators","allValid","$$writeModelToScope","this.$$runValidators","doneCallback","processSyncValidators","syncValidatorsValid","validator","processAsyncValidators","validatorPromises","validationDone","localValidationRunId","processParseErrors","errorKey","this.$commitViewValue","$$parseAndValidate","this.$$parseAndValidate","this.$$writeModelToScope","this.$setViewValue","updateOnDefault","$$debounceViewValueCommit","this.$$debounceViewValueCommit","debounceDelay","debounce","ngModelWatch","formatters","ngModelCompile","ngModelPreLink","modelCtrl","formCtrl","ngModelPostLink","updateOn","DEFAULT_REGEXP","that","ngOptionsMinErr","NG_OPTIONS_REGEXP","parseOptionsExpression","optionsExp","selectElement","Option","selectValue","label","disabled","getOptionValuesKeys","optionValues","optionValuesKeys","keyName","itemKey","valueName","selectAs","trackBy","viewValueFn","trackByFn","getTrackByValueFn","getHashOfValue","getTrackByValue","getLocals","displayFn","groupByFn","disableWhenFn","valuesFn","getWatchables","watchedArray","optionValuesLength","disableWhen","getOptions","optionItems","selectValueMap","optionItem","getOptionFromViewValue","getViewValueFromOption","optionTemplate","optGroupTemplate","updateOptionElement","addOrReuseElement","removeExcessElements","skipEmptyAndUnknownOptions","emptyOption_","emptyOption","unknownOption_","unknownOption","updateOptions","previousValue","selectCtrl","readValue","groupMap","providedEmptyOption","updateOption","optionElement","groupElement","currentOptionElement","ngModelCtrl","nextValue","ngModelCtrl.$isEmpty","writeValue","selectCtrl.writeValue","selectCtrl.readValue","selectedValues","selections","selectedOption","BRACE","IS_WHEN","updateElementText","newText","numberExp","whenExp","whens","whensExpFns","braceReplacement","watchRemover","lastCount","attributeName","tmpMatch","whenKey","ngPluralizeWatchAction","countIsNaN","pluralCat","whenExpFn","ngRepeatMinErr","updateScope","valueIdentifier","keyIdentifier","arrayLength","$first","$last","$middle","$odd","$even","ngRepeatCompile","ngRepeatEndComment","aliasAs","trackByExp","trackByExpGetter","trackByIdExpFn","trackByIdArrayFn","trackByIdObjFn","hashFnLocals","ngRepeatLink","lastBlockMap","ngRepeatAction","previousNode","nextNode","nextBlockMap","collectionLength","trackById","collectionKeys","nextBlockOrder","trackByIdFn","blockKey","ngRepeatTransclude","ngShowWatchAction","NG_HIDE_CLASS","NG_HIDE_IN_PROGRESS_CLASS","ngHideWatchAction","ngStyleWatchAction","newStyles","oldStyles","ngSwitchController","cases","selectedTranscludes","selectedElements","previousLeaveAnimations","selectedScopes","spliceFactory","ngSwitchWatchAction","selectedTransclude","caseElement","selectedScope","anchor","noopNgModelController","SelectController","optionsMap","renderUnknownOption","self.renderUnknownOption","unknownVal","removeUnknownOption","self.removeUnknownOption","self.readValue","self.writeValue","hasOption","addOption","self.addOption","removeOption","self.removeOption","self.hasOption","ngModelCtrl.$render","lastView","lastViewRef","selectMultipleWatch","valueInterpolated","optionValue","selectCtrlName","valueAttributeObserveAction","interpolateWatchAction","ctrl.$validators.required","patternExp","ctrl.$validators.pattern","intVal","ctrl.$validators.maxlength","ctrl.$validators.minlength","getDecimals","opt_precision","pow","ONE","OTHER","$$csp","head"] -} diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index c4267fd13be..c867ed04b62 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -10,6 +10,7 @@ <link href="/static/css/bootstrap.min.css" rel="stylesheet" type="text/css"> <link href="/static/js/angular-ui-grid/ui-grid.min.css" rel="stylesheet" type="text/css"> <link href="/static/js/angular-ui-layout/angular-ui-layout.css" rel="stylesheet" type="text/css"> + <link href="/static/js/angular-material/angular-material.min.css" rel="stylesheet" type="text/css"> <link href="/static/js/utils/datetimepicker.css" rel="stylesheet" type="text/css"> <link href="/static/css/main.css" rel="stylesheet" type="text/css"> <script src="/static/js/utils/startswith.js"></script> @@ -18,7 +19,7 @@ <script src="/static/js/utils/bootstrap.min.js"></script> <script type="text/javascript" src="/static/js/highcharts/highcharts.js"></script> <script type="text/javascript" src="/static/js/highcharts/exporting.js"></script> - <script src="/static/js/angular/angular.js"></script> + <script src="/static/js/angular/angular.min.js"></script> <script src="/static/js/utils/ui-bootstrap-tpls.min.js"></script> <script src="/static/js/angular-route/angular-route.min.js"></script> <script src="/static/js/angular-touch/angular-touch.js"></script> @@ -30,6 +31,9 @@ <script src="/static/js/angular-ui-layout/angular-ui-layout.min.js"></script> <script src="/static/js/angular-ui-tabs/angular-ui.bootstrap.tabs.min.js"></script> <script src="/static/js/angular-moment/angular-moment.js"></script> + <script src="/static/js/angular-animate/angular-animate.min.js"></script> + <script src="/static/js/angular-aria/angular-aria.min.js"></script> + <script src="/static/js/angular-material/angular-material.min.js"></script> <script src="/static/js/utils/datetimepicker.js"></script> <script src="/static/js/jsplumb/jsplumb-2.0.7-min.js"></script> <script src="/static/js/angular-gantt/angular-gantt.js"></script> @@ -104,55 +108,60 @@ ui-grid-edit ui-grid-selection ui-grid-cellNav ui-grid-resize-columns ui-grid-auto-resize class="grid"></div> </div> - <div ui-layout options="{flow: 'row'}"> - <div ng-controller="GanttProjectController as ganttProjectCtrl" style="overflow:auto; margin-left:12px; margin-bottom:4px; "> - <div gantt data=ganttData - api=options.api - show-side='true' - view-scale="options.viewScale" - from-date="options.fromDate" - to-date="options.toDate" - current-date="options.currentDate" - current-date-value="options.currentDateValue" - column-magnet="options.columnMagnet"> - <gantt-tree enabled="true"></gantt-tree> - <gantt-movable enabled="true" - allow-moving="true" - allow-resizing="true" - allow-row-switching="false"> - </gantt-movable> - <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> - <gantt-dependencies enabled="options.dependencies"></gantt-dependencies> - <gantt-contextmenu enabled="true"></gantt-contextmenu> + <md-content margin-top='10px'> + <md-tabs md-dynamic-height md-border-bottom style="height: 100%; width: 100%;"> + <div ng-controller="GanttProjectController as ganttProjectCtrl" ng-init="enabled=true"> + <md-tab label="Tasks" md-on-select="enabled=true;" md-on-deselect="enabled=false;"> + <div gantt data=ganttData + api=options.api + show-side='true' + view-scale="options.viewScale" + from-date="options.fromDate" + to-date="options.toDate" + current-date="options.currentDate" + current-date-value="options.currentDateValue" + column-magnet="options.columnMagnet" + style="height: 75%; max-height:1200px; width: 100%; overflow-x: hidden; overflow-y: auto; margin-left: 8px;"> + <gantt-tree enabled="true"></gantt-tree> + <gantt-movable enabled="true" + allow-moving="true" + allow-resizing="true" + allow-row-switching="false"> + </gantt-movable> + <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> + <gantt-dependencies enabled="options.dependencies"></gantt-dependencies> + <gantt-contextmenu enabled="true"></gantt-contextmenu> + </div> + </md-tab> </div> - </div> - <div ng-controller="ChartResourceUsageController as chartResourceUsageCtrl"> - <highchart id="chart_resource_usage" config="chartConfig" style="width: 96%; height: 100%; margin: 12px;" ></highchart> - </div> - - <div ng-controller="GanttResourceController as ganttResourceCtrl" style="overflow:auto; margin-left:12px; margin-top:12px"> - <div gantt data=ganttData - api=options.api - show-side='true' - view-scale="options.viewScale" - from-date="options.fromDate" - to-date="options.toDate" - current-date="options.currentDate" - current-date-value="options.currentDateValue" - column-magnet="options.columnMagnet"> - <gantt-tree enabled="true"></gantt-tree> - <gantt-movable enabled="true" - allow-moving="true" - allow-resizing="true" - allow-row-switching="false"></gantt-movable> - <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> - <gantt-contextmenu enabled="true"></gantt-contextmenu> + <div ng-controller="GanttResourceController as ganttResourceCtrl" ng-init="enabled=false"> + <md-tab label="Resources" md-on-select="enabled=true;" md-on-deselect="enabled=false;"> + <div gantt data=ganttData + api=options.api + show-side='true' + view-scale="options.viewScale" + from-date="options.fromDate" + to-date="options.toDate" + current-date="options.currentDate" + current-date-value="options.currentDateValue" + column-magnet="options.columnMagnet" + style="height: 75%; max-height:800px; width: 100%; overflow-x: hidden; overflow-y: auto; margin-left: 8px;"> + <gantt-tree enabled="true"></gantt-tree> + <gantt-movable enabled="true" + allow-moving="true" + allow-resizing="true" + allow-row-switching="false"></gantt-movable> + <gantt-tooltips enabled="true" date-format="'YYYY-MM-DD HH:mm'"></gantt-tooltips> + <gantt-contextmenu enabled="true"></gantt-contextmenu> + </div> + <div ng-controller="ChartResourceUsageController as chartResourceUsageCtrl" style="height: 25%"> + <highchart id="chart_resource_usage" config="chartConfig" style="margin: 12px;" ></highchart> + </div> + </md-tab> </div> - </div> - - </div> - </div> + </md-tabs> + </md-content> </div> </div> {% endraw %} -- GitLab From 261b801347fe01bb3520ba1dad713fc9f3039486 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 17 Aug 2016 07:53:28 +0000 Subject: [PATCH 626/933] Task #9607: added queries to get predecessor and successor ids for a given mom2id --- SAS/MoM/MoMQueryService/momqueryrpc.py | 30 ++++++ SAS/MoM/MoMQueryService/momqueryservice.py | 112 +++++++++++++++++---- 2 files changed, 122 insertions(+), 20 deletions(-) diff --git a/SAS/MoM/MoMQueryService/momqueryrpc.py b/SAS/MoM/MoMQueryService/momqueryrpc.py index b9a9548f8d6..c611638755b 100644 --- a/SAS/MoM/MoMQueryService/momqueryrpc.py +++ b/SAS/MoM/MoMQueryService/momqueryrpc.py @@ -38,6 +38,18 @@ class MoMQueryRPC(RPCWrapper): logger.info("Received %s projects" % (len(projects))) return projects + def getPredecessorIds(self, ids): + logger.debug("getSuccessorIds(%s)", ids) + result = self.rpc('GetPredecessorIds', mom_ids=ids) + logger.info("GetPredecessorIds(%s): %s", ids, result) + return result + + def getSuccessorIds(self, ids): + logger.debug("getSuccessorIds(%s)", ids) + result = self.rpc('GetSuccessorIds', mom_ids=ids) + logger.info("getSuccessorIds(%s): %s", ids, result) + return result + def main(): # Check the invocation arguments @@ -49,6 +61,8 @@ def main(): parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') parser.add_option('-P', '--projects', dest='projects', action='store_true', help='get list of all projects') parser.add_option('-p', '--project_details', dest='project_details', type='int', help='get project details for mom object with given id') + parser.add_option('--predecessors', dest='id_for_predecessors', type='int', help='get the predecessor id\'s for the given mom2id') + parser.add_option('--successors', dest='id_for_successors', type='int', help='get the successors id\'s for the given mom2id') (options, args) = parser.parse_args() if len(sys.argv) == 1: @@ -71,5 +85,21 @@ def main(): else: print 'No results' + if options.id_for_predecessors: + predecessor_ids = rpc.getPredecessorIds(options.id_for_predecessors) + if predecessor_ids: + for k, v in predecessor_ids.items(): + print ' %s: %s' % (k, v) + else: + print 'No results' + + if options.id_for_successors: + successor_ids = rpc.getSuccessorIds(options.id_for_successors) + if successor_ids: + for k, v in successor_ids.items(): + print ' %s: %s' % (k, v) + else: + print 'No results' + if __name__ == '__main__': main() diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index af8fdb6b26e..d285fca3846 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -55,6 +55,20 @@ def _isListOfInts(items): return True +def _toIdsString(mom_ids): + if isinstance(mom_ids, int): + ids = [mom_ids] + elif _isListOfInts(mom_ids): + ids = mom_ids + else: + ids = _idsFromString(mom_ids) + + if not ids: + raise ValueError("Could not find proper ids in: " + mom_ids) + + ids_str = ','.join([str(id) for id in ids]) + return ids_str + class MoMDatabaseWrapper: '''handler class for details query in mom db''' def __init__(self, dbcreds): @@ -99,18 +113,9 @@ class MoMDatabaseWrapper: if not mom_ids: return {} - if _isListOfInts(mom_ids): - ids = mom_ids - else: - ids = _idsFromString(mom_ids) - - if not ids: - raise ValueError("Could not find proper ids in: " + mom_ids) - - ids_str = ','.join([str(id) for id in ids]) + ids_str = _toIdsString(mom_ids) - logger.info("Query for mom id%s: %s" % - ('\'s' if len(ids) > 1 else '', ids_str)) + logger.info("getProjectDetails for mom ids: %s" % ids_str) # TODO: make a view for this query in momdb! query = '''SELECT project.mom2id as project_mom2id, project.id as project_mom2objectid, project.name as project_name, project.description as project_description, @@ -160,6 +165,70 @@ class MoMDatabaseWrapper: return result + def getPredecessorIds(self, mom_ids): + if not mom_ids: + return {} + + ids_str = _toIdsString(mom_ids) + + logger.info("getPredecessorIds for mom ids: %s" % ids_str) + + query = '''SELECT mom2id, predecessor + FROM mom2object + where mom2id in (%s) + order by mom2id; + ''' % (ids_str,) + rows = self._executeQuery(query) + + result = {} + for row in rows: + mom2id = row['mom2id'] + pred_string = row['predecessor'] + pred_id_list = [y[1:] for y in [x.strip() for x in pred_string.split(',')] if y[0] == 'M'] + pred_id_list = [int(x) for x in pred_id_list if x.isdigit()] + result[str(mom2id)] = pred_id_list + + for mom2id in ids_str.split(','): + if not mom2id in result: + result[mom2id] = [] + + logger.info('predecessors: %s', result) + + return result + + def getSuccessorIds(self, mom_ids): + if not mom_ids: + return {} + + ids_str = _toIdsString(mom_ids) + + logger.info("getSuccessorIds for mom ids: %s" % ids_str) + + condition = ' OR '.join(['predecessor LIKE \'%%M%s%%\'' % x for x in ids_str.split(',')]) + + # TODO: make a view for this query in momdb! + query = '''SELECT mom2id, predecessor + FROM mom2object + where %s + order by mom2id; + ''' % (condition,) + rows = self._executeQuery(query) + + result = {} + for mom2id in ids_str.split(','): + result[mom2id] = [] + + for row in rows: + suc_mom2id = row['mom2id'] + pred_string = row['predecessor'] + pred_id_list = [y[1:] for y in [x.strip() for x in pred_string.split(',')] if y[0] == 'M'] + for mom2id in ids_str.split(','): + if mom2id in pred_id_list: + result[str(mom2id)].append(suc_mom2id) + + logger.info('successors: %s', result) + + return result class ProjectDetailsQueryHandler(MessageHandlerInterface): '''handler class for details query in mom db @@ -171,24 +240,27 @@ class ProjectDetailsQueryHandler(MessageHandlerInterface): self.service2MethodMap = { 'GetProjects': self.getProjects, - 'GetProjectDetails': self.getProjectDetails + 'GetProjectDetails': self.getProjectDetails, + 'GetPredecessorIds': self.getPredecessorIds, + 'GetSuccessorIds': self.getSuccessorIds } def prepare_loop(self): self.momdb = MoMDatabaseWrapper(self.dbcreds) def getProjectDetails(self, mom_ids): - if not mom_ids: - return {} - - ids = _idsFromString(mom_ids) - if not _isListOfInts(ids): - raise ValueError("%s is not a proper list of ints" % str(mom_ids)) - return self.momdb.getProjectDetails(ids) + return self.momdb.getProjectDetails(mom_ids) def getProjects(self): return self.momdb.getProjects() + def getPredecessorIds(self, mom_ids): + return self.momdb.getPredecessorIds(mom_ids) + + def getSuccessorIds(self, mom_ids): + return self.momdb.getSuccessorIds(mom_ids) + + def createService(busname=DEFAULT_MOMQUERY_BUSNAME, servicename=DEFAULT_MOMQUERY_SERVICENAME, dbcreds=None, @@ -207,7 +279,7 @@ def createService(busname=DEFAULT_MOMQUERY_BUSNAME, return Service(servicename, handler, busname=busname, - numthreads=2, + numthreads=1, use_service_methods=True, verbose=False, broker=broker, -- GitLab From d3a809b967cb2468bf2e3abba86f2731ba58aaed Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 17 Aug 2016 08:47:29 +0000 Subject: [PATCH 627/933] Task #9607: process successors for inserted tasks --- .../ResourceAssigner/lib/assignment.py | 32 ++++++++++++++++++- .../ResourceAssigner/lib/raservice.py | 2 ++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index b0c49e49459..651c0b37a15 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -48,6 +48,9 @@ from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SER from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_BUSNAME from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_PREFIX +from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC +from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME + logger = logging.getLogger(__name__) class ResourceAssigner(): @@ -60,6 +63,8 @@ class ResourceAssigner(): otdb_servicename=DEFAULT_OTDB_SERVICENAME, ra_notification_busname=DEFAULT_RA_NOTIFICATION_BUSNAME, ra_notification_prefix=DEFAULT_RA_NOTIFICATION_PREFIX, + mom_busname=DEFAULT_MOMQUERY_BUSNAME, + mom_servicename=DEFAULT_MOMQUERY_SERVICENAME, broker=None): """ ResourceAssigner inserts/updates tasks in the radb and assigns resources to it based on incoming parset. @@ -72,6 +77,7 @@ class ResourceAssigner(): self.radbrpc = RARPC(servicename=radb_servicename, busname=radb_busname, broker=broker) self.rerpc = RPC(re_servicename, busname=re_busname, broker=broker, ForwardExceptions=True) self.otdbrpc = OTDBRPC(busname=otdb_busname, servicename=otdb_servicename, broker=broker) ## , ForwardExceptions=True hardcoded in RPCWrapper right now + self.momrpc = MoMQueryRPC(servicename=mom_servicename, busname=mom_busname, broker=broker) self.ra_notification_bus = ToBus(address=ra_notification_busname, broker=broker) self.ra_notification_prefix = ra_notification_prefix @@ -89,6 +95,7 @@ class ResourceAssigner(): self.radbrpc.open() self.rerpc.open() self.otdbrpc.open() + self.momrpc.open() self.ra_notification_bus.open() def close(self): @@ -96,6 +103,7 @@ class ResourceAssigner(): self.radbrpc.close() self.rerpc.close() self.otdbrpc.close() + self.momrpc.close() self.ra_notification_bus.close() def doAssignment(self, specification_tree): @@ -193,6 +201,7 @@ class ResourceAssigner(): self._sendNotification(task, 'scheduled') self.processPredecessors(specification_tree) + self.processSuccessors(task) else: logger.warning('doAssignment: Not all claims could be inserted. Setting task %s status to conflict' % (taskId)) self.radbrpc.updateTask(taskId, status='conflict') @@ -240,6 +249,28 @@ class ResourceAssigner(): except Exception as e: logger.error(e) + def processSuccessors(self, task): + try: + successor_mom_ids = self.momrpc.getSuccessorIds(task['mom_id'])[str(task['mom_id'])] + + if successor_mom_ids: + logger.info('proccessing successor mom_ids=%s for mom_id=%s otdb_id=%s', successor_mom_ids, task['mom_id'], task['otdb_id']) + + for successor_mom_id in successor_mom_ids: + #check if the successor needs to be linked to this task + successor_task = self.radbrpc.getTask(mom_id=successor_mom_id) + if successor_task: + if successor_task['id'] not in task['successor_ids']: + logger.info('connecting successor task with otdb_id=%s to it\'s predecessor with otdb_id=%s', successor_task['otdb_id'], task['otdb_id']) + self.radbrpc.insertTaskPredecessor(successor_task['id'], task['id']) + else: + logger.warning('could not find predecessor task with otdb_id=%s in radb for task otdb_id=%s', successor_task['otdb_id'], task['otdb_id']) + else: + logger.info('no successors for otdb_id=%s', task['otdb_id']) + + except Exception as e: + logger.error(e) + def getMaxPredecessorEndTime(self, specification_tree): try: predecessor_specs = [tree['specification'] for tree in specification_tree['predecessors']] @@ -352,4 +383,3 @@ class ResourceAssigner(): claim_ids = self.radbrpc.insertResourceClaims(task['id'], claims, 1, 'anonymous', -1)['ids'] logger.info('claimResources: %d claims were inserted in the radb' % len(claim_ids)) return len(claim_ids) == len(claims), claim_ids - diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py index d00f7945c7d..775d484b03e 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py @@ -140,6 +140,8 @@ def main(): otdb_servicename=options.otdb_servicename, ra_notification_busname=options.ra_notification_busname, ra_notification_prefix=options.ra_notification_prefix, + mom_busname=options.mom_query_busname, + mom_servicename=options.mom_query_servicename, broker=options.broker) as assigner: with SpecifiedTaskListener(busname=options.notification_busname, subject=options.notification_subject, -- GitLab From 801dda6ebb491490081605b05e22164175999d6f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 17 Aug 2016 09:33:05 +0000 Subject: [PATCH 628/933] Task #9607: get task with all properties from radb upon insert/update --- .../lib/radbchangeshandler.py | 18 ++++++++++-------- .../ResourceAssignmentEditor/lib/webservice.py | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py index c16481807e6..0df9611f2f8 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py @@ -44,7 +44,7 @@ CHANGE_INSERT_TYPE = 'insert' CHANGE_DELETE_TYPE = 'delete' class RADBChangesHandler(RADBBusListener): - def __init__(self, busname=DEFAULT_NOTIFICATION_BUSNAME, subjects=DEFAULT_NOTIFICATION_SUBJECTS, broker=None, momqueryrpc=None, **kwargs): + def __init__(self, busname=DEFAULT_NOTIFICATION_BUSNAME, subjects=DEFAULT_NOTIFICATION_SUBJECTS, broker=None, momqueryrpc=None, radbrpc=None, **kwargs): """ RADBChangesHandler listens on the lofar notification message bus and keeps track of all the change notifications. :param broker: valid Qpid broker host (default: None, which means localhost) @@ -61,6 +61,7 @@ class RADBChangesHandler(RADBBusListener): self._changedCondition = Condition() self._changeNumber = 0L self._momqueryrpc = momqueryrpc + self._radbrpc = radbrpc def _handleChange(self, change): '''_handleChange appends a change in the changes list and calls the onChangedCallback. @@ -77,18 +78,19 @@ class RADBChangesHandler(RADBBusListener): self._changedCondition.notifyAll() def onTaskUpdated(self, old_task, new_task): - '''onTaskUpdated is called upon receiving a TaskUpdated message. - :param task: dictionary with the updated task''' - new_task['starttime'] = new_task['starttime'].datetime() - new_task['endtime'] = new_task['endtime'].datetime() - task_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'task', 'value':new_task} + '''onTaskUpdated is called upon receiving a TaskUpdated message.''' + #ignore old_task and new_task, which miss some properties via this update mechanism + #get task with all expected properties via radbrpc + task = self._radbrpc.getTask(new_task['id']) + task_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'task', 'value':task} self._handleChange(task_change) def onTaskInserted(self, task): '''onTaskInserted is called upon receiving a TaskInserted message. :param task: dictionary with the inserted task''' - task['starttime'] = task['starttime'].datetime() - task['endtime'] = task['endtime'].datetime() + #ignore old_task and new_task, which miss some properties via this update mechanism + #get task with all expected properties via radbrpc + task = self._radbrpc.getTask(task['id']) updateTaskMomDetails(task, self._momqueryrpc) task_change = {'changeType':CHANGE_INSERT_TYPE, 'objectType':'task', 'value':task} self._handleChange(task_change) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 7a040d9c6bf..8b80f2d00ac 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -673,7 +673,7 @@ def main(): global momqueryrpc momqueryrpc = MoMQueryRPC(busname=options.mom_query_busname, servicename=options.mom_query_servicename, timeout=2.5, broker=options.broker) global radbchangeshandler - radbchangeshandler = RADBChangesHandler(options.radb_notification_busname, subjects=options.radb_notification_subjects, broker=options.broker, momqueryrpc=momqueryrpc) + radbchangeshandler = RADBChangesHandler(options.radb_notification_busname, subjects=options.radb_notification_subjects, broker=options.broker, momqueryrpc=momqueryrpc, radbrpc=rarpc) with radbchangeshandler, rarpc, curpc, sqrpc, momrpc, momqueryrpc: '''Start the webserver''' -- GitLab From e23a65e592e3817fa8d375ead6d6827edb04e6fc Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 17 Aug 2016 09:54:31 +0000 Subject: [PATCH 629/933] Task #9607: minor fix, momrpc -> momqueryrpc --- .../ResourceAssigner/lib/assignment.py | 3 ++ .../ResourceAssigner/lib/schedulechecker.py | 44 ++++++++++++------- .../lib/webservice.py | 4 +- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 651c0b37a15..ce5f20e942d 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -35,6 +35,8 @@ from lofar.messaging.messagebus import ToBus from lofar.messaging.RPC import RPC, RPCException from lofar.parameterset import parameterset +from lofar.sas.resourceassignment.resourceassigner.schedulechecker import movePipelineAfterItsPredecessors + from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as RADB_SERVICENAME @@ -263,6 +265,7 @@ class ResourceAssigner(): if successor_task['id'] not in task['successor_ids']: logger.info('connecting successor task with otdb_id=%s to it\'s predecessor with otdb_id=%s', successor_task['otdb_id'], task['otdb_id']) self.radbrpc.insertTaskPredecessor(successor_task['id'], task['id']) + movePipelineAfterItsPredecessors(successor_task, self.radbrpc) else: logger.warning('could not find predecessor task with otdb_id=%s in radb for task otdb_id=%s', successor_task['otdb_id'], task['otdb_id']) else: diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index 8975c0838fd..007210019bd 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -37,6 +37,30 @@ from lofar.sas.resourceassignment.resourceassigner.config import PIPELINE_CHECK_ logger = logging.getLogger(__name__) +def movePipelineAfterItsPredecessors(task, radbrpc, min_start_timestamp=None): + try: + #only reschedule pipelines which have resourceclaims and run on cep4 + if task and task['type'] == 'pipeline' and task.get('cluster') == 'CEP4' and radbrpc.getResourceClaims(task_ids=task['id']): + logger.info("checking pipeline starttime radb_id=%s otdb_id=%s", task['id'], task['otdb_id']) + + predecessor_tasks = radbrpc.getTasks(task_ids=task['predecessor_ids']) + predecessor_endtimes = [t['endtime'] for t in predecessor_tasks] + if min_start_timestamp: + predecessor_endtimes.append(min_start_timestamp) + + max_pred_endtime = max(predecessor_endtimes) + + if task['starttime'] < max_pred_endtime: + shift = max_pred_endtime - task['starttime'] + logger.info("Moving ahead %s pipeline radb_id=%s otdb_id=%s by %s", task['status'], task['id'], task['otdb_id'], shift) + radbrpc.updateTaskAndResourceClaims(task['id'], starttime=task['starttime']+shift, endtime=task['endtime']+shift) + updated_task = radbrpc.getTask(task['id']) + if updated_task['status'] not in [u'scheduled', u'queued']: + logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change to %s", updated_task['id'], updated_task['otdb_id'], updated_task['status']) + #TODO: automatically resolve conflict status by moved pipeline in first free time slot. + except Exception as e: + logger.error("Error while checking pipeline starttime: %s", e) + class ScheduleChecker(): def __init__(self, radb_busname=DEFAULT_RADB_BUSNAME, @@ -97,31 +121,17 @@ class ScheduleChecker(): def checkScheduledAndQueuedPipelines(self): try: now = datetime.utcnow() + min_start_timestamp = now + timedelta(seconds=PIPELINE_CHECK_INTERVAL) scheduled_pipelines = self._radbrpc.getTasks(task_status='scheduled', task_type='pipeline') queued_pipelines = self._radbrpc.getTasks(task_status='queued', task_type='pipeline') sq_pipelines = scheduled_pipelines + queued_pipelines if sq_pipelines: - logger.info('checking starttime of scheduled/queued cep4 pipelines') + logger.info('checking starttime of %s scheduled/queued cep4 pipelines', len(sq_pipelines)) for task in sq_pipelines: - #only reschedule pipelines which have resourceclaims, and hence run on cep4 - if self._radbrpc.getResourceClaims(task_ids=task['id']): - predecessor_tasks = self._radbrpc.getTasks(task_ids=task['predecessor_ids']) - predecessor_endtimes = [t['endtime'] for t in predecessor_tasks] - predecessor_endtimes.append(now + timedelta(seconds=PIPELINE_CHECK_INTERVAL)) - - max_pred_endtime = max(predecessor_endtimes) - - if task['starttime'] < max_pred_endtime: - shift = max_pred_endtime - task['starttime'] - logger.info("Moving ahead %s pipeline radb_id=%s otdb_id=%s by %s", task['status'], task['id'], task['otdb_id'], shift) - self._radbrpc.updateTaskAndResourceClaims(task['id'], starttime=task['starttime']+shift, endtime=task['endtime']+shift) - updated_task = self._radbrpc.getTask(task['id']) - if updated_task['status'] not in [u'scheduled', u'queued']: - logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change to %s", updated_task['id'], updated_task['otdb_id'], updated_task['status']) - #TODO: automatically resolve conflict status by moved pipeline in first free time slot. + movePipelineAfterItsPredecessors(task, self._radbrpc, min_start_timestamp) except Exception as e: logger.error("Error while checking scheduled pipelines: %s", e) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 8b80f2d00ac..f80bb2da906 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -510,7 +510,7 @@ def getTasksHtml(): if not tasks: abort(404) - updateTaskMomDetails(tasks, momrpc) + updateTaskMomDetails(tasks, momqueryrpc) html = '<!DOCTYPE html><html><head><title>Tasks</title><style>table, th, td {border: 1px solid black; border-collapse: collapse; padding: 4px;}</style></head><body><table style="width:100%">\n' @@ -540,7 +540,7 @@ def getTaskHtml(task_id): abort(404, 'No such task %s' % task_id) task['name'] = 'Task %d' % task['id'] - updateTaskMomDetails(task, momrpc) + updateTaskMomDetails(task, momqueryrpc) html = '<!DOCTYPE html><html><head><title>Tasks</title><style>table, th, td {border: 1px solid black; border-collapse: collapse; padding: 4px;}</style></head><body><table style="">\n' -- GitLab From b0f6526a90b83ad687e74583c536b5187aec1b19 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 17 Aug 2016 10:16:10 +0000 Subject: [PATCH 630/933] Task #9607: show dependencies <6hours or <20tasks --- .../lib/static/app/controllers/ganttprojectcontroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index 3f2ea94bc6b..d0bd0eaadd1 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -147,7 +147,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS } //only enable dependencies (arrows between tasks) in detailed view - $scope.options.dependencies = (fullTimespanInMinutes <= 3*60); + $scope.options.dependencies = (fullTimespanInMinutes <= 6*60) || numTasks < 20; var editableTaskStatusIds = $scope.dataService.editableTaskStatusIds; -- GitLab From 27a38c61511a7decca8b34c0be7cef79c1a9b194 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 17 Aug 2016 12:54:46 +0000 Subject: [PATCH 631/933] Task #9682: Put feedback and parsets in /data/parsets to allow the get_metadata step to rerun succesfully --- MAC/Services/src/PipelineControl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index a867662a91d..7daf1092480 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -510,7 +510,7 @@ runcmd docker run --rm --net=host \ -e SLURM_JOB_ID=$SLURM_JOB_ID \ -v /data:/data \ {image} \ - runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} + runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -P {parset_dir} RESULT=$? @@ -543,6 +543,7 @@ exit $RESULT """.format( lofarenv = os.environ.get("LOFARENV", ""), obsid = otdbId, + parset_dir = "/data/parsets", repository = parset.dockerRepository(), image = parset.dockerImage(), cluster = parset.processingCluster(), -- GitLab From da1d507788187e4cb01fe5207c6c0a3e8a40a80a Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 17 Aug 2016 13:13:08 +0000 Subject: [PATCH 632/933] Task #9607: added link to observation/pipeline logfiles --- .../lib/webservice.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index f80bb2da906..d0065fe8eb6 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -31,6 +31,7 @@ from threading import Condition from datetime import datetime import time import logging +import subprocess from dateutil import parser, tz from flask import Flask from flask import render_template @@ -546,6 +547,8 @@ def getTaskHtml(task_id): html += '<h1>Task %s</h1>' % task_id + html += '<p><a href="/tasks/%s/log.html">%s log</a></p> ' % (task['id'], task['type']) + props = sorted(task.keys()) html += '<tr><th>key</th><th>value</th></tr>\n' @@ -631,7 +634,24 @@ def resourceClaimsForTaskHtml(task_id): return html +@app.route('/tasks/<int:task_id>/log.html', methods=['GET']) +def getTaskLogHtml(task_id): + task = rarpc.getTask(task_id) + + cmd = [] + if task['type'] == 'pipeline': + cmd = ['ssh', '-t', 'lofarsys@head01.cep4.control.lofar', 'cat /data/log/pipeline-%s-*.log' % task['otdb_id']] + else: + cmd = ['ssh', '-t', 'mcu001.control.lofar', 'cat /opt/lofar/var/log/MCU001\\:ObservationControl\\[0\\]\\{%s\\}.log*' % task['otdb_id']] + + logger.info(' '.join(cmd)) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + if proc.returncode == 0: + return out, 200, {'Content-Type': 'text/plain; charset=utf-8'} + else: + return err, 500, {'Content-Type': 'text/plain; charset=utf-8'} def main(): # make sure we run in UTC timezone -- GitLab From 7566d454813cfe6212a33ffcd040086c8c383e6a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 17 Aug 2016 14:24:38 +0000 Subject: [PATCH 633/933] Task #8415: Added dynspec Dockerfile --- .gitattributes | 3 + Docker/CMakeLists.txt | 1 + Docker/docker-build-all.sh | 3 +- Docker/dynspec/Dockerfile | 147 +++++++++++++++++++++++++++++++++++++ Docker/dynspec/bashrc | 15 ++++ Docker/dynspec/chuser.sh | 29 ++++++++ 6 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 Docker/dynspec/Dockerfile create mode 100644 Docker/dynspec/bashrc create mode 100755 Docker/dynspec/chuser.sh diff --git a/.gitattributes b/.gitattributes index e2a508e2662..d6a225875dd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2356,6 +2356,9 @@ CMake/variants/variants.node521 -text CMake/variants/variants.phi -text CMake/variants/variants.sharkbay -text Docker/docker-build-all.sh -text +Docker/dynspec/Dockerfile -text +Docker/dynspec/bashrc -text +Docker/dynspec/chuser.sh -text Docker/lofar-base/Dockerfile.tmpl -text Docker/lofar-base/bashrc -text Docker/lofar-base/bashrc.d/00-casacore -text diff --git a/Docker/CMakeLists.txt b/Docker/CMakeLists.txt index 3bb17c3f7ba..2aab64bc7d2 100644 --- a/Docker/CMakeLists.txt +++ b/Docker/CMakeLists.txt @@ -57,6 +57,7 @@ endforeach() # Install everything else install(DIRECTORY + dynspec lofar-base lofar-pipeline lofar-outputproc diff --git a/Docker/docker-build-all.sh b/Docker/docker-build-all.sh index fbd69c3bb1e..2bcc65546c9 100755 --- a/Docker/docker-build-all.sh +++ b/Docker/docker-build-all.sh @@ -12,5 +12,6 @@ cd ${LOFARROOT}/share/docker build lofar-base && \ build lofar-pipeline && \ build lofar-outputproc && \ -build lofar-tbb +build lofar-tbb && \ +build dynspec diff --git a/Docker/dynspec/Dockerfile b/Docker/dynspec/Dockerfile new file mode 100644 index 00000000000..1f2c63f57e2 --- /dev/null +++ b/Docker/dynspec/Dockerfile @@ -0,0 +1,147 @@ +# +# base +# +FROM ubuntu:12.04 + +# +# common-environment +# +ENV USER=lofar +ENV INSTALLDIR=/opt + +# +# environment +# +ENV DEBIAN_FRONTEND=noninteractive \ + PYTHON_VERSION=2.7 + +# +# versions +# Requires boost 1.48 +# Remove casacore? +# +ENV CASACORE_VERSION=2.0.3 \ + CASAREST_VERSION=1.4.1 \ + PYTHON_CASACORE_VERSION=2.0.1 \ + BOOST_VERSION=1.48 + +# +# set-uid +# +ENV UID=1000 + +# +# set-build-options +# +ENV J=6 + +# +# Base and runtime dependencies +# +#RUN sed -i 's/archive.ubuntu.com/osmirror.rug.nl/' /etc/apt/sources.list +RUN apt-get update && apt-get upgrade -y +RUN apt-get install -y sudo +#python2.7 libpython2.7 +# apt-get install -y libblas3 liblapacke python-numpy libcfitsio3 libwcs4 libfftw3-bin libhdf5-7 libboost-python${BOOST_VERSION}.0 && \ +# apt-get install -y nano + + + +# +# setup-account +# +RUN echo 'ALL ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \ + sed -i 's/requiretty/!requiretty/g' /etc/sudoers && \ + useradd -m -u ${UID} ${USER} && \ + chmod a+wr /etc/passwd /etc/group + +# +# setup install dir +# +RUN mkdir -p ${INSTALLDIR} && chown ${USER}.${USER} ${INSTALLDIR} + + +USER ${USER} + +# +# ******************* +# Lofar User Software +# ******************* +# +RUN sudo apt-get update && sudo apt-get upgrade -y && \ + sudo apt-get install -y g++ gfortran flex swig bison subversion \ + zlib1g-dev libatlas-base-dev liblapack-dev \ + libncurses5-dev libfreetype6-dev libpng12-dev \ + python-dev python-tk python-pyfits tk8.5-dev fftw3-dev \ + libbz2-dev libghc-readline-dev \ + git git git-core git-doc git-man git-svn \ + valgrind + +RUN sudo apt-get install -y libboost${BOOST_VERSION}-all-dev \ + wcslib-dev \ + cmake cmake-doc cmake-curses-gui make \ + libgsl0-dev \ + python-matplotlib \ + python-sphinx \ + libcfitsio3-dev \ + python-numpy \ + num-utils \ + python-scipy \ + libblas-dev \ + python-sip-dev \ + openmpi-bin openmpi-common \ + ipython + +RUN cd $INSTALLDIR && svn co http://usg.lofar.org/svn/code/trunk lofarsoft && \ + export LOFARSOFT=${INSTALLDIR}/lofarsoft && . $LOFARSOFT/devel_common/scripts/init.sh && \ + cd $LOFARSOFT && ./bootstrap && cd build && cmake -DCASACORE_FROM_LATEST_SVN_REVISION=ON . && make rebuild_cache && \ + rm -rf $LOFARSOFT/src/Pulsar + +ENV LOFARSOFT=${INSTALLDIR}/lofarsoft + +RUN sudo apt-get install -y libhdf5-serial-dev python-h5py + +RUN . $LOFARSOFT/devel_common/scripts/init.sh && cd $LOFARSOFT/build && make dal + +RUN cd /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-CEP2/src/ICD3-ICD6-Rebin && \ + g++ -O3 -s -Wall -o DynspecPart *cpp -I /opt/lofarsoft/release//include -L /opt/lofarsoft/release//lib -llofardal -lhdf5 && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-CEP2/Beam2Dynspec-Rebin /usr/local/bin/Beam2Dynspec-Rebin && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-CEP2/src/ICD3-ICD6-Rebin/DynspecPart /usr/local/bin/DynspecPart && \ + cd /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-CEP2/src/ICD3-ICD6-Quicklook && \ + g++ -O3 -s -Wall -o DynspecQuick *cpp -I /opt/lofarsoft/release//include -L /opt/lofarsoft/release//lib -llofardal -lhdf5 && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-CEP2/Beam2Dynspec-Quick /usr/local/bin/Beam2Dynspec-Quick && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-CEP2/src/ICD3-ICD6-Quicklook/DynspecQuick /usr/local/bin/DynspecQuick && \ + cd /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-CEP2/src/ICD3-ICD6-Complete && \ + g++ -O3 -s -Wall -o DynspecAll *cpp -I /opt/lofarsoft/release//include -L /opt/lofarsoft/release//lib -llofardal -lhdf5 && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-CEP2/Beam2Dynspec-Complete /usr/local/bin/Beam2Dynspec-Complete && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-CEP2/src/ICD3-ICD6-Complete/DynspecAll /usr/local/bin/DynspecAll && \ + cd /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-Tool/src/ICD6-Linear-Polar && \ + g++ -O3 -s -Wall -o Dynspec_Linear_Polar *cpp -I /opt/lofarsoft/release//include -L /opt/lofarsoft/release//lib -llofardal -lhdf5 && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-Tool/Dynspec-LinPol /usr/local/bin/Dynspec-LinPol && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-Tool/src/ICD6-Linear-Polar/Dynspec_Linear_Polar /usr/local/bin/Dynspec_Linear_Polar && \ + cd /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-Tool/src/ICD6-Rebin && \ + g++ -O3 -s -Wall -o Dyn2Dyn *cpp -I /opt/lofarsoft/release//include -L /opt/lofarsoft/release//lib -llofardal -lhdf5 && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-Tool/Dynspec-Rebin /usr/local/bin/Dynspec-Rebin && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-Tool/src/ICD6-Rebin/Dyn2Dyn /usr/local/bin/Dyn2Dyn && \ + cd /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-Tool/src/ICD6-Substraction && \ + g++ -O3 -s -Wall -o Dynspec_Substraction *cpp -I /opt/lofarsoft/release//include -L /opt/lofarsoft/release//lib -llofardal -lhdf5 && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-Tool/Dynspec-Substract /usr/local/bin/Dynspec-Substract && \ + sudo ln -s /opt/lofarsoft/src/Dynspec-Toolkit/Dynspec-Tool/src/ICD6-Substraction/Dynspec_Substraction /usr/local/bin/Dynspec_Substraction + + + + + +# +# config +# +COPY bashrc /opt/bashrc + +# +# entry +# +#COPY ["bashrc", "bashrc.d", "${INSTALLDIR}/"] + +COPY chuser.sh /usr/local/bin/chuser.sh +ENTRYPOINT ["/usr/local/bin/chuser.sh"] + diff --git a/Docker/dynspec/bashrc b/Docker/dynspec/bashrc new file mode 100644 index 00000000000..e0755976685 --- /dev/null +++ b/Docker/dynspec/bashrc @@ -0,0 +1,15 @@ +#!/bin/bash + +# lofar +[ -r ${INSTALLDIR}/lofar/lofarinit.sh ] && source ${INSTALLDIR}/lofar/lofarinit.sh +export PATH PYTHONPATH LD_LIBRARY_PATH LOFARROOT + +# qpid +#source ${INSTALLDIR}/qpid/.profile + +# lofarsoft +echo "sourcing init" +source ${LOFARSOFT}/devel_common/scripts/init.sh + +export PYTHONPATH=$PYTHONPATH:${LOFARSOFT}/release/lib/python/dal +export LD_LIBRARY_PATH=LD_LIBRARY_PATH:${LOFARSOFT}/release/lib diff --git a/Docker/dynspec/chuser.sh b/Docker/dynspec/chuser.sh new file mode 100755 index 00000000000..9ac3603c1a0 --- /dev/null +++ b/Docker/dynspec/chuser.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# Correct UID +export UID=`id -u` + +# Configure user +if [ -z "${USER}" ]; then + export USER=${UID} +fi + +# Create home directory +if [ -z "${HOME}" ]; then + export HOME=/home/${USER} + mkdir -p $HOME && cd $HOME +fi + +# Add user to system +fgrep -q ":x:${UID}:" /etc/passwd || echo "${USER}:x:${UID}:${UID}::${HOME}:/bin/bash" >> /etc/passwd +fgrep -q ":x:${UID}:" /etc/group || echo "${USER}:x:${UID}:" >> /etc/group + +# Set the environment +[ -e /opt/bashrc ] && source /opt/bashrc + +# Run the requested command +if [ -z "$*" ]; then + exec /bin/bash +else + exec "$@" +fi -- GitLab From 29f4475f25f6c3a9bf38de96fc72217ac9ad3a05 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 17 Aug 2016 14:28:52 +0000 Subject: [PATCH 634/933] Task #9522: Use IO & RT priorities again now that outputProc has sys_nice and ipc_lock capabilities on CEP4 --- RTCP/Cobalt/OutputProc/src/GPUProcIO.cc | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc index 039f03423da..1f50d347525 100644 --- a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc +++ b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc @@ -114,20 +114,12 @@ bool process(Stream &controlStream) * Real-time observation */ - /* - * Disabled elevated priviledges on CEP4 due to issues with Docker. - * We don't seem to need the priorities anyway, and memory limits will - * need to be enforced by Docker or SLURM, not us. - // Acquire elevated IO and CPU priorities setIOpriority(); setRTpriority(); // Prevent swapping of our buffers - lockInMemory(16UL * 1024UL * 1024UL * 1024UL); // limit memory to 16 GB - - * - */ + //lockInMemory(16UL * 1024UL * 1024UL * 1024UL); // limit memory to 16 GB // Deadline for outputProc, in seconds. const time_t outputProcTimeout = -- GitLab From 226c843b572d7f9abc5b1d5638cdda17f295b8f2 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 17 Aug 2016 14:32:11 +0000 Subject: [PATCH 635/933] Task #9607: prevent data deletion for tasks with unfinished successors --- .../app/controllers/cleanupcontroller.js | 55 +++++++++--- .../static/app/controllers/datacontroller.js | 84 ++++++++----------- .../lib/static/css/main.css | 4 + 3 files changed, 83 insertions(+), 60 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js index 45321de5445..13de0463b74 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -1,8 +1,8 @@ // $Id: controller.js 32761 2015-11-02 11:50:21Z schaap $ -var cleanupControllerMod = angular.module('CleanupControllerMod', ['ui.bootstrap']); +var cleanupControllerMod = angular.module('CleanupControllerMod', ['ui.bootstrap', 'ngMaterial']); -cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$http', '$q', 'dataService', function($scope, $uibModal, $http, $q, dataService) { +cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$mdDialog', '$http', '$q', 'dataService', function($scope, $uibModal, $mdDialog, $http, $q, dataService) { var self = this; self.getTaskDataPath = function(task) { @@ -24,22 +24,51 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$h } self.deleteTasksDataWithConfirmation = function(tasks) { - du_results = []; + suc_tasks_promises = []; for(var task of tasks) { - dataService.getTaskDiskUsage(task).then(function(du_result) { - if(du_result.found) { - du_results.push(du_result); + if(task.successor_ids) { + for(var suc_id of task.successor_ids) { + suc_tasks_promises.push(dataService.getTask(suc_id)); + } + } + } - if(du_results.length == tasks.length) { - openDeleteConfirmationDialog(du_results); - } + $q.all(suc_tasks_promises).then(function(suc_tasks) { + var unfinished_suc_tasks = suc_tasks.filter(function(t) { return t && t.status != 'finished' }); + + if(unfinished_suc_tasks.length > 0) { + var unfinished_ids = unfinished_suc_tasks.map(function(t) { return t.otdb_id; }); + $mdDialog.show($mdDialog.alert() + .parent(angular.element(document.querySelector('#popupContainer'))) + .title('Error') + .textContent("Cannot delete data for " + unfinished_ids + " because there are unfinished successors") + .ariaLabel('Error') + .ok('Ok')); + return; + } - console.log(du_results); - } else { - alert(du_result.message); + du_promises = []; + for(var task of tasks) { + du_promises.push(dataService.getTaskDiskUsage(task)); + } + + $q.all(du_promises).then(function(du_results) { + var unfound_du_results = du_results.filter(function(r) { return !r || !r.found; }); + + if(unfound_du_results.length > 0) { + var unfound_ids = unfound_du_results.map(function(t) { return t.otdb_id; }); + $mdDialog.show($mdDialog.alert() + .parent(angular.element(document.querySelector('#popupContainer'))) + .title('Error') + .textContent("Could not find data to delete for one or more tasks") + .ariaLabel('Error') + .ok('Ok')); + return; } + + openDeleteConfirmationDialog(du_results); }); - } + }); }; function deleteTaskData(task, delete_is, delete_cs, delete_uv, delete_im, delete_img, delete_pulp, delete_scratch) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index a7f4e8870ca..a3b24463599 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -338,70 +338,60 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }) }; - self.getTaskByOTDBId = function(otdb_id) { + var _getTaskBy = function(id_name, id) { var defer = $q.defer(); - if(typeof(otdb_id) === 'string') { - otdb_id = parseInt(otdb_id); + if(typeof(id) === 'string') { + id = parseInt(id); } - var foundTask = self.tasks.find(function(t) { return t.otdb_id == otdb_id; }); + var foundTask = self.tasks.find(function(t) { return t['id_name'] == id; }); if(foundTask) { defer.resolve(foundTask); } else { - $http.get('/rest/tasks/otdb/' + otdb_id).success(function(result) { - var task = result.task; - if(task) { - task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); - task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); - - if(!self.taskDict.hasOwnProperty(task.id)) { - self.tasks.push(task); - self.taskDict[task.id] = task; - self.taskChangeCntr++; + var url; + switch(id_name) { + case 'id': url = '/rest/tasks/' + id; break; + case 'otdb_id': url = '/rest/tasks/otdb/' + id; break; + case 'mom_id': url = '/rest/tasks/mom/' + id; break; + } + + if(url) { + $http.get(url).success(function(result) { + var task = result.task; + if(task) { + task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); + task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); + + if(!self.taskDict.hasOwnProperty(task.id)) { + self.tasks.push(task); + self.taskDict[task.id] = task; + self.taskChangeCntr++; + } } - } - defer.resolve(task); - }).error(function(result) { + defer.resolve(task); + }).error(function(result) { + defer.resolve(undefined); + }) + } else { defer.resolve(undefined); - }) + } } return defer.promise; }; - self.getTaskByMoMId = function(mom_id) { - var defer = $q.defer(); - - if(typeof(mom_id) === 'string') { - mom_id = parseInt(mom_id); - } - - var foundTask = self.tasks.find(function(t) { return t.mom_id == mom_id; }); + self.getTask= function(id) { + return _getTaskBy('id', id); + }; - if(foundTask) { - defer.resolve(foundTask); - } else { - $http.get('/rest/tasks/mom/' + mom_id).success(function(result) { - var task = result.task; - if(task) { - task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); - task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); - - if(!self.taskDict.hasOwnProperty(task.id)) { - self.tasks.push(task); - self.taskDict[task.id] = task; - self.taskChangeCntr++; - } - } - defer.resolve(task); - }).error(function(result) { - defer.resolve(undefined); - }) - } + self.getTaskByOTDBId = function(otdb_id) { + return _getTaskBy('otdb_id', otdb_id); + }; - return defer.promise; + self.getTaskByMoMId = function(mom_id) { + return _getTaskBy('mom_id', mom_id); }; self.copyTask = function(task) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index 6f5286b791e..d3f0e3afec9 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -25,6 +25,10 @@ body { z-index: 1010; } +.md-dialog-container { + z-index: 1000; +} + .ui-grid-cell { overflow: visible; } -- GitLab From d292a75a42506930241522852c5376a1b065de95 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 18 Aug 2016 07:22:32 +0000 Subject: [PATCH 636/933] Task #9607: prevent data deletion for tasks with unfinished successors --- SAS/DataManagement/CleanupService/rpc.py | 6 ++++++ SAS/DataManagement/CleanupService/service.py | 12 +++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/SAS/DataManagement/CleanupService/rpc.py b/SAS/DataManagement/CleanupService/rpc.py index 590c1e62ae0..7a30f41cc30 100644 --- a/SAS/DataManagement/CleanupService/rpc.py +++ b/SAS/DataManagement/CleanupService/rpc.py @@ -49,6 +49,9 @@ def main(): print "This will delete everything in '%s'." % path if raw_input("Are you sure? (y/n) ") == 'y': result = rpc.removeTaskData(otdb_id) + print + if not result['deleted']: + print 'Could not delete data for task with otdb_id=%s' % otdb_id print result['message'] exit(0 if result['deleted'] else 1) else: @@ -56,3 +59,6 @@ def main(): else: print path_result['message'] exit(1) + +if __name__ == '__main__': + main() diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index 738157aec85..9853a50e22e 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -91,6 +91,16 @@ class CleanupHandler(MessageHandlerInterface): logger.error(message) return {'deleted': False, 'message': message} + radbrpc = self.path_resolver.radbrpc + task = radbrpc.getTask(otdb_id=otdb_id) + if task: + suc_tasks = radbrpc.getTasks(task_ids=task['successor_ids']) + unfinished_scu_tasks = [t for t in suc_tasks if t['status'] != 'finished'] + if unfinished_scu_tasks: + message = "Task otdb_id=%s has unfinished successor tasks (otdb_ids: %s)" % (task['otdb_id'], [t['otdb_id'] for t in unfinished_scu_tasks]) + logger.error(message) + return {'deleted': False, 'message': message} + path_result = self.path_resolver.getPathForOTDBId(otdb_id) if path_result['found']: rm_results = [] @@ -220,7 +230,7 @@ def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, brok busname=busname, broker=broker, use_service_methods=True, - numthreads=1, + numthreads=4, verbose=verbose, handler_args={'mountpoint': mountpoint, 'radb_busname':RADB_BUSNAME, -- GitLab From 0e9247e842646f6b62608c424a4c0f3897ef7b58 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 18 Aug 2016 09:02:30 +0000 Subject: [PATCH 637/933] Task #9607: show aggregated bars for observations/pipelines --- .../app/controllers/ganttprojectcontroller.js | 79 ++++++++++++++++++- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index d0bd0eaadd1..54a3890a557 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -126,9 +126,11 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS var ganntRows = []; if(numProjecs > 0 && numTasks > 0) { - $scope.options.fromDate = $scope.dataService.viewTimeSpan.from; - $scope.options.toDate = $scope.dataService.viewTimeSpan.to; - var fullTimespanInMinutes = ($scope.options.toDate - $scope.options.fromDate) / (60 * 1000); + var lowerViewBound = $scope.dataService.viewTimeSpan.from; + var upperViewBound = $scope.dataService.viewTimeSpan.to; + $scope.options.fromDate = lowerViewBound; + $scope.options.toDate = upperViewBound; + var fullTimespanInMinutes = (upperViewBound - lowerViewBound) / (60 * 1000); if(fullTimespanInMinutes > 28*24*60) { $scope.options.viewScale = '7 days'; @@ -149,8 +151,77 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS //only enable dependencies (arrows between tasks) in detailed view $scope.options.dependencies = (fullTimespanInMinutes <= 6*60) || numTasks < 20; - var editableTaskStatusIds = $scope.dataService.editableTaskStatusIds; + //start with aggregating all tasks per type, + //and plot these in the upper rows, + //so we can see the observartion and pipeline scheduling usage/efficiency + for(var type of ['observation', 'pipeline']) { + var typeTasks = tasks.filter(function(t) { return t.type == type;}).sort(function(a, b) { return a.starttime.getTime() - b.starttime.getTime(); });; + var numTypeTasks = typeTasks.length; + + if(numTypeTasks > 0) { + var typeAggregateRow = { + id: type + 's_aggregated', + name: ('All ' + type + 's').toUpperCase(), + tasks: [] + }; + ganntRows.push(typeAggregateRow); + + var task = typeTasks[0]; + + var rowTask = { + id: typeAggregateRow.id + '_task_' + typeAggregateRow.tasks.length, + from: task.starttime, + color: '#cceecc', + movable: false + }; + + if(rowTask.from < lowerViewBound) { + rowTask.from = lowerViewBound; + } + + for(var i = 1; i < numTypeTasks; i++) { + var prev_task = task; + task = typeTasks[i]; + + if(task.starttime > prev_task.endtime) { + rowTask.to = prev_task.endtime; + + if(rowTask.to > upperViewBound) { + rowTask.to = upperViewBound; + } + + typeAggregateRow.tasks.push(rowTask); + + rowTask = { + id: typeAggregateRow.id + '_task_' + typeAggregateRow.tasks.length, + from: task.starttime, + color: '#cceecc', + movable: false + }; + + if(rowTask.from < lowerViewBound) { + rowTask.from = lowerViewBound; + } + } + } + if(!rowTask.to) { + rowTask.to = task.endtime; + + if(rowTask.to > upperViewBound) { + rowTask.to = upperViewBound; + } + } + + typeAggregateRow.tasks.push(rowTask); + var aggTaskTotalDuration = 0.0 + typeAggregateRow.tasks.map(function(t) { return t.to - t.from;}).reduce(function(a, b) { return a+b; }); + var usage = aggTaskTotalDuration / (upperViewBound - lowerViewBound); + var usagePerc = parseFloat(Math.round(100.0 * usage * 10.0) / 10.0).toFixed(1); + typeAggregateRow.name += ' (' + usagePerc + '%)'; + } + } + + var editableTaskStatusIds = $scope.dataService.editableTaskStatusIds; var ganntRowsDict = {}; for(var i = 0; i < numTasks; i++) { -- GitLab From 89affaae9d9b44ac9ec4f1a357e05ee1d49b974e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 18 Aug 2016 09:05:34 +0000 Subject: [PATCH 638/933] Task #9607: Table mode instead of Tree mode in project gannt --- .../lib/static/app/controllers/ganttprojectcontroller.js | 2 +- .../ResourceAssignmentEditor/lib/static/css/main.css | 2 +- .../ResourceAssignmentEditor/lib/templates/index.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index 54a3890a557..8a47ceca07c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -36,7 +36,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS currentDateValue: $scope.dataService.lofarTime, columnMagnet: '1 minutes', timeFramesMagnet: false, - sideMode: 'Tree', + sideMode: 'Table', autoExpand: 'both', taskOutOfRange: 'truncate', dependencies: false, diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index d3f0e3afec9..04e51917753 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -66,7 +66,7 @@ table.uib-timepicker td.uib-time { } .gantt-side { - width: 150px; + width: 180px; } .top-stretch { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index c867ed04b62..5bad31f3b23 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -122,7 +122,7 @@ current-date-value="options.currentDateValue" column-magnet="options.columnMagnet" style="height: 75%; max-height:1200px; width: 100%; overflow-x: hidden; overflow-y: auto; margin-left: 8px;"> - <gantt-tree enabled="true"></gantt-tree> + <gantt-table enabled="true"></gantt-table> <gantt-movable enabled="true" allow-moving="true" allow-resizing="true" -- GitLab From f21d248e4fabace8f0a650e18db2cb59729b00b8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 18 Aug 2016 11:33:25 +0000 Subject: [PATCH 639/933] Task #9607: added missing dependency --- CEP/Pipeline/framework/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/framework/CMakeLists.txt b/CEP/Pipeline/framework/CMakeLists.txt index b628f2e5821..736e7e26114 100644 --- a/CEP/Pipeline/framework/CMakeLists.txt +++ b/CEP/Pipeline/framework/CMakeLists.txt @@ -1,5 +1,5 @@ # $Id$ -lofar_package(Pipeline-Framework 0.1) +lofar_package(Pipeline-Framework 0.1 DEPENDS MessageBus) add_subdirectory(lofarpipe) -- GitLab From b566e886677d2c150344aa4fff0a04328514b0c0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 18 Aug 2016 11:46:32 +0000 Subject: [PATCH 640/933] Task #9607: leave live mode when selecting from/to date --- .../ResourceAssignmentEditor/lib/templates/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index 5bad31f3b23..d09b4578025 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -62,7 +62,7 @@ <div class="col-md-3"> <label>From:</label> <p class="input-group"> - <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.from" is-open="viewFromDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> + <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.from" ng-change="$parent.onViewTimeSpanFromChanged()" is-open="viewFromDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> <span class="input-group-btn"> <button type="button" class="btn btn-default" ng-click="openViewFromDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> </span> @@ -72,7 +72,7 @@ <div class="col-md-3"> <label>To:</label> <p class="input-group"> - <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.to" is-open="viewToDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> + <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.to" ng-change="$parent.onViewTimeSpanToChanged()" is-open="viewToDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> <span class="input-group-btn"> <button type="button" class="btn btn-default" ng-click="openViewToDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> </span> -- GitLab From 8e21a4152851cc3f826e3537246eb3ef21d3b68c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 18 Aug 2016 13:42:52 +0000 Subject: [PATCH 641/933] Task #9682: Dont pull docker images from nexus anymore, since it spams the logs and crashes nexus. We distribute the images after build for now. --- MAC/Services/src/PipelineControl.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 7daf1092480..7c780af556f 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -492,16 +492,6 @@ runcmd {setStatus_active} # notify ganglia wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} ACTIVE&host_regex=" -# pull docker image from repository on all nodes -srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-pull \ - --kill-on-bad-exit=0 --wait=0 \ - docker pull {repository}/{image} - -# put a local tag on the pulled image -srun --nodelist=$SLURM_NODELIST --cpus-per-task=1 --job-name=docker-tag \ - --kill-on-bad-exit=0 --wait=0 \ - docker tag -f {repository}/{image} {image} - # run the pipeline runcmd docker run --rm --net=host \ -e LOFARENV={lofarenv} \ -- GitLab From 3eab3e717e7e75c705a710877dba762b71a69f10 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 18 Aug 2016 13:56:45 +0000 Subject: [PATCH 642/933] Task #9522: Provide sys_nice/sys_admin to outputProc --- RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh index 3614561231e..fba0338156f 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh @@ -342,7 +342,7 @@ OUTPUTPROC_CMDLINE="$OUTPUTPROC_VARS $OUTPUT_PROC_EXECUTABLE $OBSERVATIONID" if $DOCKER; then TAG="`echo '${LOFAR_TAG}' | docker-template`" - OUTPUTPROC_CMDLINE="docker run --rm --privileged -u `id -u $SSH_USER_NAME` --net=host -v $GLOBALFS_DIR:$GLOBALFS_DIR lofar-outputproc:$TAG bash -c \"$OUTPUTPROC_CMDLINE\"" + OUTPUTPROC_CMDLINE="docker run --rm --cap-add=sys_nice --cap-add=sys_admin -u `id -u $SSH_USER_NAME` --net=host -v $GLOBALFS_DIR:$GLOBALFS_DIR lofar-outputproc:$TAG bash -c \"$OUTPUTPROC_CMDLINE\"" fi echo "[outputProc] command line = $OUTPUTPROC_CMDLINE" -- GitLab From d2c557a6b5028aa3256c8c87c7d623c458d3f2e1 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 18 Aug 2016 20:21:30 +0000 Subject: [PATCH 643/933] Task #8361: Add cap_sys_admin to outputProc to allow setting I/O priorities, fix real-time budget for outputProc to allow setting RT priorities --- Docker/lofar-base/Dockerfile.tmpl | 2 +- RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 77228e242bc..34e2d90f3f4 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -44,7 +44,7 @@ RUN apt-get update && \ pip install numpy && \ apt-get purge -y python-pip && \ apt-get autoremove -y && \ - apt-get install -y nano + apt-get install -y nano sudo # # open security holes (allow smooth user switching, allow sudo) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh index fba0338156f..77fef760786 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh @@ -342,7 +342,9 @@ OUTPUTPROC_CMDLINE="$OUTPUTPROC_VARS $OUTPUT_PROC_EXECUTABLE $OBSERVATIONID" if $DOCKER; then TAG="`echo '${LOFAR_TAG}' | docker-template`" - OUTPUTPROC_CMDLINE="docker run --rm --cap-add=sys_nice --cap-add=sys_admin -u `id -u $SSH_USER_NAME` --net=host -v $GLOBALFS_DIR:$GLOBALFS_DIR lofar-outputproc:$TAG bash -c \"$OUTPUTPROC_CMDLINE\"" + # "echo 950000 > /sys/fs/cgroup/cpu/cpu.rt_runtime_us" is needed to configure the real-time scheduler for this cgroup + # --cpu-shares=24576 (=24*1024) provides this process with as much share of the CPU as 24 other containers (note: CEP4 has 24 cores/node) + OUTPUTPROC_CMDLINE="docker run --rm --cpu-shares=24576 --cap-add=sys_nice --cap-add=sys_admin -u `id -u $SSH_USER_NAME` --net=host -v $GLOBALFS_DIR:$GLOBALFS_DIR lofar-outputproc:$TAG bash -c \"sudo echo 950000 > /sys/fs/cgroup/cpu/cpu.rt_runtime_us; $OUTPUTPROC_CMDLINE\"" fi echo "[outputProc] command line = $OUTPUTPROC_CMDLINE" -- GitLab From 39043701e62352c31f9683c8ef58f99082f8efd4 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Aug 2016 07:46:51 +0000 Subject: [PATCH 644/933] Task #9607: special case for dynspec projects for Richard Fallows --- .../RAtoOTDBTaskSpecificationPropagator/lib/translator.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 15d4c860a2d..3b409aca9e8 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -232,6 +232,11 @@ class RAtoOTDBTranslator(): logging.info("Adding inspection plot commands to parset") parset[PREFIX+'ObservationControl.OnlineControl.inspectionHost'] = 'head01.cep4.control.lofar' parset[PREFIX+'ObservationControl.OnlineControl.inspectionProgram'] = 'inspection-plots-observation.sh' + + #special case for dynspec projects for Richard Fallows + if project_name in ['LC6_001']: + parset[PREFIX+'ObservationControl.OnlineControl.inspectionProgram'] = '/data/home/lofarsys/dynspec/scripts/inspection-dynspec-observation.sh' + return parset -- GitLab From 8b22e8c46fdabc2e9a831ee725b13ec6b4036ea1 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Aug 2016 09:02:50 +0000 Subject: [PATCH 645/933] Task #9607: get tasks by mom_ids or by otdb_ids --- .../ResourceAssignmentDatabase/radb.py | 23 +++++++++++++++++-- .../ResourceAssignmentService/rpc.py | 4 ++-- .../ResourceAssignmentService/service.py | 4 +++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index f974a13f872..a3833bc4b62 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -155,7 +155,10 @@ class RADatabase: raise KeyError('No such status: %s. Valid values are: %s' % (status_name, ', '.join(self.getResourceClaimStatusNames()))) - def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None): + def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None): + if len([x for x in [task_ids, mom_ids, otdb_ids] if x != None]) > 1: + raise KeyError("Provide either task_ids or mom_ids or otdb_ids, not multiple kinds.") + query = '''SELECT * from resource_allocation.task_view''' conditions = [] @@ -173,10 +176,26 @@ class RADatabase: if isinstance(task_ids, int): # just a single id conditions.append('id = %s') qargs.append(task_ids) - else: #assume a list/enumerable of id's + elif task_ids: #assume a list/enumerable of id's conditions.append('id in %s') qargs.append(tuple(task_ids)) + if mom_ids is not None: + if isinstance(mom_ids, int): # just a single id + conditions.append('mom_id = %s') + qargs.append(mom_ids) + elif mom_ids: #assume a list/enumerable of id's + conditions.append('mom_id in %s') + qargs.append(tuple(mom_ids)) + + if otdb_ids is not None: + if isinstance(otdb_ids, int): # just a single id + conditions.append('otdb_id = %s') + qargs.append(otdb_ids) + elif otdb_ids: #assume a list/enumerable of id's + conditions.append('otdb_id in %s') + qargs.append(tuple(otdb_ids)) + task_status, task_type = self._convertTaskTypeAndStatusToIds(task_status, task_type) if task_status is not None: diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py index 7f8e3f22e7b..44834eed42e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py @@ -178,8 +178,8 @@ class RARPC(RPCWrapper): otdb_id=otdb_id, status=status) - def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None): - tasks = self.rpc('GetTasks', lower_bound=lower_bound, upper_bound=upper_bound, task_ids=task_ids, task_status=task_status, task_type=task_type) + def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None): + tasks = self.rpc('GetTasks', lower_bound=lower_bound, upper_bound=upper_bound, task_ids=task_ids, task_status=task_status, task_type=task_type, mom_ids=mom_ids, otdb_ids=otdb_ids) for task in tasks: task['starttime'] = task['starttime'].datetime() task['endtime'] = task['endtime'].datetime() diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/service.py b/SAS/ResourceAssignment/ResourceAssignmentService/service.py index 234019ae0a1..7e01393bee2 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/service.py @@ -215,7 +215,9 @@ class RADBHandler(MessageHandlerInterface): upper_bound=kwargs.get('upper_bound').datetime() if kwargs.get('upper_bound') else None, task_ids=kwargs.get('task_ids'), task_status=kwargs.get('task_status'), - task_type=kwargs.get('task_type')) + task_type=kwargs.get('task_type'), + mom_ids=kwargs.get('mom_ids'), + otdb_ids=kwargs.get('otdb_ids')) def _getTask(self, **kwargs): logger.info('GetTask: %s' % dict({k:v for k,v in kwargs.items() if v != None})) -- GitLab From 672da79a168b49e8b31b521a200f55d22bc36c44 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 19 Aug 2016 09:10:20 +0000 Subject: [PATCH 646/933] Task #9522: Provide sys_nice/sys_admin to outputProc --- Docker/lofar-outputproc/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index 9ab90ed15ee..7e67934e8c3 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -31,7 +31,7 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "ln -sfT /home/${USER}/lofar/var ${INSTALLDIR}/lofar/var" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ - setcap cap_sys_nice,cap_ipc_lock=ep ${INSTALLDIR}/lofar/bin/outputProc && \ + setcap cap_sys_nice,cap_sys_admin=ep ${INSTALLDIR}/lofar/bin/outputProc && \ apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ apt-get autoremove -y -- GitLab From 97085b00724af33171baa95104865b7c01d9285d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 19 Aug 2016 09:11:10 +0000 Subject: [PATCH 647/933] Task #8415: Reduce log spam --- RTCP/Cobalt/GPUProc/src/Storage/StorageProcess.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/GPUProc/src/Storage/StorageProcess.cc b/RTCP/Cobalt/GPUProc/src/Storage/StorageProcess.cc index 8927cce9c00..a56faadc905 100644 --- a/RTCP/Cobalt/GPUProc/src/Storage/StorageProcess.cc +++ b/RTCP/Cobalt/GPUProc/src/Storage/StorageProcess.cc @@ -73,7 +73,7 @@ namespace LOFAR if (itsParset.settings.correlator.enabled) for (size_t i = 0; i < itsParset.settings.correlator.files.size(); ++i) { - LOG_INFO_STR(itsParset.settings.correlator.files[i].location.host << " == " << itsHostname); + LOG_DEBUG_STR(itsParset.settings.correlator.files[i].location.host << " == " << itsHostname); if (itsParset.settings.correlator.files[i].location.host == itsHostname) { Protocols::TaskFeedbackDataproducts msg( -- GitLab From a275e1c423722c7ca3024b4d5cbedda7c88f8600 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Aug 2016 09:17:55 +0000 Subject: [PATCH 648/933] Task #9607: get mom tasks ids by mom_group_id --- SAS/MoM/MoMQueryService/momqueryrpc.py | 14 +++++++++ SAS/MoM/MoMQueryService/momqueryservice.py | 33 +++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/SAS/MoM/MoMQueryService/momqueryrpc.py b/SAS/MoM/MoMQueryService/momqueryrpc.py index c611638755b..385ee6cd18d 100644 --- a/SAS/MoM/MoMQueryService/momqueryrpc.py +++ b/SAS/MoM/MoMQueryService/momqueryrpc.py @@ -50,6 +50,11 @@ class MoMQueryRPC(RPCWrapper): logger.info("getSuccessorIds(%s): %s", ids, result) return result + def getTaskIdsInGroup(self, mom_group_ids): + logger.debug("getTaskIdsInGroup(%s)", mom_group_ids) + result = self.rpc('GetTaskIdsInGroup', mom_group_ids=mom_group_ids) + logger.info("getTaskIdsInGroup(%s): %s", mom_group_ids, result) + return result def main(): # Check the invocation arguments @@ -63,6 +68,7 @@ def main(): parser.add_option('-p', '--project_details', dest='project_details', type='int', help='get project details for mom object with given id') parser.add_option('--predecessors', dest='id_for_predecessors', type='int', help='get the predecessor id\'s for the given mom2id') parser.add_option('--successors', dest='id_for_successors', type='int', help='get the successors id\'s for the given mom2id') + parser.add_option('-g', '--group', dest='group_id', type='int', help='get the tasks ids in the given group mom2id') (options, args) = parser.parse_args() if len(sys.argv) == 1: @@ -101,5 +107,13 @@ def main(): else: print 'No results' + if options.group_id: + task_ids = rpc.getTaskIdsInGroup(options.group_id) + if task_ids: + for k, v in task_ids.items(): + print ' %s: %s' % (k, v) + else: + print 'No results' + if __name__ == '__main__': main() diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index d285fca3846..83c518f0c08 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -230,6 +230,33 @@ class MoMDatabaseWrapper: return result + def getTaskIdsInGroup(self, mom_group_ids): + if not mom_group_ids: + return {} + + ids_str = _toIdsString(mom_group_ids) + + logger.info("getTaskIdsInGroup for mom group ids: %s" % ids_str) + + query = '''SELECT mom2id, group_id FROM mom2object + where group_id in (%s) + and (mom2objecttype = 'LOFAR_OBSERVATION' or mom2objecttype like \'%%PIPELINE\')''' % ids_str + + rows = self._executeQuery(query) + + result = {} + for group_id in ids_str.split(','): + result[group_id] = [] + + for row in rows: + mom2id = row['mom2id'] + group_id = row['group_id'] + result[str(group_id)].append(mom2id) + + logger.info('task ids per group: %s', result) + + return result + class ProjectDetailsQueryHandler(MessageHandlerInterface): '''handler class for details query in mom db :param MoMDatabaseWrapper momdb inject database access via wrapper @@ -242,7 +269,8 @@ class ProjectDetailsQueryHandler(MessageHandlerInterface): 'GetProjects': self.getProjects, 'GetProjectDetails': self.getProjectDetails, 'GetPredecessorIds': self.getPredecessorIds, - 'GetSuccessorIds': self.getSuccessorIds + 'GetSuccessorIds': self.getSuccessorIds, + 'GetTaskIdsInGroup': self.getTaskIdsInGroup } def prepare_loop(self): @@ -260,6 +288,9 @@ class ProjectDetailsQueryHandler(MessageHandlerInterface): def getSuccessorIds(self, mom_ids): return self.momdb.getSuccessorIds(mom_ids) + def getTaskIdsInGroup(self, mom_group_ids): + return self.momdb.getTaskIdsInGroup(mom_group_ids) + def createService(busname=DEFAULT_MOMQUERY_BUSNAME, servicename=DEFAULT_MOMQUERY_SERVICENAME, -- GitLab From b50f0f549894f0bbcc7cc8bce0a34cf713341727 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Aug 2016 09:19:57 +0000 Subject: [PATCH 649/933] Task #9607: select group on a task loads the tasks in the group, displays them, and selects them --- .../static/app/controllers/datacontroller.js | 72 +++++++++++++++++-- .../app/controllers/ganttprojectcontroller.js | 2 +- .../static/app/controllers/gridcontroller.js | 16 ++++- .../angular-gantt-contextmenu-plugin.js | 3 +- .../lib/webservice.py | 12 ++++ 5 files changed, 95 insertions(+), 10 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index a3b24463599..72026b03b07 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -90,6 +90,13 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.selected_task_ids.push(task_id); } + self.setSelectedTaskIds = function(task_ids) { + self.selected_task_ids.splice(0, self.selected_task_ids.length); + for(var task_id of task_ids) { + self.selected_task_ids.push(task_id); + } + } + self.selectTasksInSameGroup = function(task) { self.selected_task_ids.splice(0, self.selected_task_ids.length); var groupTasks = self.filteredTasks.filter(function(t) { return t.mom_object_group_id == task.mom_object_group_id; }); @@ -394,6 +401,41 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, return _getTaskBy('mom_id', mom_id); }; + self.getTasksByMoMGroupId = function(mom_object_group_id) { + var defer = $q.defer(); + var url = '/rest/tasks/mom/group/' + mom_object_group_id; + + $http.get(url).success(function(result) { + //convert datetime strings to Date objects + for(var i in result.tasks) { + var task = result.tasks[i]; + task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); + task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); + } + + var newTaskDict = self.toIdBasedDict(result.tasks); + var newTaskIds = Object.keys(newTaskDict); + + for(var i = newTaskIds.length-1; i >= 0; i--) { + var task_id = newTaskIds[i]; + if(!self.taskDict.hasOwnProperty(task_id)) { + var task = newTaskDict[task_id]; + self.tasks.push(task); + self.taskDict[task_id] = task; + } + } + + self.taskChangeCntr++; + self.computeMinMaxTaskTimes(); + + defer.resolve(result.tasks); + }).error(function(result) { + defer.resolve(undefined); + }); + + return defer.promise; + }; + self.copyTask = function(task) { $http.put('/rest/tasks/' + task.id + '/copy').error(function(result) { console.log("Error. Could not copy task. " + result); @@ -853,8 +895,10 @@ dataControllerMod.controller('DataController', $scope.dataService.getTaskByOTDBId(otdb_id).then(function(task) { if(task) { $scope.dataService.setSelectedTaskId(task.id); - $scope.jumpToSelectedTask(); - defer.resolve(); + $scope.jumpToSelectedTasks(); + defer.resolve(task); + } else { + defer.resolve(undefined); } }); return defer.promise; @@ -865,8 +909,26 @@ dataControllerMod.controller('DataController', $scope.dataService.getTaskByMoMId(mom_id).then(function(task) { if(task) { $scope.dataService.setSelectedTaskId(task.id); - $scope.jumpToSelectedTask(); - defer.resolve(); + $scope.jumpToSelectedTasks(); + defer.resolve(task); + } else { + defer.resolve(undefined); + } + }); + return defer.promise; + }; + + $scope.loadTasksByMoMGroupIdSelectAndJumpIntoView = function(mom_group_id) { + var defer = $q.defer(); + $scope.dataService.getTasksByMoMGroupId(mom_group_id).then(function(tasks) { + if(tasks) { + var task_ids = tasks.map(function(t) { return t.id; }); + + $scope.dataService.setSelectedTaskIds(task_ids); + $scope.jumpToSelectedTasks(); + defer.resolve(tasks); + } else { + defer.resolve(undefined); } }); return defer.promise; @@ -879,7 +941,7 @@ dataControllerMod.controller('DataController', } }; - $scope.jumpToSelectedTask = function() { + $scope.jumpToSelectedTasks = function() { if(dataService.selected_task_ids == undefined) return; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index 8a47ceca07c..c14bb17ea27 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -70,7 +70,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS directiveElement.bind('dblclick', function(event) { if(directiveScope.task.model.raTask) { $scope.dataService.setSelectedTaskId(directiveScope.task.model.raTask.id); - $scope.jumpToSelectedTask(); + $scope.jumpToSelectedTasks(); } }); } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index ecd9a4d7585..4ad4e37b616 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -176,8 +176,17 @@ $scope.columns = [ if(mom_col && mom_col.filters.length && mom_col.filters[0].hasOwnProperty('term')) { var mom_id = mom_col.filters[0].term; - $scope.$parent.$parent.loadTaskByMoMIdSelectAndJumpIntoView(mom_id).then(function() { - mom_col.filters[0].term = null; + $scope.$parent.$parent.loadTaskByMoMIdSelectAndJumpIntoView(mom_id).then(function(task) { + if(task) { + mom_col.filters[0].term = null; + } else { + //getting the task by mom_id did not find a task + //maybe the entered id was a mom group_id? + //let's try to loadTasksByMoMGroupIdSelectAndJumpIntoView + $scope.$parent.$parent.loadTasksByMoMGroupIdSelectAndJumpIntoView(mom_id).then(function(tasks) { + mom_col.filters[0].term = null; + }); + } }); } } @@ -416,7 +425,8 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - dataService.selectTasksInSameGroup(task); + var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; + dataCtrlScope.loadTasksByMoMGroupIdSelectAndJumpIntoView(task.mom_object_group_id); }); if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index a984d944720..7f4c7306f32 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -27,6 +27,7 @@ dElement.bind('contextmenu', function(event) { //TODO: remove link to dataService in this generic plugin var dataService = dScope.scope.dataService; + var dataCtrlScope = dScope.scope.$parent.$parent; var cleanupCtrl = dScope.scope.$parent.cleanupCtrl; var docElement = angular.element($document); @@ -74,7 +75,7 @@ ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - dataService.selectTasksInSameGroup(task); + dataCtrlScope.loadTasksByMoMGroupIdSelectAndJumpIntoView(task.mom_object_group_id); }); if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index d0065fe8eb6..56b5ccec688 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -266,6 +266,18 @@ def getTaskByMoMId(mom_id): return jsonify({'task': None}) +@app.route('/rest/tasks/mom/group/<int:mom_group_id>', methods=['GET']) +def getTasksByMoMGroupId(mom_group_id): + try: + mom_ids = momqueryrpc.getTaskIdsInGroup(mom_group_id)[str(mom_group_id)] + tasks = rarpc.getTasks(mom_ids=mom_ids) + + updateTaskMomDetails(tasks, momqueryrpc) + + return jsonify({'tasks': tasks}) + except Exception as e: + abort(404) + @app.route('/rest/tasks/<int:task_id>', methods=['PUT']) def putTask(task_id): if isProductionEnvironment(): -- GitLab From a99c97ba66cb9b42136129dbe9a4d44b5578190d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 19 Aug 2016 09:20:01 +0000 Subject: [PATCH 650/933] Task #8415: Use docker-run-slurm.sh script, to contain resources and propagate kill signals in SLURM --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- MAC/Services/src/PipelineControl.py | 2 +- RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index e8d8adae319..138835c5bb1 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -70,4 +70,4 @@ globalfs = yes # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker run --rm -u {uid} -v /data:/data --net=host {docker_env} lofar-pipeline:${LOFAR_TAG} {command} +cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker-run-slurm.sh --rm -u {uid} -v /data:/data --net=host {docker_env} lofar-pipeline:${LOFAR_TAG} {command} diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 7c780af556f..b79095bc2a2 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -493,7 +493,7 @@ runcmd {setStatus_active} wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} ACTIVE&host_regex=" # run the pipeline -runcmd docker run --rm --net=host \ +runcmd docker-run-slurm.sh --rm --net=host \ -e LOFARENV={lofarenv} \ -u $UID -e USER=$USER \ -e HOME=$HOME -v $HOME/.ssh:$HOME/.ssh:ro \ diff --git a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh index 77fef760786..2333b3230c1 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh @@ -344,7 +344,7 @@ if $DOCKER; then # "echo 950000 > /sys/fs/cgroup/cpu/cpu.rt_runtime_us" is needed to configure the real-time scheduler for this cgroup # --cpu-shares=24576 (=24*1024) provides this process with as much share of the CPU as 24 other containers (note: CEP4 has 24 cores/node) - OUTPUTPROC_CMDLINE="docker run --rm --cpu-shares=24576 --cap-add=sys_nice --cap-add=sys_admin -u `id -u $SSH_USER_NAME` --net=host -v $GLOBALFS_DIR:$GLOBALFS_DIR lofar-outputproc:$TAG bash -c \"sudo echo 950000 > /sys/fs/cgroup/cpu/cpu.rt_runtime_us; $OUTPUTPROC_CMDLINE\"" + OUTPUTPROC_CMDLINE="docker-run-slurm.sh --rm --cpu-shares=24576 --cap-add=sys_nice --cap-add=sys_admin -u `id -u $SSH_USER_NAME` --net=host -v $GLOBALFS_DIR:$GLOBALFS_DIR lofar-outputproc:$TAG bash -c \"sudo echo 950000 > /sys/fs/cgroup/cpu/cpu.rt_runtime_us; $OUTPUTPROC_CMDLINE\"" fi echo "[outputProc] command line = $OUTPUTPROC_CMDLINE" -- GitLab From f187882237582f40ba16f8ba85b0d3fd5b70de9c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Aug 2016 11:54:32 +0000 Subject: [PATCH 651/933] Task #9607: added unique function to Array prototype --- .../ResourceAssignmentEditor/lib/static/app/app.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js index d06132cd041..34baf77717e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js @@ -43,3 +43,17 @@ var secondsToHHmmss = function(seconds) { app.filter('secondsToHHmmss', function($filter) { return secondsToHHmmss; }) + +//filter unique items in array +Array.prototype.unique = function() { + var unique = []; + var length = this.length; + + for (var i = 0; i < length; i++) { + var item = this[i]; + if (unique.indexOf(item) == -1) { + unique.push(item); + } + } + return unique; +}; -- GitLab From 8df351c4bad182373d906ae3d0229ab5826b6286 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Aug 2016 11:54:57 +0000 Subject: [PATCH 652/933] Task #9607: automatically filter by group when all members of group are selected --- .../static/app/controllers/gridcontroller.js | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 4ad4e37b616..113bff315cb 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -269,8 +269,8 @@ $scope.columns = [ } else $scope.gridOptions.data = [] - $scope.$evalAsync(fillProjectsColumFilterSelectOptions) - $scope.$evalAsync(fillGroupsColumFilterSelectOptions); + fillProjectsColumFilterSelectOptions() + fillGroupsColumFilterSelectOptions(); }; function jumpToSelectedTaskRows() { @@ -290,6 +290,37 @@ $scope.columns = [ row.setSelected(selected_task_ids.indexOf(row.entity.id) != -1); } + //find out if we have selected all tasks in a single mom group + var selected_tasks = $scope.dataService.selected_task_ids.map(function(t_id) { return $scope.dataService.taskDict[t_id]; }).filter(function(t) { return t != undefined;}); + + if(selected_tasks && selected_tasks.length > 1) { + var selected_task_group_ids = selected_tasks.map(function(t) { return t.mom_object_group_id; }); + selected_task_group_ids = selected_task_group_ids.unique(); + + if(selected_task_group_ids.length == 1) { + //we have selected tasks in a single mom group + //find out if we have selected all tasks within this mom group + var mom_object_group_id = selected_task_group_ids[0]; + var all_group_tasks = $scope.dataService.tasks.filter(function(t) { return t.mom_object_group_id == mom_object_group_id; }); + + if(all_group_tasks.length == selected_tasks.length) { + //we have selected all tasks in a single mom group + //apply filter on group column to see only tasks within this group + var group_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_object_group_id'; }); + if(group_col) { + var mom_object_group_name = all_group_tasks[0].mom_object_group_name; + var label = mom_object_group_id + ' ' + mom_object_group_name; + + var groupSelectOptions = [ { value: mom_object_group_id, label: label} ]; + fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[7]); + + group_col.filters[0].term = mom_object_group_id; + } + } + } + } + + $scope.$evalAsync(jumpToSelectedTaskRows); }; @@ -326,7 +357,7 @@ $scope.columns = [ var tasks = $scope.dataService.filteredTasks; //get unique projectIds from tasks var task_project_ids = tasks.map(function(t) { return t.project_mom_id; }); - task_project_ids = task_project_ids.filter(function(value, index) { return task_project_ids.indexOf(value) == index;}) + task_project_ids = task_project_ids.unique(); for(var project_id of task_project_ids) { if(momProjectsDict.hasOwnProperty(project_id)) { -- GitLab From abea4af9af97c37b2c007bf55fedfb1ce4e3fccd Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 19 Aug 2016 12:37:56 +0000 Subject: [PATCH 653/933] Task #9764: Provide input filename differently to PyBDSM to allow it to derive a proper logfile location --- CEP/Pipeline/recipes/sip/nodes/imager_source_finding.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/nodes/imager_source_finding.py b/CEP/Pipeline/recipes/sip/nodes/imager_source_finding.py index 2d5cc1fe13b..464fb940c4a 100644 --- a/CEP/Pipeline/recipes/sip/nodes/imager_source_finding.py +++ b/CEP/Pipeline/recipes/sip/nodes/imager_source_finding.py @@ -97,6 +97,9 @@ class imager_source_finding(LOFARnodeTCP): pass #do nothing bdsm_parameters[key] = parameter_value + # pybdsm needs its filename here, to derive the log location + bdsm_parameters["filename"] = input_image_local + # ***************************************************************** # 3. Start pybdsm @@ -104,8 +107,7 @@ class imager_source_finding(LOFARnodeTCP): "Starting sourcefinder bdsm on {0} using parameters:".format( input_image_local)) self.logger.debug(repr(bdsm_parameters)) - img = bdsm.process_image(bdsm_parameters, - filename = input_image_local, frequency = frequency) + img = bdsm.process_image(bdsm_parameters, frequency = frequency) # Always export the catalog img.write_catalog( -- GitLab From bce1699f2b5cf61badb29f68f58dd3cf6ce697e0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Aug 2016 12:58:26 +0000 Subject: [PATCH 654/933] Task #9607: made unique function of Array prototype faster --- .../ResourceAssignmentEditor/lib/static/app/app.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js index 34baf77717e..ce05ea5a84b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js @@ -46,14 +46,12 @@ app.filter('secondsToHHmmss', function($filter) { //filter unique items in array Array.prototype.unique = function() { - var unique = []; + var unique = {}; var length = this.length; for (var i = 0; i < length; i++) { var item = this[i]; - if (unique.indexOf(item) == -1) { - unique.push(item); - } + unique[item] = true; } - return unique; + return Object.keys(unique); }; -- GitLab From acb5adb65ed44eb041d400970fd385d069dfda14 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Aug 2016 12:58:47 +0000 Subject: [PATCH 655/933] Task #9607: improved filling of filter combos --- .../static/app/controllers/gridcontroller.js | 64 +++++++++++++++---- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 113bff315cb..290e0ff68d4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -332,22 +332,21 @@ $scope.columns = [ }, true); $scope.$watch('dataService.filteredTaskChangeCntr', function() { - $scope.$evalAsync(fillProjectsColumFilterSelectOptions) - $scope.$evalAsync(fillGroupsColumFilterSelectOptions); + $scope.$evalAsync(function() { + fillStatusColumFilterSelectOptions(); + fillTypeColumFilterSelectOptions(); + fillProjectsColumFilterSelectOptions(); + fillGroupsColumFilterSelectOptions(); + }); }); $scope.$watch('dataService.initialLoadComplete', function() { $scope.$evalAsync(function() { - taskstatustypenames = $scope.dataService.taskstatustypes.map(function(x) { return x.name; }); - fillColumFilterSelectOptions(taskstatustypenames, $scope.columns[5]); - $scope.columns[6].editDropdownOptionsArray = $scope.dataService.taskstatustypes.map(function(x) { return {id:x.name, value:x.name}; }); - - tasktypenames = $scope.dataService.tasktypes.map(function(x) { return x.name; }); - fillColumFilterSelectOptions(tasktypenames, $scope.columns[6]); - - fillColumFilterSelectOptions(['CEP2', 'CEP4'], $scope.columns[11]); - + fillStatusColumFilterSelectOptions(); + fillTypeColumFilterSelectOptions(); fillProjectsColumFilterSelectOptions(); + fillGroupsColumFilterSelectOptions(); + fillColumFilterSelectOptions(['CEP2', 'CEP4'], $scope.columns[11]); }); }); @@ -355,6 +354,12 @@ $scope.columns = [ var projectNames = []; var momProjectsDict = $scope.dataService.momProjectsDict; var tasks = $scope.dataService.filteredTasks; + + var project_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'project_name'; }); + if(project_col && project_col.filters.length && project_col.filters[0].term) { + tasks = $scope.dataService.tasks; + } + //get unique projectIds from tasks var task_project_ids = tasks.map(function(t) { return t.project_mom_id; }); task_project_ids = task_project_ids.unique(); @@ -371,8 +376,45 @@ $scope.columns = [ fillColumFilterSelectOptions(projectNames, $scope.columns[1]); }; + function fillStatusColumFilterSelectOptions() { + var tasks = $scope.dataService.filteredTasks; + + var status_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'status'; }); + if(status_col && status_col.filters.length && status_col.filters[0].term) { + tasks = $scope.dataService.tasks; + } + + //get unique statuses from tasks + var task_statuses = tasks.map(function(t) { return t.status; }); + task_statuses = task_statuses.unique(); + + task_statuses.sort(); + fillColumFilterSelectOptions(task_statuses, $scope.columns[5]); + }; + + function fillTypeColumFilterSelectOptions() { + var tasks = $scope.dataService.filteredTasks; + + var type_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'type'; }); + if(type_col && type_col.filters.length && type_col.filters[0].term) { + tasks = $scope.dataService.tasks; + } + + //get unique types from tasks + var task_types = tasks.map(function(t) { return t.type; }); + task_types = task_types.unique(); + + task_types.sort(); + fillColumFilterSelectOptions(task_types, $scope.columns[6]); + }; + function fillGroupsColumFilterSelectOptions() { + if($scope.columns[7].filter.term) { + return; + } + var tasks = $scope.dataService.filteredTasks; + //get unique groupNames from tasks var groupId2Name = {}; var groupIds = []; -- GitLab From 0514823cd84b892a9d2a1b09355f6bf03a036f22 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Aug 2016 13:57:15 +0000 Subject: [PATCH 656/933] Task #9607: removed temporary hard limit of 23 nodes per job for vlad's pulsar pipelines. --- MAC/Services/src/PipelineControl.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index b79095bc2a2..851c0701aa3 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -152,18 +152,13 @@ class Parset(dict): """ Parse the number of nodes to allocate from "Observation.Cluster.ProcessingCluster.numberOfTasks", which can have either the format "{number}" or "{min}-{max}". """ - #JS 2016-07-19: set max nodes to 23, so we always have at least 50-2*23=4 nodes available for vlad pulp - - defaultValue = "12-23" + defaultValue = "12-25" parsetValue = self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfTasks"].strip() if "-" in parsetValue: # min,max _min, _max = parsetValue.split("-") - if _max > 23: - _max = 23 - # collapse if not min <= max if _min > _max: result = _min @@ -174,7 +169,7 @@ class Parset(dict): result = int(parsetValue) # apply bound - if result < 1 or result > 23: + if result < 1 or result > 50: result = defaultValue if result != parsetValue: -- GitLab From b2179791a4ffafee53564daa1a5014e8d68f33ae Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Aug 2016 14:33:18 +0000 Subject: [PATCH 657/933] Task #9607: minor fix in getTaskIdsInGroup --- SAS/MoM/MoMQueryService/momqueryservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index 83c518f0c08..eb719155679 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -240,7 +240,7 @@ class MoMDatabaseWrapper: query = '''SELECT mom2id, group_id FROM mom2object where group_id in (%s) - and (mom2objecttype = 'LOFAR_OBSERVATION' or mom2objecttype like \'%%PIPELINE\')''' % ids_str + and (mom2objecttype = 'LOFAR_OBSERVATION' or mom2objecttype like \'%%PIPELINE%%\')''' % ids_str rows = self._executeQuery(query) -- GitLab From 71a33ab2e7b2c34ce434943cb84829f28a9039e9 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 19 Aug 2016 14:38:16 +0000 Subject: [PATCH 658/933] Task #9607: update observation stop time on completing status --- .../OTDBtoRATaskStatusPropagator/propagator.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index f2c904549c4..07af4960e49 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -137,10 +137,13 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): def onObservationCompleting(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'completing') - def _updateStopTime(self, treeId, only_pipelines=False): + # otdb adjusts stoptime when aborted, + self._updateStopTime(treeId, ['observation']) + + def _updateStopTime(self, treeId, task_types=None): radb_task = self.radb.getTask(otdb_id=treeId) if radb_task: - if only_pipelines and radb_task['type'] != 'pipeline': + if task_types and radb_task['type'] not in task_types: return otdb_task = self.otdb.taskGetTreeInfo(otdb_id=treeId) @@ -155,13 +158,14 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): # otdb adjusts stoptime when finishing, # reflect that in radb for pipelines - self._updateStopTime(treeId, only_pipelines=True) + self._updateStopTime(treeId, ['pipeline']) def onObservationAborted(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'aborted') # otdb adjusts stoptime when aborted, - self._updateStopTime(treeId, only_pipelines=False) + self._updateStopTime(treeId) + def main(): # Check the invocation arguments -- GitLab From e36b94bcc7848f7bfdf7b9dacf1ba291911f2e0c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 22 Aug 2016 08:00:43 +0000 Subject: [PATCH 659/933] Task #9607: ssh -t -> ssh --- .../ResourceAssignmentEditor/lib/webservice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 56b5ccec688..ca90ee0ef90 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -652,9 +652,9 @@ def getTaskLogHtml(task_id): cmd = [] if task['type'] == 'pipeline': - cmd = ['ssh', '-t', 'lofarsys@head01.cep4.control.lofar', 'cat /data/log/pipeline-%s-*.log' % task['otdb_id']] + cmd = ['ssh', 'lofarsys@head01.cep4.control.lofar', 'cat /data/log/pipeline-%s-*.log' % task['otdb_id']] else: - cmd = ['ssh', '-t', 'mcu001.control.lofar', 'cat /opt/lofar/var/log/MCU001\\:ObservationControl\\[0\\]\\{%s\\}.log*' % task['otdb_id']] + cmd = ['ssh', 'mcu001.control.lofar', 'cat /opt/lofar/var/log/MCU001\\:ObservationControl\\[0\\]\\{%s\\}.log*' % task['otdb_id']] logger.info(' '.join(cmd)) -- GitLab From 5eb18cbf36c6a9144639d85346a6953c9cfbb232 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 22 Aug 2016 08:31:12 +0000 Subject: [PATCH 660/933] Task #9607: fixed broker test --- SAS/MoM/MoMQueryService/test/test_momqueryservice.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/SAS/MoM/MoMQueryService/test/test_momqueryservice.py b/SAS/MoM/MoMQueryService/test/test_momqueryservice.py index 77a4ec251ec..3d60a9a19ae 100755 --- a/SAS/MoM/MoMQueryService/test/test_momqueryservice.py +++ b/SAS/MoM/MoMQueryService/test/test_momqueryservice.py @@ -67,12 +67,6 @@ try: self.assertTrue('project_name' in result[testid]) self.assertTrue('project_description' in result[testid]) - def testSqlInjection(self): - inj_testid = testid + '; select * from lofar_mom3.mom2object;' - - with self.assertRaises(ValueError) as error: - result = momrpc.getProjectDetails(inj_testid) - unittest.main() finally: -- GitLab From d1098fee880731bc860ee6b93a04d326db7788a5 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 22 Aug 2016 08:41:26 +0000 Subject: [PATCH 661/933] Task #9607: fixed broker test --- .../ResourceAssigner/test/t_resourceassigner.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py b/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py index eec15481b8c..5c8c6209c4f 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py +++ b/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py @@ -55,6 +55,15 @@ with patch('lofar.sas.resourceassignment.resourceassignmentservice.rpc.RARPC', a # import ResourceAssigner now, so it will use the mocked classes and methods from lofar.sas.resourceassignment.resourceassigner.assignment import ResourceAssigner + try: + with ResourceAssigner() as assigner: + pass + except Exception as e: + if 'NotFound: no such queue' in e.message: + print e.message + print 'qpid environment not set up correctly for this test' + exit(3) + #define the test class class ResourceAssignerTest(unittest.TestCase): '''Test the logic in the ResourceAssigner''' -- GitLab From 8c35bf4d5c43c0f005a9f2e3ca003ddb91fd7f08 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 22 Aug 2016 08:57:04 +0000 Subject: [PATCH 662/933] Task #9607: fixed broker test --- MAC/Services/test/tPipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py index f4994db0295..40c6b2dfef6 100644 --- a/MAC/Services/test/tPipelineControl.py +++ b/MAC/Services/test/tPipelineControl.py @@ -105,7 +105,7 @@ class MockRAService(MessageHandlerInterface): 'endtime': datetime.datetime.utcnow(), } - def GetTasks(self, lower_bound, upper_bound, task_ids, task_status, task_type): + def GetTasks(self, lower_bound, upper_bound, task_ids, task_status, task_type, mom_ids=None, otdb_ids=None): print "***** GetTasks(%s) *****" % (task_ids,) if task_ids is None: -- GitLab From 96e34180ae01f550207b5384ff5aa1e93c4ac51c Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 12:46:45 +0000 Subject: [PATCH 663/933] Task #9127: COBALT: fix MPI incompat with 1.10. Remain compat w/ 1.6 (missing MPI Send const qual) --- .../Cobalt/InputProc/src/Transpose/MPIUtil.cc | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/RTCP/Cobalt/InputProc/src/Transpose/MPIUtil.cc b/RTCP/Cobalt/InputProc/src/Transpose/MPIUtil.cc index 30b90c677c1..9276d970891 100644 --- a/RTCP/Cobalt/InputProc/src/Transpose/MPIUtil.cc +++ b/RTCP/Cobalt/InputProc/src/Transpose/MPIUtil.cc @@ -126,44 +126,56 @@ namespace LOFAR { MPIAllocator mpiAllocator; - namespace { - typedef int (*MPI_SEND)(void *, int, MPI_Datatype, int, int, MPI_Comm, MPI_Request*); - - // Generic send method - MPI_Request Guarded_MPI_Send(MPI_SEND sendMethod, const void *ptr, size_t numBytes, int destRank, int tag) { - DEBUG("SEND: size " << numBytes << " tag " << hex << tag << dec << " to " << destRank); - - ASSERT(numBytes > 0); - ASSERT(tag >= 0); // Silly MPI requirement - - //SmartPtr<ScopedLock> sl = MPI_threadSafe() ? 0 : new ScopedLock(MPIMutex); - - MPI_Request request; - - int error; - - error = sendMethod(const_cast<void*>(ptr), numBytes, MPI_BYTE, destRank, tag, MPI_COMM_WORLD, &request); - ASSERT(error == MPI_SUCCESS); - - return request; - } - } - - MPI_Request Guarded_MPI_Issend(const void *ptr, size_t numBytes, int destRank, int tag) { - return Guarded_MPI_Send(::MPI_Issend, ptr, numBytes, destRank, tag); + DEBUG("SEND: size " << numBytes << " tag " << hex << tag << dec << " to " << destRank); + ASSERT(numBytes > 0); + ASSERT(tag >= 0); // Silly MPI requirement (Reason: MPI_ANY_TAG is -1, but only for receivers) + //SmartPtr<ScopedLock> sl = MPI_threadSafe() ? 0 : new ScopedLock(MPIMutex); + + MPI_Request request; + // const_cast is for missing const in protos in OpenMPI <=1.6 (1.10 is fixed, not sure in between) + int error = ::MPI_Issend(const_cast<void*>(ptr), numBytes, MPI_BYTE, destRank, tag, MPI_COMM_WORLD, &request); + ASSERT(error == MPI_SUCCESS); + return request; } MPI_Request Guarded_MPI_Irsend(const void *ptr, size_t numBytes, int destRank, int tag) { - return Guarded_MPI_Send(::MPI_Irsend, ptr, numBytes, destRank, tag); + DEBUG("SEND: size " << numBytes << " tag " << hex << tag << dec << " to " << destRank); + ASSERT(numBytes > 0); + ASSERT(tag >= 0); // Silly MPI requirement (Reason: MPI_ANY_TAG is -1, but only for receivers) + //SmartPtr<ScopedLock> sl = MPI_threadSafe() ? 0 : new ScopedLock(MPIMutex); + + MPI_Request request; + // const_cast is for missing const in protos in OpenMPI <=1.6 (1.10 is fixed, not sure in between) + int error = ::MPI_Irsend(const_cast<void*>(ptr), numBytes, MPI_BYTE, destRank, tag, MPI_COMM_WORLD, &request); + ASSERT(error == MPI_SUCCESS); + return request; } MPI_Request Guarded_MPI_Ibsend(const void *ptr, size_t numBytes, int destRank, int tag) { - return Guarded_MPI_Send(::MPI_Ibsend, ptr, numBytes, destRank, tag); + DEBUG("SEND: size " << numBytes << " tag " << hex << tag << dec << " to " << destRank); + ASSERT(numBytes > 0); + ASSERT(tag >= 0); // Silly MPI requirement (Reason: MPI_ANY_TAG is -1, but only for receivers) + //SmartPtr<ScopedLock> sl = MPI_threadSafe() ? 0 : new ScopedLock(MPIMutex); + + MPI_Request request; + // const_cast is for missing const in protos in OpenMPI <=1.6 (1.10 is fixed, not sure in between) + int error = ::MPI_Ibsend(const_cast<void*>(ptr), numBytes, MPI_BYTE, destRank, tag, MPI_COMM_WORLD, &request); + ASSERT(error == MPI_SUCCESS); + return request; } MPI_Request Guarded_MPI_Isend(const void *ptr, size_t numBytes, int destRank, int tag) { - return Guarded_MPI_Send(::MPI_Isend, ptr, numBytes, destRank, tag); + DEBUG("SEND: size " << numBytes << " tag " << hex << tag << dec << " to " << destRank); + ASSERT(numBytes > 0); + ASSERT(tag >= 0); // Silly MPI requirement (Reason: MPI_ANY_TAG is -1, but only for receivers) + //SmartPtr<ScopedLock> sl = MPI_threadSafe() ? 0 : new ScopedLock(MPIMutex); + + MPI_Request request; + // const_cast is for missing const in protos in OpenMPI <=1.6 (1.10 is fixed, not sure in between) + int error = ::MPI_Isend(const_cast<void*>(ptr), numBytes, MPI_BYTE, destRank, tag, MPI_COMM_WORLD, &request); + ASSERT(error == MPI_SUCCESS); + return request; } MPI_Request Guarded_MPI_Irecv(void *ptr, size_t numBytes, int srcRank, int tag) { -- GitLab From 4fdb9d36134ae99d970d4965daa41c144ccad361 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 22 Aug 2016 12:46:46 +0000 Subject: [PATCH 664/933] Task #9522: Added extra logging to trace outputProc startup slowdowns --- RTCP/Cobalt/OutputProc/src/GPUProcIO.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc index 1f50d347525..b28d52d12b4 100644 --- a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc +++ b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc @@ -169,6 +169,8 @@ bool process(Stream &controlStream) string logPrefix = str(format("[obs %u correlated stream %3u] ") % parset.settings.observationID % fileIdx); + LOG_INFO_STR("Setting up writer for " << file.location.filename); + SubbandWriter *writer = new SubbandWriter(parset, fileIdx, mdLogger, mdKeyPrefix, logPrefix); subbandWriters.push_back(writer); } @@ -192,6 +194,8 @@ bool process(Stream &controlStream) mdLogger.log(mdKeyPrefix + PN_COP_LOCUS_NODE + '[' + lexical_cast<string>(allFileIdx) + ']', formatDataPointLocusName(myHostName)); + LOG_INFO_STR("Allocating transpose buffers for " << file.location.filename); + struct ObservationSettings::BeamFormer::StokesSettings &stokes = file.coherent ? parset.settings.beamFormer.coherentSettings : parset.settings.beamFormer.incoherentSettings; @@ -217,11 +221,15 @@ bool process(Stream &controlStream) string logPrefix = str(format("[obs %u beamformed stream %3u] ") % parset.settings.observationID % fileIdx); + LOG_INFO_STR("Setting up writer for " << file.location.filename); + TABOutputThread *writer = new TABOutputThread(parset, fileIdx, *outputPools[fileIdx], mdLogger, mdKeyPrefix, logPrefix); tabWriters.push_back(writer); } } + LOG_INFO_STR("Finished setting up writers"); + /* * PROCESS */ -- GitLab From 8bf4a2ce2e6a8dacabf0c98527da0ffd642b57a1 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 12:47:35 +0000 Subject: [PATCH 665/933] Task #9127: remove Visual Studio proj files. Only user Wouter Klijn left a year ago. --- .gitattributes | 24 - .../CoInterface/CoInterface.vcxproj | 142 --- .../CoInterface/CoInterface.vcxproj.filters | 175 --- .../CoInterface/CoInterface.vcxproj.user | 3 - RTCP/Cobalt/VisualStudio/Cobalt.sln | 44 - RTCP/Cobalt/VisualStudio/Cobalt.suo | Bin 194560 -> 0 bytes RTCP/Cobalt/VisualStudio/Cobalt.v12.suo | Bin 544256 -> 0 bytes RTCP/Cobalt/VisualStudio/Cobalt.vcxproj | 81 -- .../VisualStudio/Cobalt.vcxproj.filters | 17 - RTCP/Cobalt/VisualStudio/Cobalt.vcxproj.user | 3 - .../VisualStudio/GPUProc/GPUProc.vcxproj | 422 ------- .../GPUProc/GPUProc.vcxproj.filters | 1042 ----------------- .../VisualStudio/GPUProc/GPUProc.vcxproj.user | 3 - .../VisualStudio/InputProc/InputProc.vcxproj | 161 --- .../InputProc/InputProc.vcxproj.filters | 247 ---- .../InputProc/InputProc.vcxproj.user | 3 - .../OutputProc/OutputProc.vcxproj | 125 -- .../OutputProc/OutputProc.vcxproj.filters | 129 -- .../OutputProc/OutputProc.vcxproj.user | 3 - .../symbolic_links/create_links.bat | 5 - .../t_cuda_complex/t_cuda_complex.vcxproj | 91 -- .../t_cuda_complex.vcxproj.user | 3 - RTCP/Cobalt/VisualStudio/test/test.vcxproj | 87 -- .../VisualStudio/test/test.vcxproj.filters | 10 - .../VisualStudio/test/test.vcxproj.user | 3 - 25 files changed, 2823 deletions(-) delete mode 100644 RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj delete mode 100644 RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj.filters delete mode 100644 RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj.user delete mode 100644 RTCP/Cobalt/VisualStudio/Cobalt.sln delete mode 100644 RTCP/Cobalt/VisualStudio/Cobalt.suo delete mode 100644 RTCP/Cobalt/VisualStudio/Cobalt.v12.suo delete mode 100644 RTCP/Cobalt/VisualStudio/Cobalt.vcxproj delete mode 100644 RTCP/Cobalt/VisualStudio/Cobalt.vcxproj.filters delete mode 100644 RTCP/Cobalt/VisualStudio/Cobalt.vcxproj.user delete mode 100644 RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj delete mode 100644 RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj.filters delete mode 100644 RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj.user delete mode 100644 RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj delete mode 100644 RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj.filters delete mode 100644 RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj.user delete mode 100644 RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj delete mode 100644 RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj.filters delete mode 100644 RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj.user delete mode 100644 RTCP/Cobalt/VisualStudio/symbolic_links/create_links.bat delete mode 100644 RTCP/Cobalt/VisualStudio/t_cuda_complex/t_cuda_complex.vcxproj delete mode 100644 RTCP/Cobalt/VisualStudio/t_cuda_complex/t_cuda_complex.vcxproj.user delete mode 100644 RTCP/Cobalt/VisualStudio/test/test.vcxproj delete mode 100644 RTCP/Cobalt/VisualStudio/test/test.vcxproj.filters delete mode 100644 RTCP/Cobalt/VisualStudio/test/test.vcxproj.user diff --git a/.gitattributes b/.gitattributes index f3226ca8f06..4376ab56ee0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4634,30 +4634,6 @@ RTCP/Cobalt/OutputProc/test/tMSWriterCorrelated_.run.in eol=lf RTCP/Cobalt/OutputProc/test/tMeasurementSetFormat.parset-j2000 -text RTCP/Cobalt/OutputProc/test/tMeasurementSetFormat.parset-sun -text RTCP/Cobalt/OutputProc/test/tTBB_StaticMapping.in_1/TBBConnections.dat -text -RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj -text -RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj.filters -text -RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj.user -text -RTCP/Cobalt/VisualStudio/Cobalt.sln -text -RTCP/Cobalt/VisualStudio/Cobalt.suo -text -RTCP/Cobalt/VisualStudio/Cobalt.v12.suo -text -RTCP/Cobalt/VisualStudio/Cobalt.vcxproj -text -RTCP/Cobalt/VisualStudio/Cobalt.vcxproj.filters -text -RTCP/Cobalt/VisualStudio/Cobalt.vcxproj.user -text -RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj -text -RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj.filters -text -RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj.user -text -RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj -text -RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj.filters -text -RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj.user -text -RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj -text -RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj.filters -text -RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj.user -text -RTCP/Cobalt/VisualStudio/symbolic_links/create_links.bat -text -RTCP/Cobalt/VisualStudio/t_cuda_complex/t_cuda_complex.vcxproj -text -RTCP/Cobalt/VisualStudio/t_cuda_complex/t_cuda_complex.vcxproj.user -text -RTCP/Cobalt/VisualStudio/test/test.vcxproj -text -RTCP/Cobalt/VisualStudio/test/test.vcxproj.filters -text -RTCP/Cobalt/VisualStudio/test/test.vcxproj.user -text RTCP/Cobalt/clAmdFft/appmlEnv.sh -text RTCP/Cobalt/clAmdFft/bin32/clAmdFft.Client -text RTCP/Cobalt/clAmdFft/bin32/clAmdFft.Client-1.8.291 -text diff --git a/RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj b/RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj deleted file mode 100644 index f017fd5a9b3..00000000000 --- a/RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj +++ /dev/null @@ -1,142 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|Win32"> - <Configuration>Debug</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|Win32"> - <Configuration>Release</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - </ItemGroup> - <ItemGroup> - <ClInclude Include="..\..\CoInterface\src\Align.h" /> - <ClInclude Include="..\..\CoInterface\src\Allocator.h" /> - <ClInclude Include="..\..\CoInterface\src\BeamCoordinates.h" /> - <ClInclude Include="..\..\CoInterface\src\BeamFormedData.h" /> - <ClInclude Include="..\..\CoInterface\src\BestEffortQueue.h" /> - <ClInclude Include="..\..\CoInterface\src\BlockID.h" /> - <ClInclude Include="..\..\CoInterface\src\Config.h" /> - <ClInclude Include="..\..\CoInterface\src\CorrelatedData.h" /> - <ClInclude Include="..\..\CoInterface\src\DataFactory.h" /> - <ClInclude Include="..\..\CoInterface\src\Exceptions.h" /> - <ClInclude Include="..\..\CoInterface\src\FinalMetaData.h" /> - <ClInclude Include="..\..\CoInterface\src\InverseFilteredData.h" /> - <ClInclude Include="..\..\CoInterface\src\MultiDimArray.h" /> - <ClInclude Include="..\..\CoInterface\src\OutputTypes.h" /> - <ClInclude Include="..\..\CoInterface\src\Parset.h" /> - <ClInclude Include="..\..\CoInterface\src\Poll.h" /> - <ClInclude Include="..\..\CoInterface\src\Pool.h" /> - <ClInclude Include="..\..\CoInterface\src\PrintVector.h" /> - <ClInclude Include="..\..\CoInterface\src\RingCoordinates.h" /> - <ClInclude Include="..\..\CoInterface\src\SetOperations.h" /> - <ClInclude Include="..\..\CoInterface\src\SlidingPointer.h" /> - <ClInclude Include="..\..\CoInterface\src\SmartPtr.h" /> - <ClInclude Include="..\..\CoInterface\src\SparseSet.h" /> - <ClInclude Include="..\..\CoInterface\src\Stream.h" /> - <ClInclude Include="..\..\CoInterface\src\StreamableData.h" /> - <ClInclude Include="..\..\CoInterface\src\SubbandMetaData.h" /> - <ClInclude Include="..\..\CoInterface\src\TABTranspose.h" /> - <ClInclude Include="..\..\CoInterface\src\TriggerData.h" /> - </ItemGroup> - <ItemGroup> - <ClCompile Include="..\..\CoInterface\src\Allocator.cc" /> - <ClCompile Include="..\..\CoInterface\src\BeamCoordinates.cc" /> - <ClCompile Include="..\..\CoInterface\src\BlockID.cc" /> - <ClCompile Include="..\..\CoInterface\src\CorrelatedData.cc" /> - <ClCompile Include="..\..\CoInterface\src\DataFactory.cc" /> - <ClCompile Include="..\..\CoInterface\src\FinalMetaData.cc" /> - <ClCompile Include="..\..\CoInterface\src\Parset.cc" /> - <ClCompile Include="..\..\CoInterface\src\RingCoordinates.cc" /> - <ClCompile Include="..\..\CoInterface\src\Stream.cc" /> - <ClCompile Include="..\..\CoInterface\src\TABTranspose.cc" /> - <ClCompile Include="..\..\CoInterface\test\tBestEffortQueue.cc" /> - <ClCompile Include="..\..\CoInterface\test\tCorrelatedData.cc" /> - <ClCompile Include="..\..\CoInterface\test\tMultiDimArray.cc" /> - <ClCompile Include="..\..\CoInterface\test\tParset.cc" /> - <ClCompile Include="..\..\CoInterface\test\tpow2.cc" /> - <ClCompile Include="..\..\CoInterface\test\tRingCoordinates.cc" /> - <ClCompile Include="..\..\CoInterface\test\tSparseSet.cc" /> - <ClCompile Include="..\..\CoInterface\test\tTABTranspose.cc" /> - </ItemGroup> - <ItemGroup> - <None Include="..\..\CoInterface\src\BestEffortQueue.tcc" /> - <None Include="..\..\CoInterface\test\tParset.parset_obs99275" /> - <None Include="..\..\CoInterface\test\tParset.sh" /> - <None Include="..\..\CoInterface\test\tRingCoordinates.py" /> - </ItemGroup> - <ItemGroup> - <Text Include="..\..\CoInterface\src\CMakeLists.txt" /> - <Text Include="..\..\CoInterface\test\CMakeLists.txt" /> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{0C6B2AFD-61F8-4E27-BDB8-FECDA6DC04CE}</ProjectGuid> - <Keyword>Win32Proj</Keyword> - <RootNamespace>CoInterface</RootNamespace> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>v120</PlatformToolset> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>v120</PlatformToolset> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Label="ExtensionSettings"> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <LinkIncremental>true</LinkIncremental> - <IncludePath>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include;..\..\..\static_fixture;..\..\;..\symbolic_links;C:\Program Files (x86)\boost\boost_1_41</IncludePath> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <LinkIncremental>false</LinkIncremental> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <ClCompile> - <PrecompiledHeader> - </PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <PrecompiledHeader> - </PrecompiledHeader> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - </Link> - </ItemDefinitionGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> - <ImportGroup Label="ExtensionTargets"> - </ImportGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj.filters b/RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj.filters deleted file mode 100644 index 860a9e32347..00000000000 --- a/RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj.filters +++ /dev/null @@ -1,175 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup> - <Filter Include="src"> - <UniqueIdentifier>{3503dac2-4fb3-4419-97c0-825dd34791f4}</UniqueIdentifier> - </Filter> - <Filter Include="test"> - <UniqueIdentifier>{20382d69-318e-4746-bd79-98c7cb7b6c7f}</UniqueIdentifier> - </Filter> - </ItemGroup> - <ItemGroup> - <ClInclude Include="..\..\CoInterface\src\Poll.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\Pool.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\PrintVector.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\SetOperations.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\SlidingPointer.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\SmartPtr.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\SparseSet.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\Stream.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\StreamableData.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\SubbandMetaData.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\TABTranspose.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\TriggerData.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\Align.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\Allocator.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\BeamCoordinates.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\BeamFormedData.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\BestEffortQueue.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\BlockID.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\Config.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\CorrelatedData.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\DataFactory.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\Exceptions.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\FinalMetaData.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\InverseFilteredData.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\MultiDimArray.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\OutputTypes.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\Parset.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\CoInterface\src\RingCoordinates.h"> - <Filter>src</Filter> - </ClInclude> - </ItemGroup> - <ItemGroup> - <ClCompile Include="..\..\CoInterface\src\Stream.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\src\TABTranspose.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\src\Allocator.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\src\BeamCoordinates.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\src\BlockID.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\src\CorrelatedData.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\src\DataFactory.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\src\FinalMetaData.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\src\Parset.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\src\RingCoordinates.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\test\tParset.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\test\tpow2.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\test\tRingCoordinates.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\test\tSparseSet.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\test\tTABTranspose.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\test\tBestEffortQueue.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\test\tCorrelatedData.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\CoInterface\test\tMultiDimArray.cc"> - <Filter>test</Filter> - </ClCompile> - </ItemGroup> - <ItemGroup> - <None Include="..\..\CoInterface\src\BestEffortQueue.tcc"> - <Filter>src</Filter> - </None> - <None Include="..\..\CoInterface\test\tParset.parset_obs99275"> - <Filter>test</Filter> - </None> - <None Include="..\..\CoInterface\test\tParset.sh"> - <Filter>test</Filter> - </None> - <None Include="..\..\CoInterface\test\tRingCoordinates.py"> - <Filter>test</Filter> - </None> - </ItemGroup> - <ItemGroup> - <Text Include="..\..\CoInterface\src\CMakeLists.txt"> - <Filter>src</Filter> - </Text> - <Text Include="..\..\CoInterface\test\CMakeLists.txt"> - <Filter>test</Filter> - </Text> - </ItemGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj.user b/RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj.user deleted file mode 100644 index 695b5c78b91..00000000000 --- a/RTCP/Cobalt/VisualStudio/CoInterface/CoInterface.vcxproj.user +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/Cobalt.sln b/RTCP/Cobalt/VisualStudio/Cobalt.sln deleted file mode 100644 index d4c8e81a04d..00000000000 --- a/RTCP/Cobalt/VisualStudio/Cobalt.sln +++ /dev/null @@ -1,44 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CoInterface", "CoInterface\CoInterface.vcxproj", "{0C6B2AFD-61F8-4E27-BDB8-FECDA6DC04CE}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GPUProc", "GPUProc\GPUProc.vcxproj", "{93C5C054-7BF7-478E-A8B5-E0829597AEEA}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InputProc", "InputProc\InputProc.vcxproj", "{075D5E72-BA47-4D6E-B1E1-DBCF747CA33D}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OutputProc", "OutputProc\OutputProc.vcxproj", "{AC4EED56-6048-4D80-8350-F5A753238CC3}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{1521604F-7418-4628-932D-40ED94A1E68E}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0C6B2AFD-61F8-4E27-BDB8-FECDA6DC04CE}.Debug|Win32.ActiveCfg = Debug|Win32 - {0C6B2AFD-61F8-4E27-BDB8-FECDA6DC04CE}.Debug|Win32.Build.0 = Debug|Win32 - {0C6B2AFD-61F8-4E27-BDB8-FECDA6DC04CE}.Release|Win32.ActiveCfg = Release|Win32 - {0C6B2AFD-61F8-4E27-BDB8-FECDA6DC04CE}.Release|Win32.Build.0 = Release|Win32 - {93C5C054-7BF7-478E-A8B5-E0829597AEEA}.Debug|Win32.ActiveCfg = Debug|Win32 - {93C5C054-7BF7-478E-A8B5-E0829597AEEA}.Debug|Win32.Build.0 = Debug|Win32 - {93C5C054-7BF7-478E-A8B5-E0829597AEEA}.Release|Win32.ActiveCfg = Release|Win32 - {93C5C054-7BF7-478E-A8B5-E0829597AEEA}.Release|Win32.Build.0 = Release|Win32 - {075D5E72-BA47-4D6E-B1E1-DBCF747CA33D}.Debug|Win32.ActiveCfg = Debug|Win32 - {075D5E72-BA47-4D6E-B1E1-DBCF747CA33D}.Debug|Win32.Build.0 = Debug|Win32 - {075D5E72-BA47-4D6E-B1E1-DBCF747CA33D}.Release|Win32.ActiveCfg = Release|Win32 - {075D5E72-BA47-4D6E-B1E1-DBCF747CA33D}.Release|Win32.Build.0 = Release|Win32 - {AC4EED56-6048-4D80-8350-F5A753238CC3}.Debug|Win32.ActiveCfg = Debug|Win32 - {AC4EED56-6048-4D80-8350-F5A753238CC3}.Debug|Win32.Build.0 = Debug|Win32 - {AC4EED56-6048-4D80-8350-F5A753238CC3}.Release|Win32.ActiveCfg = Release|Win32 - {AC4EED56-6048-4D80-8350-F5A753238CC3}.Release|Win32.Build.0 = Release|Win32 - {1521604F-7418-4628-932D-40ED94A1E68E}.Debug|Win32.ActiveCfg = Debug|Win32 - {1521604F-7418-4628-932D-40ED94A1E68E}.Debug|Win32.Build.0 = Debug|Win32 - {1521604F-7418-4628-932D-40ED94A1E68E}.Release|Win32.ActiveCfg = Release|Win32 - {1521604F-7418-4628-932D-40ED94A1E68E}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/RTCP/Cobalt/VisualStudio/Cobalt.suo b/RTCP/Cobalt/VisualStudio/Cobalt.suo deleted file mode 100644 index 299320f10e8dd2d392b13f4be78f7b94d1c29f3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194560 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;>-*T42&?o00RRP0|Ns$0|Udq|NsBPgc}$b z7``(wF#I0{Lm~wJ{{R1<g@J)Vnt_3Vm4ShQoq+)y=bQ`-3>?t-<zir9;9+23;ALQ7 z;Adc9;A3E55MW?n5M*FrkYZq95Mp3p5N2Rt5Mf|o5M^Ls5My9q5NBXukYHe7kYr$B zkYQk8P-b9YFl1n0kY!+CkYiwAkY`|EP+(wSP-I|WP-0+UP+?$TP-9?VP-S3XP-kFZ z&}3j>&|qL-&|+X<&|zR;&}Lv@&}Cp?&}U#^&|_d=FkoO{FlJz2Fk)a}$YEe$FkxU| zux4OjFlAt1Fk@g~FlS(3uwh_euwr0faAII!uw`Ii2w`Ag@L^zJuw!6gaDb-U1O^5M z7X}6fX9flaM+OE4dj<vuHwFd<cLoLq4+aJXPX-1CF9rq%Zw3a2I0gm=KL!Q{e+CAI z00stzKn4beAO;48L<R<iBnAcsO9lppPzDADUj_z-a0Uj32nGg*NCpOmC<X?GXa)v` z7zPH0SOx}$X$%Yu@eB+M77Ppwtqcqd$qWn(DGUq@sSFGZX$%Yu84L^z=?n}EnG6gJ z*$fN}SquyexeN>p`3wvU!3+!xc?=8;1q=)fMGOoKg$xV~#S9D#r3?%VB@7G<Wef}q z6$}gv<qQlAl?)6F)eH;_RSXOaH4F?4wG0dlbqov)jSLJ7^$ZLQO<?!uGvqUrFeos% zGUPFoFcg7x!E!TUT8x1ep4UNX0iTY7>H9TK+&;$$Dn}Z;ASFovLlHwhLl%Q4LmopK zLq3Bm*!B2KL6s9@V1nBZ%5bQ%xOt%T3z7|CNMtBx$Y$_i$YdyHC}9X?C}v1yC}Mz# zgTfZ14i{EoU}6O21CZSyj7uG|ILPOqyaf^qVaQ}CU<d%)SqybUDnlMaGD9kZ3qvYH z8G|8%0XX%8RAXW-GW-uJw_yb~sO$z|P@I6u0Z<tL;)5{Aji7R15)-71hm`@K{0}Mz zKw${-J97R9=>z3|5Dmhh{0~YOpz;M&4&ciFpgapI13>v7qz_aEfbu`68~~L8p!^TY zkDxpU%KxBz08$6Sp!@|Y3qWN6h!4V`{13`spgajG13+a1s5}6b1EBm5%IBam0F>uJ z<pU`HgUSI{r2IdF2{r$N$_`N8MlJ(*7$IfA8Wu?T0?Pkk(ERVg49WkXd=JY1Ees3{ zi=pKRDF1`X9*|o>We2Pb0P#U(04V>1$^j4`l>b5H0H~Y;@j>|?R2G2B01zLPk3s1a zl>b5H07wj!|3T#ds0;w{LFE9f3;^*#`5#nQ8@X<|T#+R1wl2gp+2_htP|=5($nc1K za0aCiJPHU&fieRq<A7+8S{_j9j0-DvNlhwE4=744PR%PxEXmBz3rI}PPE1eLQ3y*d zD$dN$vo$o(14A7J=hB>#(xOz`ywuW?qQo2>g@DqeoXlkJ)XI?j?9@ElBm?6#(=_um zL&FqPgGA#*cCck&f`Ng7rPMbwxhTIlKdnSBEVH;YF(<gBG$k`%5A1~W)FL<|BtJhV zJTosPzr0w_C9xziB(tDcFCa57FEJ@670hw1C`rvL&P>Y8$t<bV3jn#axFj_%IaS*y z6U0a@0_h7b$uCL;`I8&bo&x!u3=HzbDsUZU#SjB8y^0xP7_u317%~~M81fim7@QgM z8B!Qh!K@sHQgBHJD&=CpWlR!7DMLC#I=Bo9XGmlyVMu1kU`PemD}<b+z`z8q3qkci zC>jZ=2g!rll%Tc(hz%;^knMM3C}K!uNMy)nC}7BE$OP9(ptKEAiw$cqFfoGaV^AI- z)_zbMhX+*9fx|780oi^~{R1j{3m8fmG8yt2@(6^28Uqs}$X<|{AR3z+(ZxXRDNxx8 z5=ZyH8`#e&48aWf45bW3;CcY04i|>m4>FHf`$27XKTz@mw<p3F{1_A%iWy27k{D7L zG8u{(K&c0mVv-mV8FCm(7<A#ap)=S`pcn$R6=E2I7(y7F83MrRDxU$SK87I-T$7c8 z^#?PQFqDE#&Ih-EKo)~)4^YZwWB}DUxco+nILI7OdFIRj$_J2EMLI(fLn1>ig8~C6 zRCB;7T7f}>p@PAJ!HhwZA%?-1A(J5)+yW_P$Y)3cw}X(~uE2oodIfM@tjA!$0P>+T zLkw7N2}2A+F*v3`<$NEwElE!|fbwe&LoP!aLo$*}V0;FK!Qvu_I|oA;fl8HR22eQ& zDHBOaXV}z&QZ%|aC1U7i;p2mH5k7fBa=6Q%N`_p9B!+x&?+UjmYGkxOLG2HiiRkwG zGNdx(GUS8%A>q)nFrT3uXDO`1z{H@z2<ej&TmFIisGz<e$PMWBW3PXZT>{dDz$Oe# z;Jz`aOeNNSP~R9-UV}^l)i)*Jnl}$zS|Um+NEw#JkjjwEPy(*OL1iweZ%~318dlKy z2V^%0gUlp^53w*Xv_TW73qw9bGD9gtE(645WS<5w<THT!B=~AV0|q8W(6|67Oo_EW zosofIC#WL;u77dc59^D$fZHUXUN)xB^ca{JK`l^_-5`vq0#gJz{ekLPP)PvuBdFd3 z^+Yon(!q5dsMLbA12D}2iRh3K{-ALY4v?e4V+lzNSq#1mpt=fTHz+lLV!D_i3_L;r zvIRF*Vqjtf)qNnpgD`HD2pP~=45+;X5&`w!d>Kl?^?W9{wo7Cv0@v7}UTG1yZw?wg z0rd|-BP^gkRyBhGgENB}gA;=hgCm0*gA0Q$gBgP%gBybdgD!&!gDZm(gE@mPgA;=b zgA-WHjlq?{nZbp@k-?0?g~6G@fWd^pnZcEzmO+mpg`phHUVjEq>kHXdP<sJX6Jxp! zk~=_ZIl$vb)O4FQcr=KbCh9Q+F{CnpS|XrPhg60d21tH0W-wyVV@P9g0;hCP>)VMT z2RtTZ%K!^`P|uaRZb6iNq__i;0xTJf8JrnR!EtHIV8WowV9wyg;0BI&69#hz3kFvP zT?R)63kD|!QwCiIR|W$H3kD+wO9oR0O9pcWM+R30R|ZF-Qvj&HUd@1MYcPWf_g_ct z-h&<+Z>>5nyfmEOhk@^;RL&nxW&a8P+kc!tv;@?pXYgToE~0vAy|S|Vwx<tk)su2T z-T=3MEz(2|{W~P*vt(|{y6JN7ElJbQz(A~BJ`6=Cy&N@XWO=W+zMwJa-wsf>jUiBh zf#C&%3%Jz^EAK$<Z{m7y!3>nQ-gCesV4%__lOdI%9A`}fDlIUv3Ih{(>=;z15nKKs zm%p%D4pa{oFyw%1eo)U8lG{OLA*P+kBB1d%&{z>@oQYWbLF3FxpiU@wyfzPPYa+OR zoWcNe52Red*Ykw9LW#omj|W30LkigLJceL~9ELmwH*lK^)G`J2#2~idCPC#t$loxG zTO~pUIs8fMy@6^d&`2#PUWypf7!tw#Q^@!NrnnwM8ACEd1w#S2hXoqNg0xm|3}|{s z?X5C0FmV0V%I5izsP4CS$G@}7?y#JLg;dCaqm#c^_qngw`k-m*^mVUb;ze<>?C!hr zd^bM4eIDc^&`<(s%#NC+vn|rd407oVnIS-wgenY944@JGY=&G0Q0<t_P|Tpf5XNA@ zV8CF=0P3rPW?ew@3)n_~g26RTGD9}F{Q(+r$YV%h@L|Yj0ObdK?JihYfZG3{F;-A^ z0;NHiI6fLQpK}DN05!jbFcd*Yr$DI*G#^j|_7|*m2GxW_f!d!Sdtn$!3MUge{f99) zGbo_tdYl@uNQf~oF@n+nDfWZrv815wZ-0hTh7#~dT^_i%lMZbQfpVA&cr*r!rAR`c z@(*S=h(?ma$plr1Nmn7#=&6U;au=c-G`C0k{0nFf4>Yz9G6Ocx0-JvcX2<~70iZER zP{<G}fkE{T$lovwGLsMn%^kw}U--fwR3|}l4{j4d;SZVz0o5rWH{e!@kO9qE?t^l} z7>XG}z&SVv+}o~T2nP2C%fKU3pcz4^LL3yR{NIQ)-;P5WiZE!76x3$`2}Ya;jYfjz z8S$4NAhp==14hc)E;Gp151OkiWyoX5X23lTiL1VXjn_Fd_=3lpQ{nS}#SD53B@7h| zB@8;!%ya%p3e76t_ezUx`E$@@F3L<Jv9XOU)DU9ix0)&KNkH1tAXADNT*2eBB@B8D z`Ov->!iD%*5)4d?p!q&fpO)AVK`ws@<v&nf!RG={{sY+y!}!#~<UsT0u=<CP{h)b8 zm_BSY$bQgJ253D4$PL)kpo@X#=)XV(3E2<It5Bs_DUkiJbri(f4_Xg!1!@{~dT^BV zBn=qM8B7^m7)%*l8O#}sz~csv44^(FXe_~n!3^A!bYd`MaAh!L&;^evID>ndAT`bm zjts^O#tbeb_9UrkD<}_9)~Bal2v8CejttJ=(HmEAY?y&ZbwDFEkWn2M1`7rQ23-aV z24e<OFw2d>l)(`kd!X?PV+IQbX9i~mV-jP6nzm9J6V!AIB{5;hV9H>`V8~zw9w~DJ zkFJ<77=p*rOc=}<jKC~V32p=)eKTP&U;xFMC4&irBRHp+fk$8nkFSAduaIZFK<z)o zY@Itp07EFWbqtzaLgnjW?cWWi-W_#)2P!go7>XG(z&$Nk+l&J``v;N*t%ZZNEkL8{ zDd4qXAQ>g7D$txED0~wcVi>%^ExkPOh#;(;tA<5g0C<fMXxtDqbA_0f*TJGT7(9;) znu!JV<RHDPWcYl61Y8Fwp3%p)xfnpb4$vwnkc&a<`Cxp6t06KB3=g1v_>!8%pn*xS zaM)YFH695KzRdw&mj^5N90m)5#<&v{MI}l!+$QTfyq&V}8E8ThEcLMD-y-Mj7X;Ty zF)TRA{~xX%EQv(m>W71rI5QY9P&WQZsHaW6nu3r`)GK`m*@P%xKz&!x`b#ettC-N@ z)S}{;?3~Q3ycp;Fl++mKoYLZw)S{T+Fh7Oj(xjBkqT(3m{G`O365Wu*;%pORQ_C3l zfY5-V{N$M8qU0Di&mg^I&<Z35Hk-WCoE*?XE700TJZ2NS^1uXK9$JFi>v;^2l?9-= z!Xogxl^6!pk_j;*9}ezKW-|mblrp4(F=%uZ)GsIkuQb8wHxmQ1n4l2n0H}vi{Fz^n znwOjt<D6eqG>7c|3vpw&CE(H7YzC13%^8dtaQhU!l+a_yV7R;3U;F6dIO9h@)-vk1 zonHZ)r3AGUVVKYgA^h$<F=+fxD5gQ<+$G@DfT<D`R+sY{+iQIoh1Jg;D2?X7dJt5) zz`_Vdqt=F?lK$AB@jrU}qo#9&@6pxiF(fl2Gc>9lNP4^ewg2ym&<lAiP2WIkJYl|u z(V$s*<eUTwwJn3j|Cq4~3IR+}kgNOaZ!h#^jNx&55hg2B*Zdqbf(!E{j3#b2X5OIj zKlXSn6nr<U?@a{H@8E-L_nC3zgJx1;K7`Src?M942`XzqBdy5{@eFAUncz_n&>A$* z$Z;qGXneASA%p?c{wQJ4X3z$&`Z`6S|7jh5pm8+N+BVQiHc-n3)b;|+=i@F%VQbnT z`NA?Y?5`19e)Qel$5ww;>YK3(X{-$velbp|iMek1MY*X(0htA<IhlE>dKon})h-6E zhK{DrX1Y$srk1)Ut|o@MmQH2{y2i$4rj|xV&PI+#M&~FDKSK2<>HbGgN1$AUs4oeX zBR68Nw#SygnNaa|(ThUgc4N@`0$4b~Xi&)t!|Mi}|3T{kK`T%}ZC^<HIUTx=5L9xU zZYY(XQhsTo{S@ndmezpzpxJqtzhE@XZ^Y0W2H5|E#-E_m<T$+o8h?WM8${z&h$I2p zgK`bZCuBcp9Y0hlRtjW4sQn6B(?YEM6Brm6E<sHrWIt$M0#qqh3S>XXUKqxz2u*M` z0|NsSsNoIj8{wJ%0nI;v_AsDnN8yrVe>Z4P7Sw*wULeqTY%>D`!xZR#BoM!yfq|ie zfq|iufq|h5x>u@)fq|iy0kWdBA1XGHfq`KX0|UckC?BL3w7+RO0|UbhsMst928P*C z_FM)AhItGO4D+G<g$xV~i=gZ!3=9mQMG?yw7#Nl_FfgoOU|?7YmD|9;z_5mafnhBJ z1H(E728Q)eanRhyCI$wEV+;%oTNoG^K%K~K3=9n085kILFfcIegsR)kz`(GFfq`K! z0|NtS?giw|0}Kodpl047s2b3i@KGrHIA~)4BLl-p1_p*x3=9mXq2gy57#Khk#OI;> zi_o+LG7mJS3Yt~}v9B{QFx+5ZV7LiYcbkEM;SQ92kAZ>VJ_D!^4cS-oh=GCOF#`j` z6R7+%1_p-b44`!(5P8tV^J@kMhBr|8cMJ>+@1g9E3=9mPplr~X@>d21hHp^*4+aK? zp9~BPzZe)8enZ9nf@n}32BQD}{|_nQKw~?g)+~s|SN?%oinY0UO+6`&*}3lje<hSy zm0)W-p(Ihf976RMs7(UepGCO;gW1x<RdXS$MAT=<qL7{<qy<o+CKGx8{M(Rt>C2HX z1Xyf9yOdzL9Yz;1#4zCRCxO}@$SdhV{xM{*WVlD;@*g(83bW6bA(4Ta{b&5)m%)(! zc2^eP@vXT+mkg6v|0?uH?pMrXV_;xmWMDYJ&cKk$j@UCyc-IOdtiKJ?@B_6TK<!P? zo*dMcGbmMJrZtc#wpJFVN=T|)!IODHe4c=QLjZ3~`Y{O~*!~+>iUiR)49FuSAk#ss zDnKqntle2d<M6}X{>1JcP(K8;>jjjbAT!b>3<2PMsi4-EspR~Nr49?DCQE63ORh`! z2U~Rv@+}PGEp9L*k#oA3lKjVe_9~0!h#jck{`VHBnSe3l3vvZyC-e#M2<Q<Shad6f zANlR}3<i_6m8mlytm*epRM0$9{y-hJng<r9AR7A)vMDt7Kcf5s%{p1)8UJ!)aAOEz z0OgKShMfjC8jbWHEc)c~zS+X>y#i>l4ag73czs4$-@dEM295ji)w>xC?pF>;PFj-d z!~RC$=(Z*1XF%&WkbMJUgUS$6*Nu|D{sK`36SH&71UzeqFaD5cLqNIg--**c|E<)V z@2}dURT8JY1zS6#lp!@Iu_V7pFB!Bq7IoA)8_%dS?onS$L+qoz8Hq)yG3f=RG1;j_ zd8s+Yus;7c#uU1&-6d3i<DSDSW<c!Z0qu5+hpsjQtrWzyauXsB86k(%@Tm+b46yZp zpx6;=7d-slbM3d|?5lW}T{TJt?Ewd+L1YZt#Q<8%3sMg`DP#k<%mmemkX`o047sUA z*rp63@kL%qQE6T_G@9<vIRE0Vzo;GJ!QhpSpuHEgtnbh(K=5o1w#f~|m=-LlgQE&F zpQYf7DqJ&1CKlN9SxIVfNlbESN@7e2!U1~4pq=p8bN3S(=ReH)7d74oOZyYmRmeMT z@#lX+;~$3LQ(Mqe4T2Bbf2;xOQzD%MgiwLW+KM#(K-hl#E5Km(gT_ffWe=upm?EGt zTw=#xK&NkDnt%`i*$>*w4jLyT*8cq<?a=WTLjDJ>tp+K_g+c3ILFeg!>;_?6>X5}j zYg*Z$_M^|6LQWz<th|PtL;`B7gVtI?`VOG-oaZN(*-wRsD;xK8?s)THr#5H}EXaOj zjBF<^Hm%EVMEeo6o`&@KWAa<ops|vua)sLKYlGe_*yJJj#h5Ff&}cZWP{J*Ki^lce zz=dB11KT%=<z2V@Ug@0?sxP{AlO0>Hg!qyUJ*8vztU>K(Oi@s3-!Avt&!Fpo?O)ZW zx;0lz!Gp)hDILVdoErv>reT@~vIUf%AgwD>)>}B278RxDl|WlQkeb^wub{L9z5v6i zG%YQ)D8?x#KRG)nH8CZ%NUtOrbhHd=<(Y%1vI&(xhTxO6aHl^|`3E|)2XsykC=_t3 zM97?AU|{$F<r20Z{|Z=;{UCcmdrCkw)L6XK83qOh&=@L65n=m5H48{3b_}y0G*3aS z{THC)-yjnS+Yd4sq!K#@*$*0#1np@c*8a;33=E*LZ;*+U&fgLmzeO)UaL>CE$|;b3 z2xtugrb<w+KbfK8xP@fMM43m6OP6b3u-&l&6fU4JLB=3IBV$^xU!j-(GZ@&kVlC_> zZ=P;AWi&zV{_K~aeI3X?2eFZT4q;a%gS)SgX#m(nC+-7NKnFsA4g~-$tVFQm84MZX z89)nEW5AnOU@CIq8_VOtOC~|bu~agE3e*CI0*2?|H=k^N{P#^o(Bk(!#^)kH$9#bt ziHsqR#6!+vq`wDZ7_fUR9=xI~4emj|blJ%t1n=E{CN|AKmE(^#==?Bb&w<#q9zR4+ zf9T`?gv%fNGoYaTe;|LrFrKJ_OM&KoL33Il0Ydg0flqk^sl<*!<sZy$5RF|Gs^~2S z1_sa^AxM<4{e=4;pb!G>=_A(uyU@8j;_N3_|DlXggUlp^L5ID9<~%{_VCyGAEfUPx z0mNuL;*1E;uEbmhM+Q*)C6NJ=ou7O?A*?geN<(DP^V=)tRcC{GEuhdq#vt1;@dN_x zSJ3$+<qU{D+Mw1Gs3(%iPy%1Atjmzh04h*)!8=|}7&I6R!KXW6U$X?8=foxpDw?k4 z)G>BEx%7*3zTy`d)}8mTjU6NWLF@H@gz_)3@dt`uP>g|Ak%QKKlQW)&@HK)phw9@m zpjk7J3n62b#H~Yvtx5xxZlG~=Pz3~v%LQG>ZaPoe{{GFYWuIsBEcJt((ndI4B7BIS zMg8@^<oKWbv`o1C1C4Cp4g*m63p$Szv`-Ecs<>4mWS%lGFu=+m!uEqoSA<@47RY|k zeqzuWpTye#l7WE%wAKS;7Ge8AWf@2%b_}u~WG@V3SA{D2mVto*v}Oe)O4xqF{SVN1 z4ycv_xdCJ*Aq?t!fyTf<>JTS)gLtIld4nSVJA==w1f64mUIMiDt@O=Hb#H2|Sng4g z`j{EC?;8|O$e46rBXsnT*#4yS#6i#wAmp>_k{R+Dav2K1X9`s?ELeYw`QxWw^{scM zUM^&}c?4QXh3r!h8{u{kYsw&tKhXZ`42D03H@?jfji30WaMkqvT75RS`!cj%KTB-= z3(3a?;MIJP&J<|$2iDf`We8>nXDDI-wOT+kV%Tb9NFO$Dp3tM9?nh@r^7`VvTi5Kt zwqpYnZ!r9f!uW%@8+1}EXq8GH1M>M3p!J8w;FH#2wqeqs@(*-2Gw5t|%+!Uc9whRa zfq?<ERt>}^WIt%s7Niar2H6j?7lv`ELly_!PXIc%86-y7e$dDYNF{a*>i>W`uAo*r z$PL(4p^E-tU|;~P0R)K>vLCcY45SVh2H6j?7lv`ELlI|$tVINgkve_@Dqpd!A0v1E z36vAC^*aK<0|ua3;u3Iw+Jbvq+6}o2X$vkm)C4-t3<8}a3iC6J2Kk3nyn{gh1LQu? zSxlfAanLz|pw<~E2ZPopk#hDHq4|4|J=nqlR)V002WW0VXm6J;?|z|G$$yM3l1i5a zf|e`6LIOq;4h_)ZHK4gLW=6<bQ&vU>2GHCP=!k3%Mg|5>&;dz|3=D?QGvHmB7#R2% z85ndJ85jf@85jhi`h*!77(^Hu7(}6baYhCP2}TA6Nhn{Mk%2)5%9dkfV322IU{HYa zl^7Wqlo=Tq^cfi#R2dl<)EF5U)S+^kj0_A~j0_CgP`)lB14AJb1A{Y^Z^+2NV8qD4 zU<~D3GBPljF)}ciGcqt(K*g*W85pdgY=1@u20KOu274&qk&%JH3Ceb1WMFWGvfUXO z7(5sm7(AhTZ$<_NA4UcSUnu`8BLhPKlpVy#zz_^&hcYrSghAO6j0_BsP<AvU149gy z9mmMP5D#T1GBPkEF)}bDGcquwK*iD+85lrkWP;WaXEHJ{WHB-@WJBe085tPz7#SGy zq5MKd28JR=28LoNzm$=Ip^TA%p&ZJuWMp8dVq{>bhVp9}85rsq85ruJ{B1)r{bw*x zt2`iTvukEvNosmgVo54=+pC!Y${Y;x7Fh7w1vk&2c(=@)66|YRViB`1n6@LYZGp`2 zAe#=|c#2pW5#yYnkqVj=3NFddPA$f^4hm_q2h+M>!u1cRXNS8SA*cRnV}O(uAX6h4 zav5?MK&2a~^_~dc%?mmI6SPZ<(CO=-dt;awkw<7iW)i}nqkNWtHo_6H{{nQ9DL(Va z@&8g1);}17Z?wVf0&?sJoumt!|H9na9iCrWl3D~#>BeTJ#@JIj#&S5<<cxf53%PvA zPwYfwr7Ra#Lp=jMPzRVHH3yshPH6VK<d^5=<R_*S$7JS#HtG?#%M<y=1k`npFKAqT zg8F-)RTq${84wF`8b4@F<zQNW>&XxVU4QGzu;H)Tn_f#5yO57ln$&Bt&D=w7sMts2 z@I&<9Kskxn^{1dwW;X_qf8wEc2Y^Dp2<5~MNPkT-r}53n#d&R0_e}7qm0>-OZCx(r z8ZyLMggryp{}~KPzn9INr}2GX#bN#{^Uv$f!?s2j)N&35pZ%=BU<y8w5_G~E=&VZ6 zjU%%LrT-yutN3dBp@p1f>YsEARxGMZUxLkdwBG-YnSOEiv|uULnIV)R9zHt)%4eV+ z3*?*`kk3jPHh+KH`H#hJlEw1h7k`QM_hTDh0mU2P9?udQ#~-MS1D$t-xo!ZIBSF0} z(AhM|V_@+N*+}Q=K>Yjn&s~e}?_S6qmszCJv33&Dp0zcMq}~_;Iy1GDArpK)H?|u@ z^cbAMvr#b&Weg_Z^d7_D!~oL)x<LixYLJUF8LGhNeZyKhpb*EMQ$VNwyCn3LDE*1s z#xUV-up_%JALvFmSYHoDJA-ew0NDYGbwd4AV%4GVUjzB02t3~nTIG%V1V%(j2Ri3J z1}Rk{ulEO)w<X{cZ$Ya(Dj6IZK&KahPD2OLpw?kALorI}3py_y)C-36PzT$YQ=pza zuCYMWlb2%PZQoRe67cEppp$n&F$bEzg6!qI#X!3~28~P%3{6Z749(E~eJc~>ZU{?e z28Ir(SQirmLpKuxLl2bS$Hc(U&&0qm5y}VkQztVqFic@$V3^9pz%Y%8fnhom14AVX z1A`Yc1H*DA28P*83=DIa7#QX<F)+-7>RZ6Xz_5^sfngDpzl4c_VJQ;>!!js;1rr0q zN+t$|RZ#vKCI*JJObiU`m>3w=L&Y{SF)(amVqn+|<!@zTVA#gQz_6W(fnf(!Y!?#) z!)_)9hCNXJ5hezPV^H=%CI*H>ObiT%q5Pvv3=Aik7#NN-F)*Bfih=xhnu&qo3=;#x zS*X}~CI*HJObiScnHU%@LB*~xF)&<ZVqmz&#K3TyiGkq;69dCdCI*IEObiTnplU#7 zfu@x2GchncfQmh0Vqkd8#K7<b%74bh!0?=jf#C&||B8u$;WZNj!y7379TNk?dnN{k z4^aLmCI*JjObiTPm>3woLdCu_F);jKVqo~m#K0iR%)p??%)s!6iGkrSNFIbiYqgm{ zcSfSNCqbw4gKjqh-G&G`WeJpC5vOM0Urmml+d(}!(7htg3_c9;44|{gK=bsVHC&KU zJ;=S9(=c|0fa)yJm^5W)6QP$`pt=O&KXiA(M%f@WD5%Z=?Q;O#4>*)c7Empd#*hZC z{XlgN=rny$uNrhJBBE3QovaL65(?^tUSlBC|2GETq>8uy$q2hY5mW==R*8_=%)r3F z1zmqm*nV*54xt&1MNa>BDgy%pvFndOXN#g)iNYnve$XAT#Lj<%PSr%Q5RF65{0Hd1 zB+!`vAfv9(xc!E&?*nNO!15lsEA1h(ZIJ$PE(2u#Fjy$X(TKri`$F+Or`}v*ion)# zhs+s+PS_-;^#VKH5!9jqo%D&X-2y64k{Q&u$EPMuYOnsjSG@j^qBm%9D#QQ(Hz*8$ zP@aQeLU;FJpMjw4^lsv3k4zXW84U59)Qwpp!%o?ZVMu2HwWUCH0;pXC>BoRtjG)^B zK&KfLgYSw2%@u({8g%|3=%&>zWY;RhoGgy%UdXO>M7lt4-#UTswaf+I?*Y2i0OS{B zHIVimXl>vzvR#fkHbpo^5E>I9H%~(R0=hSQ9m7!SUqS8`e@CbAC$|p;D=`si4O={6 zOK<4v^cX<xQ6^Rf24+?U1{PKZ23A%E1~yj6Tp$N40|O^30|OT;0|PfyjF**xfsd7e zfuEIuK>#Wy#LB=R%*wzZ0_BUbGBAj<GB8L$`BJP54AQI&3^J??46;x$c~%An1y%+I zMJQhxN~^LmFsMP<8mtTqnou^Rj2k7X9Ri@8wV?BuHqj}~6K;chgWIo&QXX=yLp)0F zA5y-6?)}{};O<6@Lt(lca|9W-QWfF@cLoNA3v}`U_R<D)c_0%DB)_w;Ffg#PFfgz| z`5Y_^44f<s3|vq?4+{eWFAD<$ACxb^!oVO1Wec+~Fo>`)Fo;6=;w%gd5-bc1l2E=h z3j>1;lr6`?z#z}Uz@Px-E3q&zD6=pys6hE@EDQ|lEDQ`9P`(xm1A{gT1A`8fugAi` zpwGg<U;yPCu`n<gvoJ82K>21Y3=HNh3=9@fz7-1tgEb2SgAJ5#$HKs14`n;DFfcf= zFfcen`K~Ms3~o@i2MYs(Cuj_Vg@M5vD(1_=z~INiz~B$%N3$?61hFtM1cUB>hl<6q zFffF(Ffc^0Ffc^2Ffc?x#ba0)7-FI9coqhR1Soqd3j;$k3j;$63j;$Y3j;$M3j;$s z3j;$2R6Ywz=dds^<gzd@<gqX?<U_>@Sr{0KSQr?JSr`~fpkie#3=HKg3=9=eehmu) zLp2Kn!zvaAhFTT|hB_7ohI$qThD9t442;YS3{1?>_-AHdU}a`tU}I)rV26rvGBYr6 zF*7i5Gcz#oK*ji&85sDP85jhZ85k^>85o3^85o3_85l&M@}N3JoSA_^f|-FqlbL}* zikX2ynwfz?1}ZPd%)lVe%)p=k<ts5WFepRW8q5p~YRn7_>QKHZl-6cuV9;S^V9<r~ z^_dwM444@h44D}ijG$sB%nS_X%nS@>P`)*kwqj;raD%dKnHd=Dm>C%CnHd-ypkhwU z3=Gc93=A&J3=FPNF?VJLhE8S%22Uv8o0);Zhna!FmzjaVkC}mCB?|*X05bzaATt9) z5L8VFGXp~?GXp~yGXq07R4kI2fgy^SfgzfifguJe7RSuM5YNoOkig8qkO&n^W@cbW zVP;@RWoBSVgNkJ^GcaT_GcaU9`8muC47tn<40+594Ea#8LS_brB4!4LV$fI)RIH4d zfuWq4fuVw#fuRyAR?W=7P{Yi?Pz&YPGczzWFf%YTGBYqVLB(2_85mlb85r82{0?RY zh9%4l3|-6&4BgBO3_Z*Y486<@43n7|82XtR7$z_?Fid1-V3-6|f0CJjp^Alp!G(o^ zVLCGd!wjhSEM^9V*~|<KbD;cr%nS^mgEJQ}GcYWKiY;blU|7b?z_1j`U(d|IumTj$ z%nS^xpkiy785q_wGcc@!@;5-~P0S1oo0%CHwlFg=Y=w$#XJ%m7!OXz0lbL~G7gTHy zGXuk3W(J0R%nS_sp<)M_85j;RGcX)xW?(o16+6bvz;K+If#C#{e+o*UVP;@B3uT{Y zW?;C$%)oFF%D>Faz;Ff1zQ)YJaGjZf;Rcj{i<yDpHZudm9Vq`EGXukYW(I}_Q2rxk z28PGX3=B`8{AbJz49}tLm&^<dub3GaUPJkBnHd<~LD?Ue85lk?GcbID^1m=MFnncZ zVE6{*|6pcd_{q$`@C(ZS!_2_&7sLi((D)w<14APV149!F14A>ktY`(@4a~y8&<^Ey zvM?}ou`n=nvoJ99K*jo47#R9l7#Jo%`IA@}7$&nYFie5+r-9<1g@IuPls}7wfnhca z1H&8^28Ov%vH2_v3=3Eo7#2eLi&+>Lmas4|EM;L}SjNJ@u$+Z~VFgs~EDHm}YA732 zwwz;OU|7$>z_5XZfng&H1H&d328PWn3=CUX7#Oy)FfeRmVPMz}Rlk#kfngU51H*0> z28KOQv3)EI4EtFa7!I&7FdT%69cE!*IKslfaFm6C;TQ`8185@m1PcShNfri%Q!ES& zr&$;nK!+}Y><8iVEDQ`6pn5N{Ffd$ZVPLqz!oYA9Dt4WPf#C)V1H(-y|27K)!yOg| zhPzO{5-S740~Q8`hb#;Xk60KO9z(^SvM?|_V_{%;4&}dOVPJU0!ocvFg@NG>RO}rK z1H*e328ItT3=AKkVxL(U7{0JDFnncUVE6_V`@zD%@RNms;TH=7!*8hAUl7g0!0;b5 zQO(N009pt8Wk6dXsP{2~HkRV*TZ8VV0*xkv*1v=1O)?n@z;|bW)<}R(r3THPfyS1L zz@nf%L?9k!p9eB;1lk2bzn(Veh88oD#y`-<6F@6OAv12Eu_nk#_OS5|(2Z6fhDa=e zW&x5IFmEUUjaY-uT}B-t!LAz8dIp^pJCDY`CV%`A;zmXAIk5B_WwT(Q_9z>+aRgYp z1I<6*qHzp>cKd^Ff&`71A?B)#8O#_=89?`8z|L!j<bF`P%3;7>iVz-AbY%eDy`2wT z#l*k>I>mSk1EC$}F${hTVGN!OE)1RwjtmM6kn&4`0hD7wdvidp2bEFj3<~hO9<#xF zK|pSX%=m*=3qV%K#DMPs1(k82SvSzx`>=7Ic<?>JpcBcG8B!TQyUjs6VL>PM=Ysi( z3<V6C43KmOTH`mE=G72qGcquM&WhSccB-Mx=qxC%GQcAbpc<wGd<$zncqIwyx&u(k z2hE;A&M=|fygc$*!PtB5nDu5D19`KyAislFEPzHb5GTWeYFN-JEzr0+Y_^tx5p=3G zeM&Q8eGPIsXnp{6PHYha>YNhljA$@JB7CnLvJ5OvLFaB?BRgG^Hug?W-+{*XKznY_ z3^Kn#%1Oxl9rA8DSa}DV`H5#J2j9Q|S~~!$1t29H=ydl}bn+45QA<!QRm6aNI}T`u z3Y4?Z+iaLBA^ve>U|^tiT?(p&K)D<;TMA+!YE+OPNt+GDoQHs{pFmwR3(8%fIk^Hl z_z$*PlGxluI0d5j9zd~*E(VD|&`ks6w?@gS)j;tFTE7Qc0SRg=fbJA5VE~nfpnH1q z8Qd5^D?mW~AW$5DYAw(W1y4}&46Wz?VKD+qU68dm)UKtlotye8+|#7(zpVZ5B@rus zN*ZsLgq}``JbMDED^5_je|80h^Y_Hhpc9&_O$Wy|r2PwNDT2<<0L`O7)<mf9<yD;Y zRAT2^6aK)O$I_sSG#LK>C$#<sbf^<(od_-rT7Tlgz`)=M4PjjBkj42K85m&a4-mE= zbg}`mZd5io>u*dMA$NFyOd@PQXlE}-C3Z}X{j!Xpk{)6nA^S}UpTA1-`9HRd3=E+A zR6s63&Jq9rgU-^1UtyJ;OvD{lq^DnU^FMl7jXc{1DuL0@3&-@`|NlAAo}Fd4+vg>P zrmrIZPa=8!H|Sg%P}xz;kk63D&_(t94=T|?Wj4qj%zM~CC3+foZ*)8Z=<J6)@J(&$ z;9lRG-!oS}$&HRbyX~u|#1?JPwd^Qq0~FV0Mg~T@hK4SNx)v7Z2D%1>(;vb7$LPku zz>q*f{&!$xU;y1a1d1V8{ZPyh4(|7YT1=o_2e}M+;F=6{5-ms_E(|JvK>G<{E4*;2 zLly^(-@^Fl_PaBH;<l8b0Ni_rxdf&flh$BhVgwbXuzSETRbYzXvp*PoLuCn(wlgr0 zQ~n^^A46zxLxDkqp@PAJ!HhwZA%?+`!I!~>L4m=MA%FqY0R=TEk%uZk3w=S&5l}|U zVA#Xb&!Q^US7S5p$A&x>;eH-yA^{~t7-q<2pyaqgLgf$0fuMF0{ad{Hr@gCr%1&^s zNn>P*Zjj!`iOus2^lN`%AAxpa$Y;oBfZSf6#}Lc_+JyibQ39O=0=ttMGX*0=6d0Ho zWsrP^kipI32AwGYl_pgFm=Ll47_^Ea2<ir?v3MyVM#x=TAVq}jCwTr6xQq=Z!G1AD z1_scbU&PpNO2qjqVGNLU^q?#SGLsON0^PX;RRn1-VjC|7<r+|31j^2c-UV@MT`PX} zB(~f$HDLWNkQo(M>WO^o6Udby3@TfT87@%0{sh$^Fkd0}Zn39-m`Z&6%TVW?VB^j4 z4Ctq>L3&x0+pj8`^aOPMn$2sbDYGB6fB~FBKz_!=mnigq34;Oya*Ij<JaP$&FVL<s z1qRUh^?D2j44^$7&J3Wq1l=%!IM)%hc^*_BFf%bQurM(&ure_)>}O(NU}s`r;9z24 z;DqiK;bvlB5MW|p;02xk!NkD84;2@L(!xv(3?iVtCQ!aO69a<;69a=JlrPQ1z#zlK zz#z-Sz)--%z#z}Wz@Wjzz@W&)z@Ws$z@W^;z%YS{fkBmtfkBOlfk7RrPZLUOGchpe zFflObGBGgZGchpeGchn2K;?p%7#NJ17#K{L7#K{UV&+T?3>Hib3@%Iz3|34G4Ax8x z3^q_XJ0=DOZzcu?2POsvN2r)Hlm>-`8xsSAFB1cU2NMH>CldpM7gWv%O8YS}F!(bu zFiZi>twY6vnHU&Cm>3vBnHU(tm>3wsnHU%%m>3u$nHU(Nm>3wMnHU&im>3vhnHU)2 zm>3x1nHU%nm>3umnHU(7m>3w6nHU&Sm>3vRnHU(-m>3w+nHU%{m>3xHm>3wcm>3wc znHU&ypmvorF)$QC*&z3Va4{1DLkSZDLn#vjLm3kTLkklFLj@B9LnTyQHI%MpVqmCa zVqj=zVqj<h`2&1n6=<&wuJfM=xBqMz7#KiTy@OIWv{=DQrIK*|DQKPtpJAZ(pEb1X zCD#5-@JJNIR6^yyF%jpVgZ2r5M(>EVKbM5_2aOpBpFe2Nz`)=E4Fia;@RNlkoWBk# zHSw87PWy8v11bG~!skzd!X32M97N-DD@<+<0|Nu-40sTqQ2c|&tw8E<VNm+BVPIeg zBq974K<^+0nMv4wg6Cf{xI*nF*8eJui2H9LGbf-{GNIW?*j{FkTI41_Y_uNK2iNFs zmX_<$OgYlLA;eWa|0n1KCXlN@_-QHwL%kZ}mPgP?ehkAS3hO^Y?SEMR&V<2)f%<df z#I%M=8MMW|o(L6}3su!yx6`Og;41R1g$L;9e**?`R-h0XSL9sKU-oILsNrv`q%Ei3 zUD5?DECR(hG6tPG3>rDdT+2<r^OHd(8K(c~cYg|K{}t%`5m@^XH20qmUIznNuYlZ& zL)~*5#DF}{4{G0nRuzCwAb^Y$<ul|lpze+WwM0u97CkQSdlB+7zWH?(6U!G4DdcmK zV6lU{{fjxf1KEd#IU5IBPgSK@bihL9{?(WY!LM1hi@$^RIKu3J(V!ce(dUlocmD#Z z`5$}#2m5N6VulO`#*24c&Pb^)$-Csg_QFj?&?U?m?fxVN^gUU_!~dWZpwq%*$6MGq z*`Uk7Ht<^YA8dm?kX3rfGoY_%T>gP-D^N_*YWxQ@)0o1L$bdVKg6cU?eoSUanc017 zqK2{i%=cGkWG<X20Scjkj(=L$ztjzXP)uTuNq|m#2dx|g$wF2WKw>)=Jldbku;h&$ z^V{&PIcv^(=w2vp2Q5X0=gh%V{-e)tfP91LS5ng3)I<B44wiIj7_JZh-Io{zTK*09 zH>^y?UH)UU-<iReArZVP$_KpGs06&<x`d&Ep@hM6;_cVdA|H1q7a3lh^jZvb$SsC# znDyV2A)fz<YXQ!DJBP)?+x+>B@8vc6Q_fvOjE9kb|1LQz1~BV%+$%QF$FH%U5oHw< zTAW%$$}MIlCdhZ}((nEo?Cn3?=^rz!KrJuy)jXh*y_6xuHjp=cM_7&d@!KBr>$Imx zF(CRmu$+dVK{W#GTqb&!|Kzp*QAa<~!+@Us7b6DD@`IT81MLR@wUkpCu+<LN-Pg?% zkYBauMAiKQsT2;-MrDk03$&^ivN{*}>^e~CcZkO27jpj<oBwTm^HWlD6kJl%GV?M^ zGV}91Q*4!zQw=OFk}OPhO)LyjbxjOYQ*|xPQcQKz%*@hKEey<(%*>LM6kIZk3v!AW zK>f08@SV`qJ#7b6Q$cbIX(xoi@*VR0KFE!rlt<hOUC?^ReGCi?Gf-AKk>XQw%2q^u z3G3nEnjv6d0L|E3BHMMOtuzIVU{SX63cY3G20r}`<liC&$Qf<f;PnR#44{=jv|eRO z{EiIdenu)oDFdYU0ZK=ZbcxtUgQ*f!u7g(0sWUJzEF(Ln$Vo@A^bZ?Xi$U4>4$29z zQIr4%kPq^pty~6%+u(6s^d2^7WB@e2k3DyTCh<Wbg1a0A#WQ9*6nj4y)DMJ=?XDY; zFhi}Y(AQ~#!VX;w7SEtN(df6DnAEli_Ba5=8+uBH`3E$2Ps|B8gvOsuiJ1QgWnf^4 zgtmA<W7>qUA|nICDX1dC_7gmRoDtEoBJMUL14af0*!fq4><67&1a&G_ik$fm6Gr0i zpEYG5xc-1Kgn@wpR91n)0jnd?1Z@}@7(lD%LBgQ59w`i<GBpXj3IvpX7EFHp-e4UQ zcb?-EF6|}i#-RB<klDx>q!)yHC|o~B>HIZleQP<m1O)94C|l`mI#JMZhR>#p`6^p< z-(Wj;8susOo-n}fPhm)6NC)3u1=_s@3jG-}``eVIMJ7vma|JdZnwdEnnzOKlJ_7@# z;ZG?2n-X4sgVI_?ge-1WAS3bjpP4ccod05sVqjnZo#_t>A>6hiWI`B;zyH&e2IudG zljMJb^S>mAe-zUCYfAGU==KKCo-0rbFqvVE_tre6MZX+2zVt}Q;IX}ktsjht2M}w@ zpvZr@44|_@L2GN1!DR`bBU?k>N0#riA5Ya?xJwaqpa@DRBU0L6JbyojAt~6y)8fau zghjt|`*yKBsK;Fn63YK(MAZLDNF@{~P6^>yr1VGFeuDWQC1wcO4U$h_WMBZT0R*uL z*>6t7`a96Q&Y%+q5vc-XGH#s8$iM*FyAD!8$bK^-)_;KPg<+7Hgm4BU0|RLPDoEWM zx{Y7J_OIZc=OpcHVf66<Y$KV-YC!7~h`Fx-6e`5y781)Z#HH!TSEZA7fjUTMA_Hh& zF33-ycF)}o*(=5?TUUy%`DyX!w#IGHnk`t0fYI3fw21EUk3O0J>+fP8RRHxU?F`nv zo?M-_z;@yL&sM*B^Fa5_!h8jzvHOm&DE-D?Vd)>2|1rlPK>by0eLPH+pmd<%yi$72 z_jx;>{A;<#8dd0uZO<EKcn~gs2=0Gk3};|q0G&4giVI8|Fhz1m=>M1zZhta@)*67; z^bu=+0V4wgXs<WOWrY1tu>VPN{Zqoo!0-lSIT80yf$qM5D#c2H`k$cnl(72{u_{6n ztYBnd0G)mV5~knyJ)!iETvmfpK5}^g+EENTK?400NKonEuB-dui_>+kOX62g>v2ni z4zogu5&Wrwe(R42`5%2`3KRy|azDB{P@gfGA(~xWcZ-0|=Odp4E~=%s-<3vclYv42 zghBC*j!zAm@*lHQ!xaLUDj{K@8h105Ayw@Eu1)bZohhq>LFbHs{Edv!{Xcl`U&0pm z$aw@*=C9bjsdVdk&oXQAjjfESk8`oj(W3hpl~1_*qyG8(H6-kR0i8&I>S9zLX#H(8 z0|P?}G`*wBV&^rGu>Z%Lf%@ft3km)=X8^V3u=@if3i3Z_zAKFc|93EgPMrnmW?*P! zWMF7wWMF89-Ur&s$iUFX$iUDJ<##eNFmy38Fmyxty^IVDeT)nY{ZRfyMh1pSj0_Bu zq5P?g3=Gp485pKR`7;?A7-lgtFwBPX=Q1)d%wuF=m=EPIWMp7i#K^#~7|LJD$iT3S zk%3`3l)n<xS7KyfSPkW`Wn^Gj$H>629?IVc8cShhVAu@hZv~C*FfuS~hw^uV`kIUk z47;KHy^IVD`xqG*_Cxsx85tN3F)}b5hVqXxGB6y2vQIEFFq{O9RYUn_7#SGOLfPj* zVF+bkVq{>r3}s(sWMH_)$iQ$N%D>6Tz;Fx7zQf4Ca2LwH&&a^=fRTaWA(a0ZlqMM& z7@k7;&lwpQUNAB+yoB;!gW3#G_B%!fhWDU49LoR1$iVQKk%8e0l>d#9f#Exp{S!3S z&B(y;8_NF+qCt1Uf);}^fx?2c`6AFv5c2v3;@4jjs{fG7He4k?vKmO4Zy4F{>>Uy% z|I;Tl*w5!)t^_n6;4AYXDIY=(eEE-<+d=jnC<L(eXc6Z}gW5jldM+e~$vxkBws2bZ z0u43L1xzHgeLx|wYQXD%g#QW6n+GkgoqRdOE8t(I+0<PJ6+lM_ljvKJk5TciLF9kZ z;y;7oj_7VK_1d^NyXU3Fdo>fbB36|lQa`G1F?odQe{&j~KhXm!`=CxCWWO2V`7dx9 zh==BTsIhpd35<|?)j)~}*>6sG{2x63m`#HHQ$S@f$Z{h3f98beKfwNn#W>VhywnWv zDQ*xY^xJ<6EB~oA{)gN@L|%n*C+7a+Jy*SBd(F#B5(*O5^Fm_=7D6Bz;&%{vgTnKF zNU#5><$pqVt2Mtcj8xY7;5luj0z(3almzy<m`wv-{=vroh$;O*b$%(s9Qkvsp2_b` zHe0tbZBGfb#eT8^D0T^B`mH~OrGH%Gf9U;sPzwNC9}ry~s2})qm8p?UL-6|kCxLe^ z`mfPr$D8JnLxphpM|l5bIs*d(XiqC@&O){Yl|6@%`1{w)i5UOOVqjnZoi0bL{R>Da z|1F4^{{h_<2wHDLto=(!u-}~U{13_H{|Zna1sWwowfxsdxtE*#@_*>~A5<1V&h`WK zzd&a|fldL&Jf(HYzD>%dO-gHeJ@&HA)vneBjgEj)CNhRbJ_7^8hCy8ZV;k|!V0bCZ z(DA_AGT`cQX+}4RXi?(JI)twgtifFWVe>zz*T+`-p!lWr@6$#GZ&qn-X)2<m{v%xe z6JCD?N?G~P8V3=wxLIqE+Mk5%w;*Et3v~K@2?_RZ0FB8&ok!Syg5#gy_CF{LKp1K) zUTO;?@#ha%5Yhhto##+ag8z3gGBAKn0tGp83&HtoTAyy7!jQ^<IMW=oq8YT?8FUVN zJVOEaq&v{5&7gCsK;>RBc%`}c&Qq1o)2^|;+4q0Z)AP(TNgwwH`5lCp(Y^e`RgS~j z!NE{pgXZ@k=W&5rc=<<4Q>8!F$L;9aSzSCc*;5-D-=H)H!yx~Vir3NI|G3(}nC*W^ z`G>m7eFs~zImdOw9Sp}7ZFu+Ph#&0SKUf%mXi~yp-yrsXCIjeX08pyOHUF8x5bz*f zO6~BshWqZdv!>hi{vtIz2$z3^+y9``ok8Ia!o-CA9?)1fR3TyesXza6fTZ%5VEspO z`}+uJPJ?Lw6I}nv2)c!%9$Fqijm1kHM=JlxFaOBd+kzPD#mw#a?@H|HDcYi+RPfNs zU2D_K2RBZUGuDYMP+AD&e;0;)hH{2H@Y%|sJJdnFa?qZ3(2g3=+VcR05QYeb7zP9I z{q~vg`voAkc%ZAo)Cn2U2)d}k;wiur@K;ZJ@xHr-pv#_6QWfr;MX3D7=6+BtfKCtr z)wLit`b{F(d;?O4Uk@P_5TAZ~@M7)uUis%Szm5ho70lU<du1xN*uoSm!9RV4&HDum zpbhI;&<j}17~&Zm8B!R^7(iD^gSu2H3^5F-YLI!L%Gh}K+=sLLMedZaRQ-6xRt7r1 zj>zc3@IVFq-Ji?=S~4iaQypMuv4K$eYe|Fo@6(J744^Z>K!HKne!}zLpxjYGLiu-& zk%3_@$Z{ga-$A#QLzQBsK=bd_3=9mFB-nouGzSeejj;a>penIbAp1f7hhglhP(`mW zGB6y03SCIj{migadx;{pW1FCA@KJ=$FTj=o%NSA_k{KX3n1Qlh3WEzn5knc)QWUpZ zTr!|?29&_1msz`NYTOiVnONHW$mHHWacB&|LJCBK)*_Ie0dd)kt=z<|7E>l3e6mIn z1E^jB9iR-_0m}P-;Xc{@(~cVCO>Nj0$qhRClH8&OYcvu{f1rb(v1)`0g4Q2`LK23d z!g#40j0_BWpehL4Zw^(7odTu*8U_Z2GHC2#SA{Bi8?+__Dn!VBOCtK;Aip=0VE;W5 z+P{{B_g^qnF)%PRkYN8KB>SfkX#aqEtDqh_=(IA(iM^nDzK9`)fwJ}=wdVTC-+x1n z`(b_bbZ~nRvS%9GT^msi$G;tATJ~t!>Cf?H`+qXyE?ft`{|Smu(5fD6`^<5-$rZr+ zq)HiBl1zTRxMFAGqVfFiJHya3pk+m%{D6$1Sq&>S)cg-R?+13T3aBY~#Pidm+X;c8 zI-Ax!NIEO=8Fmi<vHsu0Naz55+K#{d{r~^}R0akHQ27Pg!vs2+0(753I|Bnl2Ll5` zCj$dR7Xt%BHv<Dh4+8^3F9QQZ9|HqJKLZ291O^6%i3|)3lNcBnK)Y0@FfcI80-w79 zxj7Nk#sZzSI-h}oVKxH;!x9EayKf!?0|V#`)I|&o3=0_;7?v_HFf3+ZU|7w-z_1d0 z-U0)|as~#56%3GlQlPs^)-W(I>}6nJ0Nu5)j)8$;Jp<@8K?a7643NE~n;94wwnER9 z-NwMcu$=+2#~E}h({2VxeRBwU?&<*s28R6%3=9Vu7#NN-Ffd$YU|={5J(Kq+0|Ub` z1_p+c3=9m{7#J8%GeGvL-(p~3ILpAmaGn8jcgzI_$Qh~^85kHYgYWo+jH%vaU|_fo zJzo~I$Lbyf1H%Ib$k_~c7#JAtGB7Z_W?*2r&%nR{I&%PY4m=wp1H)qm$eHw@d)h#I z6P`0LFuY)3V0g&@nJfJaJu~?&17z>`d+=Eg3=AI`7#Kb=Ffe>&U|{&d09n`agMop8 zi;;l=6t_PaAbZ_^GcYiKj^z5wz`*biR9Ap<BO?O?;rfqo{aeStz~Boq6LhCE$Xo*W z842V6mPD@qf#o*>R)eKqf!2(IS%m!$IzJs>3tWSN38g&-H5M=R7PRIRs)BI%Q-A;C zN2K+~pcCOir^P2SK<Y8XNPH2>siW}>p#ABP+eMA$MDKmFA;oiNV18Df^<iUD#~h(v z2U9%*T>c^a4=T;YUYliUcO6*2o_$%&`kWdU&`M`m%7W2gXTym(baOwh^OsW?iWo8( zK)s0~hIob&hDz|Q*pPXW7l$NegI@N{clvq6)n>v?R#-0;<`)nR_X=5zp}c;!lp#oX z)zkJv0>wO^56vq4lMA{)9VIst4j<yzf02LY3;I|os7;Q3?h9gFa0bIX{&RwxSALqA zJM&c3-^R%**k;E-J_BL$*Z)$|eg^eyL8qpIdKHk;hnVwIUx<8GlC1hWU2y%e{JX!o z#6jn$g8YDtv%%+?q0ccP>_b1pnAYb{BA?$zjq~@=(*mXCC-%M_xhIUFhdZplgzjo= zE89WiHlUd#&~ExWWS<^Is>=zT%8l+LL~jX^ra-rK5ub_~7(n}V9?;Q;q@H_U!~nXD z7xgSHCkD_7ySWT*;QPsP!8c{cFklk}#Up4%9O#C(e+<LphHUgy0P;7e&+N*O#sIpZ z4s@qZAozqi&?$4EGa^CjbQl;|!7V-fBaEQZoEo{1*z*=a_F-E`2Ft4f;58SZ)d!$k zzajo$0_}>TTZ%PeFl4X<pER7uPzpYo#TT63(iu`2Ks81__+~&*PDH+~9OiyshEnjn zc_xDk186?ZkpWa!CNfllSB@|+2!MNZ^obKt=>qZ#=Iv52*JAEv1<f#m;t1qw(7g$u zlS}g%iWo3ef?BK$4EzkB(~M~AAJF+=#SGaDApd}NM3U+sTy9qZ_x5S)cF_5kpk4Hs zZU&_wP%Z}b0rD9jvzw_56%5$M;TgdDFrLvV4-lJ|(aR-RszTIF=ru2><N~dyLN3RV z)j-k|XhcX0x-+5_e9{Q!>8YUGM<L}F<Te;kNP^a?fbJG6W5{E`Y#D=eK*mf<7<9pB z&VkYt$gPmH4ml?lRF;CKC?T-|Qy&9fBUTJ84?uQ-YFyAAXCV738FCpwC#&QzKyFP1 z(RmDzwjiX%R{*}(6I8E(?1QvDL8c{vZ{WydNMV4f0Oh|VXl(~6+aa|bcGZyZ0*$Qv z8K!j|BD^3WiFyAAs67K3Z#y=~a|a^7moUULz<g8606GIP2Yiw}q_&#@9*d)GsRUXH z0&-6}LlFb0gimEazN-*)>zM&V3<GSI1XAlE@&bCk2bl!Q6QDc;$`_EE;0fJ}z`y`% zr<D<iHEc7u$O|H37|2;d!N34IwdDW-yFv9Jq>OQ9K%IRewVh9B_YHCx2r2<UHbY7! z$SN674UDV?)XD{&xoN||z%Y&KIUf|4pi}`V%|P=Fpc@uS89*nQ$6(w$sm-7bZWn=0 z$4&;9dXQ2HbT8`y8iz3PEd$JbOOV{A#K6E1$3SQngpzBB8NhR>lgJJUN=7<BH#vdQ zP9g)O^%~3&%-{i@I|HR2(9FTE0dW^<?+!U-lal&DGaD=D>V8B?hwf%<V+zQlVhjwR zJ9_EY@5bMYK`+5!EjP@TGOqZ*R0+xTp#82_$c_`z%2i_9si6KQ<fcK$4Gf@m7|h2` z45i>+G^Fk51fE$3m105QHW;Y%FM{^mmVxKhXj?DC#_OozGtgWysP?D?k4Yl)V7139 z@GK*Jd<41yk+cy=L|#L*0ZSMV{%~XfwVNS(?@|~*;|9e@ttilroOe{O8A0_jtQ~~f zVxsOH2gH;v$Z8;Y&xrwavL?OalTe!my~f2=lAx=DrB2YjveW2Pl7M>YkQl^OGD6CC zklR3`#F}&sY5Zk5C{2P&I8c7UHX;R*M{Y}|GJsY*g4%b9(2`9IJljv7bVF>(hASRG zEgF#9L49w~y_2YXKZbk;P?_z>kOM8r5o$qk2Ws_z)`8ILt_5O!3yMGDYN#OaLUB-Q z4l>8ezyMm4@rO?NgjoOJiakV09?Ve6fEfLN)yufb2<)n1F$!AIa)VAGV8UPqhLrY> zVLk@k3R4W8)o=v2LqI+AOz=E9%y*!*O+V=7JN#u0wN4KS0FS7{#<yTwA3!A)X!j+g zB!kpx+zbp1hv?>e&<HavZuw$h0G$x~i%$NaM=r%Sl8Y;sf=1{-tHKzdW4fU@#&kjP z4=TMu<4u%|w-D=pYNcjGsfOGpb7M$^?=yhS{(#oMeW6>clu$`UuKz*t1gfzh`4n*$ z1Y|WCq#g@FSqTJLrHR>B2W@yVU;wSMOJV@+HP;2-lWxEO8jmz#Fl0ytvn(0R7*ZHa z8FU%az-4b5Ln?G#o*e@dBl7wZVipnEGeOplg49A>3>mKnmAvT;u=YeMgDXP?18Dsn zz8&})3`~r9&~>Dsbs!)!31KHD1_sccNsu~1_FED@e}xe=(+#p4gh6H!!v9Iwe*p47 zz8z2?|AXuWt<eS1gzN^%yE7rqA4sh9OUzBRRSHNgD$Xn}NleN~^(;xvRZ^(P&B-ga zRZ7k&(#=cEO)V}+Ois0OEXvjkD9SHLEh?!5Up#1?SX`W%o0L;&>sXYnq@ZsHGM^M2 z;mlyg02<N+O*mstGC6|xs=0uN$U%eJJ`AACMnIQ!#4vyccR-VNG2m;^K-CuL&;U@S z4ch**bDF@HAd|pvYy7^M%ght}iDQu>XlxFoC5K_(nmvV@TNb+{zw0-a@J`pow!@UK z0XeOM!W;cQZ;;bKvs<K&Bj7FxK=~Y!qhT$U81VczM2!6XyQGcuAo^#xT?#6VKqJ?n z8|OfyImr8%>`fRs(<%dd7DwMb9p<Q_M(Ms!SQ>+k1z^tZ!p4{IpOXd}gZ7;JA-0^w z!%@}bj7pw|mz5xNEi)*7VHi|GVY@*O7AJZPWemv-70_8k(1}!xU?)BqWGxQ#*dtW_ zfyy6zJMdxU4``nND6GJqCP46jHi<xjhv52ikp1{hARuS`wF-&$<GTR?<bTjU36S4G zn1I*8(w0ob-#-BIKfV(PK=y<7Vu1E!5Np2z69WSW3E_`#1v)7FL3>3&`$LGe-<Aoo zw*eFwgwvlP-W|vw`$6`?Fvv_oSPyjWF;o#T_7kdqLHlB+L(2@Pv3Myx63+iLWH1EJ zUg0tj6#t+-IH3JBpfJFt4q4oY39_dIBnB$0ag}YLl^>uo9kknzzO@Ad1L!>SZ48jc z0i=wD%#`OcC_qns1NGZ!yEiHZJd%aj5dj*@hcz>BpH0NT5Cl3`jmG6Z$W@>fDWLWS zsLln=`h!N~aMy$QRs(?ghoA=19I{<a&KxNyZGqg5x@y6V0dv<OWF!K#2kQdaE+j1l z6WfDDA7{hvCy1(W@LCU0;}q070<}6pqoJT3T07|EE70f)Xg#7YcnlM?wvw3gk5K;$ zv=0ze_JA;`Od*7&m>3uYh^~J?H6U&i$tnMTfX?oR8cEpypx!Z5F%}9`{(}4s+7}6; zu_(b1(qe+_&jU#iV?Vwfcrg1xc7rg;OhTBKk%55^8vc~-e}Ec;w-W~%t-v*^lv5Jp z9uOK(l%E_^oRL_R8k1g78k3z`l$V-Q91~KMm{(koU!1C!44U&{V6(|9&B=lGF$-O+ zVnT~ki;82ib278?Vx03+Qe&KRN{dTUi(-Pq{1l2ylTtE^iesGflM-`EbVCx0vrSAa z3}b>qoCBbOc<gpb%}K0u%u8`f%u5MKEH1|8gbZ?=U}k29;)Jlw;?l&N;F8jm%zU_e zpkbm{mRwO#ln?4oz&&rAQBqQ1rLSL{oRONFSge<unOu}#oS#;rmz<xgpORXZngi;f z>*p3Hm1gFo=o=XrfE(~`$yPA|MfvGPiMa}HnK`M&3K|s_W|}d+F#QS;mncA-qF`vC zXAl$S9Fv)soKu>T8k3x#Tac3qI%fs$6mUWd#TN)AMWuP!DBgwo3`=Y~r6%UO<rn3q z7GVqPe0<iB9Nk69G5H0ldC5632wRbuddZ-baj5<*C&K~GrA0-lc_ml_pd_`pB*q(> z(n}D|fC%Yj=EWBz78R$0rqNLyQbeLdGV@AO(~A;IQXyHu*vtUQJ%r02L%b(2lT-eh zF+uh)gOU$1_7muTFoNc1Kzq-LE&tp=ccOqSC*u4cQ2U!u{|mHQ8no}7So^(5IRDv@ z!3ggOsG#y6w8tKFo)WS4vmy0Ak&54<<e22rl*AZfO1^9o(;$h3FCsT!D+<EVEO*H- z&&$bAOeu~jNzE<DNi0c?FDlI|$;?fSF^CV&1QlDDGU!UNSrq1sqsR<)0u`-@qEpYg zpdhBqM9&}wEa6;QT#}!gS(R9lnV(l2;~eVZs9>sRpjVPul%84wIw~BVqscAJ<I%zZ zrS`*9>S8VUJoA$CGg3h%Rd7juc4{%U;x7)nh4ec=pVa-cu<<{{+)z3LXv`Gb`gNJf z(d)VwO#QR)Ms}gsvuU7hDH!{JL4A;b@*lew+Q;QAUYvgIfbZc|YZ)lLKNNp+#n{vm z-XuV&FWfwX;@vWHN>V|29)fWXqT_3S5iWm0^U=`45(@<?e<1clNGwV)gffxFABeFZ z+<d_>1tI{me-R1eFMk+GX@7(IOAx!Tk>u2WevAwZ;MrKB>K`M5<3Es)hmhFZh%UxN z()gbd-Ww1=;Xe&}<_pLL=yqY_b1@Nr|0$^cBUJx`>;;`M1ER6nhb|^WQv1UQI@5`6 z3WQHi{ELxjKQaA3(3v{K`d<#o{u~BU_Ev({c!DPBK&Nqo%0mVQVemOLLu2I{q9#CJ z@d#S=gl#<qx;ogH4d`T}OLQBvA%7JCWZVfdehON1jmp<!D8s1bK(Py2{gDse7Y90j z8nIIkG<t--`xBJr5EwJOKqHHa;Io>r@9-!JNi8nH*7hpH-rm4lU*hX-6=QArI1_C5 z<Wk!m7^58N1*P%1i6t3&$)%vsf;SDp1xykl>lr|!z)1{*=Ao&z`Ww`iKwirVn?Fxx z5cynXxWeV>#orA~sU}AQY(VSA5Gez~f}|{D{n*SyUc&>)ajd)4cNw4FTqm^6Qfcku z{T|qEc_eOp7k$6d9kS0qB+fqAIsj1kgU*sAe$^tZ{ffF%2fdWKM@RP$#5odo-){Gs zKQBr=d!g|``I{0OU7+neNP2>hpwaXg2IAraJ^rze5*nZm(ZR>8U|nSA{Gy`NoWzoR ztV30mgz_wwkx6okE{NYlz%vVZ48;ru4Ef--eV}}k!%&*ye7oO_UvA^a8KPk?o#un) zcpzbhAVD)0#Lw`dhadg!Pd8y8E&qW0g*m%~J*1Kuyd?A4+CH6j3twh_^4ygg18l2* zKzoGI&pf$8$NWc{|BD$iz$bZt&dh-Hd_i-)p!5P+mji0^grKci0j(mS^)7Do6oh-m z61_D6axu300$m-XWhxIov69*?Q&5V6>}W`4h-X0GnVZ9q&j4DD2|9@lblO24SVcN? zmj|dHORrWVvGbR(asb@eAcFwyUPmu?N*Vm#O`CaWkK#nX9g|<&|H_()t=xgEF2J-B zG@}H<pfhtpCumYT20<wZwrUJ{2N`Y47X}73s8>NWXul7rCIk5sBn+ZK7{n%oLF%?& z5VFcSbU|>76T<=(FWE~VJs@!q4Z@(X1JQ&r;qezkyeD9j)Bnvy>VFcp-w5vtT+sLf z==@pGd9k3hOxVW^4E0P53@p&#BF26K>%YKf+k$$8#M*y=333)I$gPC!HzqXxg3^Zr znMnxmV`5;q0ax_@zcmABr(ZP#D5gQ9eP#>>3?>Y247v>F44^gO77U<O;h-~gKrGOW z5Jn6xU@-%RM-<k7-ze0NdCCm6@;CPOH*((uR3D+Y$3gWgW*Y-p7pP^H%uvX+;kiPV zaIs2P#+(J=o|&K=3rau87+YT)xjzAFk6#<a;YYarfi3hv;Zw?>__*it{F2!J;a|8^ zGS}E;x?^eg7citU4Bqw^_Vy=eHFpU^2tz)D8+d;ts4an+I^q|%Hm6*5c)up*>%I%C z@9SebRUA@(5}W>sZ4aQ15P{BCf}943-p0a}_R-aW+G5^s6_swBsrZ-2_p5i+^t&?H zS_9<v-=5Ja{Zkr#uw5;nHG1Uin!U@gJNjFRys_`n+F45HTCLIdA%Mya$gY-KB#ld6 z8W!OP%2lAbJ<y4T84TP1sYV~|Ef$+9SFyz}@7r?@EM*5|uaN=+rR$&QmH#M7KN$@5 z@>@DXU6ZY+I!rtA+S~RbA_wBi?})K-<kAV0>yi1O+zmQ?kb!|-C$^HC53!fV#GYjV zTHUdaq<jS_ok6VuVoNqe%}(fC2jmz4#R#aygFL1R8k0p<1BoSD@U089?M;!sDj9ad z8K~9=t%e0{%^?5GDsomK!`gYIq<GK`539&d59GKKd-?^Hd!TU!(8*t*v%x?oD1u6g zeDF<Dpz{ep`<oaTxEUE32L7xygddPoCnyDDTk{Rt-Uk|Of!#F2zz|Qz^M62dU7(qN z+@n*N=P!YLf-Ns%s)VI>&?w^@veOZ9Bi^8z4ixf`{SzP-@~8)?Ck<le7Ep-{I!}|B z6WkdXK=*{tAlt{}<Qz;FLq<d688A<N1l@{vWI$YpI?IEZVn|6_pjkxFJv7AZ<^Z)D zA(*_HfnNPDP%4AeaiB7e^jS1esR-(8VQ+tf%28PR5maJMZDqby|5D~+)TOt@TNAF# z0F5O>LJ2{F+>y#~i^BHvCbIQI@&e2~_%>04$|PLt@3D(R%m$sHOL|D7rexfC4RkZo zz}HEz(gCwZBIfpO28IwioE3{`8JaMF#(YSL6T)LK#I%7yyDvbuEDp`xSD^F{DLX(r z_)5TM{lIEq(A{8zZBB-?J{kHcC!q2Ra<|zOn$~2XPz9BnpcV!4sb?TQsNDv-Arf`H z7{q0u)q;bmg^xY_j)CvDBd<;b`5bb4RyGM|IKxUJSczE<p3_I{76jGmpquJGkR7YU zwHINf4W%_MqCbT`CWxyogRTyiN8iw4KP}AHq@OMZi#OQ0^)Oe1c16<blsnMsBGPIG zL@q_dlQa0vjckTg1|Nn@22dUb^&ml)rGqvh)iZ!jEg&~X&@1f`uJ1tQ7QX&6sICLu zF!^PO<TKRv7Pc7`$f`T+ex~RA5osxunDr~zd<)8dr40LL9=?BBGji%?)q5d1ilSkl z*>+f48AgMqK<PDpi^yfDr5rWd*w{h=eVr2I4hvA&<S<Bn4UQ36ZpxCp+wv$VhlB3# z&|?6N8A0w>oksB71=Q2neZjYG6oFTsf!4p2Feoq}pEIKXKGO?!LV*JKOfNkK(ESEs z49*Oo-T$Dn6EWA6!jQy}&X5bf`3JP3CK-HQIs=0>`0iOkXF|}{ZJ@PN<zV-KYGwxT z+JqByDk(svJZUF5!|EnP>O*f`EUwO2fh%u-@&_RdIfVl?O{apxCz+upRQpJlm+f7K zGm{kpvd?B{V3~*6LU#X`UNs72{u7i!LATO?a!@h@Yi`<-i3?n%<fT7l-nW&SE{(-! zMGTOWv9qD8zd)gU1|y}s8({wvv;Ks@`Adwez_&OtfY+fAN-rS!PLlTjfJP`my0Bx= z`XA7F!l1jNLFGDjRj8tinILx}gG7n(KcV$MAbVjLWF{dzi;00jhUoQ2#)S4CF@o;T zCdK~iOo;OzAZ-KKdN|yp-G~wdJ+FgW^Vn)2bajxNqaJKuSia+UdX=TV|1x(r25jfR zA@4mww98?$V#qa1Fay2j|3PIhsq1ecB?tbH1GPrc$0jgCE`#BE#A4SYKQ~_d%+ge~ zgX5wSY)<<Hoyw1a^*?Cj;L02>=D_PuJ}Eo+@6>#eUXNLufM#1ksWF>@_!F&Q`36FR zbc139mLHI3O0bW;?IJt;sW--oE%!n4HTE_?tj)*P?|+Y&@{hp&LvXnd3BUjU36*^y z`O8cU3_L`af1rKeAYIrosQic74WhBDLKVHs#J~W$xdtRkjQ>IFwn1vKVbJ>5)kw8J zHZ|yC^GRI)N$C8g#S9D#YoKmGw+kD;hKYdzbfYIoPXFW-%O!I<4L|WV#(?;sFhs_n z+<<+K0Mh>2H)zv8Xl#<u+;|Sdj`>q=JbIwS`g`s1eRJa^&teM$?DY|(JfQde*@VY8 zu$2a&y0?_Ue$Djr#g9MKm`SGYlKT1W5y-EgHI$(HbwDL70|V%EAJW#3k-yG`*zrYB zJVNH?5c^nR{TlRHJ<xbF=<XoU{$$kqgCPEU&cMJxuhD)`jY(Q>4KXH%I;I0#BNLBt z^BX8$L91rUhS2>Xi1rZroEx^3i>wCbThLiQQP438TqoI+e{eJdLn;FU1LjHTpxsl% zw=0MpS3;btjC<Axw6X^@cb&=r$pMg+9JvgTbzM3P3=Fd{`jrFaTGaRg%~@zOfKI!h z&pqI<RXd3D4N=cGK<zt}Fu=y7Kx0q%?vMr5f}nB;ltYo{x<KN{t#{0ueLy1vi^z^0 zO4^9H^BHO!K}yC`3=9n9@BE|0ov39U_LVQ#N(M}ou<`*^v2hLin=XjI!Q~ft+!Xuy z3vLXV3^@#tQ9M6}Fa}Qs7Y0uTM+OB3NO`2d07~PaWA-7drScfk85F?#7xEc$7_u2a zH|2o(8lem>42}$-TbfN7AY;3rb!VWFWl$ai?Lmd5M@XraMA5C~l%zM9J7O4+^8u{q zhn$-+*U>`q%1MU7x8504I^k+1q2I8GxNk9-i1XKt37tPja{sTF#PK&vP(u|52KE11 z85kJ$kkJ2m$i%?#1Zp1P{vT)u98@tD3S|FE1_p-pB-sCyiGkq))GT7`Cv^Tea(@kK zEMDp*69dCHs0vWtMf7iIJN}o!5M}f1)xu->r`+|omCL_z6K2J7OFXE&!5n|V)mvCc zcK;L7e<i-1M(7kCNO=iqp@GiYf%Q>PXAUZ^?62TGpe7W1EO*k0-$wbM6~M^x2V#@5 zmXFf;TTo94bpJW1TrY;s+b}SEWdMy3(5KCYD`wGCCb?}l^mxHt{v+21kXq^x9q(r) zHXP9X150P<ePL8SqJDt2k`^*BFp$5eg*r7b`bamZ7RJ`rLstj$!|6fLioky78ptQu zWngXx)d1Vb?ir9)df`r6v@8EWeI?KuBGCLks0|3ZzNrM-g9Y6>2D&R4)WX7k>!QH6 zbH9JR4^6ZzjgrYx|GEj=4p`7hrJv~3&L_MsjNI}GRNsMeCn&s-`<9TF0i;cbdY4<} z-ffc?p1W0?!8@Zl(^NtkHWvh{J75@Ey+bGo21X{x_2&>i0|ThZB*4JH-~zD)G_H}y z0BW&g-oFO&IZPWS4Ql^v0_Q^F=YN@*85kr;82_jC{3{1D1A`I?_7gh)c{2k8185yF zDC{vq2vdZYnUwhtQ=-oQ+(AP7QxKGyp#J|e<ns@DeTZ7`fp%YlP8<TYaq=1RkZw)` z)$qS&ybzKwei~n+Rh_6_8~q+zPI5KWbuu?G)-^PAG0=5%HFVT9FmN<*wlr{ab8|MP z*Zec?5*$>1z}m~iwBM0iIiOk!TYZbH22x*1AHUIY*Wp&f%Ip&}_<peN<AkO+O837I zTmO*je^9B4Sql-f=OLLPAYgyq?#gElHQ#!Fa|Qm31m$E<9!JKIQV&^#w2@az+wYWy zAK~)fl<MdIoMK{N0Nv#S3QuC}CouoYcz}U{VLLQEg3Kg@C7BWTUlC(Jf&Mqi;~%ml z*$+CG3b&6y^Uuhs54TE$OaRjTGw8i6P)h{T=D_U9qK_ki)@IV{{CCRpe+ff8184<b zDFf!tPgmuo@|-U`J3oc=GBa~{Ek>M=fCyXAX^#P!1*tiid8v9CH8s^P2CjyVL(BiL z)t`{DT+n%jkkZ|Hzho^(Oq2SQlM*IHGxsePf#xQJ@BjaYT*FgSQ*Gg7Y++<!WT9(j zWMG7ze$Z=USnm{fubXiFlfe+kE_n9dyvK7D3v4Dz`1bOE(lWB!Ky2JLgVHl<oM$lj zuM&B&pwA)x28H8ClgQRjOb;4Vw}Wyds1`%+8-VzbS`E~rN@M_?6a^Z|fyCxq(~8>g z4p08GT5i6te@=Jx!V;Uw41bG^^p?mGQ~nu%f*S`0wf{FTFff3|#6huuLm7&&Dl-EE zsJ#jjB*uP1=fCe{U|={5^&`kkLU<-g=ii%B{r;PCObiSn(DpYm{wFa0O>+J7k%@sp z0ct;?_5)~^4OCiy*480b!-3Z1nlc!J*Y0OCpsutiVF1mNxX0~DXDE2v{gJmX=YGn$ zz1YS%!x;P+2HW{Vpxg#3FF^SUw4(ueBo|x#2U=}{>i-M|#`W=;*GdlyWG-WtRp6N; zfc+$F$Oz64vda&MenRmFTR%%~{YhT=P0R^+R)sB=Y=YlEPTsRsGUL3>J$7gg29+W( zj7V{yQ3%ioJo?_}f$o36*5(kd2MMp-a%BKrV4e>ig)e3Bd?fgX<=x+zwUgS{1%8sh zfh~MM^Qxda0$=|RzrC=KhS7w|PIUPTbV|Rt;veC2+~aTPEdx|O=oSRzo4cQEYCrw2 zvVY0`N9wx&4AwPCVo8^XP@wewA7b+_t^7~8{sFB}LiiV*1*-o+yCt_kLkV3T8~-iR z`LB>(24odt2}31=0(cK<9z!BSIs>TA!KNQsOo@Ssk)MHqL5KwV13~pa)DU9)PiX%O zDDP|~!T#S&3=CIDu%FuNum2;hKZfkB1MOvVSFmULE%z@piIuPTYpJR(=p-(X&yX?H z(MZ%UI^}=r)nC|WWpIszVOI@lXD??uvF6qP>7lc$Tp#qjPJW85*Mx8FG$`-C8Q}0E zoc>K22(3Q{rI1}Dq<<A=28MsoG(^~bGlJvapjq1EB-p>2r2PkGgzleb1o<C^q29tv zZD0beh=eF1#{Y!IpFn3-93;X2Tad<oVD1Ld5Eqd~=9R#&4Ajfd2I)n)q&Jd`t1wKE zZ@mLu+XJ}~wKxWT=^fIo8=!oP+1do<&r*h2a`MuzYJSbsuH5ua^j;Tep)^Wg3FK#b z-Ty*<`Gvpz2J%0)5fn%W_=#5(q*!XEY0p0LQM<J573L|*gj$b~J#46JSQx-3p1&LL zxohIwj=H`STUbHb?)djgL3Y^C^E?mI_71@MX{f#hg%J9RMo`KC-EIsz=c12+UaLY) zh*}j2%Ay+xq$?MOe1>v{Jm~4!DGZ<-02&Vitx<)X@07|A&j2}p2Q=%K%K+*n88E~% z1T$nZKvaQ52<suF0u+*<Py|g3k$)N}Y5jOmYuuE<k^%QjUphEsApN3v22lG0bV~{7 z+yc=0F3^g6Ed~aLdjvumlyX3NL3tyGf&5)Qpj|NJ?VCkS$=LHP_L3izx|<m&Iq{U3 zv02n!BxnZ#q^v}YbYpKjg3@a!g9n2vLjX8UmNF!P@7l{_NP(}80i`TZUbw@+z(B9l zC5Ww|$QdC6&9yTyd}d%^s3MT=u;uOo2GAKqS<toSW(@HRjtnUbWekZ7c?_VHL@5k0 z45(_5d7!Wa^=}v$KpPyV5wIV3x~C*f5YCg~;5!oObGj088A43^lR*0)rA-BD0TRMH zNm~C6x{U<4iJ<X^{R|8Ypp#oc?K#{k5i)y0U0dk*H(~#q5<Gul9|Hr!Q4-qU-<TK} zgow7^jL`f$xc>pVDT!GBi!qaO{|so<6%lsmEOO?bwU|lk{}34eVB8B{r3+b!f$lzR zd_86c200SKpTPVJBWM*WDC|KPn|<hF#z^b02&X?YLg|m>{Aa@qTHOtC6>&TEA!@Oa z1ATrCsC`1r{sr81F}9WkcGaL-to)YW^oaWxrYF3J{^owM{TsIPOR$9#F0s;_96N|y z9ywiQ6HCLGpb+PP80Y+?#GDfNW$~bHbACZ;UUE*1Q)*(aTYgb)YEeu`QDR<k0eB)G z)J6i`G)p-BnG?EyiV?JT@Ei%{pCdCV^PlEKjX#|y!Tvs^^iPcc39UZ@9U5?v1pAXn zTK{TJ_5D||%!vAj_?y058C)1F8B7=)84MX*89=3yD?=@VEdyvRT@phnLpnnZLpXR1 zF=WRW`YbADyAd?=g56X-@SHMeUt%KoHdADi^%&B?Cu@P`nKKxi7;+d=7#J9A89;?9 z=!`hfJs0HNWroWw*nLRI9iUl*Y6eFJBL+7HSMW`0t_+3@x(rTWwlRY)gCT<<gA0QJ zgD!(314z!1L6^aR!GOV$!GOV;!IHs%!3_+Z8H^cf8T1%Z7|OvX>4DaJfc7pIF!)n^ z{y1n18q@<JbbmCly#vf%5#jMG*s3JZiGL*w8PNI942JimChNRT2^0wk|2jN>O6*S1 zvL8?#i;RcE_#G%`px+4w>a~<I7_EJ!{G`WKS<oYSuHat39&GE<K&vPS#Uk!i9K#{} zKz%lBqg<s7_mvsjPEC!9KD^1&*7<48WCds`jhuoAr%!Ax1W5caFbwqk4YBPXVn$Oy z;fQ|}^aCe@aI<*N+VI0^1qrR<BG_&rBeee;GA0GODG1be|1`k!U-S(>$SJF)F*SE5 zCEN%$+bPkiy7@flaw(KHJScVMFqAOpf@@RIC?w>>4-gA^MGR;a)<CZxClr1lH$zq` zBF4o^z-J2JOKHgrV(Fixq*H>jvJ3Sdov@$1KpjiY1%=b#zrT%e`9dh)l``yoo_at@ z#9~3T!|AOrrr!CEZ8hBze#rfQ=L8rSKrM1m=zwrOcnla6PuRwB>2-b}E!z*E_8Dm8 zzmma`0W^vVId>H_nhI(?f=0bTY45G~Kc0U-Ek6gAI#lJWKW~@ClA;i+%W$8y6vhC$ zJr_1oHyr9uY`1Sg>bHJxnP=^b(~h3vEn2m}Ii?WX{wPZ328<cb43Y95l!8FFT*WZJ zTC3>yGk{jThIf`eVf1d4on)N0M%`T*bSX7P$?nYH10DAVjY|Kb)BZbR^B?Y2bj1w# zPDny;_Y)4U42H$?vZgZMcy!Z1c-;-La5gW{`QD%s3mJn>B?p~O14@rM44CyNz1ANR z8-D2F0GpRZga`88I?!AYG9NU;bf@jN@!JpgKbo+s@?>2X`-W}xFrr0zYJl?(V*JV< zZT!k5zdSD|KQW~^rYtqNB)`ZpCB>zvEXE){I5W2(Clyr`t|UG?wJ0w&M=u#Pu?iZX zLRb)lW`T2lN@|RAPHAyTYEevZn4dy%X;MmNQ89EZOxMKN%m_XpR+3s=5|f+{vbh3} z9l<2p5t3M(4PLJjmRVeym=j!5nv$6x1GWseN4$_M8R+^WfFTq<>xh`cL{5{SGzuDr z2w{jvIWY;8BSCYNe&Eq}SbbOwUbP9*lTE@YyoCC%uvP4cnJaS3DNtJr(%%NHE=^-d z1kWIXb_s(<A3*C|0vJFueW0Ak0G@dv|5P+mXZArS-I|ecV-TWj!BsbbX8kJ}7#I!> z$m}O--xK{T4^UbG#R_^49$g(Qy@5tr2m9J1M6U-uCzXK5OYyZ<k#)fQ%)!XOK+J89 z)LF3sJ82zPU53qtpf!r0$WC3P<rLc0#h{V_-<iRvJ_qG0&^lK7tqKFp#o{X=aF>ec zaf!+Yr87`#l!2k20hH1RtqMcEV<m|pAAI*q2}2A6f*sFb$Pmu}Iz0_pG=?FUp_BnM zx{=8c&j1?9E@DVzsARw;4qC~T%8&*=>lETbK|0>JNNGw#FT+4(9ir_9I{gN;I<1HS zT^*$MnZp3;$<wE1rPQY&UxCJXTp7|B(!e(?mM{b|lrp4(F~mPCjG%r0gUdgl9FIOz z1#7dT@ApGKHxabk7PPt$c5WJ|Ecn6zI#*zD`42UpgKj1S^`<~M6}c<}rAz#ya>%Md zr3I+v2Rd_+eq|rzymiC~4QNCQ-^@Rv4o0+(N*F-tG?5{f!3~^7A-PYF0d&_G14A7} zYX(WH!w@Z5;$sNgI1Om*3%0%!lFQ=2r@apBEm^tXHK?Gyv7ofV0Nz1OzZIuO44|9a zeZlJkOTo8@`GR{TpdDqn*Uh4r!Js@rO1l&^J2@E7(<8+XXTWKPwA!E2+uuN^j-j3t zoDW~64Z4p2G)^lv>_^G4uQ&pwLF_V+y#A2^w3CXGb2<r?qOiIUbgCApy#m>n3F<FH z?oo#DA-gz1^Le0C{XjJgDBmYD$Zk8={&#`aTKUwjo#`QobFs}ALRLSKOJ-)}#Sj_@ zf)CQT2ZRO`<tN90R!6yc2I(b(W<)?MAMzPupeG@tw*?^UgHT05>sDYphAG{D-bD8J z9cYxIl!3haBSGUFptEowu_Sg~;nWL5>m!#_)@M2G);j<?vmSX27Q{Y(Qyj~DNeKg` z=cf=m@<VC+6wwC2R?ee~fpb>~dlh5<ywXW(tC><>ba-JKhqYoD?CT#9tsvwU9H?A@ zw1xbLK7pB%9q6RZ9e~Oj(0vz~44@PHAY~1Lk7%)jV~JrR`1DEow7L<kHrzdJSS!t$ zA(SDW0TKS7dzFhAK>Kh&^BAD?$iSdW(cK-ClmH<6LGxMYZDHh8q6c1o5YJEm?x%xB zc1su%O6BBjpEQ<AgsOIU9&ZC}yTzE{B4y<F5!vM*Y3*(5l^?jrSd{_{-Tx<M&pvqd z%iT^+*%litB_`-D7uXyk=%hem>tEz}0p(s$s}8h+;v9wH$H>UQpu)(&0P34_Gcqu+ zGD6NKWoKky0F63wGD7Z(<Yi=F5N3p|+vR6uU=U<vU=U!0tRrP+gsdkQV`N|uWn^Fw zXJlZIWQ3gYD#Zx7>r$GLfkBp$fkB=Tvi2Ty{t)O4Rz*ez24zMD1|voW217;$231A| z1~o<o26aXT1`S3A22Dl=1}#Pg25m+L1|3EQ23<x520caw27N{b1_MS021iB)24hAB z22(}`1`|dG1~Wzm1`9?826ILR21`Z;h5$we1}jDe27g8d1{+2O23tl320KOu275*Z z1_wq425&|N1}8=a24_YF1{X#K23JM~1~*0q26sjV1`kFC22Vx?1}{bih6F|i246-7 zh6qLmhA>7325UwJhCoIJh7d*uh9E`;hG0ephEPTZ1|LQS20umyhHyp(hDb&Rh8RW$ zhA2h`hG<3xhFC@hhImE>hB!tBhAKt|h6+XohD1gNh9pJ?hGa$th7?8yhEzrdhBQV7 zhB8J5h73jqhD=5VhAc(~hHOR#h8#u)hFnGlhCD_FhI~c_h5|+ghC)ULh9X7=hGIqr zh7v{whEhfbhJXM6|1XEql~5Wa2CB!=aSQ{jyn@^W0xGYHperyL7z&9fpRn!NDq~1x zNM?Yv5J7vjQW#tqiWtfmVi+*@Vd7ScO9s?xfs6%4gKxegx9q1z`3cIukkhS$7(lHt z(3lWtQ~{JfL3>Rhx2r%x6f|a%$H2hwY>14L<FAMP7$7T)92s(;y?sO-4XK$IF)%QE z803BhrFYP{CFX4~koFO&^&<lVXgAU`21@#5gxh4G6OD@*vKc`4U73T=VZ+@XA+7BN zS}9z_kc~8U3#x4}MIkY)in108)ItEAL<p+aKqrc4G2}6T@&_cHfLNgZ7AUkqbFZLw z9WiT{5p%zwlTZywnja;+_73}aAgFZ&IV7F@@lEQKF4%kjpf~}QFz9op*j0ndOHkPZ z+9UI4kmpC(cmpT~K<!gdDgo_J0PSZ7t?h%2G=M??TioFff%$aY`A&X|ld@C`x(S_L zd&WuM-H#}55iwlCfPEi7D9yHk=OYKUhR4<72d(&n?EEEvT?J{q7*KeVcP<Fw9q3gI zpj)ZP4QW#Sfq#r1+o&62b&@YbFhe*)5%@d+P)&kf5`u1Fgq3-63=9nOs9xrQ>M%%a zCz&Ch0W|+o!2sHk1?k;|GJx*V0HtcsNN5RzHiI@p494B9AW=vyDnW-*3AEp$m;o{y z0&+d5%?cV{iD4kO<p2tG&{zy8M<bt29L#`iB^79&EctaQaj6TlRfImb2CH2#+cTj0 z1mq+18WmF|B+ir>7#Lc}PFIw)4N%8!AtxW7A=@Ryg)E{CMrdsiW_XbjvXBxLv?2Nn zoqR-oEscKK3;GBRC~pwImI&hW|8yt`U~x&l&k46r@Qo>eP6|Chr`Sb|*WxM(&|?jo z%aPT<>J@$P+8o;U@?bHA@CCVLHM;LneFR#&0y^V`{8*u+JVRX_imgo_1HQEfUtR*m z3g|?GZUzPh@=xNT#21LxEc#p$sQg101GyK}x&@sauw+2<0&+@l1FwdLjM`O##{iQV zgtrT>c(FxbZ`77|9IkW2u7K)s^f5$A+KjWv9>1neY~gF$A4yl(6siAGy7}mSGr5~> z-$1Dg-S1ll*!{@y2TEz!@(;2aNXnb9Vb~~<_h`GXFQlan8m$4fSwXAWV55Gi46Y0n z;FUB4&fh_q{~=}`{}MCkY&M8WV(ce${tj}SKupC?US$TYLxiXyWWN#kL<WdjY$WLX zspkxk`*cC>z@`RW>?Sh<1L%%&kT@~+gVt7q)MCRR`$6`8ht4%&Q-dycml<@|J4Dpa zIy$CF6n2{z$Rrph?vCp{gDm}n+VG%xF;Gt|kD&y7UIuIr8D>7Pozq;!l()KOf{)3Y z<xl0Lu${4w|5o$`gUtV!e*9H2?akamiyyfw*YAERdHpE1H8`NLWb)dBgRT4n#Xou< z4l_*Ve%AD5NiDrNPe|(Nb%%?dptXCjbPuCJr77~9SUmVF7W7?EeFGBzxN5BohKCW) z@8-#^b3Bru_y61d#97#KHgc{YRQ?z;n1feuLP`rvlAQD3{6R<c5PkldIRl~le=adF zFkB{~{0k=W{3inUk1~SJ@`t4w%n-s9i6GH_Lidk?ZW6vqg8$P=I{(j{(D~~m*Z<kf z3=CW(#6N-hpJe+Bm?3K#Krur&{aX+`{|vNh_9+SBUrJK^TM%{r87b+nikX4o6Ug%n z4A51N_}YIKM4f+sn*{&YF(dAuA}0I^w0{_`gJ&U$KmW0r8PZ+@1pzVkgHG}Ssl|rL zDgWA;LG$7e(}=O3Q27J8OOurNpGngF3l;>+-<u2!3=c?%|K-dK44}O-pb#L&{{;FU z;PX!(l3@R86745Y{xIHUU|_gUg8g$zI{y%K4jd??uwz*JAJm@!r2*`!P(>FpL)LbI zL}|VL7Bo8mnr}e81s+>371A@cyVm&YTkz7n8*>ldT6XVv2ro2TL4Jc_kh@`+()Dix z<9}?eREYnjrQ@xAJHPrVeUj9(mawhI)_#Ne9YhoE=VUPaTgE&oOvI^v-lm0Tq#Kxv zk;iZd<v&pW7c@2qvI`rAm4C0H@q|qcy4XXK`rn2`)j!zM2D-g4{!?ZK2GA%ph)>x6 zhJ?=F1eZT=N$~#*X3$Q2knM!)U(h@$f$(Pp)#dL<u>TD+1H(3m^$c_9+JC`b8h~a> zK(nR!3~u0+WT2T<NN%WIc~$OP*KrBSeanxTJ=&=S+6x4WX&4Qf0Y;qT5P-B+88TBB z0QD($Y64yT51W4kjj^HbrU_>7fR4B>FK;f3?%g({$yed#j%iu1K&=m$Utu)DFXZ1p z3>iPgG!bDoF|5J9{{a@_i1B&k(Ev~%A5<0-KVr0Tc8PDznpCN<3tidaS0-3u8!;j# z++b=3`usIQ<sWD#C4u%g<2~?>eB#^R8%b*aSWx}^^KB&c|3J5wz+wxNhSh(h<fGk4 z=Pwfz{-6>7(`JYWIrbl5hODs%nE+aqN!%`K^n63=esKkXH10B4=c5yx!umJyUQ;L3 zvH`h{Fdi!BuTrD@$zVvkwqjPd#>1L|Soym4hqHHLJCBcWm?5iZU`k=I0<TV@?dn}( zN0+HJ!;L$<k<&4#g+R=CYsevn${yP1&$$sX{$oj0`};8o?a!kmjlWoePvJrhbp#L8 z{{`KW1R75OmB9!Z+^lKL3=E)EvLF@2l)r?=pF#G%Ai@8qnHd;BXJ-&=KY{UIhGz^6 z3@=Hr{~R;qz8sMKp!3g4z-M8QGUE@@3CAcGJ~|<tZ3o($jW`7`GcPAIFEt<}0&xT& zLI}>ncJLqECM=9;1JZs+lrFHDm{JDN&Yv6x$Z5054Ei_U+DE<O%sTzd^Y}+m9$yt` z{D9I541@0dnm54iCzSp{{Z9id;SLibr~UDs8FWq##00|j8!!+We*=Z*2Wa?0OvO)r zW(MuSf~X<JeggTA5p=H^=p+V^J0PawC%=<4{$W69{tZ0-0J0l|@!JfM{msn402(g< z@yI(r0Cmk4<!e4~EshJ76)QRXCWHOlxydJ=YCvNX<RcgcxfT;27?Avf>VNdrU9esy z`aCqcI>;CY<NHO@xqS7(Yu1~F=rcWAi|zCUOn<{f273M-`+gfx2w<0i`1#1XiWNLU zw#W3g>xVws6|n<!rW-7tVKmH#_~{b^690(uAG<F>{s)aKpr-)rsv%*L*xxyu`^oor zZjFVK_To9L*v7gr!-7)(6I1>XYX7`qfZXQ>iaAUdVv77nDu0NvpTPVV$@9O=EDQ{w z`>H@LBgTFL{cpxM3=9lkp(Pf`OhTBQg|ziw1n!?A+5g-~_LEos6O;b2&GFz%|N0(_ zqYZsuMOw87x84_=<jxNbQBbOaVa$+SF~IpBy-Ww?d~`8L{$6?UdH(!wJhjhNkFf~) zHBTbGj>GgdLS&%V-=X^z<bP}>AG$h7m>BX;EPlrpm>`mowER`o4ISi}Dco_6@I3>= zVDvw>l#kv2B^y>3KR&0brEe)~-Mg_RiFE(ZpfLRtF8>S(uK#(@06G5y6l2J7gNx12 z!oUE!GZiFH*nZFqBuFK84C?=b>;;`$0-~|2LKPKaVPJRy6(VduXapRp7z+im|2G5V z{1jsC7h?gP>k2W982btJKmH<>ln_(#lTs{*_1Bb^|FHfq`fV5)3@ujc<wI=0I;!Y@ zyWqE(@gb-e2MQr%%)qdZ!t_h2`?1$Zpi+#uJGvzTPJfQ#2ok97*U?c`*?5}N9K2+J z{f|5v3i37Vr2ha0&>7`<&^bx%aB-KGl1pie#hhxi1iGUro%KSbPx|ivz&`(sZO<2I zWVruya`5#Zx4P16j&8Xd&|Qt~)}=!O5`U=uPfGgV#FRe-)?bm_{*+~5U;vGmf>Hr7 z_7m!VfX-n511$qVW)i}RET9{lAgYM5pW6KoRTjkfD`ESMs6PIq$pTrg2MPgDI$y`~ zQtHl$^?SD#pRbu>w$MTn8kVRfHApok-ZUWTpP2p!_L3ds-zat^&TExBzpOvOQPTGJ zQ84H_cx1nV*qA=WE;7*Lx9H_JG9TpEle_(2c1?2O+2xgK9-(;5727G6*nLKv=&k{Y ze`3l%ME#9fK0<mQKlxs)J=d_*?N6%I(dkvJ(@3qmK_Nux`Wy1vFU0sCTQ3ANB#0@0 z2$jE}GXz2B1QMHXbXiCl|2HBy{|B-chCy*c2ph66Fo5=qfYcG=e?sFwzZe)8KxZd{ z+yF9@5H@9DU;wR|0jVR#egfk^B#-}FBH0hg32Y6Ue<amzy4`ZC>GXW|Kk3A;D_AoK z@;`dt8q|(O7lZh-;ats`+RIUzF=x6DEV^1ai+F#6;tCtyF$nyRT;_xPj&1w}Sq&sC zl%=oBYMy%&Y+N&GnULE?4Q%@YvH2KPY%uyCTgiv%e}OIgyTmlCr-Zxf%Vf*HvB!3| zHmYB-@#x!sfvsjlE&pMCaP&Fo(^@TGI~YHhPh9;p({!eJ61H8A#FRe-*8eg5WME(b zodHX1`DcqX{y>cV1lB*3-2ZSOsr_$E)cO~Y-$58#8bKGEO2Ym}BL>ikJLo1L_@MD8 zkiDSuctJEm1~<!{1+o8Xpxf`r`4Uui5zf0A41w%|XYb8>JXf*6W}<{|FApVcT59kA z0Ig+5y)6K7ZW(CRH)w4;=-e&P$(W$E>YzJPKz9itpJ%4WP{IH@56Elk+}gAUKR3S< zsmrlDed!Rk+UghC^$+Car(_0%`;d1#L+(({hK~O*Ft`qybJRi@Ag8K<?srLMC<5O+ z2I}cBFo624<ln#o2@hh^1L8~`^s{vkV@&928{0S&%r}ranSsHRfq`KG)lZKF?ZASh zJmSx*b7laoiU*}n%#fC1U|`rqw{vSiYmh7%K(5Gxo?wT3pAV?D1Il%va7R9e4pvu! zavA7UT2L;7g|Y|(Xayd*=hi@WPSCFBM!4t7zyMlrxq)sm0IGv58F1g7gqhOa7(gc_ zppGIlFoZG?cd7_=?y&@&jtp{j5JL!qGeZFMEMm|}vY`71WAL1Oi2E#WNG^ihOM&f1 zhja$e+A4BW7HMS=v1_W)ODI^*K#ZcH#~&z9fpR}Fy9*f@_78!3?}`}+pIQgHOAR#Z zQOV%Q06J?JQU|6mfJzY1y+)wY19UC*W(LsuVfw@|qMiZ83BGbJ80nslU<OEO2Rhj; zkPhd%;yzszSK34GpM%<dARnTym_k<viUZKeX`m+b8V1NMccBc}_bxaxfNlwJVF2aX zM1~j!ABKE}Wbl1(F$|!37j!;9=te~Jct*GbbcX??wT7HSjG(O|T?SD8C}Z$oaAk00 zaADA6C}t>Q$OE4kWxxPx#er;qxfpcf#Vfj%*rcD8j((mcddQO+LtP9E43h}>7u($t zpj#9&8A{+c=jbwkZmKF^&}A@W&|@%R&|olR&;;*T0i9R|+BXVm4}#9qMYLtHoxuxf zC4%m;dO`IT7AVcYY8G7v#L12L+b-npb^+Ojtt<eQREV|?DEEspFfeQ)5DOsFD5-Ht z&;6iwdn!0zrb2I@PiFw#1Dp%ZSquy;L*gV<NS*=p>ri*|LUvT(zZ;K%VKqgqXzH~> zk;^tvE+^av2lY-NXI@;PaoU3AB-*ugkbMs;tJxSqD|F~p!_)F+TS!bb48T1(gxg!# z(itcQKy9pEI`$OEPg{ij2pgLPorzw;kO3~=N*O>U^F|`>R>EB}Q<D1;?S7c+FjsFe zFkGYKO-1D2Yk)cuj+~c4B^fCE&|8$qYCx$C)S9dw615HC)P~K!AU}d~)OwnyH<0bv zS|zalrZad92h?l!VaQ|vjY)xa%at&I?oMD}038T-Y>?L`pfM(pyFtB%cyPP4lp&r0 zecpwEVI~6uLk$CI_b4YZ<b%gDL3gtw*zpX84Dk#m44`|D5TY>*xeTQYpizuWhIsIa zC!p3-B?G913+nSQFbL7{E^}hbPeSPfv-JvES#XU`X#!LRfzEoM^oDsz4hUgDKFtPO znbQFt>BZm1z}7#)bt@jFck9J7fO01K4W&^G3=D11em3OpC0I*cfdMjK0P1}yFlaDT zFn~^q1NF?1(<y>YT1t*#0QIIo{dz>2b_4G-!XILwG)75Z2G-sLjU9qke&ZT-MjzKe zlnm%SPf$ICx$Xue3UVuY?S!ckRDOf%DA4Q=_3uF^cD{qyJdPeNu(BV~=D@x#4_A0# zR}Bdh@^;Lj&Wm27aQ=+i;Rh-`aLpe&F_bc-F{CkoPP_u$iw3%zuz(?lA(a7H1`;;2 zUgTc&+xK3)QCLsmeX-XaP=6XUKa7qE&%YZJI{%d9`F9@{1_sc2T~JIAV?TlUe@4*V zv@m~x%p`;ZSr`~VYlcDUh_Ro*`Ugf((*@LH1i1lZCLtWg0@*JDQb&ya1lFH{&(a0m zuS=}`0W65~M^90h{s-Ir@A&c$Xq*N#77Q8DjRBAAgXT9uW92~%pz$bB%>lcsb*ba$ zejWa6!A@z?i=R99yywKSD*FHbD-?#`Jqq>Hv;4sqe#H!+77u1Qv~GsU;S-C~&okMd zcMQJUbp_kb5z=-%gK`Z5)6@Tm`5{nkh0KGLxvPU^tJ=@yF4ayxd%*RB=|*`hDF#$- zBj0C9Pxm9o4#*v#`h#%!V?@;X8=$+wi7kIZSr`~VYi2>QNR0gi_P;TJZq4FhB>w!l zRHX8cu>B@fum6%*Aa|n^=YLS&52O-12Ce@B-McPAg8#EwAa|mJOvKiH$Y9Vub7|I# zUgi4x)3*w8f90BmZG}}Z11SAadj8|5L010ZYQLh6Ga=^2D6KySF8>EHBr<^R`vLXe zAmx|o<K*a+Ny78z{J*kn(vSL3&?*^Fo<_##JGL_!^1$n{Am<38_ht~`1W^aEhq4n- zK&cjirwse^>#+BLKs69{8Bl-O`ZoXmuH<tKlU)9Ly|iC86ZiQlgv&o;qRM|h63U+f z76t}Tc@2t1V(cfd|Bz7}YBwmIg3Kg@OIbi`bRen-+YcHSf~dttg34chMg|5+s2i}U zK^JQvssCX@_5Cj`pw1v@xD_<QKSau3V#W_}<p<Cl81+sZLXA!G&)=cVS}%P4AJq0A zYWbhRu=YyWwz+TBlyA(R;`qCG=4KfztyfgPLwL}H1m=L)pxu;U4wwL);sO!{VXy!a z0TRn+$YOwu{WCDo>lRz&u`G}qK-mE_+eM#|MP%DSWBs^RC*o>}p!dR1`H1+3)U^x@ zIUp}GKxoj~7Z-SVgTg&C**mo|BtJVf&o;^2Jkd1S)XdP**d*1!!ZKDTI=Hg9BsEvh zIX@>SHMt}+Kd)HNBe6K6Brz!`RYxJWI61#4Co@S$AuP41I5R)b)<n+$1a%agOLIy} zi&Aa#QcFvU5_5DE0!ou|@Yxj$TF}J~HHX7FKer&UD7A<KWLt1%RVo8B1IHvrEY5LH z%}XuHOxA<au7#zEIhiGuFar_{qhX#1E-A{)OQ*7{L4&dkoUkO%0%8nCXM!>vcR*-B zQGRlaJA~FNORgv=${+0KCnLtyk>^lAt)CJGL=PPMdQD^<klb*JvoB^gYl(^1ihCKw zj1E&l@dau%AmdDiEC$f-4AAI2Xgmnivdv*A1<zN)Mqa}h{1_lDcS`#|5L1#EQW!EB zKr6REwjs{rMyw<wx746k_`%jZF5bBIjj`_KTbhTKpJjgim;>9W818lAn7ycxe)_Zx z11wDJ7<d`DpfhblVMQyt%gL=h$yv#QNY#jTVF^*UL?<(#yBh>RW5JmW1q`VSkekp_ z!87R$42le(n_}?I<L5CLGZ-;gGlVgCGPp2!GB`3QFhFW<1qM(F0$KqKSswuEYb$_9 zN%I+U7_u2aYg}WXr3mfvKj!=!a;XR^N0IrEQgTPgjR$^Sr(HJ8mOt}zvw=Of(I3bR z!ZC&~!>Ip&Z9N+Do!FV+kq%J*`sllbSLS}m_3AqE^Frq)kA>Lw4ii_BuVNfJYuG2W z|Jd~3Q9v|bvT|O8l&2@C@q=7*g4pQ3E@4Py0NpMNnol=pFvi$JMBnopDJeI^9<7qp zJ<&She|q8S0I3JQ*v3>!8DdaYQGoInwp0bmXP`A%wG4fX_(~Vh*c|HG3SWjy2GHv1 ze1>9%e1<fJ9~9<4P#FbUX#rYGM0#t3-1X~_5)!hW1hfMcQHmp%MxfBcwr3Jq4JbD< zFn~_r8;IT`zWy7cCx-ARtTe{g69<h}K+Y#zN8^=GxYzB2QU%EUp!|YuPh~IzXgzBY zLkU9w1E~In!~$q;Z{YXe!onCil@OnjKxf_Gqj9*y))<l7rYB}E8zlBXbF!#+s)0(K z4Gf_3*T`+J(PrEVy+nY;BBEr#KDq~rS?n^PG8VLM3UoT@8al-&Vz(Z;e?U12vT_tQ z-V@J&zETY4Vh;ue272wQ0F7=NFqkorv+5B&hk;5B^cf(~K0A=VAtgjPU03jty9W-| zBTHt;VTfTs_a(OaJOsRZDUYF;p#Z#wHx)$}EMD8e_v8+)d<QBgaIH5(9a#kRkwCY_ zfL4i?FyLD60V-KQr<>Eqm!MW9DXTPLr3&UMOHet2E&XDugvBOk<l`BQ%N5Y9DX8Rt zthoiT5N$M2S!2dvKtj%i`5sZG5*k4R`4v=S<T5ZYoS?B^3H1~Zdppr*m2t%#x;l_= zKsAB_1E}{(pA<@d9S_-Ch=?onatM@KiQA(OX;B^k-;zGKeGN+CpxOfUwrG4lg~jP6 z@JJ20z5}&kLHmxgDauXQ+aaLTiCqR#C*EKH-G)xC%jw%1L#-Ln^B2gc*lI^~b+9-C zof$^!9r4sHL9wS#VoFg^xsy%Db~mV9N7`-{%-R7x-azpS%8Tf2Jal!Cblu6oz(D?f zE=qDFYAVI7!EyNmQza~=f=&xPN8|dB@?If&je%=-C!{{u%D})d1v=jV8KHo*GIAMk z@9aSzeJWv4KsiN6fdSNJECtVI<ufQS7&3s)!2q>U!Wf(xK&3ip%m7i7gYL{rVF1<q zN#K19pzvm3uo{GWf5~kpV>{Oka$DoTkCcE)Gg{9-VwNh{>q1bT#8!7;R}Cs*9t1}Q zWH2=)H?!`ZRJ{8AUu^4DKqJy1jC*$V9EIbjv<^Q|9AUPIAfpSQw3P@x;|4S@4az&o z451O%vW{E`GIUgobIkf!vH;sCbtv@A4baFd;q`B(gw{WU&mWd2Vg1_+76t}A=&BTA z>?d^ou?!;vgDMI3zhz-yP$9v70_XoTDljrID3M_QCz95Gn=%mE{{%|miX_<o9cle{ z$FN^N8vx!fmj=G$7Bp&|$B@jB3Z9z<wfI5#3$)`WgMlwcqDRhT$x2V@Nqw{CUa!D* z8a(DIkbjIrdj$h%_8W$s8E~(2!8Jo&z)-@#z+lS&TE4}=V9fwJMcR=8a&8uAr?N9c z8UyG|JX;112GBe*sI8pDP|A?bP{R-oodbuBh@#FN;ya%Y6l;)D5ysMEC}T)ws9-1n zp9qRrQ^>%8zXuNTJ815UT-z>DSbu=_4uUsOLdzot77*hjzNKz1nc$^(iA9wOMij4Z z8%q24P-o4Md64t&K&?s8DvM(9>J8hZjzShqp_}gFZ$2N6m>|ZBbv^4X3geI3^*_FL zCbExXQ1<*{`XAKBHwrQBOx2w2^xbcFj_V9{R?vtgsIEZ9gzI0>7%M23V8@{K?;!I; zNvMB1SQr>U`(;5U5@SDs`X8KqKyCnGkeP&VFAD<$=*~`%I>PoFGZ5N;#?HvVAV7lu zCz7=O-<W~G{Tn3vKaquj0kmHX<o^dlDE(v3vcxd>G6XPqGJw)Q=)_J??=um66FcaH zR!EDu{@oVKm2z*j9;LaSTzp|Ww;0xP02CJF;A2B5{Lssf9PoLVpfkQR7&f--%zVu? zM>^$qXIWBltO2MV2jwSZj4gbIZv6q-zld%BBjoh;1)F$vZ67-DE!MG--21oUw=mXp zL+$b((zAn%$AJ20nEhw;(Kb-enYd9oP@RCje-B$L7h62z5_>U((m(F<1GM`e)GNqj z0JT|BOA%0O0u*u&nGWf*{kyz<n$$#}^@e)W1+b<ET;YUEe4x)CAzc5N5Ox2Y6bbd; zOcn+P(0)Nsm=I$>f%=b8m=Us{8dO$+%p`>8k#znD=yXQhCTcJ+kzD`YXJKHF12yv* z7#N80Kj`LETrL2$zl0bW7-UHZ|0gV<87GK|=zT7H?N8Kvfw~?5l!Fo(K>H;iz3FeO zYWBNKQ(3h{)7IT(_f#X$+AvW3AY+INpaB8lAdz1f7{KQ`gX(oB23TFC!2s&D7c*os zq=R=Jr+`;bgGP`+t$$Fh@5zwDP{E+XpumvK0OEuCiJ(#%GNS<MnJO?K%tLm!4MPZn z9fK8v74)QN&`O6CB((|*Y7C%pYS1at*%&r}N)C|epp#&27*NM8LH8Aa+z301+KvHa zRxShR<dH-Mm}oJBCIbVw2Mr4bP}pq0AY_$u=z`!DCx!(oUb3L62ymwnrUtZo4aO!! zgUo}e0kN4FK(`oxY7q2#2m8D;@@i<vX>*4og8#(6czXA<^9iFsy`v1E^M;Y*7{rFz zh>ad9`~R_r2Pot~IR=!{h@XoWxO4B=9H0Ly=Qh-`hEM-D2Y24U7GkJk!+-xf`nm^V zRs^S-pUt0it4(kso700ZmmRI3U8taxh>RiO_+gN>U(oX*X8R94JV;A_84O?V7vEm! z8uI1k7A<}59>VpHDWUt{!Q=n1kcEUQe)10s0|V&H5|Aol>?d&k7RmK5BP#<#8L0Rp zs{S=4)c*$iA2go;3ImXz2w`?sME{!@{}ZbJLH4SXQ2+C=LfT6pcglHT+r<HrAr7yA z&L4nA#zC`Cpb{|~yo(C5ixN~af?5TjxnI=LWyoC#pqd$U9ui`P4x|IIBLP%vg7%Mq z%3sL3gFFV%s(a8pGU#3akhzHck}(Xp*R&TiR5Ii;fX<@??ePNLg9M^s^&e!e95Q+g z>UV+cOJM-r8d1yuav#V=pq2<s1<0RC;4{N=8DMh@5dULS4Vtg)n7HR+iaw9dZRf+0 z0)Itvk>@KpR2UeF7#SE&sxmNuX434{85lGe85kz&K~f5%0Rw|IG=@R3jfk6fLwx=L z7XF|fET|*{jprlpI6yxWcE4TFqmU?tJLiu)$zm;eR-8s*FKno^KTy}SU>gI#HhYe) z4l*KUJ9px3m6VGY+^oW~FJ9x_fo=C1B8A~+?HgqEC$4k^t4Gn(Q>fj7PDbH)L4`}l zgKfCH;z&(T2Zpr&lZohmniA^&lU)A_vO@MZfl?MR^&f%pcScZOS`At{g3Kg@MOhgb zo<S86w%?4<{5yj(BLjmj3HD2}Le>j_oIs5Ipgl<-wb(GI{|~AWU>KVkbTK(r#Q8_b z3=8kL>|U5ue72>pqtk3nbOLC-H_S8`jcx*h|7V!gKj`glP-zMpIdBD^xC`pjmoNk} zlrp4(G3ah5(3qfSZL7z^Ezd+ez4IsB7ukQ2^tRMsJ3k0L9AM=pq43CHIGP%B>H2E9 z*G$hkjvSYKI2+q+BO=|vSfs8X1g+-<?aKkJF-&EMVW@zgz7N_@U&-+A@5G3{qlYG{ zdF03YYdx915SlMxDH%k^FhE*+puM^i2$bL0PGAR(K!I!nwN*fA1oMU}BMNS)x|UPN z*zx4jFV6XjUu0N!-otiA323Z3o}q}Llz~$Bhcgr~q=8RTL5!&tG3YY5GMF-0FgP<9 zF*t&0SbU+=S`18#en@RFba`z2bu0`F*&uHb)&DXh)c<7AW@KQ{g2pp8`_RR9urM%y z#!Nxt#FW2;)_;N06bysRB!u@O&A$?3KY{))xc{j`g8$dDFfjapIuewAG55G(8)HH2 z<OB7M7#I>5KsN*6-?xA*_keORr012&5Wo<^5W#@$<^pW0FvURaSx|iy!N9<9iB9XY zLHn*u89?`nQfK$OKJ*?1P-#-c0J)10GAGNx;7iAyp!nBGf%?CYbyB!$2=v(jR6eM0 z2ia!`>I*Y4EFQ4^I;cG<P`w5^$JPzHJ`mK02d%9Eod%xEfGi41YdH)I3{BvB4I!s? zK;~)bzivK-A&DU!d_Oa2{V&L64B%BNN9dLZK&yg4c>veiJzS~8AAHJA9)mN34?{e7 zmIQQP4WxYq@&{<23~2S<9Xk1h(o_#B3nAk~=zVR_2obs%%!dmYKqp}1FFQd!S;)v4 zIqNbJ`$VbdL-dhnY<UNBjvZMS%>T<7D7jw`GLx6bK&#z)*!+b$p9jhJ$>7`EKq>az zAiMz?QR2c}i`aLA8B4I0RG|4#Q0o%kN(csq?+o<Y<4tV(CC|V3R*o?+d}3f=m`Cs) zuNVeDvd{l=W(Z|)VQ^#sja-{B=z;fhgW5UBeVi1AA_ho&6fwjzlrU6+%TUm|6wp0_ zDRkT|1uFH)*>8_t4uZ-?Q27QK(FfhR4Y@HDT^*#Y09uPi>s!EJ^9&##Vmqe_k$2GD zNUUEVxe(S?fW@8~0|Uc0vg<nPl!VA7G{`sD%0FZ^uu}Xu0|Uc1vfWPKoy&-N8hc5O z%iqW=9uVihF))DE{N5U5e#ch=qOSc!tbOES*euJyz|HWO(Z$NZ%D}4H(%9M5*}&99 z*WAg?T-U_h!d2JN!pT(E)xg5Y($v!2(bd(lwg!5LEZSkRSv4+JF`>n&Ma41MIhk2` zG0yoZsWHwurNt$wMKQr)ehS5<Nhz5{#WBwLNr^cnx*>_h+0La!MX7luF+m~D0Z?(6 zu`$I($uY^LDTy)OsYQ9IIUs!*sUX$CCHdK@#Sl@wjGCHi7Xw#AM^k4rT_<BxOI;IJ z6GL4~Co=<GV`DQ@OCuv^BS#~n+J}SElO?IeC2$XxIOk*WVRACDUfc@vqN9<UtGS7* zp{|p;iLtJsp^Jg8qpP8#u7QE0fwQH7o12@naczx_Z)S0_TV_sbu^ryHD=8|?%f=c- zVAq4=sKhfb8O8B>#5)@vGo(8^g*r(tEVH;YF(<gBG$k`XrnoXUDL*GOIX)*dFS{6? z9Kqg%B)Vc~j1iyswvit*&Zu5=EGTeEEJ=*<$xlwqi3v%~Er=;`O3X_MNGvXP&Mzv0 z9MuhRr;%QAvTjjonTM;Ri(YYAo`HdZUP*2N2~l$m=I+5=GC;gbg1?>*Zhr-rCMAL5 zv?xEhIL0Y8G1o1>C^xmpEit(yzbG@cnD~Tue{g#g5^>0WgoqID!6!q*gA|tRHwL9I zk&1s*|B;Xr2WbM$NGwW?NiQgk$;R6F&`U0@sj0SbGPW?XFtX4!Gcqs&RkMb=78d3P zx&{UY25uJSrskF|MzxF#jBE@+>RisIf@u!$s%}t~r{H*mWW3@SN*f{E3{G%gl3t-F zTg8-s>$KFOlGGwYV*?Wd<9GuDgP0ODfq3V{0?;iJ1SR$I%8F78@{3ApYO0+LT-+R8 z4GeWn4a`k-P23EfbuElQja*AtBM>w)Ff*yGF)~U`G%_?#)iq8su+TM0PBzd@G&eWa zH84yuGcimtFtRjHc3Kb1k!1NXpeR4RC^1(7-1>^~3-fgGbX0H;2vq>xT~S(+nU}5* zlAoWGommp&9O~j2Q)Z%P5R;jgoKu>T8k16#S(aK9Us72>N=@K&j(iuB(Xux&HZ#E8 zW`rj;m(-lZO2@nuSQCbXlEdjJeLZAiV20I0pnghzL26!dP7JsKmXlbLU!<3uLu>)z z0?#hPw@`piIKn9j21X_Z2GAXu5FwodpmjdV3=9k|L$nhE@dtv0_z?N@4%ket%TVnM zAUp?QLA(fBUJv0jFn|WMKx~)c+HU}P4jDs&Kn_U=6w9D}l`ey{VS_%Xg6Lg9%4FhO z-bkJXGa+FC+9?a>GUy!uowW%%8^mQ~G}0m7Mv#yI0bL^k6Vqd00B@3V8NTg#m{(ym zBoGRqD}F$twM>ve_`xM}Yte#ahY>OQ$jab!0oGh`vUGGYaxpj5H8lmbLS38;buCPd z-E<wD4Gf)J4UNrREljC8%8Iis25P!sw!p|}xscun9x5YupcIRYA?X=(js!$h?|>=; z0|Thka~Tl{8RBaM2?-C-ZNV@xSb^s<Vg}h@K8MkmVFF4opq9a4Z52>&xC0gjFd7mT zu`n(J14AAI0|RLPQ%6K=k)CUg0|O(2)1|>#HbR<2#W8N4LGf;xIVGt@Bn<or*#GZ| zyjl<T-wBxiNG~X<-uDJiWR%2%sQBdk+=86c3KD$ve(N^BXA_GZ7?>EGW>d{qF8Sqo zIr)hx#W4jX6<L`jF=p|ODP@Uy$*CzY5r_ow%~Pkj1K_&kf`S?%=Of_($;jZO8yo_e zfHku)u`su=u+TL!FiF%kG)ytpwJ=Xj(lszhHZVyuw=_3QG_6I-OO!EFQO*&A?Q4MS z4>?4q`EA6CT3o9JkyjspX6oG-G8sUtQ9-LU;~Cr-+!#U_;<4Vj;Y`QXF~qJ^!t`YZ zLn1>F186@3VrK%#Z+YMw2|+#%!MI@rv<d^X_Ic%yTKf!I^#fX`09oyv#sIoe1mth@ zeR&KF0SpWbd&o|Kl&p*)W`!`wAGl6@0L2VwrEm{rS(7fhiP?WZ?eq68vM?}!&I<y0 zkQn<3o&TfB$iSda!v2SAEDQ`6K$a7^|HK@s78^y*{<~W&3=E(ZqagE$@jrq6|KPKf z4M_0+Jr>ZdrXbsiIe*BUsPmr<Nw8mq6}0A!c>9q@P$9YyBsu3VsUz8sFo}E?Z0`?< zCf^kDb%N}LVNy*2?f)Tf{~ByRkT19lF9P3t1zNAUpzGL8=Skb&zj?Ln^K71_ew3VU ML#l5^)sq?m0CfbdB>(^b diff --git a/RTCP/Cobalt/VisualStudio/Cobalt.v12.suo b/RTCP/Cobalt/VisualStudio/Cobalt.v12.suo deleted file mode 100644 index 404858a92a6f30be2ab9ca0b84a16404a8ae6984..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544256 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 BR22XK diff --git a/RTCP/Cobalt/VisualStudio/Cobalt.vcxproj b/RTCP/Cobalt/VisualStudio/Cobalt.vcxproj deleted file mode 100644 index 59dc452569d..00000000000 --- a/RTCP/Cobalt/VisualStudio/Cobalt.vcxproj +++ /dev/null @@ -1,81 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|Win32"> - <Configuration>Debug</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|Win32"> - <Configuration>Release</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{8AD5FFB5-58CA-4A1F-8519-A8401156EF54}</ProjectGuid> - <Keyword>Win32Proj</Keyword> - <RootNamespace>Cobalt</RootNamespace> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <CharacterSet>Unicode</CharacterSet> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>Unicode</CharacterSet> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Label="ExtensionSettings"> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <LinkIncremental>true</LinkIncremental> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <LinkIncremental>false</LinkIncremental> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <ClCompile> - <PrecompiledHeader> - </PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <PrecompiledHeader> - </PrecompiledHeader> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - </Link> - </ItemDefinitionGroup> - <ItemGroup> - </ItemGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> - <ImportGroup Label="ExtensionTargets"> - </ImportGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/Cobalt.vcxproj.filters b/RTCP/Cobalt/VisualStudio/Cobalt.vcxproj.filters deleted file mode 100644 index 47cfb34efae..00000000000 --- a/RTCP/Cobalt/VisualStudio/Cobalt.vcxproj.filters +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup> - <Filter Include="Source Files"> - <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> - <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> - </Filter> - <Filter Include="Header Files"> - <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> - <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions> - </Filter> - <Filter Include="Resource Files"> - <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> - <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> - </Filter> - </ItemGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/Cobalt.vcxproj.user b/RTCP/Cobalt/VisualStudio/Cobalt.vcxproj.user deleted file mode 100644 index 695b5c78b91..00000000000 --- a/RTCP/Cobalt/VisualStudio/Cobalt.vcxproj.user +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj b/RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj deleted file mode 100644 index a70638c7d2b..00000000000 --- a/RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj +++ /dev/null @@ -1,422 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|Win32"> - <Configuration>Debug</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|Win32"> - <Configuration>Release</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - </ItemGroup> - <ItemGroup> - <ClInclude Include="..\..\GPUProc\src\BandPass.h" /> - <ClInclude Include="..\..\GPUProc\src\cpu_utils.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\gpu_incl.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\gpu_wrapper.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\KernelFactory.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\BandPassCorrectionKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\BeamFormerKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\BeamFormerTransposeKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\CoherentStokesKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\CoherentStokesTransposeKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\CorrelatorKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\DedispersionBackwardFFTkernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\DedispersionChirpKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\DedispersionForwardFFTkernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\DelayAndBandPassKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\FFT_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\FFT_Plan.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\Filter_FFT_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\FIR_FilterKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\IncoherentStokesKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\IncoherentStokesTransposeKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\IntToFloatKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\UHEP_BeamFormerKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\UHEP_InvFFT_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\UHEP_InvFIR_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\UHEP_TransposeKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\UHEP_TriggerKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\MultiDimArrayHostBuffer.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\PerformanceCounter.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Pipelines\BeamFormerPipeline.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Pipelines\CorrelatorPipeline.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Pipelines\Pipeline.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\Pipelines\UHEP_Pipeline.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerCoherentStep.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerFactories.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerIncoherentStep.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerPreprocessingStep.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerSubbandProc.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerSubbandProcStep.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\CorrelatorSubbandProc.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\SubbandProc.h" /> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\UHEP_SubbandProc.h" /> - <ClInclude Include="..\..\GPUProc\src\FilterBank.h" /> - <ClInclude Include="..\..\GPUProc\src\global_defines.h" /> - <ClInclude Include="..\..\GPUProc\src\gpu_incl.h" /> - <ClInclude Include="..\..\GPUProc\src\gpu_utils.h" /> - <ClInclude Include="..\..\GPUProc\src\gpu_wrapper.h" /> - <ClInclude Include="..\..\GPUProc\src\KernelFactory.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\BeamFormerKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\BeamFormerTransposeKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\CoherentStokesKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\CoherentStokesTransposeKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\CorrelatorKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\DedispersionBackwardFFTkernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\DedispersionChirpKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\DedispersionForwardFFTkernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\DelayAndBandPassKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\FFT_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\Filter_FFT_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\FIR_FilterKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\IncoherentStokesKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\IntToFloatKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\UHEP_BeamFormerKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\UHEP_InvFFT_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\UHEP_InvFIR_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\UHEP_TransposeKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\Kernels\UHEP_TriggerKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\MPI_utils.h" /> - <ClInclude Include="..\..\GPUProc\src\MultiDimArrayHostBuffer.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\gpu_incl.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\gpu_utils.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\gpu_wrapper.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\BeamFormerKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\BeamFormerTransposeKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\CoherentStokesKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\CorrelatorKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\DedispersionBackwardFFTkernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\DedispersionChirpKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\DedispersionForwardFFTkernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\DelayAndBandPassKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\FFT_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\FFT_Plan.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\Filter_FFT_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\FIR_FilterKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\IncoherentStokesKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\IntToFloatKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\UHEP_BeamFormerKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\UHEP_InvFFT_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\UHEP_InvFIR_Kernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\UHEP_TransposeKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\UHEP_TriggerKernel.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\MultiDimArrayHostBuffer.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\PerformanceCounter.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Pipelines\BeamFormerPipeline.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Pipelines\CorrelatorPipeline.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Pipelines\CorrelatorPipelinePrograms.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Pipelines\Pipeline.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\Pipelines\UHEP_Pipeline.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\WorkQueues\BeamFormerWorkQueue.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\WorkQueues\CorrelatorWorkQueue.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\WorkQueues\UHEP_WorkQueue.h" /> - <ClInclude Include="..\..\GPUProc\src\opencl\WorkQueues\WorkQueue.h" /> - <ClInclude Include="..\..\GPUProc\src\OpenMP_Lock.h" /> - <ClInclude Include="..\..\GPUProc\src\PerformanceCounter.h" /> - <ClInclude Include="..\..\GPUProc\src\Pipelines\BeamFormerPipeline.h" /> - <ClInclude Include="..\..\GPUProc\src\Pipelines\CorrelatorPipeline.h" /> - <ClInclude Include="..\..\GPUProc\src\Pipelines\CorrelatorPipelinePrograms.h" /> - <ClInclude Include="..\..\GPUProc\src\Pipelines\UHEP_Pipeline.h" /> - <ClInclude Include="..\..\GPUProc\src\RunningStatistics.h" /> - <ClInclude Include="..\..\GPUProc\src\Station\StationInput.h" /> - <ClInclude Include="..\..\GPUProc\src\Station\StationNodeAllocation.h" /> - <ClInclude Include="..\..\GPUProc\src\Storage\SSH.h" /> - <ClInclude Include="..\..\GPUProc\src\Storage\StorageProcess.h" /> - <ClInclude Include="..\..\GPUProc\src\Storage\StorageProcesses.h" /> - <ClInclude Include="..\..\GPUProc\test\cuda\tFIR_Filter.h" /> - <ClInclude Include="..\..\GPUProc\test\Kernels\KernelTestHelpers.h" /> - <ClInclude Include="..\..\GPUProc\test\TestUtil.h" /> - </ItemGroup> - <ItemGroup> - <ClCompile Include="..\..\GPUProc\src\BandPass.cc" /> - <ClCompile Include="..\..\GPUProc\src\cpu_utils.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\gpu_utils.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\gpu_wrapper.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\KernelFactory.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\BandPassCorrectionKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\BeamFormerKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\BeamFormerTransposeKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\CoherentStokesKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\CoherentStokesTransposeKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\CorrelatorKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\DedispersionBackwardFFTkernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\DedispersionChirpKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\DedispersionForwardFFTkernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\DelayAndBandPassKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\FFTShiftKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\FFT_Kernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\FFT_Plan.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\Filter_FFT_Kernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\FIR_FilterKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\IncoherentStokesKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\IncoherentStokesTransposeKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\IntToFloatKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\Kernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\UHEP_BeamFormerKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\UHEP_InvFFT_Kernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\UHEP_InvFIR_Kernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\UHEP_TransposeKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\UHEP_TriggerKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\PerformanceCounter.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Pipelines\BeamFormerPipeline.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Pipelines\CorrelatorPipeline.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Pipelines\Pipeline.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\Pipelines\UHEP_Pipeline.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerCoherentStep.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerFactories.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerIncoherentStep.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerPreprocessingStep.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerSubbandProc.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\CorrelatorSubbandProc.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\SubbandProc.cc" /> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\UHEP_SubbandProc.cc" /> - <ClCompile Include="..\..\GPUProc\src\FilterBank.cc" /> - <ClCompile Include="..\..\GPUProc\src\global_defines.cc" /> - <ClCompile Include="..\..\GPUProc\src\MPI_utils.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\gpu_utils.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\gpu_wrapper.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\BeamFormerKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\BeamFormerTransposeKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\CoherentStokesKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\CorrelatorKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\DedispersionBackwardFFTkernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\DedispersionChirpKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\DedispersionForwardFFTkernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\DelayAndBandPassKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\FFT_Kernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\FFT_Plan.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\Filter_FFT_Kernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\FIR_FilterKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\IncoherentStokesKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\IntToFloatKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\Kernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\UHEP_BeamFormerKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\UHEP_InvFFT_Kernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\UHEP_InvFIR_Kernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\UHEP_TransposeKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\UHEP_TriggerKernel.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\PerformanceCounter.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Pipelines\BeamFormerPipeline.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Pipelines\CorrelatorPipeline.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Pipelines\Pipeline.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\Pipelines\UHEP_Pipeline.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\WorkQueues\BeamFormerWorkQueue.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\WorkQueues\CorrelatorWorkQueue.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\WorkQueues\UHEP_WorkQueue.cc" /> - <ClCompile Include="..\..\GPUProc\src\opencl\WorkQueues\WorkQueue.cc" /> - <ClCompile Include="..\..\GPUProc\src\rtcp.cc" /> - <ClCompile Include="..\..\GPUProc\src\RunningStatistics.cc" /> - <ClCompile Include="..\..\GPUProc\src\Station\StationInput.cc" /> - <ClCompile Include="..\..\GPUProc\src\Station\StationNodeAllocation.cc" /> - <ClCompile Include="..\..\GPUProc\src\Storage\SSH.cc" /> - <ClCompile Include="..\..\GPUProc\src\Storage\StorageProcess.cc" /> - <ClCompile Include="..\..\GPUProc\src\Storage\StorageProcesses.cc" /> - <ClCompile Include="..\..\GPUProc\test\cmpfloat.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tBandPassCorrection.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tBeamFormer.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tCoherentStokes.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tCoherentStokesTranspose.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tCorrelator.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tDelayAndBandPass.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tFFT.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tFFT_leakage.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tFIR_Filter.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tGPUWrapper.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tIntToFloat.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tKernel.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tMultiDimArrayHostBuffer.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tStreamReadBuffer.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tTranspose.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\BeamFormerKernelPerformance.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\KernelTestHelpers.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel2.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\tBeamFormerKernel.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\tCoherentStokesKernel.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\tCorrelatorKernel.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel2.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\tFFT_Kernel.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\tFIR_FilterKernel.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\tIntToFloatKernel.cc" /> - <ClCompile Include="..\..\GPUProc\test\Kernels\tKernelFunctions.cc" /> - <ClCompile Include="..\..\GPUProc\test\Pipelines\tCorrelatorPipelineProcessObs.cc" /> - <ClCompile Include="..\..\GPUProc\test\SubbandProcs\tBeamFormerSubbandProcProcessSb.cc" /> - <ClCompile Include="..\..\GPUProc\test\SubbandProcs\tCoherentStokesBeamFormerSubbandProcProcessSb.cc" /> - <ClCompile Include="..\..\GPUProc\test\SubbandProcs\tCorrelatorSubbandProc.cc" /> - <ClCompile Include="..\..\GPUProc\test\SubbandProcs\tCorrelatorSubbandProcProcessSb.cc" /> - <ClCompile Include="..\..\GPUProc\test\SubbandProcs\tSubbandProc.cc" /> - <ClCompile Include="..\..\GPUProc\test\tBandPass.cc" /> - <ClCompile Include="..\..\GPUProc\test\tMPISendReceive.cc" /> - <ClCompile Include="..\..\GPUProc\test\tSSH.cc" /> - <ClCompile Include="..\..\GPUProc\test\tStationInput.cc" /> - <ClCompile Include="..\..\GPUProc\test\t_cpu_utils.cc" /> - <ClCompile Include="..\..\GPUProc\test\t_gpu_utils.cc" /> - </ItemGroup> - <ItemGroup> - <None Include="..\..\GPUProc\share\gpu\kernels\BandPassCorrection.cu" /> - <None Include="..\..\GPUProc\share\gpu\kernels\BeamFormer.cu" /> - <None Include="..\..\GPUProc\share\gpu\kernels\CoherentStokes.cu" /> - <None Include="..\..\GPUProc\share\gpu\kernels\CoherentStokesTranspose.cu" /> - <None Include="..\..\GPUProc\share\gpu\kernels\Correlator.cu" /> - <None Include="..\..\GPUProc\share\gpu\kernels\DelayAndBandPass.cu" /> - <None Include="..\..\GPUProc\share\gpu\kernels\FIR_Filter.cu" /> - <None Include="..\..\GPUProc\share\gpu\kernels\gpu_math.cuh" /> - <None Include="..\..\GPUProc\share\gpu\kernels\IncoherentStokes.cu" /> - <None Include="..\..\GPUProc\share\gpu\kernels\IncoherentStokesTranspose.cu" /> - <None Include="..\..\GPUProc\share\gpu\kernels\IntToFloat.cu" /> - <None Include="..\..\GPUProc\share\gpu\kernels\IntToFloat.cuh" /> - <None Include="..\..\GPUProc\share\gpu\kernels\Transpose.cu" /> - <None Include="..\..\GPUProc\src\cuda\CMakeLists.txt" /> - <None Include="..\..\GPUProc\src\cuda\cuda_config.h.in" /> - <None Include="..\..\GPUProc\src\cuda\gpu_wrapper.tcc" /> - <None Include="..\..\GPUProc\src\opencl\CMakeLists.txt" /> - <None Include="..\..\GPUProc\src\opencl\Correlator.cl" /> - <None Include="..\..\GPUProc\src\opencl\DelayAndBandPass.cl" /> - <None Include="..\..\GPUProc\src\opencl\FFT.cl" /> - <None Include="..\..\GPUProc\src\opencl\fft2.cl" /> - <None Include="..\..\GPUProc\src\opencl\FIR.cl" /> - <None Include="..\..\GPUProc\src\opencl\math.cl" /> - <None Include="..\..\GPUProc\src\opencl\NewCorrelator.cl" /> - <None Include="..\..\GPUProc\src\rtcp.log_prop" /> - <None Include="..\..\GPUProc\test\cmpfloat.py" /> - <None Include="..\..\GPUProc\test\cuda\CMakeLists.txt" /> - <None Include="..\..\GPUProc\test\cuda\tBandPassCorrection.sh" /> - <None Include="..\..\GPUProc\test\cuda\tBeamFormer.sh" /> - <None Include="..\..\GPUProc\test\cuda\tCoherentStokes.sh" /> - <None Include="..\..\GPUProc\test\cuda\tCoherentStokesTranspose.sh" /> - <None Include="..\..\GPUProc\test\cuda\tCorrelator.sh" /> - <None Include="..\..\GPUProc\test\cuda\tDelayAndBandPass.sh" /> - <None Include="..\..\GPUProc\test\cuda\tFFT_leakage.in_.parset" /> - <None Include="..\..\GPUProc\test\cuda\tFIR_Filter.parset.77_Stations" /> - <None Include="..\..\GPUProc\test\cuda\tFIR_Filter.parset.AARTFAAC" /> - <None Include="..\..\GPUProc\test\cuda\tFIR_Filter.parset.small-test" /> - <None Include="..\..\GPUProc\test\cuda\tFIR_Filter.run" /> - <None Include="..\..\GPUProc\test\cuda\tFIR_Filter.sh" /> - <None Include="..\..\GPUProc\test\cuda\tIntToFloat.sh" /> - <None Include="..\..\GPUProc\test\cuda\tKernel.in_.cu" /> - <None Include="..\..\GPUProc\test\cuda\tKernel.parset.in" /> - <None Include="..\..\GPUProc\test\cuda\tKernel.sh" /> - <None Include="..\..\GPUProc\test\cuda\tMultiDimArrayHostBuffer.sh" /> - <None Include="..\..\GPUProc\test\cuda\tStreamReadBuffer.sh" /> - <None Include="..\..\GPUProc\test\cuda\tTranspose.sh" /> - <None Include="..\..\GPUProc\test\cuda\tTranspose2.sh" /> - <None Include="..\..\GPUProc\test\cuda\t_cuda_complex.cu" /> - <None Include="..\..\GPUProc\test\cuda\t_cuda_complex.sh" /> - <None Include="..\..\GPUProc\test\cuda\Vizualize_leakage.py" /> - <None Include="..\..\GPUProc\test\iperf-cbt-locus.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel.in_parset" /> - <None Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel2.in_parset" /> - <None Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel2.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tBeamFormerKernel.in_parset" /> - <None Include="..\..\GPUProc\test\Kernels\tBeamFormerKernel.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tCoherentStokesKernel.in_parset" /> - <None Include="..\..\GPUProc\test\Kernels\tCoherentStokesKernel.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tCorrelatorKernel.in_parset" /> - <None Include="..\..\GPUProc\test\Kernels\tCorrelatorKernel.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel.in_parset" /> - <None Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel2.in_parset" /> - <None Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel2.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tFFT_Kernel.in_parset" /> - <None Include="..\..\GPUProc\test\Kernels\tFFT_Kernel.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tFIR_FilterKernel.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tIntToFloatKernel.in_parset" /> - <None Include="..\..\GPUProc\test\Kernels\tIntToFloatKernel.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tKernelFunctions.sh" /> - <None Include="..\..\GPUProc\test\Kernels\tKernelPerformance.py" /> - <None Include="..\..\GPUProc\test\Kernels\visualizeBeamformer.py" /> - <None Include="..\..\GPUProc\test\Pipelines\tCorrelatorPipelineProcessObs.parset" /> - <None Include="..\..\GPUProc\test\Pipelines\tCorrelatorPipelineProcessObs.sh" /> - <None Include="..\..\GPUProc\test\SubbandProcs\tBeamFormerSubbandProcProcessSb.parset" /> - <None Include="..\..\GPUProc\test\SubbandProcs\tBeamFormerSubbandProcProcessSb.sh" /> - <None Include="..\..\GPUProc\test\SubbandProcs\tCoherentStokesBeamFormerSubbandProcProcessSb.parset" /> - <None Include="..\..\GPUProc\test\SubbandProcs\tCoherentStokesBeamFormerSubbandProcProcessSb.sh" /> - <None Include="..\..\GPUProc\test\SubbandProcs\tCorrelatorSubbandProcProcessSb.parset" /> - <None Include="..\..\GPUProc\test\SubbandProcs\tCorrelatorSubbandProcProcessSb.sh" /> - <None Include="..\..\GPUProc\test\tMPISendReceive.in_parset" /> - <None Include="..\..\GPUProc\test\tMPISendReceive.sh" /> - <None Include="..\..\GPUProc\test\t_cpu_utils.in_parset" /> - <None Include="..\..\GPUProc\test\t_cpu_utils.sh" /> - </ItemGroup> - <ItemGroup> - <Text Include="..\..\GPUProc\share\gpu\kernels\CMakeLists.txt" /> - <Text Include="..\..\GPUProc\src\CMakeLists.txt" /> - <Text Include="..\..\GPUProc\test\CMakeLists.txt" /> - <Text Include="..\..\GPUProc\test\Kernels\CMakeLists.txt" /> - <Text Include="..\..\GPUProc\test\Pipelines\CMakeLists.txt" /> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{93C5C054-7BF7-478E-A8B5-E0829597AEEA}</ProjectGuid> - <Keyword>Win32Proj</Keyword> - <RootNamespace>GPUProc</RootNamespace> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>v120</PlatformToolset> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>v120</PlatformToolset> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Label="ExtensionSettings"> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <LinkIncremental>true</LinkIncremental> - <IncludePath>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include;..\..\..\static_fixture;..\..\;..\symbolic_links;C:\Program Files (x86)\boost\boost_1_41;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v5.5\include;D:\Users\klijn\Downloads\boost_1_55_0\boost_1_55_0\boost</IncludePath> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <LinkIncremental>false</LinkIncremental> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <ClCompile> - <PrecompiledHeader>Use</PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions);USE_CUDA; NR_COHERENT_STOKES=4</PreprocessorDefinitions> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <PrecompiledHeader>Use</PrecompiledHeader> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - </Link> - </ItemDefinitionGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> - <ImportGroup Label="ExtensionTargets"> - </ImportGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj.filters b/RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj.filters deleted file mode 100644 index 6c0532571aa..00000000000 --- a/RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj.filters +++ /dev/null @@ -1,1042 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup> - <Filter Include="src"> - <UniqueIdentifier>{52b27d78-7e52-4da5-96f4-7ec7aacebf9f}</UniqueIdentifier> - </Filter> - <Filter Include="src\cuda"> - <UniqueIdentifier>{6d5d776e-f435-4271-ba8a-08cfe01119ad}</UniqueIdentifier> - </Filter> - <Filter Include="src\cuda\Kernels"> - <UniqueIdentifier>{a4f44168-d60e-4eea-b9a2-263bf0cb7b57}</UniqueIdentifier> - </Filter> - <Filter Include="src\cuda\Pipelines"> - <UniqueIdentifier>{050b51f0-c906-4fc1-8f40-48ce89066bba}</UniqueIdentifier> - </Filter> - <Filter Include="src\cuda\SubbandProcs"> - <UniqueIdentifier>{b04db8d2-92c1-4a96-93fb-6f94cd4bb17a}</UniqueIdentifier> - </Filter> - <Filter Include="src\Pipelines"> - <UniqueIdentifier>{30105c21-2cdf-461e-8738-7f43e4c608c0}</UniqueIdentifier> - </Filter> - <Filter Include="src\Kernels"> - <UniqueIdentifier>{8a81bd65-646d-43ba-ac7b-5beb99de019c}</UniqueIdentifier> - </Filter> - <Filter Include="src\Station"> - <UniqueIdentifier>{531671df-db5c-4132-8255-ef0970f07246}</UniqueIdentifier> - </Filter> - <Filter Include="src\Storage"> - <UniqueIdentifier>{f0428178-4eef-4140-8af8-178e4c927400}</UniqueIdentifier> - </Filter> - <Filter Include="src\SubbandProcs"> - <UniqueIdentifier>{4189daf6-f0e0-4bb1-8bd6-0d96bbc218d2}</UniqueIdentifier> - </Filter> - <Filter Include="test"> - <UniqueIdentifier>{91363a0a-dbf1-43e9-a345-cc3e6fe4fa1f}</UniqueIdentifier> - </Filter> - <Filter Include="test\cuda"> - <UniqueIdentifier>{51b333f8-a7d1-4342-9046-a9e5a8e71783}</UniqueIdentifier> - </Filter> - <Filter Include="share"> - <UniqueIdentifier>{6366e12c-9513-4476-af75-1c5f0e9a6295}</UniqueIdentifier> - </Filter> - <Filter Include="src\opencl"> - <UniqueIdentifier>{df0cf1e4-c813-41cb-b576-495868d16308}</UniqueIdentifier> - </Filter> - <Filter Include="src\opencl\Pipelines"> - <UniqueIdentifier>{d4c8e2db-d252-4de7-8587-e22411d36bbf}</UniqueIdentifier> - </Filter> - <Filter Include="src\opencl\WorkQueues"> - <UniqueIdentifier>{4022e73c-c37c-44ff-a0e3-8345663f831e}</UniqueIdentifier> - </Filter> - <Filter Include="src\opencl\Kernels"> - <UniqueIdentifier>{773522c4-e9e7-4c46-be83-2ba0cf75ac54}</UniqueIdentifier> - </Filter> - <Filter Include="test\Kernels"> - <UniqueIdentifier>{d12b2f89-11b7-4abe-b8d3-b244619f1bc9}</UniqueIdentifier> - </Filter> - <Filter Include="test\SubbandProcs"> - <UniqueIdentifier>{0ddb3aaa-b339-4fc9-933d-51ab12f2f419}</UniqueIdentifier> - </Filter> - <Filter Include="src\cuda\SubbandProcs\steps"> - <UniqueIdentifier>{523a5915-d119-4fbc-bf32-f57136137d3e}</UniqueIdentifier> - </Filter> - <Filter Include="test\Pipelines"> - <UniqueIdentifier>{0b1f82fc-7678-4044-9aab-79b3f9f6d64a}</UniqueIdentifier> - </Filter> - </ItemGroup> - <ItemGroup> - <ClInclude Include="..\..\GPUProc\src\cuda\Pipelines\BeamFormerPipeline.h"> - <Filter>src\cuda\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Pipelines\CorrelatorPipeline.h"> - <Filter>src\cuda\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Pipelines\Pipeline.h"> - <Filter>src\cuda\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Pipelines\UHEP_Pipeline.h"> - <Filter>src\cuda\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerSubbandProc.h"> - <Filter>src\cuda\SubbandProcs</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\CorrelatorSubbandProc.h"> - <Filter>src\cuda\SubbandProcs</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\SubbandProc.h"> - <Filter>src\cuda\SubbandProcs</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\UHEP_SubbandProc.h"> - <Filter>src\cuda\SubbandProcs</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\gpu_incl.h"> - <Filter>src\cuda</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\gpu_wrapper.h"> - <Filter>src\cuda</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\KernelFactory.h"> - <Filter>src\cuda</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\MultiDimArrayHostBuffer.h"> - <Filter>src\cuda</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\PerformanceCounter.h"> - <Filter>src\cuda</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Pipelines\BeamFormerPipeline.h"> - <Filter>src\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Pipelines\CorrelatorPipeline.h"> - <Filter>src\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Pipelines\CorrelatorPipelinePrograms.h"> - <Filter>src\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Pipelines\UHEP_Pipeline.h"> - <Filter>src\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\BeamFormerKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\BeamFormerTransposeKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\CoherentStokesKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\CorrelatorKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\DedispersionBackwardFFTkernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\DedispersionChirpKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\DedispersionForwardFFTkernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\DelayAndBandPassKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\FFT_Kernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\Filter_FFT_Kernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\FIR_FilterKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\IncoherentStokesKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\IntToFloatKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\Kernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\UHEP_BeamFormerKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\UHEP_InvFFT_Kernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\UHEP_InvFIR_Kernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\UHEP_TransposeKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\UHEP_TriggerKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Station\StationInput.h"> - <Filter>src\Station</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Station\StationNodeAllocation.h"> - <Filter>src\Station</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Storage\StorageProcesses.h"> - <Filter>src\Storage</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Storage\SSH.h"> - <Filter>src\Storage</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Storage\StorageProcess.h"> - <Filter>src\Storage</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\test\cuda\tFIR_Filter.h"> - <Filter>test\cuda</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\gpu_incl.h"> - <Filter>src\opencl</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\gpu_utils.h"> - <Filter>src\opencl</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\gpu_wrapper.h"> - <Filter>src\opencl</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\MultiDimArrayHostBuffer.h"> - <Filter>src\opencl</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\PerformanceCounter.h"> - <Filter>src\opencl</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Pipelines\BeamFormerPipeline.h"> - <Filter>src\opencl\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Pipelines\CorrelatorPipeline.h"> - <Filter>src\opencl\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Pipelines\CorrelatorPipelinePrograms.h"> - <Filter>src\opencl\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Pipelines\Pipeline.h"> - <Filter>src\opencl\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Pipelines\UHEP_Pipeline.h"> - <Filter>src\opencl\Pipelines</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\WorkQueues\BeamFormerWorkQueue.h"> - <Filter>src\opencl\WorkQueues</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\WorkQueues\CorrelatorWorkQueue.h"> - <Filter>src\opencl\WorkQueues</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\WorkQueues\UHEP_WorkQueue.h"> - <Filter>src\opencl\WorkQueues</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\WorkQueues\WorkQueue.h"> - <Filter>src\opencl\WorkQueues</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\FFT_Plan.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\Filter_FFT_Kernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\FIR_FilterKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\IncoherentStokesKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\IntToFloatKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\Kernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\UHEP_BeamFormerKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\UHEP_InvFFT_Kernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\UHEP_InvFIR_Kernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\UHEP_TransposeKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\UHEP_TriggerKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\BeamFormerKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\BeamFormerTransposeKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\CoherentStokesKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\CorrelatorKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\DedispersionBackwardFFTkernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\DedispersionChirpKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\DedispersionForwardFFTkernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\DelayAndBandPassKernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\opencl\Kernels\FFT_Kernel.h"> - <Filter>src\opencl\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\Kernels\CoherentStokesTransposeKernel.h"> - <Filter>src\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\gpu_incl.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\gpu_utils.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\gpu_wrapper.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\KernelFactory.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\MultiDimArrayHostBuffer.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\OpenMP_Lock.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\PerformanceCounter.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\RunningStatistics.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\BandPass.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cpu_utils.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\FilterBank.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\global_defines.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerFactories.h"> - <Filter>src\cuda\SubbandProcs</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\CoherentStokesTransposeKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\CorrelatorKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\DedispersionBackwardFFTkernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\DedispersionChirpKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\DedispersionForwardFFTkernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\DelayAndBandPassKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\FFT_Kernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\FFT_Plan.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\Filter_FFT_Kernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\FIR_FilterKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\IncoherentStokesKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\IncoherentStokesTransposeKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\IntToFloatKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\Kernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\UHEP_BeamFormerKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\UHEP_InvFFT_Kernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\UHEP_InvFIR_Kernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\UHEP_TransposeKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\UHEP_TriggerKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\BeamFormerKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\BeamFormerTransposeKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\CoherentStokesKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\Kernels\BandPassCorrectionKernel.h"> - <Filter>src\cuda\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerSubbandProcStep.h"> - <Filter>src\cuda\SubbandProcs\steps</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerPreprocessingStep.h"> - <Filter>src\cuda\SubbandProcs\steps</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerCoherentStep.h"> - <Filter>src\cuda\SubbandProcs\steps</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerIncoherentStep.h"> - <Filter>src\cuda\SubbandProcs\steps</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\test\Kernels\KernelTestHelpers.h"> - <Filter>test\Kernels</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\test\TestUtil.h"> - <Filter>test</Filter> - </ClInclude> - <ClInclude Include="..\..\GPUProc\src\MPI_utils.h"> - <Filter>src</Filter> - </ClInclude> - </ItemGroup> - <ItemGroup> - <ClCompile Include="..\..\GPUProc\src\cuda\Pipelines\BeamFormerPipeline.cc"> - <Filter>src\cuda\Pipelines</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Pipelines\CorrelatorPipeline.cc"> - <Filter>src\cuda\Pipelines</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Pipelines\Pipeline.cc"> - <Filter>src\cuda\Pipelines</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Pipelines\UHEP_Pipeline.cc"> - <Filter>src\cuda\Pipelines</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\CorrelatorSubbandProc.cc"> - <Filter>src\cuda\SubbandProcs</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\SubbandProc.cc"> - <Filter>src\cuda\SubbandProcs</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\UHEP_SubbandProc.cc"> - <Filter>src\cuda\SubbandProcs</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerSubbandProc.cc"> - <Filter>src\cuda\SubbandProcs</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\gpu_utils.cc"> - <Filter>src\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\gpu_wrapper.cc"> - <Filter>src\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\KernelFactory.cc"> - <Filter>src\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\PerformanceCounter.cc"> - <Filter>src\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\Station\StationInput.cc"> - <Filter>src\Station</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\Station\StationNodeAllocation.cc"> - <Filter>src\Station</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\Storage\StorageProcesses.cc"> - <Filter>src\Storage</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\Storage\SSH.cc"> - <Filter>src\Storage</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\Storage\StorageProcess.cc"> - <Filter>src\Storage</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tCoherentStokes.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tCorrelator.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tDelayAndBandPass.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tFFT.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tFFT_leakage.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tFIR_Filter.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tGPUWrapper.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tIntToFloat.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tKernel.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tMultiDimArrayHostBuffer.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tStreamReadBuffer.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tTranspose.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tBeamFormer.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\gpu_utils.cc"> - <Filter>src\opencl</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\gpu_wrapper.cc"> - <Filter>src\opencl</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\PerformanceCounter.cc"> - <Filter>src\opencl</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Pipelines\CorrelatorPipeline.cc"> - <Filter>src\opencl\Pipelines</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Pipelines\Pipeline.cc"> - <Filter>src\opencl\Pipelines</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Pipelines\UHEP_Pipeline.cc"> - <Filter>src\opencl\Pipelines</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Pipelines\BeamFormerPipeline.cc"> - <Filter>src\opencl\Pipelines</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\WorkQueues\CorrelatorWorkQueue.cc"> - <Filter>src\opencl\WorkQueues</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\WorkQueues\UHEP_WorkQueue.cc"> - <Filter>src\opencl\WorkQueues</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\WorkQueues\WorkQueue.cc"> - <Filter>src\opencl\WorkQueues</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\WorkQueues\BeamFormerWorkQueue.cc"> - <Filter>src\opencl\WorkQueues</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\Filter_FFT_Kernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\FIR_FilterKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\IncoherentStokesKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\IntToFloatKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\Kernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\UHEP_BeamFormerKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\UHEP_InvFFT_Kernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\UHEP_InvFIR_Kernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\UHEP_TransposeKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\UHEP_TriggerKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\BeamFormerKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\BeamFormerTransposeKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\CoherentStokesKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\CorrelatorKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\DedispersionBackwardFFTkernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\DedispersionChirpKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\DedispersionForwardFFTkernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\DelayAndBandPassKernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\FFT_Kernel.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\opencl\Kernels\FFT_Plan.cc"> - <Filter>src\opencl\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tCoherentStokesTranspose.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\rtcp.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\RunningStatistics.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\BandPass.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cpu_utils.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\FilterBank.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\global_defines.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerFactories.cc"> - <Filter>src\cuda\SubbandProcs</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\CorrelatorKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\DedispersionBackwardFFTkernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\DedispersionChirpKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\DedispersionForwardFFTkernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\DelayAndBandPassKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\FFT_Kernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\FFT_Plan.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\Filter_FFT_Kernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\FIR_FilterKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\IncoherentStokesKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\IncoherentStokesTransposeKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\IntToFloatKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\Kernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\UHEP_BeamFormerKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\UHEP_InvFFT_Kernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\UHEP_InvFIR_Kernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\UHEP_TransposeKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\UHEP_TriggerKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\BeamFormerKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\BeamFormerTransposeKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\CoherentStokesKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\CoherentStokesTransposeKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\FFTShiftKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel2.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\tBeamFormerKernel.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\tCoherentStokesKernel.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\tCorrelatorKernel.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel2.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\tFFT_Kernel.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\tFIR_FilterKernel.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\tIntToFloatKernel.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\tKernelFunctions.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerFactories.cc"> - <Filter>src\cuda\SubbandProcs</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\SubbandProcs\tBeamFormerSubbandProcProcessSb.cc"> - <Filter>test\SubbandProcs</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\SubbandProcs\tCoherentStokesBeamFormerSubbandProcProcessSb.cc"> - <Filter>test\SubbandProcs</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\SubbandProcs\tCorrelatorSubbandProc.cc"> - <Filter>test\SubbandProcs</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\SubbandProcs\tCorrelatorSubbandProcProcessSb.cc"> - <Filter>test\SubbandProcs</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\SubbandProcs\tSubbandProc.cc"> - <Filter>test\SubbandProcs</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\Kernels\BandPassCorrectionKernel.cc"> - <Filter>src\cuda\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerPreprocessingStep.cc"> - <Filter>src\cuda\SubbandProcs\steps</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerCoherentStep.cc"> - <Filter>src\cuda\SubbandProcs\steps</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\src\cuda\SubbandProcs\BeamFormerIncoherentStep.cc"> - <Filter>src\cuda\SubbandProcs\steps</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\cuda\tBandPassCorrection.cc"> - <Filter>test\cuda</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\BeamFormerKernelPerformance.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\Kernels\KernelTestHelpers.cc"> - <Filter>test\Kernels</Filter> - </ClCompile> - <ClCompile Include="..\..\GPUProc\test\SubbandProcs\tFlysEyeBeamFormerSubbandProcProcessSb.cc"> - <Filter>test\SubbandProcs</Filter> - </ClCompile> - </ItemGroup> - <ItemGroup> - <None Include="..\..\GPUProc\src\cuda\CMakeLists.txt"> - <Filter>src\cuda</Filter> - </None> - <None Include="..\..\GPUProc\src\cuda\cuda_config.h.in"> - <Filter>src\cuda</Filter> - </None> - <None Include="..\..\GPUProc\src\cuda\gpu_wrapper.tcc"> - <Filter>src\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tBeamFormer.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tCoherentStokes.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tCorrelator.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tDelayAndBandPass.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tFFT_leakage.in_.parset"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tFIR_Filter.parset.77_Stations"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tFIR_Filter.parset.AARTFAAC"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tFIR_Filter.parset.small-test"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tFIR_Filter.run"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tFIR_Filter.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tIntToFloat.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tKernel.in_.cu"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tKernel.parset.in"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tKernel.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tMultiDimArrayHostBuffer.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tStreamReadBuffer.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tTranspose.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\Vizualize_leakage.py"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\CMakeLists.txt"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\t_cuda_complex.cu"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\t_cuda_complex.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\src\opencl\CMakeLists.txt"> - <Filter>src\opencl</Filter> - </None> - <None Include="..\..\GPUProc\src\opencl\Correlator.cl"> - <Filter>src\opencl</Filter> - </None> - <None Include="..\..\GPUProc\src\opencl\DelayAndBandPass.cl"> - <Filter>src\opencl</Filter> - </None> - <None Include="..\..\GPUProc\src\opencl\FFT.cl"> - <Filter>src\opencl</Filter> - </None> - <None Include="..\..\GPUProc\src\opencl\fft2.cl"> - <Filter>src\opencl</Filter> - </None> - <None Include="..\..\GPUProc\src\opencl\FIR.cl"> - <Filter>src\opencl</Filter> - </None> - <None Include="..\..\GPUProc\src\opencl\math.cl"> - <Filter>src\opencl</Filter> - </None> - <None Include="..\..\GPUProc\src\opencl\NewCorrelator.cl"> - <Filter>src\opencl</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tCoherentStokesTranspose.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\src\rtcp.log_prop"> - <Filter>src</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\BeamFormer.cu"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\CoherentStokes.cu"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\CoherentStokesTranspose.cu"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\Correlator.cu"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\DelayAndBandPass.cu"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\FIR_Filter.cu"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\gpu_math.cuh"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\IncoherentStokes.cu"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\IncoherentStokesTranspose.cu"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\IntToFloat.cu"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\IntToFloat.cuh"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\Transpose.cu"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel2.sh"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tBeamFormerKernel.in_parset"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tBeamFormerKernel.sh"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tCoherentStokesKernel.in_parset"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tCoherentStokesKernel.sh"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tCorrelatorKernel.in_parset"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tCorrelatorKernel.sh"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel.in_parset"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel.sh"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel2.in_parset"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tDelayAndBandPassKernel2.sh"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tFFT_Kernel.in_parset"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tFFT_Kernel.sh"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tFIR_FilterKernel.sh"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tIntToFloatKernel.in_parset"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tIntToFloatKernel.sh"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tKernelFunctions.sh"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel.in_parset"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel.sh"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tBandPassCorrectionKernel2.in_parset"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\cuda\tTranspose2.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\SubbandProcs\tBeamFormerSubbandProcProcessSb.parset"> - <Filter>test\SubbandProcs</Filter> - </None> - <None Include="..\..\GPUProc\test\SubbandProcs\tBeamFormerSubbandProcProcessSb.sh"> - <Filter>test\SubbandProcs</Filter> - </None> - <None Include="..\..\GPUProc\test\SubbandProcs\tCoherentStokesBeamFormerSubbandProcProcessSb.parset"> - <Filter>test\SubbandProcs</Filter> - </None> - <None Include="..\..\GPUProc\test\SubbandProcs\tCoherentStokesBeamFormerSubbandProcProcessSb.sh"> - <Filter>test\SubbandProcs</Filter> - </None> - <None Include="..\..\GPUProc\test\SubbandProcs\tCorrelatorSubbandProcProcessSb.parset"> - <Filter>test\SubbandProcs</Filter> - </None> - <None Include="..\..\GPUProc\test\SubbandProcs\tCorrelatorSubbandProcProcessSb.sh"> - <Filter>test\SubbandProcs</Filter> - </None> - <None Include="..\..\GPUProc\share\gpu\kernels\BandPassCorrection.cu"> - <Filter>share</Filter> - </None> - <None Include="..\..\GPUProc\test\tBeamform_1sec_1st_5sb_noflagging_2SapDivNTab.parset" /> - <None Include="..\..\GPUProc\test\tBeamform_1sec_1st_5sb_noflagging_2SapDivNTab.run" /> - <None Include="..\..\GPUProc\test\tBeamform_1sec_1st_5sb_noflagging_2SapDivNTab.sh" /> - <None Include="..\..\GPUProc\test\cuda\tBandPassCorrection.sh"> - <Filter>test\cuda</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\tKernelPerformance.py"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\Kernels\visualizeBeamformer.py"> - <Filter>test\Kernels</Filter> - </None> - <None Include="..\..\GPUProc\test\SubbandProcs\tFlysEyeBeamFormerSubbandProcProcessSb.parset"> - <Filter>test\SubbandProcs</Filter> - </None> - <None Include="..\..\GPUProc\test\SubbandProcs\tFlysEyeBeamFormerSubbandProcProcessSb.sh"> - <Filter>test\SubbandProcs</Filter> - </None> - </ItemGroup> - <ItemGroup> - <Text Include="..\..\GPUProc\src\CMakeLists.txt"> - <Filter>src</Filter> - </Text> - <Text Include="..\..\GPUProc\share\gpu\kernels\CMakeLists.txt"> - <Filter>share</Filter> - </Text> - <Text Include="..\..\GPUProc\test\Kernels\CMakeLists.txt"> - <Filter>test\Kernels</Filter> - </Text> - <Text Include="..\..\GPUProc\test\CMakeLists.txt"> - <Filter>test</Filter> - </Text> - <Text Include="..\..\GPUProc\test\Pipelines\CMakeLists.txt"> - <Filter>test\Pipelines</Filter> - </Text> - </ItemGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj.user b/RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj.user deleted file mode 100644 index 695b5c78b91..00000000000 --- a/RTCP/Cobalt/VisualStudio/GPUProc/GPUProc.vcxproj.user +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj b/RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj deleted file mode 100644 index 4dd0ad3526f..00000000000 --- a/RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj +++ /dev/null @@ -1,161 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|Win32"> - <Configuration>Debug</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|Win32"> - <Configuration>Release</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{075D5E72-BA47-4D6E-B1E1-DBCF747CA33D}</ProjectGuid> - <Keyword>Win32Proj</Keyword> - <RootNamespace>InputProc</RootNamespace> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>v120</PlatformToolset> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>v120</PlatformToolset> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Label="ExtensionSettings"> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <LinkIncremental>true</LinkIncremental> - <IncludePath>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include;..\..\..\static_fixture;..\..\;..\symbolic_links;C:\Program Files (x86)\boost\boost_1_41</IncludePath> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <LinkIncremental>false</LinkIncremental> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <ClCompile> - <PrecompiledHeader> - </PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <PrecompiledHeader> - </PrecompiledHeader> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - </Link> - </ItemDefinitionGroup> - <ItemGroup> - <ClCompile Include="..\..\InputProc\src\Buffer\Ranges.cc" /> - <ClCompile Include="..\..\InputProc\src\Buffer\StationID.cc" /> - <ClCompile Include="..\..\InputProc\src\Delays\Delays.cc" /> - <ClCompile Include="..\..\InputProc\src\Delays\printDelays.cc" /> - <ClCompile Include="..\..\InputProc\src\RSPBoards.cc" /> - <ClCompile Include="..\..\InputProc\src\RSPTimeStamp.cc" /> - <ClCompile Include="..\..\InputProc\src\Station\filterRSP.cc" /> - <ClCompile Include="..\..\InputProc\src\Station\generate.cc" /> - <ClCompile Include="..\..\InputProc\src\Station\generateRSP.cc" /> - <ClCompile Include="..\..\InputProc\src\Station\Generator.cc" /> - <ClCompile Include="..\..\InputProc\src\Station\PacketFactory.cc" /> - <ClCompile Include="..\..\InputProc\src\Station\PacketReader.cc" /> - <ClCompile Include="..\..\InputProc\src\Station\printRSP.cc" /> - <ClCompile Include="..\..\InputProc\src\Station\repairRSP.cc" /> - <ClCompile Include="..\..\InputProc\src\Station\RSPPacketFactory.cc" /> - <ClCompile Include="..\..\InputProc\src\Transpose\MPIReceiveStations.cc" /> - <ClCompile Include="..\..\InputProc\src\Transpose\MPISendStation.cc" /> - <ClCompile Include="..\..\InputProc\src\Transpose\MPIUtil.cc" /> - <ClCompile Include="..\..\InputProc\src\Transpose\MPIUtil2.cc" /> - <ClCompile Include="..\..\InputProc\test\stationReceiver.cc" /> - <ClCompile Include="..\..\InputProc\test\stationSender.cc" /> - <ClCompile Include="..\..\InputProc\test\tDelays.cc" /> - <ClCompile Include="..\..\InputProc\test\tGenerator.cc" /> - <ClCompile Include="..\..\InputProc\test\tMPI.cc" /> - <ClCompile Include="..\..\InputProc\test\tMPIUtil2.cc" /> - <ClCompile Include="..\..\InputProc\test\tPacketFactory.cc" /> - <ClCompile Include="..\..\InputProc\test\tPacketReader.cc" /> - <ClCompile Include="..\..\InputProc\test\tRanges.cc" /> - <ClCompile Include="..\..\InputProc\test\tRSP.cc" /> - <ClCompile Include="..\..\InputProc\test\tRSPTimeStamp.cc" /> - <ClCompile Include="..\..\InputProc\test\t_generateRSP.cc" /> - </ItemGroup> - <ItemGroup> - <None Include="..\..\InputProc\src\Delays\printDelays.log_prop" /> - <None Include="..\..\InputProc\src\mpirun.sh.in" /> - <None Include="..\..\InputProc\src\Station\generateRSP.log_prop" /> - <None Include="..\..\InputProc\test\tMPI.run" /> - <None Include="..\..\InputProc\test\tMPI.sh" /> - <None Include="..\..\InputProc\test\tMPISendReceive.py" /> - <None Include="..\..\InputProc\test\tMPIUtil2.run" /> - <None Include="..\..\InputProc\test\tMPIUtil2.sh" /> - <None Include="..\..\InputProc\test\tPacketReader.in_16bit" /> - <None Include="..\..\InputProc\test\tPacketReader.in_8bit" /> - <None Include="..\..\InputProc\test\tPacketReader.sh" /> - <None Include="..\..\InputProc\test\tRSP.in_16bit" /> - <None Include="..\..\InputProc\test\tRSP.in_8bit" /> - <None Include="..\..\InputProc\test\tRSP.sh" /> - <None Include="..\..\InputProc\test\tRSP.stdout" /> - </ItemGroup> - <ItemGroup> - <Text Include="..\..\InputProc\src\CMakeLists.txt" /> - <Text Include="..\..\InputProc\test\CMakeLists.txt" /> - </ItemGroup> - <ItemGroup> - <ClInclude Include="..\..\InputProc\src\Buffer\BoardMode.h" /> - <ClInclude Include="..\..\InputProc\src\Buffer\Ranges.h" /> - <ClInclude Include="..\..\InputProc\src\Buffer\StationID.h" /> - <ClInclude Include="..\..\InputProc\src\Delays\Delays.h" /> - <ClInclude Include="..\..\InputProc\src\obsolete\MPI_RMA.h" /> - <ClInclude Include="..\..\InputProc\src\obsolete\TimeSync.h" /> - <ClInclude Include="..\..\InputProc\src\RSPBoards.h" /> - <ClInclude Include="..\..\InputProc\src\RSPTimeStamp.h" /> - <ClInclude Include="..\..\InputProc\src\SampleType.h" /> - <ClInclude Include="..\..\InputProc\src\Station\Generator.h" /> - <ClInclude Include="..\..\InputProc\src\Station\PacketFactory.h" /> - <ClInclude Include="..\..\InputProc\src\Station\PacketReader.h" /> - <ClInclude Include="..\..\InputProc\src\Station\PacketStream.h" /> - <ClInclude Include="..\..\InputProc\src\Station\RSP.h" /> - <ClInclude Include="..\..\InputProc\src\Station\RSPPacketFactory.h" /> - <ClInclude Include="..\..\InputProc\src\Transpose\MapUtil.h" /> - <ClInclude Include="..\..\InputProc\src\Transpose\MPIProtocol.h" /> - <ClInclude Include="..\..\InputProc\src\Transpose\MPIReceiveStations.h" /> - <ClInclude Include="..\..\InputProc\src\Transpose\MPISendStation.h" /> - <ClInclude Include="..\..\InputProc\src\Transpose\MPIUtil.h" /> - <ClInclude Include="..\..\InputProc\src\Transpose\MPIUtil2.h" /> - <ClInclude Include="..\..\InputProc\src\Transpose\ReceiveStations.h" /> - <ClInclude Include="..\..\InputProc\src\WallClockTime.h" /> - </ItemGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> - <ImportGroup Label="ExtensionTargets"> - </ImportGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj.filters b/RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj.filters deleted file mode 100644 index 9f43d187163..00000000000 --- a/RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj.filters +++ /dev/null @@ -1,247 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup> - <Filter Include="src"> - <UniqueIdentifier>{c12d8bb3-acd2-4457-9a0a-6959967e3d3a}</UniqueIdentifier> - </Filter> - <Filter Include="test"> - <UniqueIdentifier>{bde46e8a-a92b-43d4-8641-dd64bb37dd31}</UniqueIdentifier> - </Filter> - <Filter Include="src\Buffer"> - <UniqueIdentifier>{52deec4f-edc9-4849-bb82-7ce18b3aeb31}</UniqueIdentifier> - </Filter> - <Filter Include="src\Delays"> - <UniqueIdentifier>{8fe086e8-829a-4107-833b-f31242df2f44}</UniqueIdentifier> - </Filter> - <Filter Include="src\obsolete"> - <UniqueIdentifier>{f4b8635d-e70e-4830-94f9-2005d091dd8d}</UniqueIdentifier> - </Filter> - <Filter Include="src\Station"> - <UniqueIdentifier>{a60f4598-e2bb-488f-b282-61711a9abf47}</UniqueIdentifier> - </Filter> - <Filter Include="src\Transpose"> - <UniqueIdentifier>{f832b4d5-4102-4daf-bdfc-869b3683a70f}</UniqueIdentifier> - </Filter> - </ItemGroup> - <ItemGroup> - <ClCompile Include="..\..\InputProc\test\t_generateRSP.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\test\tDelays.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\test\tGenerator.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\test\tMPI.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\test\tMPIUtil2.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\test\tPacketFactory.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\test\tPacketReader.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\test\tRanges.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\test\tRSP.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\test\tRSPTimeStamp.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\test\stationReceiver.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\test\stationSender.cc"> - <Filter>test</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\RSPBoards.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\RSPTimeStamp.cc"> - <Filter>src</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Buffer\StationID.cc"> - <Filter>src\Buffer</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Buffer\Ranges.cc"> - <Filter>src\Buffer</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Delays\printDelays.cc"> - <Filter>src\Delays</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Delays\Delays.cc"> - <Filter>src\Delays</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Station\PacketReader.cc"> - <Filter>src\Station</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Station\printRSP.cc"> - <Filter>src\Station</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Station\repairRSP.cc"> - <Filter>src\Station</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Station\RSPPacketFactory.cc"> - <Filter>src\Station</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Station\filterRSP.cc"> - <Filter>src\Station</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Station\generate.cc"> - <Filter>src\Station</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Station\generateRSP.cc"> - <Filter>src\Station</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Station\Generator.cc"> - <Filter>src\Station</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Station\PacketFactory.cc"> - <Filter>src\Station</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Transpose\MPIUtil.cc"> - <Filter>src\Transpose</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Transpose\MPIUtil2.cc"> - <Filter>src\Transpose</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Transpose\MPIReceiveStations.cc"> - <Filter>src\Transpose</Filter> - </ClCompile> - <ClCompile Include="..\..\InputProc\src\Transpose\MPISendStation.cc"> - <Filter>src\Transpose</Filter> - </ClCompile> - </ItemGroup> - <ItemGroup> - <None Include="..\..\InputProc\test\tMPI.run"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\test\tMPI.sh"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\test\tMPIUtil2.run"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\test\tMPIUtil2.sh"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\test\tPacketReader.in_8bit"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\test\tPacketReader.in_16bit"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\test\tPacketReader.sh"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\test\tRSP.in_8bit"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\test\tRSP.in_16bit"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\test\tRSP.sh"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\test\tRSP.stdout"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\test\tMPISendReceive.py"> - <Filter>test</Filter> - </None> - <None Include="..\..\InputProc\src\mpirun.sh.in"> - <Filter>src</Filter> - </None> - <None Include="..\..\InputProc\src\Delays\printDelays.log_prop"> - <Filter>src\Delays</Filter> - </None> - <None Include="..\..\InputProc\src\Station\generateRSP.log_prop"> - <Filter>src\Station</Filter> - </None> - </ItemGroup> - <ItemGroup> - <Text Include="..\..\InputProc\test\CMakeLists.txt"> - <Filter>test</Filter> - </Text> - <Text Include="..\..\InputProc\src\CMakeLists.txt"> - <Filter>src</Filter> - </Text> - </ItemGroup> - <ItemGroup> - <ClInclude Include="..\..\InputProc\src\SampleType.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\WallClockTime.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\RSPBoards.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\RSPTimeStamp.h"> - <Filter>src</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Buffer\Ranges.h"> - <Filter>src\Buffer</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Buffer\StationID.h"> - <Filter>src\Buffer</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Buffer\BoardMode.h"> - <Filter>src\Buffer</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Delays\Delays.h"> - <Filter>src\Delays</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\obsolete\TimeSync.h"> - <Filter>src\obsolete</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\obsolete\MPI_RMA.h"> - <Filter>src\obsolete</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Station\PacketFactory.h"> - <Filter>src\Station</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Station\PacketReader.h"> - <Filter>src\Station</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Station\PacketStream.h"> - <Filter>src\Station</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Station\RSP.h"> - <Filter>src\Station</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Station\RSPPacketFactory.h"> - <Filter>src\Station</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Station\Generator.h"> - <Filter>src\Station</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Transpose\MPISendStation.h"> - <Filter>src\Transpose</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Transpose\MPIUtil.h"> - <Filter>src\Transpose</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Transpose\MPIUtil2.h"> - <Filter>src\Transpose</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Transpose\ReceiveStations.h"> - <Filter>src\Transpose</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Transpose\MapUtil.h"> - <Filter>src\Transpose</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Transpose\MPIProtocol.h"> - <Filter>src\Transpose</Filter> - </ClInclude> - <ClInclude Include="..\..\InputProc\src\Transpose\MPIReceiveStations.h"> - <Filter>src\Transpose</Filter> - </ClInclude> - </ItemGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj.user b/RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj.user deleted file mode 100644 index 695b5c78b91..00000000000 --- a/RTCP/Cobalt/VisualStudio/InputProc/InputProc.vcxproj.user +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj b/RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj deleted file mode 100644 index f90dad407fa..00000000000 --- a/RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj +++ /dev/null @@ -1,125 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|Win32"> - <Configuration>Debug</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|Win32"> - <Configuration>Release</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - </ItemGroup> - <ItemGroup> - <ClInclude Include="..\..\OutputProc\src\FastFileStream.h" /> - <ClInclude Include="..\..\OutputProc\src\GPUProcIO.h" /> - <ClInclude Include="..\..\OutputProc\src\InputThread.h" /> - <ClInclude Include="..\..\OutputProc\src\IOPriority.h" /> - <ClInclude Include="..\..\OutputProc\src\MeasurementSetFormat.h" /> - <ClInclude Include="..\..\OutputProc\src\MSWriter.h" /> - <ClInclude Include="..\..\OutputProc\src\MSWriterCorrelated.h" /> - <ClInclude Include="..\..\OutputProc\src\MSWriterDAL.h" /> - <ClInclude Include="..\..\OutputProc\src\MSWriterFile.h" /> - <ClInclude Include="..\..\OutputProc\src\MSWriterNull.h" /> - <ClInclude Include="..\..\OutputProc\src\OutputThread.h" /> - <ClInclude Include="..\..\OutputProc\src\SubbandWriter.h" /> - <ClInclude Include="..\..\OutputProc\src\TBB_StaticMapping.h" /> - <ClInclude Include="..\..\OutputProc\src\TBB_Writer.h" /> - </ItemGroup> - <ItemGroup> - <ClCompile Include="..\..\OutputProc\src\createHeaders.cc" /> - <ClCompile Include="..\..\OutputProc\src\FastFileStream.cc" /> - <ClCompile Include="..\..\OutputProc\src\GPUProcIO.cc" /> - <ClCompile Include="..\..\OutputProc\src\InputThread.cc" /> - <ClCompile Include="..\..\OutputProc\src\MeasurementSetFormat.cc" /> - <ClCompile Include="..\..\OutputProc\src\MSWriter.cc" /> - <ClCompile Include="..\..\OutputProc\src\MSWriterCorrelated.cc" /> - <ClCompile Include="..\..\OutputProc\src\MSWriterDAL.cc" /> - <ClCompile Include="..\..\OutputProc\src\MSWriterFile.cc" /> - <ClCompile Include="..\..\OutputProc\src\MSWriterNull.cc" /> - <ClCompile Include="..\..\OutputProc\src\outputProc.cc" /> - <ClCompile Include="..\..\OutputProc\src\OutputThread.cc" /> - <ClCompile Include="..\..\OutputProc\src\plotMS.cc" /> - <ClCompile Include="..\..\OutputProc\src\SubbandWriter.cc" /> - <ClCompile Include="..\..\OutputProc\src\TBB_StaticMapping.cc" /> - <ClCompile Include="..\..\OutputProc\src\TBB_Writer.cc" /> - <ClCompile Include="..\..\OutputProc\src\TBB_Writer_main.cc" /> - <ClCompile Include="..\..\OutputProc\test\tOutputThread.cc" /> - </ItemGroup> - <ItemGroup> - <None Include="..\..\OutputProc\src\gnuplotMS.sh" /> - <None Include="..\..\OutputProc\src\outputProc.log_prop" /> - </ItemGroup> - <ItemGroup> - <Text Include="..\..\OutputProc\src\CMakeLists.txt" /> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{AC4EED56-6048-4D80-8350-F5A753238CC3}</ProjectGuid> - <Keyword>Win32Proj</Keyword> - <RootNamespace>OutputProc</RootNamespace> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>v120</PlatformToolset> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>v120</PlatformToolset> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Label="ExtensionSettings"> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <LinkIncremental>true</LinkIncremental> - <IncludePath>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include;..\..\..\static_fixture;..\..\;..\symbolic_links;C:\Program Files (x86)\boost\boost_1_41</IncludePath> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <LinkIncremental>false</LinkIncremental> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <ClCompile> - <PrecompiledHeader> - </PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <PrecompiledHeader> - </PrecompiledHeader> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - </Link> - </ItemDefinitionGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> - <ImportGroup Label="ExtensionTargets"> - </ImportGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj.filters b/RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj.filters deleted file mode 100644 index 68d2de9084d..00000000000 --- a/RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj.filters +++ /dev/null @@ -1,129 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup> - <Filter Include="Source Files"> - <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> - <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> - </Filter> - <Filter Include="Resource Files"> - <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> - <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> - </Filter> - <Filter Include="test"> - <UniqueIdentifier>{1f37e2bb-b5d7-4a2c-8cd6-6cf697a2ab2f}</UniqueIdentifier> - </Filter> - </ItemGroup> - <ItemGroup> - <ClInclude Include="..\..\OutputProc\src\GPUProcIO.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\InputThread.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\IOPriority.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\MeasurementSetFormat.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\MSWriter.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\MSWriterCorrelated.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\MSWriterDAL.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\MSWriterFile.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\MSWriterNull.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\OutputThread.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\SubbandWriter.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\TBB_StaticMapping.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\TBB_Writer.h"> - <Filter>Source Files</Filter> - </ClInclude> - <ClInclude Include="..\..\OutputProc\src\FastFileStream.h"> - <Filter>Source Files</Filter> - </ClInclude> - </ItemGroup> - <ItemGroup> - <ClCompile Include="..\..\OutputProc\src\InputThread.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\MeasurementSetFormat.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\MSWriter.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\MSWriterCorrelated.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\MSWriterDAL.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\MSWriterFile.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\MSWriterNull.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\outputProc.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\OutputThread.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\plotMS.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\SubbandWriter.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\TBB_StaticMapping.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\TBB_Writer.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\TBB_Writer_main.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\createHeaders.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\FastFileStream.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\src\GPUProcIO.cc"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="..\..\OutputProc\test\tOutputThread.cc"> - <Filter>test</Filter> - </ClCompile> - </ItemGroup> - <ItemGroup> - <None Include="..\..\OutputProc\src\outputProc.log_prop"> - <Filter>Source Files</Filter> - </None> - <None Include="..\..\OutputProc\src\gnuplotMS.sh"> - <Filter>Source Files</Filter> - </None> - </ItemGroup> - <ItemGroup> - <Text Include="..\..\OutputProc\src\CMakeLists.txt"> - <Filter>Source Files</Filter> - </Text> - </ItemGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj.user b/RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj.user deleted file mode 100644 index 695b5c78b91..00000000000 --- a/RTCP/Cobalt/VisualStudio/OutputProc/OutputProc.vcxproj.user +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/symbolic_links/create_links.bat b/RTCP/Cobalt/VisualStudio/symbolic_links/create_links.bat deleted file mode 100644 index 4453bc7f27a..00000000000 --- a/RTCP/Cobalt/VisualStudio/symbolic_links/create_links.bat +++ /dev/null @@ -1,5 +0,0 @@ -rem To add the symbolic links. Run this program from a administrator command prompt -mklink /D GPUProc ..\..\GPUProc\src -mklink /D CoInterface ..\..\CoInterface\src -mklink /D InputProc ..\..\InputProc\src -mklink /D Common ..\..\..\..\LCS\Common\include\Common \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/t_cuda_complex/t_cuda_complex.vcxproj b/RTCP/Cobalt/VisualStudio/t_cuda_complex/t_cuda_complex.vcxproj deleted file mode 100644 index 0d758a37693..00000000000 --- a/RTCP/Cobalt/VisualStudio/t_cuda_complex/t_cuda_complex.vcxproj +++ /dev/null @@ -1,91 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|Win32"> - <Configuration>Debug</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|Win32"> - <Configuration>Release</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{0F2BF295-EFF5-491F-8B5E-9B45C4E23C84}</ProjectGuid> - <RootNamespace>t_cuda_complex</RootNamespace> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <CharacterSet>MultiByte</CharacterSet> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>MultiByte</CharacterSet> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Label="ExtensionSettings"> - <Import Project="$(VCTargetsPath)\BuildCustomizations\CUDA 5.0.props" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <LinkIncremental>true</LinkIncremental> - <IncludePath>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include;..\..\..\static_fixture;..\..\;..\symbolic_links;C:\Program Files (x86)\boost\boost_1_41;C:\Program Files (x86)\AMD APP\include;..\symbolic_links\GPUProc;..\..\..\static_fixture\UnitTest++</IncludePath> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <LinkIncremental>true</LinkIncremental> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - </ClCompile> - <Link> - <GenerateDebugInformation>true</GenerateDebugInformation> - <SubSystem>Console</SubSystem> - <AdditionalDependencies>cudart.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> - </Link> - <PostBuildEvent> - <Command>echo copy "$(CudaToolkitBinDir)\cudart*.dll" "$(OutDir)" -copy "$(CudaToolkitBinDir)\cudart*.dll" "$(OutDir)"</Command> - </PostBuildEvent> - <CudaCompile> - <Defines>USE_CUDA</Defines> - </CudaCompile> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - </ClCompile> - <Link> - <GenerateDebugInformation>true</GenerateDebugInformation> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - <SubSystem>Console</SubSystem> - <AdditionalDependencies>cudart.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> - </Link> - <PostBuildEvent> - <Command>echo copy "$(CudaToolkitBinDir)\cudart*.dll" "$(OutDir)" -copy "$(CudaToolkitBinDir)\cudart*.dll" "$(OutDir)"</Command> - </PostBuildEvent> - </ItemDefinitionGroup> - <ItemGroup> - <CudaCompile Include="..\..\GPUProc\test\complex_kernel.cu" /> - </ItemGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> - <ImportGroup Label="ExtensionTargets"> - <Import Project="$(VCTargetsPath)\BuildCustomizations\CUDA 5.0.targets" /> - </ImportGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/t_cuda_complex/t_cuda_complex.vcxproj.user b/RTCP/Cobalt/VisualStudio/t_cuda_complex/t_cuda_complex.vcxproj.user deleted file mode 100644 index 695b5c78b91..00000000000 --- a/RTCP/Cobalt/VisualStudio/t_cuda_complex/t_cuda_complex.vcxproj.user +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/test/test.vcxproj b/RTCP/Cobalt/VisualStudio/test/test.vcxproj deleted file mode 100644 index 74e4380c241..00000000000 --- a/RTCP/Cobalt/VisualStudio/test/test.vcxproj +++ /dev/null @@ -1,87 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|Win32"> - <Configuration>Debug</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|Win32"> - <Configuration>Release</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{1521604F-7418-4628-932D-40ED94A1E68E}</ProjectGuid> - <RootNamespace>test</RootNamespace> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <CharacterSet>MultiByte</CharacterSet> - <PlatformToolset>v120</PlatformToolset> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>MultiByte</CharacterSet> - <PlatformToolset>v120</PlatformToolset> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <LinkIncremental>true</LinkIncremental> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <LinkIncremental>true</LinkIncremental> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - </ClCompile> - <Link> - <GenerateDebugInformation>true</GenerateDebugInformation> - <SubSystem>Console</SubSystem> - <AdditionalDependencies>cudart.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> - </Link> - <PostBuildEvent> - <Command>echo copy "$(CudaToolkitBinDir)\cudart*.dll" "$(OutDir)" -copy "$(CudaToolkitBinDir)\cudart*.dll" "$(OutDir)"</Command> - </PostBuildEvent> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - </ClCompile> - <Link> - <GenerateDebugInformation>true</GenerateDebugInformation> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - <SubSystem>Console</SubSystem> - <AdditionalDependencies>cudart.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> - </Link> - <PostBuildEvent> - <Command>echo copy "$(CudaToolkitBinDir)\cudart*.dll" "$(OutDir)" -copy "$(CudaToolkitBinDir)\cudart*.dll" "$(OutDir)"</Command> - </PostBuildEvent> - </ItemDefinitionGroup> - <ItemGroup> - <CudaCompile Include="kernel.cu" /> - </ItemGroup> - <ItemGroup> - <ClCompile Include="..\..\GPUProc\test\cuda\tcreateProgram.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tDelayAndBandPass.cc" /> - </ItemGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/test/test.vcxproj.filters b/RTCP/Cobalt/VisualStudio/test/test.vcxproj.filters deleted file mode 100644 index 267ccf80c93..00000000000 --- a/RTCP/Cobalt/VisualStudio/test/test.vcxproj.filters +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup> - <CudaCompile Include="kernel.cu" /> - </ItemGroup> - <ItemGroup> - <ClCompile Include="..\..\GPUProc\test\cuda\tcreateProgram.cc" /> - <ClCompile Include="..\..\GPUProc\test\cuda\tDelayAndBandPass.cc" /> - </ItemGroup> -</Project> \ No newline at end of file diff --git a/RTCP/Cobalt/VisualStudio/test/test.vcxproj.user b/RTCP/Cobalt/VisualStudio/test/test.vcxproj.user deleted file mode 100644 index 695b5c78b91..00000000000 --- a/RTCP/Cobalt/VisualStudio/test/test.vcxproj.user +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> -</Project> \ No newline at end of file -- GitLab From ae8c44ffb1813f39e841ed90496fcf97bbb9080f Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 12:54:26 +0000 Subject: [PATCH 666/933] Task #9127: minor updates to top-level INSTALL and README --- INSTALL | 10 +++++----- README | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/INSTALL b/INSTALL index e6ec66a18b2..c96d99c9399 100644 --- a/INSTALL +++ b/INSTALL @@ -37,8 +37,8 @@ package manager. For almost all cases, you also need the development packages For Debian/Ubuntu (likely incomplete list, but goes a long way): apt-get install subversion cmake make g++ gfortran python-dev python-numpy - apt-get install libboost-dev libblitz0-dev libfftw3-dev libcfitsio3-dev libxml2-dev - apt-get install libpng-dev # for AOFlagger + apt-get install libboost-dev libblitz0-dev libfftw3-dev libcfitsio3-dev libxml2-dev liblog4cplus-dev + apt-get install libpng12-dev # or latest version; for AOFlagger apt-get install libreadline-dev libpqxx-dev doxygen # optional apt-get install libgsl0-dev # optional, for AOFlagger's RayleighFitter apt-get install libgtkmm-[2.4|3.0]-dev libsigc++-2.0-dev # optional, for AOFlagger's rficonsole @@ -102,8 +102,8 @@ Instructions for Manual Build from Source All tests should pass, however, a few packages (not selected above) require GPU hardware or a database to pass all tests. -- You may want to add the installation path bin/ to your PATH: +- You may want to add the installation path bin/ to your PATH by sourcing (not executing!) the lofarinit script: - source "$HOME/local/$LOFAR_RELEASE/lofarinit.sh" # for Bourne-like shells, or - source "$HOME/local/$LOFAR_RELEASE/lofarinit.csh" # for C-like shells + . "$HOME/local/$LOFAR_RELEASE/lofarinit.sh" # for Bourne-like shells, or + . "$HOME/local/$LOFAR_RELEASE/lofarinit.csh" # for C-like shells diff --git a/README b/README index 89b36ddaf5a..04c81278006 100644 --- a/README +++ b/README @@ -27,6 +27,7 @@ CMake/ CMake configuration & build helper scripts CMake/variants/ compiler- and hostname-specific build configurations CMakeLists.txt Top-level CMake configuration & build script COPYING License text +Docker/ Docker container templates and build scripts INSTALL Build and installation instructions LCS/ LOFAR Common Software: frequently used LOFAR libraries LCS/LofarStMan/ LOFAR Storage Manager for casacore MeasurementSets -- GitLab From f3e5beae55f91bd2c5b1aef8d63162cd8495e5af Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 12:56:09 +0000 Subject: [PATCH 667/933] Task #9127: Cobalt GPUProc: 2 comment fixes --- RTCP/Cobalt/GPUProc/src/cuda/Pipelines/Pipeline.cc | 2 +- RTCP/Cobalt/GPUProc/src/cuda/SubbandProcs/SubbandProc.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RTCP/Cobalt/GPUProc/src/cuda/Pipelines/Pipeline.cc b/RTCP/Cobalt/GPUProc/src/cuda/Pipelines/Pipeline.cc index e84a3048457..a168d03ebb2 100644 --- a/RTCP/Cobalt/GPUProc/src/cuda/Pipelines/Pipeline.cc +++ b/RTCP/Cobalt/GPUProc/src/cuda/Pipelines/Pipeline.cc @@ -213,7 +213,7 @@ namespace LOFAR //sections = program segments defined by the following omp section directive // are distributed for parallel execution among available threads //parallel = directive explicitly instructs the compiler to parallelize the chosen block of code. - // The two sections in this function are done in parallel with a seperate set of threads. + // The 6 sections in this function are done in parallel with seperate threads. # pragma omp parallel sections num_threads(6) { diff --git a/RTCP/Cobalt/GPUProc/src/cuda/SubbandProcs/SubbandProc.cc b/RTCP/Cobalt/GPUProc/src/cuda/SubbandProcs/SubbandProc.cc index 404d92618c6..79810a2bcd9 100644 --- a/RTCP/Cobalt/GPUProc/src/cuda/SubbandProcs/SubbandProc.cc +++ b/RTCP/Cobalt/GPUProc/src/cuda/SubbandProcs/SubbandProc.cc @@ -86,7 +86,7 @@ namespace LOFAR } // NOTE: For an explanation of the different buffers being used, please refer - // to the document bf-pipeline.txt in the GPUProc/doc directory. + // to the document pipeline-buffers.txt in the GPUProc/doc directory. devA.reset(new gpu::DeviceMemory(context, devA_size)); devB.reset(new gpu::DeviceMemory(context, devB_size)); -- GitLab From dc4b872491bb88007a6a1808adef7ba0f2b938f3 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 12:56:36 +0000 Subject: [PATCH 668/933] Task #9127: add setcap_cobalt sudoers file --- .gitattributes | 1 + RTCP/Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 RTCP/Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt diff --git a/.gitattributes b/.gitattributes index 4376ab56ee0..826c72630ea 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4629,6 +4629,7 @@ RTCP/Cobalt/OpenCL_FFT/src/libOpenCL_FFT.a.not -text RTCP/Cobalt/OpenCL_FFT/src/main.cpp -text RTCP/Cobalt/OpenCL_FFT/src/param.txt -text RTCP/Cobalt/OpenCL_FFT/src/procs.h -text +RTCP/Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt -text RTCP/Cobalt/OutputProc/scripts/bf-output-loss.sh eol=lf RTCP/Cobalt/OutputProc/test/tMSWriterCorrelated_.run.in eol=lf RTCP/Cobalt/OutputProc/test/tMeasurementSetFormat.parset-j2000 -text diff --git a/RTCP/Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt b/RTCP/Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt new file mode 100644 index 00000000000..97ab1655685 --- /dev/null +++ b/RTCP/Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt @@ -0,0 +1,5 @@ +## Allows lofarbuild to add the listed capabilities to any single writable file for automated roll-out. +## Attempts to disallow adding another set of capabilities. +## Does not attempt to disallow adding the listed capabilities to other files, which would be trivial to bypass. +Cmnd_Alias SETCAP_COBALT = /sbin/setcap cap_net_raw\,cap_sys_nice\,cap_ipc_lock+ep *, ! /sbin/setcap cap_net_raw\,cap_sys_nice\,cap_ipc_lock+ep * * +lofarbuild ALL = (root) NOPASSWD: SETCAP_COBALT -- GitLab From 5c7c049ddc68c78ee0ee7df185c011a973877b74 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 12:57:17 +0000 Subject: [PATCH 669/933] Task #9127: add trivial new TBB Writer fix in usage print --- RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc b/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc index dce414512d6..48a9fed67c4 100644 --- a/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc +++ b/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc @@ -69,7 +69,7 @@ struct progArgs { bool keepRunning; }; -static volatile sig_atomic_t sigint_seen; +static volatile std::sig_atomic_t sigint_seen; static void termSigsHandler(int sig_nr) { @@ -311,7 +311,7 @@ static void printUsage(const char* progname) { cout << "TBB_Writer version: " << LOFAR::OutputProcVersion::getVersion() << " r" << LOFAR::OutputProcVersion::getRevision() << endl; cout << "Write incoming LOFAR Transient Buffer Board (TBB) data with meta data to storage in HDF5 format." << endl; - cout << "Usage: " << progname << " -p parsets/L12345.parset [OPTION]..." << endl; + cout << "Usage: " << progname << " -s parsets/L12345.parset [OPTION]..." << endl; cout << endl; cout << "Options:" << endl; cout << " -s, --parset=L12345.parset path to file with observation settings (mandatory)" << endl; -- GitLab From e80260dd0c229f7c5aeb1ec1ee409544ae987c3d Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 12:58:00 +0000 Subject: [PATCH 670/933] Task #9127: commit pending COBALT doc updates --- .gitattributes | 2 -- RTCP/Cobalt/GPUProc/doc/Cobalt-hardware.dia | Bin 6236 -> 0 bytes RTCP/Cobalt/GPUProc/doc/Cobalt-hardware.png | Bin 133603 -> 0 bytes .../doc/cobalt-data-flow/cobalt-data-flow.eap | Bin 1361920 -> 1542144 bytes RTCP/Cobalt/GPUProc/doc/pipeline-buffers.txt | 2 +- 5 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 RTCP/Cobalt/GPUProc/doc/Cobalt-hardware.dia delete mode 100644 RTCP/Cobalt/GPUProc/doc/Cobalt-hardware.png diff --git a/.gitattributes b/.gitattributes index 826c72630ea..3b0f7668512 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4430,8 +4430,6 @@ RTCP/Cobalt/CobaltTest/test/tMultiPartTABOutput.sh eol=lf RTCP/Cobalt/GPUProc/doc/2ndtranspose.txt -text RTCP/Cobalt/GPUProc/doc/BGP-Cobalt-procs.dia -text RTCP/Cobalt/GPUProc/doc/BGP-Cobalt-procs.png -text svneol=unset#image/png -RTCP/Cobalt/GPUProc/doc/Cobalt-hardware.dia -text -RTCP/Cobalt/GPUProc/doc/Cobalt-hardware.png -text svneol=unset#image/png RTCP/Cobalt/GPUProc/doc/DAS4-fs5-jenkins-install-instructions.txt -text RTCP/Cobalt/GPUProc/doc/bf-2nd-transpose.dia -text RTCP/Cobalt/GPUProc/doc/bf-2nd-transpose.png -text svneol=unset#image/png diff --git a/RTCP/Cobalt/GPUProc/doc/Cobalt-hardware.dia b/RTCP/Cobalt/GPUProc/doc/Cobalt-hardware.dia deleted file mode 100644 index 2ade22c3509af734be5cd23374492b8fdcefc15e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6236 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 CIPGHq diff --git a/RTCP/Cobalt/GPUProc/doc/Cobalt-hardware.png b/RTCP/Cobalt/GPUProc/doc/Cobalt-hardware.png deleted file mode 100644 index 011012b79c382dfe704ba06fcc1b6924179f6cc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 133603 zcmeAS@N?(olHy`uVBq!ia0y~yVBW#Nz?8wk#K6FCSm@St1_lP^VkgfK4i1jn5B(o7 zFfecyctjR6Fz_z`Va5enJ>?7x3Jjhujv*Cu-psAE3BCLM$9L0Ut*Hw&B&A(Fl{7U? z9$s4TG5FHrmztiL6LvakR0U-Qv^fb2eNs_UR1MZJO5u<+PTTSBegEH*y6@Hc*XQoY z*#G}U>6^{x>|Vc!DxbUcdK|aAiV!1Hql1C~2Mbf<kNpK3qZwIDqoSiZS(t3(Y^zMz z+1abVIw}ZoNGY1eH#Id`$n@>~1yaqda6H>%bJ5dN8~1DK>!)wG;$UHN)Lyk})r_B6 zSy@S7{y`3nMK((eO-*BW7OC3ES(oWlPX(EE>*n_S`~C9v4-ED7^&vvX4lI;OWr4b+ zNoY!y;gXA2uZsTS_1f#8AaIz&)YSA$&B80(EKG(zjw{8TXD#)f-qqcGIJvH_?weM# zgMz@8#cS8v7C-Z;ICHdhU$y|pkqs<M;}is(r=L1?YSSj8vg}5O3CfC!hLxXEKx`fk zU0vOsJ9oah#$*`ckpI(p*4x|L)6>!pJ$~`xh390ojp6b~6pH`<`<r~cZ(}q!OOuSc zy1KNq^edwZn|&KL2*~ZP{T&t%Q1IZZlR|_;Uf#M@tF(4+b>I=5u-Imaq<!6<6)QAs zo;`Vzvfc<3GS<RsJ{g&rjmKB8*)TOqEK_i=^mzR8<;grz0S=ZX!MywX&O(@xApILR zZ8~-Obo24=@9qkViKVT#<1jNSe-|SuDOr}?_@h`;Pw(7`6ANPN?(eHzx^(G>9}f?= zFLvvdlGrQI!I<*t$w>{~$NW!@c8kA#`}Tu`<tgV`pFVwBAHV<5<8|xwzG*cl=<hCj zyJ^FQgzaJ-_S2=!^WNOv-e2}}(<Y-cHDAAc;pqwDIC5aos#U+fybLbOZj{Jb<}<VD z>J!(#PZQKMG&=5Tff9hSP5r+)5a#9!&o%Pz@3U1<U|?uC{`jKxHH8PSUPXn3u=Jmw zXKP*er{dcyP!yRwdhsG6GLo}jKXzA()<I=~j)xqweMR2h-X0ztHhmo(E7q@1UwA() zHTCJccXrj^-uTbA+n9Au&><uw#LJ7z#;)|0NYNBQjw1z%IyyYJlL{XmVtt|kF-Em| zo=xSYTenUfJb3Wv(W^Iac5Y%~YrJve#<u%DGmTpBz5=;<?UJQS6Q5<jxv}w07<;3G zf<$9q$ENx7<ry5jy}PGRH#ai6bpCw)|9^j(mq{=&Fx1r6YHMjd_}IwIeqlN<Q=^35 zojsMCw`~)Xleev!GJX1VXjZuJN@M!ese9}GN-=Dx{G4WIx9`M>6XoykJ$?TC^y$<5 z3(D$k?%lh0^5n@66}PwLo(ObP5IEeb9lq|#lP3orGqdw4xJx!V@VICA?&aX%c=7V( z!-o$I&CJY9Ohg0(7#M#1{{3AmCoQe*@Ix`(C>Dkjr%&JBS-iZZg+*U~eM`%cb+Nm< zy1PAF=iAr!9e#M{?p;O(OG`@&p=;}6y|r#$z54X++r33ky#fOR<>cfN6BBK1_Z~Rl zpfvHs5<Z?2%nZAB?_RuU5ucpRiZyF=R8)?fJ!@O{r=qm9R9t-evu9~i^&78r_Vldz z+1t{>;{D7;L7>C&!P(j7Gk!`Lr}6B*3d+xJ3rrXB)IJJb9o8#t&d0D|{d##35fK4_ z2QOZ99Dev~mzIsqog+t<tX<3dFCs9o@cq5LA3l6Ia^#4laoPb#Mn*<Y&y#I!ZOfLa z<>kFQIaxh<dh*@bj0~-<t=ZSt{r&UP*eobIT3Qwi1OyzG&a&0DGEkG3k=c`b+w9WB zwzjq>U=Y1M&(+=C_`aNys_N2Z%h=?kB_$Vj{pDau+A(3qj2ky@aP*t!-a2yl@Zq>x zP_ARl=+f9<|6eXI?%t+UZkd~VDvcQyELozWsJL+b>QkprZ_l}Dlzgm5-G82lsOZzD zPc5yjiwg^{UcEX|&0Ifz-<~B)QqIq_WnieR{3#^#?#@o;)U536>AKP1etu3jG&I!L zUw`P(p=HaK{rOX4ZEc;G_wK}r6Z=j*xV(Zn`O};^b6#ItySw&xSyfe4Y;0_Da<ZRa zUweD|jvW<=i4T|g&aUDuz1+HZ@#DVxSFT<)w3KGLsJ-O0rjELiQPJI9rIXcsA3c6N z+bnn1%9WXAWoF(_I$a(;KR^HPpFezZHaiL)I>p7s?GOqL4K)?uZ(1;YvbulLGw#=g ztqwgoIXO867IW6D(b+AUsQP7A;VwN%wrO)bnA!Q|SQvWbY<I0)%iC8{QgR?Db=k6I zQ$Fo3f8RD)KW@*Cy?f_=xpH%J`ooov9wlA+xY)g)i6QO$yt}KJc9*?nV&LH9oEYn> zBE-zh{IEsdzAnX$ovq|u_oq)qGkj!ZWqrGCMEBMF{PfXWR#w*MT$@DMj$ONw>Q_zI zkC$7p`vCv`Rjah#P7nCseDuXv-f8Xa&+qN6K685d)Tv*yE^BFPXVwd_yp7pkC;MB~ z+S2l?_WaqiyJxO{_wJqJ1%bu{&iD8B9=3Z`VKZ-Cw*P!PUWN%1CrT>ZWs|QuYq57a zkFU4itT(snU3RWs%^lWcY+`bREAdcDD#L-t$NN{daIK5kdFc3YcdMqKi{Ibh|Ni^6 z3A=V#-FSNB=FQ4mTQXD9eSLhMotw+ez`@6t*2#C~wT_<Ny2Xo`87h8$Qq}zwl98L6 z`-}G#W4A=yjvYH3u6u=to7>s#Td_hzMJ46gnVECv&P_{8>+J0GIhEYe-7PI8m6esn z_u{d_i=|7KvKq&`ESr8^faA!6!wr#gmDBe8mw9^Pgh#|9_GQrtIX5>wy|y;m*Vi{O zF;PuTZHjnTXXn}Z_VaTBLPARJ>?rK*?cKtDRai)9*REX*46DP|z6y2l_0`qZe*O4( z|C!eg>un}_^z`)b@baELcW&M4)xW>K7XM?Hk&(f`@Z|aP%RhQ*3m+U{T=VMa_Wb+H z{N|o|(!z1%K|pNm+b2(07%p79*4NST0L1k0^vul2(9qBb2@Q>midwd8nUM}ZgMg3_ z7bj<6Xy{y%Od*B?H*e1LQj<vfaC2+6c-YrP8#fAyzI9Y+6X%)nR50`8@A4^$Kc8?4 ztF4LIIjJNjI5;>aX3wHUht8j0AMPF*6SL<!r+cr|)f+cD-cEZxT|a)`0p`riOs%Ad zgMMix3%vL3+jsB4freABFP@)ozf!}8TTf5#>({RzA|fJYoV+Q};duD^_3c}?iZVQS z^@=N3HRaKf&gFh{tx8^8`2GF;{k_%8SFJiV!-gT@%8J0YwzlRkHXOfp7Cq$(`_C@s z<pgSDu$}qTvE|9%C2=MuCN}l|WEd8#TQ_g=<jIasVPVtS+SnK#+^_$?cHO#Vd!}`F z&l30h?IlxUZfRkmvhvbudC&EHB`5Q5Zc;TgoH&2}|1&d<H8nN2ZrwU}?p%Z2N3UEF zVYqPjuJ2>xZ>>E&J%{pl?A$qX4(~_yOINR!K0i0tH2a#*Je!%*r-z4xlpJhgJ>Dnl z?d|<K)J{|L<cSj;?+#3uHqEZ)$A^A-dk@x(HP3&%xoOPMFlWx4J(ZtN<TdUMZ*Ofi z)ajS_b>j5t<Y(Pqw1pdg7_VHj#-{AejHafh1q&85G&Jnl^Jib}?-groE@$jDH8Cl$ zm@{*x<WnA9UEK>7_H}<|yb1C0I`!@C?WIeX{`~nf@uQ)sscCk0_N4-S=Cn$es84lD zryLy}=gytW(C`5a;!-(S4*mW8T~}8(a=k1AL&L?z?#v7Y7I&_%k6&rMso66wF0Q1+ zgyF#P<LyQZ&eiBhZmRoRHKnRCVMbFE6Eibr^+kB|4u+t)2wy0o>WrRMLisXqVC z&9#2^>=_Fa<G+ZQn3|`jrXDzOV8#rI9l{J3!p=|hP+7KoIWxnRD_1nO3W<qLn><-t zL*u|1+r6JU5*z2ulLIvv6#@eTrOoqlGBXdlae=|RJ3EUXALEsXQErWU?JGWS-aPj{ z8O7eeQ>THzvL#DuetuFlF%dCHndZJq^}YHcp3i!}THOqfHAsrHFew@v7e9FCq;H@) ztGSsuFH2?d{ZpDINtu~H&*!(av_u%GKjL4wYgg6HO{tZYm3#K=Io>bt@9&?Vle1>! z%Fh1&?c27A2?{Ds*4Na$xxfDZp32Wj$;q>4&DvN0|J}=%mQ`O~%$hZ8Yxeb|>=#?E z6zk~fwpM!8F7@{G^kih%u<6B@Q_5|u%l+maIwO9QFO%g_zmm1JbpP?~Tefs`b=})r zy?xWBP20A8dwF?zQBhHBY;0Ou+W&vQ^;g>Ly*68~tGj#cnl&9Rk2a;Aj@wrg85(-^ z-o3cU$e*vSYHMq2=M>Ik+-wu1Yq^yn*U{0@%ZrQQ!I4hk=jZ3Y4?TAG?%h+Tw2Y07 zt*xUS9UBvE6LhO|rnsK^^z^i)r6ngzQ-6Q|rcFkNw<qXE>?<lSH;)W*iHef?eEWw& zTU*-=>-!fvmnpZgy7$Zd{rA^eTs%A_<;ka~r#BY*g2Aah3tqi?b#rt2{<^<X-?j>h zh}e|8xUeR2bJ)6=$gr@omzPwnt*xc<dvhfe-b%V~$s9lOVyZh2w^`nuj+y#XEZcre zwk&$$;qHF?#l^+KLPAfTK7FVl*B`&9V&kSwMU|C58=2Yn)%-kk@MHX&NuX#qG&H<> z_bw9?)5M7rBQ%W-!QfK0VZt=`RjSLy0*^g95;0%U%PjX6%m3_|JX-!U3>g3D&2}&I z3JDQ0JMj72x4xd9H(x*P+GVxL=TxIxP>>MAh0B+nmjy*fN3UP&BjFqzJo&kK?Nuok z1&yB{AG5D{wIlz&UC{0Ji?6G%-`t#ja+m$Oh>b~ica<_U>@I(Qsjhs-OlF1);WC0z z&$n$ei}*4juJXtR2TxB<<M=hF+?*BKWK~sF*T?N;Vvw;cV);BtQAtUuudk1xVSW7m zeRY4O_>9u_X0rdf+0@i@<$9%r!D;2IlfJ*y5bkt|*jsUKj%A3`Q6V8A?`b-W3^o-X z7F@3|=v}d51p`Cfzdx1V-rQWZYSlXs$a@q1^4T-9+*>A0jSJSVumAAi;FBj$e*OBj zY}vB)@%!y+eiR(<lMP<(clOk&q#y5i7OY&!dB6AHuh;9VzrPC&4F$E*tgNDfgFpZM z{k^EDNN4BiZMnDm{->RpVW=}x_@nxVUthC-eBY6LoKH&s;lqb7w=ylBI8l({!R_t& zqN1WYR|E_n8Je0NJtMBKuMcWe-e3-s%KrQ7>y-4~-rhnB83s_Bv!I3XwXb+rR~JLW zt5>gTe}Aj}|F2dd&+yQT&wD|wwk1ng0;c#cOZoil?8lEE@9(eg@9eyI^=hgQKQAwD zYU<N1TecXdpPOS@%*MAaax>etJ2NZ_lWJ=AY~1*<onL+r7-*z7oRn^6lb63g*Sfr? zr{~q{*NKlEIQZJF>;KvKTuT3^r^Dl?{7-|6oBJW-HqQ0&OO`IJ{;NF8FqzHf$G5l9 z@n3~c9yoMqf8F0#p$ui3LIMH}uLHpun0fnQ=|%~+n&01e<<879?f&#;hGFtDpP4~X zQC*#e3432U#m36^A8&7Isi>~Lzo#<T)6+9Hw)W+vrB+r}ORV!GA(6XMdz#n9^^5l; z9%h?Sf6V6_D?9t~)6?~Jb#>Fv%($3+^z6#u<@>6?zk7JN-ObHSNJwbUo;@};Hj~x; zuU)%VR#I~2<Vndtd3xE`)_8h(9eVsxSk0&5_W$1A-p7v~ty#M^vb4MO^)=sryj{-~ zGnTJh$;rU*-zQ@a%e3hWjr!g`e?7fZSp86Q9JhPYV}plXjFm?=B;4GTI^$=Pya&r8 zewHXdJG*`Rg49@8Ss6PUQx8;K_AV&+(8?{YqN<uWmrGxNeXq26S6A1&`}^g82d5s; z=@0R~m+tcO++6D$a%Hc2<n86AO*p)x_V>59x3~YEq_404zBI<e<HX&&ve(kzit_RG z@k*QJ+}O}4w)Vx<M#H&Nr%s)3SL^luZf5$wKR;*8Zn~x?xP@6a-`(9kJzc%`nwYn~ zGV|(mmgh@aHG`LZ`22bM_U+=LqF=QFVq$nalCFYdMoUX;!&Nbd>WPB&1{aF{{rb9k z_wL=-?O8IC4Vk^ye{Gv7z4y}fZT5M2dD$TFT0efDikh05ipl}GGuPM0`}_OX|Nf@S z&d$!9R=FgqC}mHZ2_GNdn=nR0orzPYwz5Y?MskbkEO<I;(V|6hZ;irEvF9Z$W#!}R ztNi>-RaN!iCd-s7SFRj5qOzug7hH|4Uah@5)Is6K`7N23A2`Ov$vy2`|Lj@Xj$nD= z#n-D2&n-4s-PltnzG%7se7o9TU+(TMFE1;Lii)bLtX#Te$&n*RUc7i=04~(z{W6v6 z71GkuR_$N8YE{y9tB%COn^I3FylDE?vZAlAPh3APps&59g@HjuRn=2?%E~LnEeE0` zfByVA<>`5OyBZFD9v&WvY3@9=K0ZDMpIvXIGdBKM9I>au(5B+k6V9hM)&1uk*u}cx zs^NnBRo$JPj0{D^#hU&HW8=0QIDEMH-5tw{KTl7KR~%@Fl$$tB#`2W2l%Ag6yZZr= zagGWn1Ox>ed6n14@0XL7_SU@cdeWTUUfwVT86jzD?&zXP?;==3URyeqy}QF{E-hnS z*0V(@ptkn!&CThI45v<=TD58w&uQg0yE`fyqQzO7a(!l-`9?=eGZehMq`IlEYU|dm z8|?P3S@UL7>gfjxd#_1L9qZ}gxxvo6c*BMZM~}9C`t<10qqx<Z6L)iN3`u4?^XbHv zCoU{Zh9Qcvd-m1*G>W|*79M{1-=!sc=VoPR-``hT{pyNlX=$lX(f4<E85qF*>3OZ( z;>V62H8nR64-5PE`MkYBbl#~cn!diiOC}#be%yKOoH=u}w6%Bd0s#&drj<M<-3$$( ztHV@PR7AwZ+gn@D&ao6`IB@i+sJy)YGK0OJT$BvW&CP$BKYRA<!>5i8j+_nKwoO|X zFV)0#eSQ3KyIwy(KhW5PgvPzS)$O%cK33SoUY}=IE5)Fotn6$xW9n4aa!KD=CMU07 z-@a<qs&(u1OiV<kg&uoYpdYtKVe0a`RnCboW_NXWv$L{JoigRg)2FdJi(Fk?SWdIm zeR(0MR{!7tqo$^&wDj!j>*K@2!?$nWK7IOhc2?GtCr&Uh9P5=1kB{%?G)|c2-ljTz z!UTuDPahs!Ss8q6|H_ps6SrG+9Bh+xpXs4e_w!ThntirxD`ngJHmzFqYHjp(jn&^c zxVh8Q(k7ktQ4rAheCFDkNN26fmoI<59$z1LZ&rKz@#DwagQBaRcDg9_Tn<n)Fqm*J zUTTs4i_$5&IywfQuim<4#UZ66pwsxY&uF4YPe;d#t(yeQ&A-3By<PM9!i!bT@9yk$ z-uX;NRdwpzxxD=+WuETiS+-z7LP?3q%QHMPo=!|W=_#AKWoEv|<k_>kb(fWtlxzr> zJhH(#F3!%{`u6eT&p$ps9vK<g+uJLw<}+c!gbUZMz1w<s+02<Yzr4JB_wL=`pdcwJ zsUt^^USAts{_9JoprD|UXT<r7H*Z=R8Ex9KWr|KBbJO96d-m=HDR_LWmtWq_#Kfed zvokX@Q`WMGC2UW6keuO>ljqJIJ9X0dW{G&C#Je?ddw-ptu0LHrzOSoGOH=dZ%a?-U z;@7WT+qPxPo2!b;PFKyIHOtG(YuBz_%a$$MwsouTTr1K3<MS;F7hPYY9vyw#wOj1y zv14f|DIsBD*REaT;@~)O^5n}GFJx>g1l%?mg4zV<Cq3?{T_T|*z_IP|vu9;*ZWvb7 z{QQ*4&CUJoRr8O@I}0Bl+gJPh!N$pxC-?U9R)2L=VC%SYB}AuRtpE7;C>AlpO*4&B zxx&OHT&J{~gs+QfEM;e8*tTt(late%tL%*q3K>U^9%Ws96_npW1G3=JKAr}dr;5G> z1qDGtLN<0qPdJLeLw5)7-?vv+XP3Ku`?fM<+zT|~S^4{$E~v;8;Bfo#<)tu$ndS%? zaa^%!laSo(nKLimbCxh%vvTcPQ8BT3w$<Ox&9z?X{q4(_J(ZuIot&(`apT6;)>Z}v zP0f{Cv#-lpm-$3QR2Y=}J-nmxb6Wh~eeQBvT3QjpENmY>e_p+Mb@TDx-`}@C-6+7p zl2p+s^HlNdRPFFdlP5pixN)Ol(G+(HU0vO#rY0jJqm<AWnd$}x1-G~5GBDWL*+oX) zjNf1P^!fAMyLSEh{d<4i->P3<Ug}&GXMFSa?Z)!!g6BJh)n^n=(U05Hu{3VRtXWDB zkNzv3;<?@U`>AVdqnVkQa&mIc&9gmy=DStPy~<s?cPCDreBgk?jg4(jpFDeZZmxB> zkI$M78zx*?psuQFTm4PP(D38u=jZL~|A~l*%&{zfc57?)*|TRsJ&n_6rcRx@Iqhs# zP7Vu$j9tx*!-t!F5^itH{r>)bd3pKvEn8HqMWv*=LR*!!Z#6kEFm$*qnl^1(YisMe zb$Wm9h>3~KnlYp1$A`r0>tZ9CteaY2ynY?Ow`%I$C?1`;bLVc_w8_U$NLcvt)vK)i z7rpI3<JSv1KGjD=MhbGY1TXiSSaq_it1B`xa!C>|FRv#U%$ql_U(PmaQ;O$)<NtrZ z-@oAfjypQ~_JRcp>FLj7cb84fy?*-S$%EJQ!q>;~?tZTzAo2S8`gXxo9!bfW?fmjy z-rnl!>eHuA^_^+N3K|#tZ@F{_sO?t$?G59sIrHbs|6bb{67ebU<f&6xSy@fT+1S{o z?9Bni<qPL`8oz)3JbChD;Hlm>HOHsx$Gf?@dU|-wm^pJ|-{EV|KYjZ2>(?)CaXlIP zx*AW<laG&sx(tD_v3t|b&g$vm0S!7Bx3{<3SAKGdirTee#R`4N%F3U*(c6Cf{%vh( z8M~{b^UIZ&mzQt6(w%>QpYL3&sUO>2c&tK0L-q9Z1O)|`E?rtvQ*-LnDKRmz*VooI zr`y`t^vPH<F=*)M9GRu18@I>8&`@yp!sW}^t+#F8uCJ>Lir>typtN=Bx0k19XJ=<9 z7$oY=UAZzdH&=JHd|^jYcJ}KB4-Vw%{wvYZ)!lfd``EE#8<UT33YTYLYD{1#a*uR$ zboBIGsh=4geOp-FuV>GJhRKtK84ldJGpD`19W>(d`NTx!sHmtV&AZFrfBW)f%Z3dZ zd3j|eC40)>$HiaXwq?tKxQXHG<6`$#nToRh`}aG)zW#sq^>sII+?bG7Qc+!PZf<@! zIWRDAmpsowyGxfZmzI`hXJw_NriQPNyL;otj44w@q@}CBy}22@+>en#OgF0J)02~* zpP%RE<dn54;fQnnrRSm|^!M-Ijq<l|-`0=c7ZVX7AtvVb_nNM*E-x=H14Ch9;iW=b z+r7r==Qg}zY*)+8&D9QHm$F@qgMs10_wV`n`R&ISyY(i4r&SJ0d|4Xz;lqasJ0~eO z?Af!&JpUd8!?wJ;US*(O<=(iWA0HCs<>#+ivj)`8nfdd|%HY)0)Z5!~m5aA(1_lOB zoId^h`Sahuer*kO<4^|!n`LjF^hs>mvUTgjk3VkRiptM_Z(IHC!2jLn=i9%2@#4mf z8!ukH($dz}kJ^%PZjR;i^Yi8H>-Oy3Yx`7X=gyr=_Ac(1wSM;Y?cX;yH*eWulA4;T zRldLMZIruv`>tJ9k#V~>Y}l}CmsR$>f)!1BBb09N8O3w3B(XTe=iE4UjII6f*R3ZP zuU>un_U-DYr=~7iw8+NB=Fp)-RaI4ujg2LxC;j~W?{CSRJY$B0y!`wrQ?C5```g>w zd*zAcOP1u^-xnJdHEaI-cn1fEpr9aM-?O)FS(%znJ!V~1UCqpJ<?7X?OO|9@4|a8B zW%%&;IRB-ig+W2V!F%`YIo2!v{M|dh(=7&<zg$}zy`(uUEv>t*`RfiRp4vZu|L(8- zy{-EDyZ!%uaTg1wotg3Q(IY1nAyCSHZ1ej1`s01-eseg2n;$)Tv?6BGym@)~`R7lb z^nA?D-zcH5E$3#_%=OcziIuHxc2E%LIk4$$4UU2R4<8;nM@_r;=g*(A_xJAJy~|s# zqp7*G?r&8=L4k{l%lc2yL7UPe-e3?M6tu7Ar;wDCSNF4id3!d77q4DjQtAnekLPC) z5D|G}D!6p_?&|aNY;A3ApP!#EE-3i(>+A6S!mnPvx^?T;v!_pMe|^b3H^;Kz!o`b% zr?ux=6e`6^Dc$e@jaWHEM$TNbj{8jOj~^eEbtm`8+0J_NH22z?NCpP2rzcOHI>p1o zbL;l)*q9hG-KZlC&p23C7nGL%{rOql#!Y&nAjc7dR@1(s+}yXDQcs^cefsE;Bj@H= zE?&M|T}LM-G_>^pzrV%L&$+s}y}P~r{Q2|!zf>|aGnXw}#=_+2=XdVx+1!_xmd3`$ zPMkRLc%SU;Et$egYYGcDPMs<`(IaMC&dryXmy3#s9P5+4edETA*|ViZMP2t^U~x-K zOa#Sk!ox$YTkP|_y}f;Wco-5sK03OWYsb!=si~<eSFQT@^?JO?xs^+n965aW^39tw zCroItJmux(6;Z1i(pn-B7!<_B@M857B_D-%;-@ZgZ8#PdFUq-U_3H375rJW0X{o8D ze|{9++Ulyn*2Ky!mJrCV!_M63pwK4%q_3!~%q;as@s$;UTwGisX_j?=Dk>{?PM8o- zRP+hdk6Ex_!GgVe|Mo~4o8{ko^ZtGPlM@pwKR*kc`Q*jL#VkyYK0Z2%ijF=$JbdqN zY)t<5=cmE>#-157X1sfs$H>@t;)KT<zJ?hfaO2a{(<|?Eh|HMFQ@1?mLdV>>vOcYU ze!t(Zd0tFR%*x8DsHo`Pj>6_|z5O;eHeOy{D_5@M<m3d^_RYV3{aW$rXaS4blb9U^ zj&5#kpWeK<xHxlW%evUjrKP3XVQWsDKYxFB`TEtXwcm;>2{5-zn<loZs!_sIfTN}E zdk}wifrX4*|NGZD#l@dbPuFJ;p6}@9;Nalz-(NK6PT;+b$?QufELxQ0=H?b3eEsTG zRu&cphB<TR8tO|PK62#ApP!#2BO?zUJQyPL^wFb7KYmm&9Ox8QH_(?fN<Sx4TwLt$ z@4v}AVU1Hjz=rEwktUO7&*oO2A>qOSTMIDLAd#tex{I6Jp=F8{Hg#85g-&6r{{AjE zKY#m%4GV(omxM1_vLq-t`17Yv8zwTd^Cir`RrvVWMN{dfUZ0sps>a5_E-o!5Co3NO z`1yHxU*Eaq{`0rx->-XnYpdf9Wu}Yz?)`E{jgHUQc!}ePz{1U&FW<N^<8Bm>f`!bw z^||2;4e#&o?|*ZA`jjaxs}H7ZQ245(sK~gC{mUy;CeVQFrcIkxJWt(M`&&#-LP{!V zo~fDHvXUvHqN0opSFc|E{q61Mty@#q6v}RSZ1U{}Geb&xdj8#ATbD0)cXL~IT{p6> zuFlZV@X@13eSLjjw3x3d=$3g$b}$?2T)23#^537G{r%@bExtG3+f7q8Jk30ok(n8} zHS6l}e)-<s-uL(RcAw%_Xye#j_BLrd7YEDhoBL|5-+E3x+@T%5?tpIem5rM=85tNz zu+*~6yu951@HNRP6Rc0YeEIUrF~|KiKbiPfY`?eQ-W!IhkAMFBnKWtAl=Sp-b383q ztXtQ2{IRo>)1JM1^Y86hxn#+bb?f$(ybO|+mA!TSM^;uA14GK+55FG0zP{ed%Br`w zce1+wzwh_!HSfN@b?X**j?Bi!Mk3=T*Wz0NsvXOgsZFa2jELw6(r@ElwrttDb?Z7i zJ3(XM)!%f&LMA#WxVXFbOPOZ9ytI^oK~8R7r?7h6|9^WoZk)JLv^jo%ouq_>in{vx zxV^s)HnVF8ix`#!goLzsmoYTlx)n90s!>AHXQq*>vvc#^s10&adr}x0)~tEcQS*mW zSna|q4P%Bo*Uub0=(u;S_SbuL8!7_gryBk`^^%8=Z(qsFOJ84K-(UNC+B^_=_w?z} z)2FkuvZ@{)YBfwg_GPtAi4F&g)BZ{K_tjp$c5U6NRkJJ#lYD%9*5|XPpPRFC?b_I_ zS)$3@?+iA1yn6lMW5t`jt&HcT59+z~N{PzJ?W_8lWo~XBA0J;@`t{;s_y3@hRd?;a zegA%aeSIO2jp4=X*S~-K(9qP(%*gog@#D#pCnKYyFV`)eZJvMZ*fAysEiEk*Q`3{@ z;`UTbED|uxC@C#vWnpP)YpeV7W8<=AZIdS#Us~eHEv9qe@y80Ad0W)}?kIe`fk_ZN zEphtf$%h{+yuH28Xx07t;_2v^c&LSQFPrR~xpU9YvD{q!J#W+0b?46cnVXx}*Z-e1 zX_8?wD19AVtIYiQ$p3cV*=C8Zy6$m|bu3ID+Yd0gOIB^Pka_p|`udm{9zPEC%-r0! zUvuJ3Eq3nQxfTre*ZiC`W5$dL69gnBBcr3M|Ni>=<;$1t`S-u=mgU;@e#13sQ7tX4 zY5MVTQBhWDXJ)*3@gh$74nxDEM@bBz^$zi;@6Vez&!An!#-`@&t*t5P3#FFii!3?o zE^wIJ)%EC_HEXoP*R`~^9zAm8%a<=cvrIfgL$4k<kZ`z-cgBsS-qUyN+^MXj)YRO( zd-rZ}{WzcLi|^dL$;oeIY^-_y+wX&JJTg0X?rdvoV`XJ!XgE1p{rqfm{`SLDr%z`G z1OM4(x_WwjIl=9-X3c77V3_Ekqpf}V#0d|7|N1X40+W)Ibl?Bkd+F-crOTI#i;JtP zs4Q8!G&C$sOh!gVPEJl*diAPRr~ViRv6n3XEg4AU<(?II_=Z-a!;7!0XSqEPcdM?h zUcWH^(%;;@Mz!D(tlz(Wg@uG1Sa(rVQ*%R;bZJK6l+~+OZ{51JyQ_<Zg+)n8>Dl@D z_up^nVO-rd(dyLe>+7fgJNy0pefAgoIvA4<wQ%zB@kvTb>gwv|=H|AxwoaTl@!7Ly zOFXTUkN52@eC!q(85tffKGDO+$A^W%XO6{050$!KUpjM~oSmJOCRTiX6`I2m=JBRG z&iJ2TvqNCu#I`m!N5{r_^WxId(p+6#i;9YZf`T$LUtV7y-!E(Z?eTH`Q({hbc6Nq_ zhU??@o;rWt-@`+~JTIoK?Ay=J&n3eSU%hIor<ZrIiPhEBb>hT{H*em|%gf8n&5hok zmuh4@PpqW0)bMkwm70=3M<N%u%+sf*rytH?l$V!pQ$NTh^gXItRYj%cSY<^;#kslG z?EM>`WNMw6Yc0-j;oiNtu&`^Z8P@Fj`t-DTT-?148w_5pw7kA9*86>L`TKj0jk?j> z4or*_Ka#jxY=w|ZSXkIc^E-F${I%m_YLwu5b7$w~g$olOAL|tq6kJk&k%2){a%Q)< zep+g(r-w(tOh;$u$4^d9ZjgKJ&erI#qvod(!-3nkrB655SABVLsFj<Wn|r=p?Wr4E zwiws`DmhbgG4!<ns13lU<FQvjRP^q)+}Z2buiw7?`z+J!u=R1in!(}W?g|1fE-t@j zxp;YjO16(5KZ@(c7#JEVPCaw_^zOpPY)@5>9z6;wcE5>tdtSbDimx~#I5=3dc+Kot zvr0-!?Q4G>IePT#hlhtnwZj7DuU-GPCn7T|G4bKPZ(iHBZ9CMfzr^lKE4TQsT9zXn zOEz!5eC^t_EyC3m6&8nc&s<dckuYh}q)*SzO554l>FclGCvzl@J7+TEX60YE;xFl+ zJb7~K)~!WFMJe40YHDgLSFU{c@S(JMUP?}y)Xt)(ry9=nJMgdw3JQjWg@M+KwIuka z+nJkh|MBC;q)9@xwSV_kf6u$StMu2GmoYIh_H}=L{P_6z^YiobZ7PMt#N6&)EGaJ! z4-H+rW=+hdl+M|+XD?fpR$g8n5;A4#)TtXb6l7*<F0DyPd2(%SboiPG!{TRW43m#t zxq4MqO$|J>XOMSiM;y2NWYF3*OUs?0xt=Q_o0uB!-o2ZBeVwnjH#38bO+|o<i;Ajh zYf)R}=Vu$2q{l@?N!<zV$o<U3AY)O$u=<9)jEs(!)~P?e0*9LxEKm>^7r!*|#*K&_ z`TcRv&du$<w}WkRM+XN(!@PNNlXIWnzIBU%fhYe@!_I<Jr%t6j<`&aos8!jzdD>j- z@+4Q!`S$gG$@67dnqol>uWj45-QY4{VodB^Y*YB?$d4Zt$;prT<?SjeK_Kwdm))-; zBO`AfKc4>m-QAx*fByRQ>&A`=69O_aR?M0u1&;c2V36{-sj2DU!Gk7ZwYT?FZeF|? z6pSbTOqw=LY)$_B_ICChBPUNs$Hp~lbac%Z`BhX{Fo2r7OA=ph+h(S-z28H{s9yX? z!NL3Y<6T^q?AS3Q(VmlG!nA42)~(x@d)rJ*tL=sOjd`7yoR^BfDEPj$bVq!G#;l@+ zJbeZ-E0)c@b}ekmtR4xmBgc;2x_R@Ut`Y}}<eI`Mj10lc{c69zyE|Fkzp1GyC^$Gf zE33oh(UDH!^mB7|mb?r~PJSG@Iqj?U$v#<Y5fPDd^X=v3<^O+v?r&&l7#j=H_ifgh z^z7{285bAbxN)PWr{~nEQ@3tKF)}h*T3X7=&TVGrzjUwu$eA;5-rU^0bLY;NFJE4n zox?9<Yirxs*vP<8TwHwV<L>hJn_T5ts#mU9(b3)g`}h0(tSl@uW~y6Q%$Pj6`1Ca0 zH9<U%+#717QyqPRgM%+k-FS6%cyv@$P@JEN(CO2sYiny0Pt9DpQZqU0K$7DWdBs<% zZ4oCHyZ5vG660W*yr=T>v1`}9eSLlXO&E(|$<OnF_jZ@Rzqhyg_tbZ$HQfgf9^9CC znC;S~aGo>9`S<p;^UDYI1s@A`b#d92d;3_;mYh>lG_$X*sr>dP(!qgY<zkz>I~HNA zg+G7%SmFQm*VorwU0oZKkI$MhV?v*`mDQn7K@1H1{QS3m=P0+C^2^yM2yooqo}ZpJ z>D{|`3l=PRdU|?yclTy5Ll$EdRn>{_4AfThvR84-&7L)D(q5a*U5*h8tqLExxVp01 zeEaq-?bBNR#suAyCr_4@m4T*bq@O>2eEItI_gDEH|IFN=v&d%6#_ikr`T4(p{J3%J z)>QA`8s+8X3<o|wK7Mka_r1N<-~ausuDEmWp52cw#T!lg_y1oLxp{_;%a8Z_|A&Q# z8^6p|mSEU-A!r+GV`?s_Sthr4nn!!-ltPO;D*_j%q^5qXunFViU|?9i`TNrchYBxo zy)!7{5uWSWIPsa|35mMj-*mqv8E{;EA;2N^^TWgDwQF-jLPTy_@bmZ2oOyF)@Nz9J ztqF^J4mk7~F)*B&ZJvL7TkgMq|K`n`w{6?D=9=$Uu3R~B;)G`KvVgksLn^LE<Xzp| z*d|{sR{Zzx-(t7kLyte|T;y`J_VV%)6cm)Vt6A{fNnT!F%l7kwXp^t&e3qQ`O*~$G z>d^G3PoFX{czSwPU0p1m)6dRi=yKx3i4B`KbMy17S3i96?Ag7Y#mjqpdH<|F80I9v z(IAuBQ~dng(q+r+>i^xjb!(Pts7}W5^XL8D+>V_***Smy{5My6rOh`iIrjK?fAZ0Q zYETEm-93H578$`U+qU`5GI@D*b@&A-KHooo|5{pDY|FcQ>Wm6xG1IYQ$5vc9@L+S* z*H@d<&+n`FxM<zFzSfsPNl8gDF)^~TvZ<-5Q=-2l^ohOPv1pMJ!-L1i`%@-eXjWhS z&PJf3stPnYlN=o#ZD+Tyai22NMe&<AZ}Rc+`OmjgEuOe@$Bu3J_w$mIFNc?gvoKkn z`md;{*r?mXXSAgE+KCUJKYRQ6wH>ei{%+|zP(t|s<74yDQ@s}Z>i%kJYOY+eWJ}`V zwy&?RpFVwhbM)f>%#98lJUpPD_lm&9)AZxdeXyB2b?V!<Z*Shbd3w73@rMN)mT|?F zl$G7vU9PX9qGD{kdEL6c{(gTyKfmZ`&=ACu)VO24(%(Nn_kVNz;$nArYgFRypD!;b zH#Hq<;S|;g7Gz*JckW#Jxj8rYR)c0WPQH8h?Af#T@8g4ljMC14#-ppNtF<m(Q)~rI zC+(^V3cB>~?{8*i=E$qr&GYBSySl1cTK;@_d3ig(yqfLR8#g$lCr%QZ9ugcZZC_VY zS6Aoe#+Lc>+kyoO3<q|VzK&QVDw=n9S8M3@E!(!a^%*tkK6(1o(!#=~>dT7d%iBxl zOrBi)`PtcR+qQvb^!LA%&$GYz<40y@rhfcBAMgT>R&8ceQ&XR?S1x^@COrH4`udIg z-xQATbT^X(gXRA7!$Lx0_SgMw<CR|KKmT0bQU(SEHMM1%HhsFYvv}XWeT9XETwGim z-m32X@adD5me#i~Uyd9-nwgn-ad*F#y87`02NrDDAi$uYskyV@;h}BYw*C3@XUmo? z-qZDBcbDa!pJ%(|lflku9xBtOPEAcoS+ab&ebpBYL&L%|GYok&?&EHqa#t7d)YjD2 zit9vt`0()XvuDq^#q}n%<#Tg!Nvv+?W!SJ`gNL8rw(Z-)0|E}*Tm0(v>;K>P|2H)^ z&(6p=AaznePfw4Vn>#u>y14jr{r|t>@AiH8@S)=SySY=QOqn=QQ24yNaAsy^#23Dq z`bQoF2LwEL@j^mEf}>g_;lLs0va&Ls>v2oYfx`3m4?jP@y=E0@Y)>@!GnJH-Hf%ke zdwbi(eLd$69y~as*)-_ot5>f|ZHz(S^O>2(TwGiR$7~#yO?Lb9_b)38OHXg_biG)s z+FxJRtkFqJ>+b8jcKPz+a{Ktg!jF?wy``k2N=i!fVt1KTf6KABck`yCq-17tvh&T= zO(r|d3=IqK?kY7nJ8Pp)c6j*p9Xocga*OS$`6;w+(T?4_qa!18@9z5g`T6-XBGcqd zQc_dz@2PA){&?{s5IA!5sH&>!&K*0nw6*uw{q<52QdLzo&%fvMev7nuo{yK;r!QZw zT)Q@H`t<o`xl!KU-c?mq)z!PVY$<VYV3@gQ>(){qA0H>DrY&2(EOPAzEz;k-`RC7{ z@9*zFfA;LzMd@!RU$}6gt&OcBCoAh#zGf{;Q>cijsHLSPD+|knw@ODJ7Pz^&ef##U z_}LlB1=0T>ew+NZfBmv$%T}yd0dn3VB|SaAZ+%A;T#g8|Z8>{td;a|u+>`U<E?vJa zFT>l;T2fj!&p5qr@7`KJzjF^>-@J0gB<sqGv$M_9)6<z5=FFKhV@|>AYipU9nC$ES zsR(rntNY#AS8J^se(>q<(%08Y%gcl3<>u%2cXl4EYbiXnXOB&g<c;0wzrMV5Q4+K} z-NGqcwRbvb^gZGGySq0$%FE4T)S5qf^t5t|GchzZH?P#bf8`1b!-lO}-@ezBOu4kg zbGBLTqQD)SH-G;2_ICF5b#rYhjr8@`H#9VC+4ALBue7{P#f6I(9kp)0UcXUaNm-ef zhv&$VBRh8PWMyTYI(4e7tgN)ObhYTa7cUBmie}l>TIuV*Khh~|o^!+Dbo%e>>*E<3 zgw_2f@Wp|a&>Am%dbqRJor&RB5UAc%RaLG2_GV!X|B;6UzkdCC`SRr!AE#S$`1_A1 zKi<%GW@Yg56*1N|Z=XLr++JuQBg!~qUe)7cyjn(YYHN%Vb_i_y`a;}BPG3{A^RLIP zeaR9>(*C71H8*cuRj_4yfa90b%Y0}5`1$kjp+g%w#E(JNETrV;+h<?Xkw})?af4aX zf4*IAZtmTEwY&H4x94k5-sA@As%K=ZXl!JR<&FT2?ezCQfB0}`<>$2Z-9j}rHMefv zx}x65UDNZm&fCv#-P*NNCr?&h-Itz}G-=i>DLJ`)g^%6XmZfsvxpN27h$-Jc3A9w! ze|Of^RiUfHE^3@Pd^i~loPYmWqoky?Xz^lZIc_d4zI{v`_FbPoefs?T{QZ5kyEkv1 z{N~E5tE**YWITi?vvt_tsoV7TBRj{nH%5*;0;#E~tovVTS27%ASRc1{SJ~UB($dnD zlqD-yzWnj=v1V#faj~q7jEIOx+SysD+1cFjx$ILK8W;=>4Rdqfvh&O7L~d%?q{teZ zkY^qnez2=$*REYjNl85SrH;i)L_cokE}rst{=?}qPdCXXod^mFYMQBkO{YOgU~gb# zWaW<!iI$c-v#+n4Sb0)Ae4US<UtdRu1dG1ppO2u-VDG@A7%DI&A~G^DRDWOXZ?h@g zvv~Ehva|pG{rmsdYki41y9*z?<>b6s?B4I>?EL!Gs}n7sKY>=JKlo+ZV|e&b^qQr6 z_Sl%2ef#k6FefMH)2C0%%F2BFUc7&=ucfuC=&9GPbzc7d><lYbtS~6f&d`%Q{jIH$ z<<zJ2f)tNdNr5Yt1rHd&tAHnl?wymBot=0wgGHBvi>vR!v~~7%e|A)SeDwA8bxrBl z8JU@u78W{@n^FwE{yh9v{kD94gF~ND?UrrZCQbVypRq1xXOpPEprD}Smf!z4E2^sA z-Q3JxX7u>kvujsDpjGbk<Kz9#PEH!HFV_70#99_VefsqFtwtWJ^9)Zmv-4L~Rc*_? zUH0^psGZ%uudlBg8%Mjky2ixZxnKX^_kFcxP1Z5H92cdHd-mM9e*OEgUTFq~;NW03 zwMILE)R}xcCU0F|Cf%WU;_dD28~^Kz%{mJz3w9Q_hi>kz`1bz(`=?Kj=IKWG$HvZG zz54al)!~}CO>J#$ix)4RHtpJj?62*&{kPfwV`!8~jZzfq+_r6-dH%hcPft4A+KxTA zQ*viV;f=7ukfTki1%-tNYcK5FxpRXC%gspeZil(q*VpZ>`}^zSVs}MFMN?DLl*vz? zKc8=14qDE-Y11ZyV`ox!XEHSG-~a#3&CSdG=l}ciGPt(3_Lb@8-%tPj{msI}czXAX zmoFtRY|THly*@ZNxVX4@+Wn3j?#$^%Sy@^8>i(WOd-m+%!_5C%*<bW$7)aXJ*?epD zy%jCpn9$bN*5*B3ud=Gj%-nqP+^8)X6Pue4Z^+(bTJlDHqQ{b;nC>+KrmH2k98Z@h za<?=$2e~RKDXF!!HR9=M$gU!OIeXBm!&PqE^Y6E9{m8||<-zThe0|yS<=>_2Zg0!I z{rC6xyLa#E>gr0?E@E7`b?evq|G)1~R`>t$^Jn;)h{DImdgoe~^Bt-!e{-WTNIwEp zu5Re6o~|FSRy@UU$BrFgYa%u#9R;l``|;xkXcV>f*OX23PhVdjpC~FVDJdzDyv&Dh zaqE{aU;O9W#qKCjG%?w-bLY&5xi$9n|NeY<=v-O(bE)_AmX?+`Z{D0bb?VgV)30B> z%J?GqurKpeIrFOnPBScI@~*51+_Y&^g-zYZM@K)1e3|ge=t9AqE8R_Lvx*c&-Mli^ zOsM$#tMrl(uY`q6-}LF?pXX)%7q@=#^5xGjFNHsM8am5tS`^ijn37^5uu{Cke%gHd z`l_m`xP3J<i$1?@V&#^RkqKKLcUJ7ELEG-XM;;c0%sOOdW@cCOW5TJYIrV=So9+Z| z+q6kZfMdDe+*hmZ_AFlf_*kzryM{q>cDA&L$dfm3_GDkzQ&e2oxPz5>%G9ZnQc}y7 zFTZ``#+%#Q&!0GvaeZB^rKP2~UW~*GhO3~7>+^Rw&KXPlxVX5ydj0z0xf9d%<DWfw zGC^$PqL%J%?`RPC{p#v!PEJmPV?L4j>%c3fjvhVg8WmOb_t)3g*Vo5yNML+he<0#^ zma(xhXdLFw_qvm3&ctj?Vg+q`J9GN<<OvgI7^nBOv>e%*eLZ8Qqo?P~pP!$<zrTO^ z>wQaF4Nf)~9^E(X&w`(7vD4f0n^RL$QzpT>CsAJ_cbC0g6}sBW+Ish{T^WDG<+g0! z9xTYr(9zNH<jIqSfAURS=gys*;e7b~d3%BFGi<BPCTHs_Px|%i*PcB#W!VS4X3d`c z|L1f0^78M$epPJ>_VV)j_U+rRU%#9auO_kF@XybGzt6As=cm?Pi_d&JF0b&ffAODB zPfxE{p~1<?X>7cCpWcr@KR-9K^S`+3<*)qd)2HZdIUm1&FE1+ERr2zZ!=0u$(E6+6 zkENxh-{0TQ@BDIo{C?0%?Afz#gO*O;-+w;hvxL;eD_4#<Su=m}{k;6(*|U2S54W-N z%Ym0)w6`B$8@)Ya=Y+|VgTb?zujkkQtNi?ox5qF%ENoJzsHkY{o{Eo;j&@&O=G$zT zCMqiWN^H;W-PQ&M0p8x-lP4Risipn@_m{`Kd4_d)UPQ!=#qRxbHWdLeF+RJNHXqw^ z&NTa)g|+qmijRwS?TQKsnPO<_At)^T_|c<RFJ5S9YcIZ&6ciA^@bCRK;T?gXMffR` zHuzX%c3W$mnQveJ|KHy;EADE~ojdpPGGAjevwhXy^-inH2bGkRaBy=QTb=~1;%=GG z_$vR=vuDRXL_g(F)6qF|;lhNwQ5)K>UAvZjeO>IHiit&+Pt36_e)Hx{Pj7E~Q#X&K z<hRL*H~#%#X3gB2y2F0Trb#y2Iy7~4c^M{5o%;6GE2YT?_w+V3HN~x8y75AJR+bh+ z!}RIf*RAUl`^g9@&uVIFCad{YeSatWvcliLfAeNzcJ||QEQ`Cky4d^}uN1VXPGD<z zettf`eX@X^-9CByIs-G^Mu(36e*20K3SWh;PntBzy<g7P&+pyKmoH1_tyrPKaA0rs z_dB3Xb1PS_^s&+2xO;c?r6r!Hr|YXrFW>u+!{?y6iHV8;$CUKUl@k7Je<Z3aDkk)r zo0uH<wCnx-{m0pM@7k5H)y}A!u{)cMl{GND&cjqjav#srHsvLyD;F<X6k+<-L&?NM z<hSVC`}^nLeP!Sg9esPL_w=f-+tbd>V3ZE#zp!>@riD?>TXk=#z75|$o=gZmJn?_L zyQRMV`?Isn85p!AwYF{B243?0>*vp}s)tpyw6s2b`XnnWyKmn<<`V9x7cXCK+_>@P zw%prmA~y#G2PcM0^T_a(|N8Rsc)z^3xcKT-s}9}2?XV&Jyj=VJ1w8g)3l}YF%02$) z>ud2l$)_1)WMpQ|n)UzBXaC5^NGGR53!U4K96hRebYJc7XYbyvTeb|eFx=bw^phtk zkN%(C(UiNox2NaN-@oZ+W*kh(ojYAWJ}fNEX!En;_xJX0Og`S$*0wHox0$)Qxb*v1 zuef-5L&L)OI5`()J8^S!Gc@eoTbq-kV-^t~zrXhPw`b4NczJm*2`!I_i3zxO=Z=i_ z&qog*etdV=T2oU~Sy{PlKDXfs&;X`!eC^XuiULzgN=jn(R)vOzUAuHi=ynj(#Hmxi ze*XOX%gf1~ot*5)19DhCw%_*uX7|sbap(DVet9;A6{}YVTiuFKSh!}5PjIj>gMzB+ z)tfgxwGOZ9Fq-I5Qc}XgaAuC>WVgy`moHsvI{uiE;q~?P`MJ4Em1fSHXP0_PL{4s= zb@@A<Syi=-N~dZa#RAoYI{oL_9CSN!<VfA$Uq>fN3v#p^IdbH1JOAW;JwZmPr$o59 zyN?`kVQRc^<%)`e!iLn-)7GrfVQARA*?6+mfo)fPeSK3NZ*iZ}>5`O|wrt82kt<<d zURft6sh&D@>ciK$vuA_WKhIF<-8+3@B_n9Z=j#_QR;*rqI7BHk^W~&TLPih!`uH-^ zkM+y%ulkw=n!{nRx33Qh3fe!5e`QBediwJI{{DUY_O-OI<Rt1UD{s!cyllaO1#!_4 z$vtwmRrmH(_VxAY>gvwmVZIw<U-P5j-=9hwx&FO}E=t5Cdb(6LI0!ziliVlxw6Cx4 z_4W1Ncg@gKRek#SvGdMZwgLvifogowzfVr;Fq(L`UtUaX*)Qpeshc(#{keGleE+G~ z6)sc$?_bQLyRx~N`RUgseML1jHlRT|wW)~<7x&b<ZQi^&b5Bd_lxfqnG&LU<SVTwP z-goVk=gKu}Vzy)mG90*cY18#*&(17ZyH@wd>`Rw|UL86S_>SAledq4o(!agg81C$? zp1!sG&C8dSzrVemsO-LCxqoMO_u;+eko6LW9~Mlh|5nnt^X`=8FN&sgx;$DRzkgl) z{&kBNE32s;J9}1FLu197H8qcpbRIh76l5r+D?1UiMb5;)Kw<9Lix)Sp7ya4S$9Lml z>-6d6kB)Roo8_E1d2*ra+l7xco<DhV;^fJL|6iP(tgaum#lzQ^_qUDDY_qjHc1Q@f z?RPUZHO<X^`}Fj5(Au^w-mI*kUAaz@Y%;0L%*>nm?<82A($dnJ@zZIgc=O4Ek}11) z?aI5q?{2pBrIo?UtE#IfAAQCqq9&XA=);!zYl^0H=G4|`&F(ul-`@YJScuHi|7N1m zF;1|x0G~z0#KJ;C7#L0z@n~GUa^=dkYjs~=UA?(Ez5Lys&d{H!ie6q`Ha0eM=g#Hi z<Sc%6Ch_N=5D!n!gO5Mj*Z<?;;kmOVQ&@L;M`!2JqetJ~-2D8~Qt$cp^?W=$DG$Hx z6V(n&_;)A4%u4cOK~a&MRmqDJ6O})>^YimxyY~C=!-7{}@b29^&=B#L7Z)e1`)ll0 z3JnQ4u-|$|&x(~RJ(EM*_f`D*k{KN>efa8AkB3j6-i7qVR(1zo%Y4OsgJq&yB6zY; zT3T9K>S?=_tE;P&loT&7Z&g*5o}S*4>I^P!?!9~W?%lIz)v8q|?l4`td%v@@^Yyj0 zr_Y^h`^qjE?Ck678yFbq3I;JTH4}b&tdH8t^*Yf~;J!%5M8-yk9rgeBEnb|Qn5cMD zv#{{vWOe^pGiUPdzA_iok^A`Z?3ptwrYuhA`7e{Ib9&meKOXAp%fr{lO`SIF-mcQu z2O62zgmN$(IDB~Vk|k4S&6+h~f`LHv_1{Vg3JK@u**-qr-{0TAT-$$2fB*UO=ku?v zi8M1ad-dwo`FXa@yX9s+Khi0zGn3u1bLGmFbLYzPIHaYe9XobRFMi*gr{xD97Icg0 zf|}K{XGdpcy@G6niwO#HQe(TCuy01s{{8!B%#hfdyIN2u{rS1Myu7^9=6O6kJSwM` zOqn=wXT`@wJ9k=seP74R3tDp!6&>B)(qiyNBRV?TxJ=Qzpr9b(@m)dZ`Sa$@Td+XE z-Tk<J{Jsfug@lEz3m>)Y+-WH<FCSgvtYGkO^8<tDvyLtMkd>9SLFxR@&(9xn$)0XZ zyyCc;<>uwf#jmccl(jB9Gt*ez)O2d&-4F|zcTY}EHpp7BYE_r&3WJl^)<&oQZ`&j! zIT<tpx~H=E>Z;Jm>i*B3K9#jDd(*_qy=LuN&;XH^S>7EB8P1rPm_>^g{rdGQBCR$# zS-FpG#dNFpZ{FCHzq@m0rt#08KV4m2eSLf$Xz^=iW@d`(#TXbFO`0&Fp*Tp4XVF~4 z<YN=oJ*=vzFt8ETeNbQ#8X6iNetprRMOUs|Nj}~;@mU=MLq%of%$(@hxHvg+@$HqL z)2eQVDMxsFd7YYVp8w;=k3D<#{JNE|qO9y}J5iQ_q4<r4xKbN88ynk<pNn!9wf&g< z^5x6QFE1uGH66OPHu~hAl*Giu@bK%04jnq&&abR5X(;mJJ-4Wp%!XAQx)xSe`zk&z zTD0g8Xo7Tenqq)(+8u>knVFe4_tjQsWV|@mEB*f7UhkVKbIW*md4+|AzkdB%T2iuS z&z>3m|GPUn5<Gj?<mOG9JUKZbfnoYC6;)Mny_g%<u1%XifBv*-WiKx+&Az_wpk24b zhH^fJ1$+1IU9dpG1_bV$KArvl-`|*+n22jOOIEB{v22+eXf%zBWomp*c=+^r^WtJ+ z=1iC{VcN8?$jHcof`W>QikuvsmuLL^{1Our*ZksTVEFg%_j}Oln6EE7Iy(ORsi~~2 z6gzic+d_s<uXXp19VbqmGBY*h<l?IP{q5|50}Uq+seONc|NYCCk_)7sbKL6e=wM)Y z@$%(CyHFlxVWsx>+5Ib4WVpMt+sMsg<q~M-pXkBE%e!^^_V(M;J!cvuHcg#+^<Xo5 zX3l|52OhrvGZSw7>;0Bip{1qu`r2A<e*XOh4-duH|2_ICPQqY=Z@#h%n})9L)$7;K zpE|W^GYBN_%iFnVQBqdcs)-XX&a<s9Dk{2j=gy460{!@XXO`IQ4T_1`lX7xWTN|6K z>|F2ZdWlc{tgWq;m6daIa*T|PPwFY2o%iP5yRxF9qLPv=n>O9rlzMtc;p3p7Adl9w zXU@F4vokp_&o8-e%IekGF)??VSh?f&)o}jK*@-+BI%DyHPc7b>nwrke&Jm1@moGnl z`SRt{r@K#DW~$rT?p?9s#L=UvpPrlqO=jIY$jiYo!#Mrip32V=i{zRTjf{*K7<x>N z)mg3X7^k00Y4m&Z;Mv*PZ^9T2w^aQ5vvclT*;MPE!pFxVqoU;OYAPJ0D-;EKKHiVE zD|z<flZ}k5Y@tO=Xy{dkA7UG1?>d-w_<4Cn?I=iebaV^|n6P)R?dn~>JG#1#9Xs~! z#>T^k4=3N<RT`n|%(h|6mMg!{_RHHxERthLxV|oymzVeTwYA+nJu10e2M!-j&dB)j z@GyIX?0WqjJ9fNz`&Qj|)|Gqr{B~K-Fyvris*(Tj?c1?k7v*fLN**3+ZEbD!pKo_m zW$oIvvNAH)*2SKlG|gTh$6lV{!1e3f7cOiJHU94H;K0Dn&Yt6{sid@M_3G1Sa$dfC zIp40<>EV-w3^81b-{hocW$kKe**B|SsPo9CO-AYI=@AcaU%67UKvYiQ%-OSspRa}s zM;(!ODk<5rYSpV<rLRxyd19D+Y>s8|vK>2qoSA8S!rP6<_h0dpg}%$*Eb?PjRaJfA z2kLb^brZ-sRKp*t_2cJH-S~ZTy1TXQ?CKsL>s>Q<&h+WmuV4RusFnNT#fz_Ay_zz+ zGIPnUUAw-Ruer7H-@CiJfBdMBlbbi&JpbD-twYC;FJHUXw^m4okNvXNu3fukTv4~Q z+_`OAn4e$Xzdt{hJXTXv1MP(_P<y`o+@qu28@=?`u3g(K#@w8{db)o6xq}BkzPhR{ zEG(>6{pZ*3-_F}-he|0$_yq-B(iZYq{n0t?+?>wC4=pS#mif)CdUi&VsWBldD=RPW zoLKJ6<(oDM8OPh&+P(>6KPVTsw`%Fag$tK2cfTbh%3x6S<;7%me=%`!Yb&cHMIn|X zj!*X&nKAbnF&sE}P*Fu?$+Bh7=GXsAG?MP;k+`w=kw$r0nGgrdu^_t#*KEbb#YIJ@ zUbqnO>TmnZgST!~Jv%cqdV5}IXlU)9A0IcRo_;a+;lIDXb*^)*d;aRxtFF#YLmSWH z;^MkGJBEZ`UtapWV!mvnaeZatZrM{R0@IQc6B~a{4__a*b<38NOX*8nLEVAS(AXUX zjAluxMnXJy^K^CB)_reRWMiCR>9+GI)9v}M9zJy3HKmcUoasTqj=;rkCr_Pv^zp~h zZt>*whm*uV{@+%5q~Y<JD6wSD^Yd(zw{vlnO`0$vAv@c9=hj#UhlKzC{vPj_m%k7p z!E{jTU$@3#qxo;&zD-R{-4bt;eojWByR@`4_3g5>AH7adHB+X{nkAK(`0(OlcVmI< z#@O#MF)?Pjw?yRS?F-_JT;k*ZzrDSEv3viuix(3WqBw&OD1O;@?2BtvmDPf^d<pBf zY~Q|o`SSc58y2oydGf*qf$WtB3<`qnQ<M2Dj5wnIU0)yH|8x4&r%(Ik?d4urGco-6 z``5i!Dm5qP&HMNE#m{`yW*0bZmwp<{BRFNs6b6PRybT#gjvrT7Q+xLG^z?-b7v|;V zEr~Pwu~&B6`lq|6O%pRXvTE(^n>Q1mt=_wL?%h`kZ2e1?ESahut{1y&${Fuxj~{Q| zut7sdr=+yhHO?q$$A|fgd8Fl-7*2?}|2lf_-n{1K#ryWnYn^#&?b@{kfkKzNK)%n- zeY<SgvU&4B>H5~qn-}dqZ_mFkrXMFGC-?5b0|5z%6Q685_8OJHyL08rl~!)?S+i$H zM?_qh>L@5IEc`<{?CJIc4%0h2IAX&Ox+co-sVgWL)c^Z)<3@zE^y~!-6rO!;jQ=pj z)bPQv=Z+q^Po6!qtN5Vs?J+~{0+ppJS89R>1~#l%p<!%1`R6|cMn=ZPZoNiEMoFDj z9BPq!t4tXl9PJh_EiKiV6XxU7)7`y#@#4kT3)~zX8DG3@o!sl<>YDg$_2$i!@4gCX z-WWA=$%K8cCv|diah;oQfB)pk&MULNe))3b_;Gow63~kLKVYyq?W|SZpC1Z2iShwK z!NK8cqqbIjd^BT*#HGTxxO>d(d^LZ6ef{+G^ckHrC4uJi^()0EtNTOF67cZ#1ub0t z`%BU|E$8MYRRx6w_f77+dGlspm!AJTo61*LR^I5(%*?znJ7!mj=1i3@lQ#Z3)zs9~ z*Vosu?bNsY`}<6djIuH_D}Q`YoXnd2($&S~$<@{2&z?P-H*X%Oea*$S$WXQW%(V@I zyu7>;YhJy3_wV1|-_Os_Umv$uOHtAA0_T)kU6akz_W%3E{adQ~|G&Stx96X1s$BE* z<Ye{S+}zvS^V>U*f>x2GZF~0Y*@X)MHgftJ8Yi@P1<x?dHd0en1&uUpddP7Yw9#qB z3KeyA``TYq)@Y<BCo69eonkh*_kwg;qWG6<r$RzPmaKX29XW?Z?b3m@GbJS@U%q_V zFK6rJ<I@s-jft80p|lZr1I3&5@%ul0{u~<{`}gnP`2BTfYrJkh`26v)dv!I)<&!0I z^YZe7gD-=21J#{Y7C7ATKYk%-_PDRF@BDoG^1{M}*<9AvyB91_h*;jlWnK2>#p^2< z7rWoRb7zD6!+&dAxy2`a-QFDc>G}Ej;cKHr1qCl&zI^%4oi&RWJ9n>t^ZIpmUY?$g z&Yx#zr8oH|H#6(&zu#N^J?;Fw+DAt?O-;9c_)riTd9!Z)uIlf3m6et@b|o(aCYP2e z8qA(^Z^zNK(c3qCDqv{1b0;P?_U_*5?|ySECLS}_uC1-Tv|`e}eRes!Ih2oYNYT+W zxMX9&YTDY;Qugjn=i08AeKnO1vQHZt7#JFsE=^5Ld^kzfdqz$)@7=s<Th_X5;EIpG z&&n-kQ}biP&YeFGwQ?_sOkrhZjo2(#wGnv=S<3U;_3QuN*Z<GX%e!{rLV{6y`_?-< z3Yj+>AMcZuo^@7PV#QQR5fPUxp5IMX&P7TSSAcC>xOz2rwTT$={wz;V&ownC7}d13 zPv5&|XJRttjO(*Uk8W*DZtv~ojVxAWNvinx-&r;-Ep5)6IXidmG-#h%mXeyf(&FHk zOP4PTOGt1qC~$`+#T6A46u7yuJ&V?f+jHZ@i4zwuPW0k)D=z;0@Nm1el~td-{W&px z$-{>ZIeB`%{QUg9Nm|dHU8SeLnV*?5Q<+sQU|;?Jc{4rJ&dw@*c1Dt+VfAY5-szyF zU>8*qqW>k%Xe=_7+^1;p(&f^*h0g68PQ+(qWE2*DWVpLx?b_bvX6NYW+0&<QU%a^a z=YHAx`v0Dj)fR5M>dX}xdDA@q-iZ?@TwGl>wY0oEJapprRJ^>j^wXzLmzVqV^Yc%i zGUd*mO5?qk^X}~U_~GH<-rn9@w{Bft?tlI2)!0218(UhAe13j@>C&YSxg|8u&$EsG zBE7?B`SRuL^<Tb!Pd~f(+u5tD!wo;TmdZ(H8ANJNc1v(BVsP;A;E;*jUss!xvu58u zyK5(7+}xf$JlxLE@Z?F#E;H_p6Yt!)<2&2z>}+#=6_u7N-p`&qxwEgfIxg<sT<h`) z(|aV|{i%1age-t!+<!QB$(y^or+cUfbspLG?aKX~#qB?&H#umQ>56(BJ_#C)Sg}IF zx=d#cADiOWNk@+yId$sPvokZ98D^MfUxO?%Tk#uIq|BT-v#YDConOAJpg>~I)f%r= zt5<^#nELqGotvB6-rhb+ud}aj-GAQITv7-AKNZ)H3keT5Hs=4+zbwb(uK51Cze<1o z_g8(@k_it9VPOcEwxGRw;jdd4P8L0R`t)Hw8-s&_z`vi*=QHk_*wAp`^mP5?q@;vA zrOA;Fsi~=;5$!#dpB=<y-7j=EH95t_-Ftg`J7|%qiqPdtm+WeOI4B61<=pu2;9&Eg zKY!}F4Zd}?x4WmLENN_X1Rw4LS_-=+a<h!AtbN@dkDp;bA0O{8-`}X5nVI?Fcvlx! z+WZanlO|0v_-tz8{bbXUFty$ZrYi4E_vxvsvVJaKy=qlN(d9;Ed;9v=*VYz4KUe$X zgJR}edp^Ftv$M_Xe|<T5{(S$m&|sFN7oY1VJx{pID=#k}#;PxQ;O<>nhJrso3UkWE zS5EIa{#f3&>d48HoU*CR%*|`otXZ^ZQC)4Sr>EzQm|OSm-8*umrD$ROf|F;?goK9H z{{QzE6wC4Zd=^K4es*^DhWi_`ZvR;1+HGQDVr6ap{oUQ$*@c(m_t)*M`nu|V<v;oA zbUFT~-~atLo~HA1?l+N@OI$UVx`b@eSQ(Hhn43{OUE|i|t_wz2c897=w2R8Q=+$B# zbRy2nqt&BDC;8dGzlHnGy{|iOaO{lju_F8bFAC2WKAU5F`OM7M`_7f_)zjcV?tL`B zqpi&iv}LHcIMLRa<%~$owj9a+$Hjbc8&97;{rda+`+sJyUbak4LnGtor>8bj0{{N~ zOS$>VuY1Lc6+3tST<qSzul9FXe0)4O(}@cRY{<L2D{gPq!|JfGuq26NCmZb;9Q^(D zEi88I*s){d#*g3M-v=G%SN0~NyD(BrOl<8=bv`*8k2Bplpt0UXZ@bG6mex6RgsHU` z1WnkpY#E!^VyCIU&Uwbg-CO26Tg`9Ii}&yQ&-*S9&{!R|*2~}j`}gn9KTP&<x}^H! z{PeO{S5_`twrt<Ned2mC8@6wM|KqBPs;X_>p9nuczL`2cVMc9<{BkxIE?sI`xcc;T z{qnN1ef9tCLJ}f&m+8)~NY&I_dEmeSW%s@vJ9gCk`(tTgvE#7W;t3N3f`fw#3qOkM z$8FKReeB{zLq$c#xbw5k`!(J!kKUfQa@8s}c6R;PU0?3(EcWv9+A!B`acg_Kyk(J# zyL<aydC=I@hqY~-|Mn;-DJA9R=H}<`&%M3v`T6;W?^l<Xe+M=6q)b;SPn9VtDG3P) zS+Mut@9+8g`s>Ax=5Z=&XslSatgW2+`t|GKk&%*ZmnTdp(dv`4oi%kT>-yfFo*D0W zt(a}HQnzvYaZQTfS+sQF!o+K9BL7^|ecIR5#H6mSE-x>??6w?NYf?hOg9i^7`q_8e z`p>gb47;#w@`1#ZKXa|iZ%Hq>t(s7D^<m<^-L|#AN`iudA|h@yGP4J~3286<{VkW3 zwKaITpW{O_HEl_T2{UFygoKnlIKcSgvz&3-nI|VFFI=djrFCoBS{)UYCvV=YS+ZnH z>FY34)2$OGOyE0FTUc0VS@h(@kB^U?ot;HRL~eb(b9;Nf@^kY>JAPKy))_M-oShG= zgBn1eQXuo81{ZgOr<O|I-`nfse{)kRcZuBPmtXSl@2mawh4Zk1tE;QJy86BA#SacN zc64;im@(txV)xanSBr~^rlzELczE2|S<D{)TiL|q%a@mzKS;60IPsqaP04qJU%hp! z>hdz*55MNu|J%4})1zPyX2sOi!9hWrHf_2!yVl$3lA%q_4}n&-a-Xog>W5L<leb>H zd2^?$x@}ux{QkPHA3tVpR;bL%TGia#ykUcZP1xIzn!mrUo;>;T+1c4^jOu^=`laqS zCt<C`)!2zsru_Nv@Nhf7Jj-5zeFy$^NW40%2Hpx<eq)l=q-!maGk8-YBO}i(_Tywf zy0&TBniK|xp4|~Ui=LjHZJyY@apT90$;a(#ei-E4vGB`DWoVc=Q*up}Y5F;t{QUQ( z+1Eb2e0t^j_58cLu3o=hesYp3=wQf46ZWoJ#npOHAey=C)02~ymX`PK-E(wweEITa zXJ_Z5M~{+^_r>lgSh#el>g0PI3=EdrG)20mOu4cp^D+-F@Ao%1ojX(_*T?OR+fz~a z?2M$n{r(dtPT1A{l8~0Rwy;nLH)mmsc@%x&`qc{;CVWq2V7Rs}w)(>ZM+t=)^XJRU z$lTG}>caQ)-{0S${Pp(M(SI`Ad~0fJTU%QzL#8V$J`|ohb*hJDM8HhNKMPzdEv|j` z46B~y>-cHWA|+<#=3Tq2!o$N$OG_6`J7s<J=+SfM&dJHit$8^kpor^O+;;&L6%{Lo zTl+5?mA$zk_v_Q`Gh2i??CbsngoTNTit_UCxb*U{Is5tLy}PqhqRr7gEizKl<hB1? ztJYnnW@d*zS5;S6n+1!AiLF|(B0xi=y1JUJ+3|<BhNkAl3l|s|Zrr$GVrp7w&LP7X z6cKS^pYc8+(1yUBvo}UJo$r}xmMiu9+y_1GMN5|+?f>eqa>9-sJFMhZtXwH+Q}LmR zmD^%Y$%6xpX=!PHDlF%6NkpFexBEP|k&#jMwKWefKbXtDqO$(O1IL=jS67E$3}@c| zbQ@co3lm$SaMRA6mQvqNPFCN;v$f=95MMku3tQXMCr=74`8qp;b}Mp==@=Lo+}NHU z9}^Rk{ny*e>sX&GH)w%Z?(tXA>J}CfdtI%|-#y8jFlmyIwDfGT(?y_)#>{Nmgr#ME zALJw@K6_reeBiYDhbI>v3G4ov#Hja<>3-#%9fcadn6?$X2$wTVa*2tVGjrz6latkp zi;D8@?mAi>C-vy=?s7IZHdR&CdGqG|$lbelj%~HrvoZ#T6HW7F4}W`md+yx1iLX*( zV(!c~&*$JzY&jJZ6&1CI-*wr?i6v2b@%v(8V|mwe-ihUOadlPI(dlttd*<4K*BYB2 zK0iO7TTJJ~*|T@|)mlH#nq9MW&z?VTZf>5gAD>n#e{P=b?<XfG-@0|HDMFmHGvP%^ zTTynH(Sf6}2DYs7wT#ggTc%cjfA{yn!RF-SeU{eNhrif4soZF4YC3b~jEag%+@6Yy zNuqh@Bn^{Ty5=8te?93#zhg7o+W7tVE?*Y5D=9Ai{QbLn!Jk*JvTAE<Ra}qHyS_et z@gos#F`Wd{n;*5hR8;16O;mPYG~wDg&G;4T*S~-9f@7;uJCEe0kK8L(t-5se>e(}A zOpJ|p7d`bluHVqv)y1VXwd&&|*OK<;3l}a}6h2~EdEM`lu;7;J-}YN13K<U`k4@Nd zft%-W#A1B~nPUkF63?DLSJ%_C%ek@P`uh0Ko9ft_A3k}aqM~9__~^)<JvMP|O%oQ1 zw_I2U(gfOBCBpSaUrQ@0B;*QXcaGqm_LFDN_D)}T;sUpmg0sHDUF9!KhYcz=BxhuJ zEWaH2KPo(2Tu`u4ZkzU(MQ8J_UcFk-9>23l_4>m~-9xS~TvJn@etdj9T10Y=VKQia zHD^rR^LC!S3I})lwnpq1ZEEV(mozyZD<Lht*-p?k-e9Zq{-W8XzZpQYm;GH`Nd^+P zZruvlH=X;{>(^(`oavLXT(o?-xV-%NS^VuEzI+jR++&`9uft^ViWMjJ8Q)u__OP{6 zSpC(jSBJ8tdfUz$swyfzoUquvKQ43G(SYmc4y=vd{_ERY?yW|FI|CRPdfmAB_`*U% zUAKPeSPDLvQ9)5L@#G}cfB*h5ec2Hn9&T-E*(+uG>PV-skE+NrzqwLELZ5zp&9=3* zb=2)|ad}X(=mNL9`|)dQQZ7{8VQn$}#nhB!AR{Zg@P=#l=GCiLtE;L?N=VF@F(V~C zeZEB@(@zo0g-ezkIdMY6(6I3SzS?!`*1ajWwVHcQYm@UO!zZAec=pz<UpF_Wf4Ft{ z@Z%dBlU+I^xCN}{^4YfDo#7X)wUd2Q(owD@S1)tAPEm}>NL?4Zds^sMCl?nLJ-xoq zm9{oE2_aXv=iiS|{&H@P<>BNTa_U#EU5ndQ^778k;s7=N{!N=UH8(d;oEW&fj@{E^ zm+IUT0e1EO_B1vg1P$;0Z!x_tnd$2-A$97<6uXucD_-oa{;s5~d|PL^rn<VgwDj)8 z!)?LK{ZiA@fB*PVkRiQv*)k~!i7C^j-P=<M>M75$X>|#>a`mch#fJrF4_)jQ(*-T~ zlUo+QzwYOcAD~TJ&Y(jOo<4or%*H#*qEJbwH0?0x(7>dmpx|I{FR!fZY<6BL(Du~o z?|Ii+r0eVJlOC`2^tfS=d&?xGV9BOUn~o;`J?Bup)#&o2OA+ju>%Ap@dPT{sU9@OX z?BcD{ckSA>I(+@EUAzAM_}Ki?Us%n@qVCU*jT<*k2vD~wP*W6Cn0q2XOi(cMq8HoR z7qJf#ukJgbtfSN8cj+ec<==1KzMVT~&Zp0xot>O+`Ml@m;kmORv3bdo6c?8xZ*Fe3 zoayT1)a1<By=#}%lSvb&Oi8IPzJKLPi;9uQje;*PE}HSCa<y(sKQE_J1=?`>_V#w+ zf2RY3gD)@lmuF=KjW-wCo;cXdzIxTFUteFVD=RCjsDuOr2uOfH#kV(+(b3UYa?j7R zm5twd_vlgA(9o&fEDLVky0tod{kNBw(+{`t#>}}@{KHq}p|+Tq*a^PZKR$)Nb@K7a z0Zp}U&riR<cGIRwlP_AlOv}#h{$Eul@%8Ih^SnC+Wo3GLdTGBVFIu!{@7}+=N?%*0 zKU3xGJRneVz{DMNs4zFTxVZRYu^J=!Cs$SmGc<gEe_z;W@&5h$TUuIrdVG?SlCo<h zc#f?MUale1wP_P*goAVDbnz1@`>q{O|Lpkh@Avy=^S?enKfjTgebMrkwl<-C1~a18 zEm&~iN?ceN8^a8XLM8Q+HRbQ`9j*WK@6XT24<0Zu2#AU088c3~&%w?8{@&i%yPw<D z{fUT(sCaj0r*2v#CzsJo{zE&z89Wr2edfAurs2%>3l}~-HC0=8SDjnG+}pQreHUM3 z+5P4m^UO2umpmu`jAG8#e#gZ0`A<afuN|OCgNqk0GBD)kz7_Ld{rc6bYwP3d-`v=^ z)O-5B-|zP?cI#D2iBtRX^=rGy=dZ8B>wGR69}E}G<mKe#+|z#Y?%lcbwI9vfntlC| zeVvey(D8ox`6ih{bN$k_Qf_R$aQX7$pq1<6_TJiCy?w!Tr$fA()6cuz77tNY6yz|G zI(zwY@tYePt;^q~{F0qGefswgA1eO;`@76{c7=|Jm(!)#mrU_JW{>q0-YnMN;o$4b zyX^bSS+mm8(?NUf=I#*}7nhcjQd3pEdGqGZ9XmegPxJQmT^ql@?!&Ij%X*D3Ub&*8 zrDav~qrge0T4e3)nKMuR&ggi4e*XUI?|!bXt+m_dT9?nWEM{{P2Au$=tDAdjisqd2 zK|w)}UcHi%ll%Adw0L1*VQ6S*cJ}Mk)6)*_-?e-9?He~37(RXa6cEt6Y?+#!Do6bG zI|mOwJUw0C&(Cj8jnD(n^8f$-TE^$9A31eOOIi8xsj1qat-sTzHN~C#KXKNqtlV7P z7oi`-t>&KltuSjA=&<$=vrG=L9R2<M{qcVJ|35#cFDr|Tym`2tfA;Lz<+p6Sg(mqq ziQnj5dnFLI7GTef*Rc;WeSbG)r=+GPCnuZd-Fflk$&|^Hi+_K6dv9;`@+C_oBqXLx zoqBgyY4+n|y)8ahq~3(S-Nn`7@<@ogE%DsBbBv6PZ+4$NabivE?r-1T-u|#~_wL=> zx0|;IFIl!sFKWw*HEVLt%rNwxrn7R*nx6jt`k$YcPM&=E;o){hhF7m%we!gqm6g4_ zzJC7HsiHO4Is}zJbhnuPQ7A1f&CAQHtgKX2RLpsnY9KLh-aJiB&Bw?4-!~kotSkQc z>FJs^YhrhoX=-b)UcH*T%F@BZ<HhCW{{R1c?q48&sJx}6r6R<pps-Le%TOhzSxZbY zB_)MV&SppH>#(r5d_rNXR;{|Xw_5#0=!6oJ{}a`O3nV<E4X#Q$ZaAiqyf$3!VJ+{s zZ{I|?Qvdw;XlG|9BqY?#&fnMFtz0%u?B<@z%~{4j1m23hR53io!*=-W*|V1~3vb;b z)_Iy!BWn6|)s4qAm>F7HTP-^?{5?G#{RM@Evp?4}21Q3}>+Ab33|JSx|6GDWLb9J< z-<&xztxlkGta1|N7xOSUlu9x(xcA948eV?!vf|B~H_y(`x6i(|M$x&AgPU7gLZasP zx8BK1KZ(XAO%c4mzkd3}i4&(z)z#HiO$k-GbMc-8C>poahK7d=Z{;y^+v)qk@KnW; zB}>e5Z%vsn;lu0e`i|>2247y<e9ePn`R3yqn^zhi^2(m-UeV&TaE?Xcq5JpuZ`e@q z_*idOSJ(RZ{nxHsQ7M}yCMYic{;p5>#m!3sG_GB{_CtR2InKF$<)x*$n{QuP89ecv z)S0%2DFyNI_YWO%DlY!KHhTM_|2sZDKE8X`t|W=JSIOHs<K`wmIM662CdSaPVui*v zP;5?|IPv4hk4u*>4GIcUcJG@qY0{&DX?}e@Jw6T&4w;!RPfgV}<PrW-<YD3J<iwO4 z&%)Qe|9x&&mQul!Jbz!`vumTbo0yn@)|KwBuMZ8q8oD~{=FOY2`)YoEe0<!}(z3L) zv?)%R({uUa&C;jl9FKMA>FvFI`Lcb@4~HLK*Ve`U{`K{B>FaApk00-sv7GebWAu|# zQ?=L6k~B);uwQoK#0dja)6jqb0dev7_xJCgH0jc{wbAx<e>fO2a&q3>-Mzi1hiBjZ z)>hV5r^G7uYwKdY7Y6+M`#qk)AvjpL)yYwE@w<2LYJYuUJUelRK!llCP*6}(diwGu zOH?#9HFb4uQ%_B4X6K(aZJN<q;agjikN1fR3s0Up_3rL+{rA<o=Fg8$OiY}8H$lbl z$*rx~bFRE;IO-J=8fyP{`oqKR%nTpmvdla@Jq=YmwKO#u86ryOGB9koke8a88WVHp z%uHkbm>mn2F1?z&D*wzp+uKKvs_N<WP1f<5<k!^S&wpzl$DUm0TW1&;b_7o}IOy5& zbA9~&pr9bo-0{0RJ7ag3y?p&zTT}DvySvplHYCoPHEY?jWykwucUOLXc6z%0=g*%{ zN()Vz=d-*o@R-Hr-3JaY`^52HLtDGNU~ijqJKw<yA%=$bcJ}sHr{_$bF=NHrwRbOE zSO7k?A}%~UJRsn~%a@k<_iS8UkH+n-%FM_B9ZXUp@I&OqIUOAx9SseKXP}w(B}<mf znkDsG<c)IS<VlmheEoX*^yzkf`E|>eA5SnSDlU$VjorFs%QU^%F1CXPXKvj1@$vET z;AK7&r%v_t@_M8awcR(v<krsO=eK&yV|SITT(n5(kEfot_UF%^o9D%viHL|WFqFNy zaq&^S<&@piCQnYzYTPc9bLRfO+TU4OS#9m@*;!d?s;ZS06(!OqIi#g$hp&&DYgL+c zZ%^eCpQ7mpPMun{Vg*Os@1H*fpN1d1uULLdNv)|hTQP;_a91}7JbwK6<69*qrJ|yu z#s5y9n5f*&CwnWj&+2yTmMtd#*jKnuoDBk}Z>?Lltc{=bO<+lu^GxTx+%_%Q@f_3o z<?R>k@->m_{qXVb-Mi9KQhWC9{V4k2gI}1e?A#M4POMm=k)cul^V8GM&(BX*^F4Lu zOix!=Q!2;i&6~Zwy~TAR6tuKXS+(xk^=pae<T-Qa{{H@+|7)$_)vH(C+}xf%eR{l4 zcJub_?v)>3zI=H>$t5K{-B<#2;=8M>D}zH^+`L`8sxmS%ii$R^T9vgsZc|GO%fI$( zr%%6radGjRy<eT2oWjDwPCP0pFE=+gPyhd!;i^_lOpJ(#NW}kB3(I@6O+^I-Cth70 zuCA(j^!V{|Y3G?v3qfPwmzH=!HlH8%j+tEIXIJy1!De5@Uop9Z@-HW}6;%t0ipttn zl>`T0o~|En@!vNyE9=yG5J=COK4F4{WziE2u_a5FcE(k-g9ZxR`{g*RM4KLh`V0H7 z2(YuW+uH8si}zc;AXxg;+uq#`0_$oO9pr2(4qX43cu697ZSs;AE&oJBMOo)A4v7Bs z;^N}h*Vp&=^|iGb<y^UVG4uVK?Rj^9{rkIn-aNU>mA{^xoE&LgT~XnnEy=K8!GeUu z#D|X_35kkcjdPBSijvCLxPJ9&;L0mz%~Q>DZ>8kt^V=SbjEvNe+mjIgI?&HgPgghh z!-Ipl(-@uzUubxdCfHv4``g#=->+Z1xN*xC9xg7SFGU`=7}yj9`R)Zwn=<9eagn<c zQoY|E9%k3oUAtj}0VikYpENEmt_QA#g@(_|O-oBlEy2L&?WIeX)?5P}%(lDhgeE8F zymQ$a#e(IG53|3xvOj+ItS#@6{~U`(lc}9fp`l-&o}M1PEyuF%&yHow)K*_xIcwIe zB}-CDN=(`kMYvS8w7gtgT;k*7LqkPx9baH6(R}jOvlemvI0l9f=iXeKFlkbfRc^ZQ z+S78N^QO{ccbC0=aIpE!{b)^1O>S=P5cTtC&-U(JxMuZgX)&>~&(F^K&Nkcl(bkzY zxbDb*+saQYCi}hbcHZ2aE^k-!<I2ilIXSuL?RlbKiac&KuAlto)hn-#i7tyTepu*! z_tPEuiF4-sd33aU2^i$x-^U0#v~`EIx1?=VNnM@Ysn*`^ZqN-D&CQ4J?X8}<?cJ|m zzYZNbR3met?cdMO>d!BShJ`HygGbVuBBw4abY6J0$9%!lCr><P=1%hKZMYJCi|v>P zs7N@vf13t>`wW}PPft!x{<(7(pOd$@_l}unrv-}@`NJb4JC~HHs;ZWjlz>JpK0o)b znGfEFXJlk#z$0y0^yJJ;V?&<X`|Ir)6!i7`4<_(PnXD*(f3LT<cZ0pnIcd8Z3#o;s zf+i8~l}$~jp5<D2zWPuLr_b993x9mSUtj#m=ws%m%gcPFZL3T!ZwDQpp3C>?>1pvT zvx7CJtXZRDHa9yfE1_lM+O@gP&dgc)Sy^2C{O#s)6B?P>ySloJR+?@NJ2dNSXnT7* zXkua0CZVlUrcZx=cegl$f|k~=hlkny?#cgi?68p$=3xV^c=Ynp(%0|5G12qJ<+w@< z(87n>hxUv1eE9pj{EvC~gHHu}GUB&&Ha0R|_Pct^Uru<<r-HpzU#mVmV9b{K)gx(q z>GEYuN#RqR?5wPTE3bems@Pp6kB)S<9GT0_$=NGq3Mw0)J$ts?Z|<XjrpYb-TO`UE z87lt#*tlT2nt|2&SFGiQ;sXCN<4X)SZrtd5_JMbxfb0EzwTl-oj^A6Anx1Z+d1=Xv z84}jk)~>Fud-hp%?b85*21T(C_gAb~v1ZMhO`D8-eb0(9Pw8yBaSL=*wU4g2e%zXs zD--|gSXx?&xHl%=Xyq2aQS$EY?(d7;``dV>%ii6wjEszQc6JU94vvkzd$5`P_3PJ< z{k5j+$NMe57!nfV<Lj%dqhk@c<}m0W^4#0ozP`T?>bV%Fp9={Hm@skT!l0E&NlEwa z-Ag{+mwP=UmPumGnFIr~ygMEVPqaB7E?K^O@!Red2}vr3_k=H8xUgaScF=J*o}Qf7 ze^yR7cI?=nKYx09dhYHnf4u!NXtV2lyT3;|h5u`=zaUiq_v`g3n!!B0ytz3!dU1Px zytugd%$YN<UcI_>>C%xSM|PLLpJ!Kl>(Zs7gH5blTwHH|Stb}r{QUV-KYrhrPoJ#H z-bn2EuAunRrsUFRzBOxibk8(Szjf=@wz6s0a~J9V`TKVwd+O_JYwQ1h6@M5UpfSa= z_?g50Pv_^`_sd$BeSLLxZ}s;@RemP|+BnNDxPTVN3kr7j^|b|Vh&d9=z~I&+@$uc= z-Tm_R+akSYMfCLaoSSdopa1jc=jUHvU;qC0_VIt6*REar|NH*`2V2*#Uq5-W@a*IZ zn!(GKEMFeKr@~N0MMX)e>0rW%6DQ8hvAlfX0K<B2zokL0uC9j<A7*A^nl)>diK*%B zse5--d`xn8Ki<*N;XmI_R7|X^qeDYW>(z@FZ|?2gEq&z0_G3E=AJ3UP_xqch#Sag$ z?x~RXTn<`2IrsYedwZp)g+8Asm#VQnTRUSi=-|m)w?ISOUte8)_UxI*%>^4bUOaeE z(ZJxs>C@T2zPx-ZHQ8w)kDN`#hX)6Z)6X5*e);8>_3`@`?MQc-&3M7d$?1oR<fOjz zYilC6ZQHiytxwcaTW-hQTcX=)@35+u^uOTd<>mcif3*Jsci4jD(hCb5SFo<q{Qmy_ z{WE8LLPEAITgJ9+_3G89Pn}v9v5|>EKwiH8*XFjk12dc|D=U}nT%b35-#)w9U$=zK z?4N#t+iLDP6+T`D3A3Dt*x1<F&Sz(ta@!ugcyZ$7o0W~4Uz+Y;7UZ*c(pM<4sy#Qy za?+$p^X+OM9si-SW{dfkq(?_OSJ($G6!&48o8zQ^;o8Lu7eF(ZE6R7Kot64={mGM* z<mAVS&TU^_UCsXS;bB@@nop_K{XLb;3<d@U2~yY2p5<jI`1U5U<ZR2bWorBC3kx@H z-MV$rqD3=iMBLw3`~2)|aVe=&Kdl5WEI#A<g7x0qmZp9G{`}lr|G#e8i3CAG&{=T> zn|O<SYis}h{{DWxO=Z#7S6Bc3{w^&o?LXhH_CNz;25(VO(Vy9KTUxBGGGDOj7m81K zCs->eCG~5qWW?&G2)+1yf4;sBui^FyIe!0gcUM<dR#sR@$esQ5^~ZW7H8gzP-JgT% zXnFf-lO}=A%by)t_4So$!Gi<u@9z%}3ww0iJolDK&W#NpK75$0?*B;ild+LeL}>?y zi-$*t>5uonzrWv}cQ@<v&Wit_qmpjhtXFv`?BwLM!N+P!oZ`zC(7yHDc%P>Jn9Fhf zMs100ZEbyheYL;8g$4&Vhb1Q@EZDW{*WcgY-FqZ97Ck-n@9*#5zkaP+wQ670*Hy92 z&J!yb9QN1!?K9t;dRpv?qN!==rza=hy?ZCorW?5_<<^$Wj~_ptp000S`^!ZAwF=9h zlR~RFwG<T}zIrul!h{8D*7W?lb>+&FSFctrS|lVc?tbNsPe*5Gue5pI$45tRZ_h6; zE!|uG{@!f!e9#ud*RK;9wrt*PJb#&*;zPNEw_fTVN-%ix<jJ#hb5EaE*x<*mp4WfQ z$HnEy|5+>7u1&o^le>RYQxj9G(@dMnPqR$3eSCe>&(3mP7~taSI^VXs?S0#}ugdOy z3ywWMKmYJC*KV<g)|;!Q81gjR%YFLv>Gk#X=H}+*g@ui8*T?T~d&d$Pc{6l%n52}H z)513Qez}8ue?Dj=OI%x2aCJrC;tUh1-wQKLr0f)r9Y5YanYDoTWnkL*na1f14CeXw zp4=Bv>G)PPWxh>i(>DDnZ%!-wTrA10nvy6PIOVF>^2^>{UJI_){r;AFMe-Tji4!Ll z<W3XrH#%lEbN#k$VR3QuoEFCHubX=?;lQyub7U+nExB4heR~`I?RAP&Z+8Et&Q8ut zY!fF;aBy!DcyX-llQoB;;=;ST%frLNi;cuNJ-076u9}iKSFlD#U3|eQWtX*H3NqGZ zIl0D9pFVwKUH|WoWbF2vl8;`!+Ew`Y*s^8I($CLhWn-H*eR}?_EidoxF88^W?x8Fx z-@Rt#O3v-!eoRwVhP>LAd;3L6m4idW^y%W4io%0~z5V^~Z%*&uvE#>L_x?qT7rVN; z^2^)ZNt$TSbn)WF#H-Dx&!694{ax?3dRIrs1Mi9*yLSukw-D;vxN+l5qtvdsb7T9t zV&7`rxOHpM+t=sk+qd+bY<2(n=H_ON)uA39CvM*cO`FEY-#>GPr}Xyz`u$y9UWJ8* zjrNlJ_U&tJZGHCa8R#anWv_mGd>k7aYslk#?(Vf~o>z>PEL}QrTUu<a?B$)Hxjz{h znS1+cyZimp3?%;l`F#G#$;rw}N*z&boSxU08moQ&C|Az-l5MJc-klwdS0rwP_cJp* zXx3QpB5gM4{-w#^BV%J_v&9~`czAhji|~$gacS9Fe1Bi<`q<sq9z8hR##>xoo`3d{ zj75RM>kI}4XXnFWw=G>lLP}m<TDouFKA*RGdU~&~uTTH4otl^T?!f~FThEm%S6;k$ z@y=7DNgw(;I!=6jeLel`tgZF`>+<vS9rfFv*#G$bd+{W5nXtt<ljZH}wyfU2Vui-! zQ@)^cN)KGQGG*oSHeTs_d#gc%<%N&k<it4}kAsGH=G)aC+J0DJ*OVzD-(IT-6c-o& zSjET?7Z+Fm|F3y4dur;_mKGMRsjnV9X!v*f)925nrKM-jo;^R;+C9uc<>ko|tA3+n zxpM`>7WnV-ww@}<!?t&(ne}7Vhn$`U@9!NjapCaFI(hC~+Ej(qMfvyl{V;I3v{EM` z?cAK5rLV)}<>$AwusE)7ePX{Q`}d;>->Rmhrl!tpxw@cFAe+lR=i!B;2RK0+uo`5o z%QXH?-&y>ei6LR8zu9Y*38AaQ6ur$&O{bo8YwzgjcyoWhy{T!aw>P(`)A{d5I)$rq zY}18=E^zXuIz7@;xU29h>GrnVS!btAo3`n_kJPERPT}F=s%nqwPegvsiP*SllhgjJ zemUEuPZA$Ft>(^~IMH!wQ2oC@kpTe?OM|$%xU{siR)l1^xv}-SEe={4p!e+5RP771 zpT2(0y?OyVJNx}TmBI`Hl9HUF3novRv?AoyeEa&MqM{WcS()HN?N)}oTJAr8)v8r1 zLbP;PoR<bI)IW9ju(FMf3`4>9cd;ubii?SrJwC?EaA0-#`V}EsZHbzinju<WzrVi^ zy1*%D<%MS+zP_L<S#ooAr+T#(&VTprT|mu;XJ=<CDk?HG{Qmy__s^e<4D;-2xtJ6G z|NS1XHTBk&D^upp^IICEt)=zp^XHWzS&4~>m6euLz0RIFlarCLAP97FYhPdAq)C%r zUt7EQ^2_b{_v?OqP-IZh(BPP7<nH3)P&@Vd`uO0LSAPBa_2x~E*3@5*kM}QHq@*=< z*7WJ&k&&KDgYNIGc3*yZWk^;|jt;|vTU)ci#|7WnS*)(0!0<2ru<+*>FES)n&zdvG zXK7G&R#s7Q@yd{_LJ%;V>h<@-LuX%K-l<-#Gnw=9@-{RqUA{a$BSV8hK|$fcxw+O1 z4afWC4<DYkV~2&-RIlZiFJHQ}A|xv{_388T^H+vs1qKF2MoLcgI(zQiy7lXq2d(Vv z>||)D|M!#q+O|EVufq}&6c`>nJlt+)XU72AyBfc%gcG!S!((ZXw3HO+NY#+6%F3V9 z_2XBBWYyLETOYrFMTpiEt7SneL6<x+Ja~0=^@e4O=g+^txB5E+gQB8hTwEMO!*u<4 zrIQ|fhNh;jx5H;!J(al>=REU+)b%;u<%j3#>FIrse)Q;QcTKGN<p-~?t##&q7<NJF z@v+|Oudl8i?H2#2&TxNUZS=mHpTEAow(NPKtoSmIL&I!WXiiQ}OKWTAs<Xwfu4pna zOn&Dtu4H8NDb6@o?eoL;ddvLh-!s&HBeX&7v*QQ9)EcYPmzVqBmJzP^+m(G?&u*>E z{4>uVJ!0a$yk^as<^J=T80OhjPMSNHm*K;Q4-E?yj`ztPHdqtCzfRgb?}!2DK(Wuy z&K@?%d3kARS67!r+uIu(onvBT4jasfjf;xn(!H`h|NfyV{}(J!(6B6ec!)J3`u4SJ zVuv>*9&U3yxoW0ydR9h8f<#(Qj*hl=ccO$2%ZUVo1#cq)0vLR+-nbDVapcka_x#?H zhhLnD+go)|$A^`bbw=r`wQFrlUxn}-Hp{tTkZ^#3=P>x}_7!W_wz@s}{r&yIng9O$ z{M^&S1KOM{E)rN+P;kJ&CniQ_AOB=E->4l0ifxHiRaK9V_a8UNd3|l|-s<ni49;A< zD98{H7st2u<drKS89!Uw+73m7_NW}pSj5cEmmp!5aza2@SeWPVGXMGOmM(2gywN49 zz2MZtdA8LpEiDE-*5&VH7#6Hp!7*#<$B!Q`Y?KucU~pb|ZmxBE;*1R&HcXu=+Loy7 z-e+QDbjTp*+nbvoym!_81sxHaEK&C1fg^)MZSCKur>8SA$lKSosFkLqsOT`1yuPMe zVay}^vg-T0+|10*#2YP~!eaVyJ&7~ct<&?VHZ?bYe|b57TcWz(90rCzzrJ$casv%A zZR6wTZ|`ylUmw?c5wvjTm_ZI?O@&08?`$*PWx>)C5<POZQinIh?k;;6J8zE4XWQy; zJh|~Ms-F#)iWMmFAHTA0`EquKANT+NJAQ@Fe($qq&psqZMM*I@xVyKnygkphnj!sU zjrVjtR+*Gp+qU_s?eKecduOqFTcW3@=ZdBA`|EPE4%GhpQ+cq7^}&7~IU5TFg@(zj z6G~#=-QAsjM@Umsvng)Yffq4tMv7J96}xtQbIr`m3^exj^?hS~e!ji@+YFzmdA4(| z@uX%bJAG*7Pd`8J?z0&h&-g*RgO7$Z&(iqNX7nXSOjOj=%<S6LtD>T!SFc_*H8T?v z6}@`pih)GWqD6~Vt<vH-ys!2*+v^F_r%d_t<0JFo4VyL@ReX*N4E*@@bvV!Ay8r)T zLqc4ZU(U|X?(XW6l9GD$`gM0#*S|kMoflv1>gq~3R~;BA7;@n6zrWR$m6mOZOTj>* zZR+&t_5c6L9^MeYzfM$CG*KcfHkQ|ZMg6}&np#?`)~^>A69df~=jP@H2L}fRUVMLl z|HBy{-fHRTwb}o%ulu8+sd>=g&GYl~@9(QUY_JAYj;!c7m~dda&*F;_@kVp~-1T>E z*idk3iKlZL&(9wple6`Au3E+Qf0DGcbY#3yjr2orxBTSHOih`|UsVrp7u@><bRI}s z+oDB_uEiNuN!S1R!Fb>8+H{`#ZeJ$7e*OBP_qw%fkM4i6K7M~#NXUcx$2M&;`eC+d zU&@!0jru#+t>Xi+601(Wm>e7z$9Ml&{QkO!{LvOSL%O@WFMd0!_4oj5Z*Q-Lc3-x$ z<UJ+5=xseKV?#ql@AV#NH;}0M@!{c*sh2ahe0+TTG4IoBE{_a(n(dNxb#+xLFRm*! zI=FaYPohK|=eu`zcjxjQN&p?0si>=aHbqZbQu1Ivqv!S(oy4<V&yA*3t~Wm9HGQ4U zrFBip!OQ(z=Ym2ARAOyO)A;}EE4Sq&R~MHLAGJUS^ze0`7q%~c#$zAmcj|B<UkSf+ zx&f!XK*d49kYyqVTNm;?eW)SQwST{TR*O1wMWqGL;RPX2Te-y_9BylCi``vzuyW$U zp2Qmm8kt>PT_f|SW@lwRdGdsTVcWKCyu7?C_O4v1X=5XE_1_BvXUTXu*YNP^lM4-a zKyw+JQcusg_pR>luZ7lJlk(UyH8x+`nKY&I((VJ7mumM%L`KfdZ~J;~9%oID(PTX} z&u$NIZ_xR@aptC`4_Ch{6k3^+o7=liKXw-jv#1A4YhT~FPft(l$L)y-2~l~mbpiuJ z!`!*DxArCE@bn8PF<GRXm=L`^uM@mPZ^i1>vuDiknQ3(N+O=)v@9+Kl_wPr<!^!IY zObp-N++4h9(W2F>t&@&$)Yt!?Yh7-{yQAi3kyP(8@V30H#6(8bnT!m3tH0NMd2w+= z;^A|1t<|-)vokU>va(jKUTt0RA)&VR@0>Yv%%)38Nv*oaa|V2uRLzN1H!iEazP{dl zKewQupwH4}QOQ3(JY2YNVfni|k`@IDS%1F1zP`+N_LnbT_SO8nbT4(ngc4hp+?D_9 zIr?`ulze@`eeU4$_=4C&yP_}6a+f^3;q~?Pi|ut|b_kq|573zM<Vni6!)9h?k4~#@ za<l!!_cAaKbX=g4lG1Fm+*cbUt3}>^)ez?6*>LP|tGnc<Z*Omht`6gsGVurr32}2f zcIAqRx_bN8+lSlvm;26EQ&YRPzrH>>`SG4THp`lnCE7y6!t!o!^9>6Vdnf0UnE&+Y z({JCt1uye?d3X2rmX;$gE-wE1<;%L*-E7V=H`WV3Q`yq7d9(3s^Hr|>M&ItA;c|C( zH&fma6cx3r>TA}r)GseDKR-29`%SX9$1brY?lY(QrD%Lse#dmFbg_QIN*j*f7rKA_ zs``^Y%Vf&O4Te*^y}Tx5ZE9&bGRrhO;QUnW@NJex_dePbKl3>h?k9EX?a@n@COIuk zNlPnxvc#4}&q@EnH&>4rt^4=eU+y=TcHXIedDcRn%g%~jU0pE}I~p4sjf{+Z{&U~D zbxYp9PUVZ}1P-1{0clHxTjM@J9TB!ZZtrr*h_bDww?z7tGbbuKw`F8y9eTZMmlf#n zD|2>M)~%H`yK--vm4yBN^))-_RQLlK`??q(pELLF#a-Z5WK2s-%lNINBGI#E{|V)) zs;U(Qpi|rz7O#qxkd#bKO*Ool(%IKH&nT5k#9Sq9p^C~}PDY7S>F4G&a+>?RWn^S* zxgwd!`f8cu(QE6D&a<t)Gb>SdXV3DztgAf)emp+0J^#L5{63kuetG+SXE!|l_xJbX zdgBi-jZ~Nte|&g&dwV`V7gyHCRZO1lMGp=zZmIS3@c3Y-u=DiCj*gBKzrMb{c=-1A z{9;3nGf%2NJ>iVgd~trh{qJvYL8n7BO;u63+sS7-MOsSAEcX^@EunGxxi!(-`KF#~ zdGh@E;av<Z#(8&kY}jC6ZEao0b1r;Ao_0&^NsUkHE!jOiJrkaVKo;#jJ3n7uR(9^x zsjcE6S-cu6xz{&0HF0rs|J)HK#J6Gl_UTim<h*5=xa4rejsnG7`|AGxJNvs~iTliQ z9UYwuU-wpjpON&<BK_l|qcdmDv@Uy77T@s$wie(3_cv8FHNV+rXHTB&bX)v!SAY;> zVs38i4I$8CJy5l1;r#P>zx?Ot=f8jacyn`l|K!QR4h{$I>@5CRWWTrS>!Txj5BX|q zYt<Of=^gy>@o~Dz;eT%?nM~Q4_T<DwkI40_R;}8!$w*0wY1+)EErpMd9TZ|?XaKE! zxcg>ef#3eRzh!T32#SeKn>9=8#nvwwYJPJ%w&_n%JI%dh;TdaTHJ^aMz{cxSr%v4? zv*nP_z7;E07$zTEa13;2mV5uIl`ALS->2twe_yTp78b>Jr`RccMpG_dzI^q{l}VE( z+1S|JxqFvi-md2Nx7-^X-g_o(Iq~xHa(NTuj0*~8ww}&2#XnpW-yl`6d&9<!j@ReS zo3|lCyp!SP_Wb!fc39{fbyw23bNBAWZ>}DlX_o@jSZcONh#M9AoSAEFuB)5-{@&gn zhd@^e%(g6kHc{C<!BI?TlAf=-#3@BZ#aXjvnVOoOpKrf?%a)ejw6wIqz(8ML-{R-z zTCc`?M%>((eEdz?jpeT=wi)R^$c}7pYuje7ntE?f<z#jLw(Fsxp&MchIvFIbN<e#i z3JMHD+EoNOz&T;n%9Rgm9g3zavj__dKY399?TzI7&IgkwO?rNQet&m&vuc!riPYOK zFE3wS=Iia_^Q5pwNa*09a_5^zkER~&5><BVsrdWLG`Od`t<5bcXwsrZk8W+vmX(o- zxcu8A!z9Yt_|1L!OV<x^dwF?1KGxfOed*GrZ++R82Wb3wq%5u<_r&n`?mc_vI4$&< zWwLUwKWO&HIE_c-iP901m%ALq#pB}p1)e;4;!?%a&M*J$>E1*6@hy9PKGAdMXlCb+ z+ma!;Cod~&SM_&2#lx#suU@`niHvPkNJxl?X<B-Ec4p?=ySu-GMnTRvIbHfFAYgtC zbil*3Y18)Y+gDXpRsR0o+uPgqv*!ycyB#?SO3k2)SgfsoUt1g9GUr06w2iH;<(rdV zgq@aOKH1B8aEjS2B{w%WJw3e-Rt5$I-`?DOJL}MqBTp_YbiQEz`RC{77cK<USh#0j zxpL*p%ggSLjt?&{_kTNUo#~smZ|&><`J4!dm94D&xi$Oxy?wQ}PoC^_(3Q2CyKcdP z4HX}g7O}mVbK&I|(9HOYJwNXM|L1<Cih;pIYVD>?lMXOe8>OYCJ$(4^!>#k@`RnJO z=T1sWTJASDYG={YX}Zw?Jw|i=%8QG+d3nEn`SRu@XQE21gxyQy=~JeFZ`;nv$(c24 z){XLImtVH?$u0`eShZ>u=oYcdlXfY6`}XbiEKP0g<jcXKp`jP#m##Z_Wo2-54fEtl zlLF>XRCX^?Jla1!a)xm_pSpkJ#WP%7Tt07CM1=3=>Qhly=jP_lzq?EH+xm6u^4@zZ z%<0p&XktH=Aa7ODalkM&CFR<hNaqI)bA>h93P4MnldHM=Zrr@N@a^ht+ol~?-Kz21 zSxhE#;x(>51yIN4dVKvtyPG#}9?@%XTWo1*+1c6o@c(8FkyGE^-d??GRma@!mX`Y; zjxL@vZQ8b)pPwd867u)=FE0LU_M4lNbFN)&l&9y(OP7LteRVA?DvtF?{)vc~uB@uc zs%pK-exC}zs-=pp?cekB?boo2UtaD%eez^&EiEPn5fPCk%a-}gHd`C3{Qkzq!$*&@ zMoAbN8lF6PQnvE@r7Krb5)&1bl^?%&@#5jbgSQ+*L%&{MA1}L2u4VoD{D_DMc7M>$ zKWXXNA0Hn-Ki~fT;lqz#TwMGlF!cE6UteF}xf4@ycbQ7%><Q5_jUFBzYhrd@x_w*P z?#t)T#YII*1vL`elT%Vs^78sl86--Sy}h;d!-o&Audi2LsxaxCo3F2LcOvKlhvZ$K zUs?*}Yp}Dk&z?E6v%7nL#mA&`b1diBU%Yaq<jf31H#fK9=jZlTeg<96#u*<y{qe=c z?lRw0>Rl~b{FcAJwYA&yexK7q2}#M=Jr#m>JUl!(>lfNod`J)$7B<;5d-m;}#m{46 zV-FubyuaqBlAhkWRjaacbNA-lG`ex{?p@hasZX9hRebB@>w9*`4hs{t=7SE``S|$a z_tg{@6=el3d;aI==anm0IyyS84K4Wd>1lUMiwpaF8=sj*OO=;1Gnkl~wzjq|G_Ly5 z_WS2gMR#k@cF_KGCU@oS7D=Fig0@Zj_wQf4SotB>Dq%IBj^5tA6(1j6T<jjGkq{Vo zagwUHS<a0I&z^x=KS}8-59fY<etx@#)BhhIAInu&J1<$-lQ^Th`}D=d?&>}>1f-;_ zs=mDV)(5&oeae)SgoK2oq)Bt<);>DYxlW_bM<IM&j3cWKpRARNuI}BW?XzY{iL_~J zX;~FLInfs>C!Jvu<><(`XWE1b6Q)jmS}VhFU`60!mmNYWx>{OJmwwsV*?|WCR-f!J zpVHjav_n9A^R{i@zI{7pz{4OQAn@VK%ggia>-!{)(@suOojMiNXV#D3S5j2eWEu48 z73yj=8OHcGT!#(zR0PMy&Ye9wIxbFb^_Q2+{pb75u}DlvNXW=Iv8<_K+cvZEtvhxJ zT)lerhw)QY?`b_!rmsFdJ-uY%osR)4Lzb*4cyxrbu&^-BO_?_}>Eg9()22@S`|GPV zXt{xcon2gT@Z=AXEv>D&IXTnx<MT2zB}GMBZ{^?L_xI0F&`m;*McX|*tTbJY7F@Y{ z_28`;zkdC)VN<+%<;ns5^Q%HvSM=w+zI4DM-y<<GG5hYP&z}R$OH04@#Uy4gP!4AR zxlZ4C;jBvx3=x0kaL<?%4Z1Bws&}P-aIkRP8Y^}6_U=wGy_k;qFMfS}t#QXvUEST! zug_`Wp4#7H+cxwEvN7<;*+}q~J~=V5k(r%|;lgFX&(F?Io-|2IOY4vU&s`(KqZ<4& z78lN(>FEx?vb90<!JD^l15fWPe$K$KapT689{*z+&K8}4^DGLLUd2a7o_sv%&fU9_ z_4`jqJQd3{c9TrX&hFmz_QuBM_1;Ma64%$o9=?CXsi(Ks+uQr6m8s|MjJ3;NK0e;h zy4R%9vBxIH)3dX$@7!GL@^5c%iwg-I`jwg?zPI}OzWV?FCMvtj+0|H7f6J*jecQOR zvokZt&ChS$*1U>%?%%sqVkhqZ|4%yh{*J<CYt#Olo6}F9KE1g8{k^@##l^|5K<kyw zPk~a*437EsdTMHESy^3vuE+Z2-@kg*l_>E%No{r5TBqyG?0hXx(?9k}f(Ci6pPsJo zKiBH$*4z84L9;`kb*>ZLFBvXbwrtVc)vi|;mpN8V;bi}{P2v0d`_HrQW^%P|%Duhq z%o(3$kAMIE{$5*C^X|^#_WIz-6FzV-wiRV%t(r9}%FC;3`t<8Ni=TgZTDc>B+Jp&< z7cZVaKfbKYOy}kC-!oz-PM9FjpLXNKqiNHpFV22Z^2SdmW=FxH7S0d57A<Pp9TX(O zm3q8S_J<fddpoEnl2_qt{*EWr)^_j5$Hx!9Zfk4%{QP|VA;X#<1q=>eUR>(Vo}Qk) zQl?z@nZEw%?(Pos_VnzWJNNFE%*$o(@0~rGRn7GG*VpK+S)mCD39hcK3$9<<yrcfV z-K~96($bYOy(UvQ-CbQ-r*dfO>(_sL#JWFy%H+w_e|~K2@8`F(`uY6){AJ6Qot~!q z`lO%Xq;ov$4No;THcFf2OqepIr=ufd{g<5CGiJ=t(9n>Ol+4V|uCA<H8@;{n#z$-W z=jZ3UYaTv5O*dQDe|5F=DG{!tr%rLr-1+X^yJ^#=IXS)EcN}yFebpC@Q~UmZet!Pj z+uNYb-QOI)A>m+r{a@Dp_n^gIPfv^2Trg-~eRp#@f7Dvgu~B<%gXQ_Isz;m`c)-rE zzJ62n_jmXA*H>3m2oyEniru(j!-ub5zkdGgyzzmG?17h;mv7#*>A}{OD>K)}?X8ep z>*{Q2YrFS&+M~^i#>SHm&r?%VbMKdHz5egtKksQe4{tB}XD@A*bK&aMzrWw_XJ%#& zOpb_%$jNzgb91_`jt<Mb*<XHtfB)o3%E5Jx0S_iOeOR)5`R5sF7fK)A3pi%*=F7{= z-@bkG^72}^aG}pS{Y5h@i`9JINAItz4GX(=a<cl)4HI_heSd%7eP`t1Hs0*D(cjIc zFfvG+<!sozx!Ddh>fYYk`Y>6=BIU-0#BOo@W8PC;U0i<r_>psK3+L93&Q9IPO)O$w z2Mly9EM{<D&N|%2ySx0oo7m5V&h0Z;OqMKJVj<Z+x&PN<_kI->m2Yovw?|C~XahBv zH+-=F|7Y=<HD_L4+O4@c{k)uQ)t9rg%{`rWDp!7b!daxUy=?M%OWkYh?Ft?+^dJ6K z;N$B%aiNTm(4|EsYW5OVB`*%Q^Mh7eR)5c%@-Z?rbZhDBYYhzz(&l*|PA+;d;rsjh z=VzPyH*mjb0bN^i!COEgMr!WYuU|iKZ%a8j>Bo<XeRa!HQ&W?ZlET(RJbdybB_}5) zI{NhfomSHG?d#8FwfQng-V1nFXLt&9nZZfh>Te6$pP!%qJa5j~+2+PuZ``_N^p`<e zQZi6Oq_p&_ar!w4DXAjE?j=i9;wqk;oo&AQANR)6yIEOTaSuCsdt2WY78b6L+q-Jj zDy4#TdR(nehUVt};6piuzeH6D%(ts`x-H)CdY4hM>Y)6Sr%%(*&EZ_1ot0%3@#)Tu z8y4Eyr>|TQxi29pxpK{#nvaiM_gJWLb}~FX7NDo6ckSA>Z{NRf-@28Tlk?%-&<kn? zd*9#KnC$52xH<iNTc~0c6FZ-bK}Orz_bm4U<O~uV+I7}0zPUA9+$y=Zv(r&f^KH`2 zO{o>KoD2uf&$pj>YP(+awm0YJ+jDYqmX(#g?cME=b7zNOth<|AT1v{68@zIEpM$y= z?%Ov{(|)?=S0``p?$-x*3ol!?%*n~=4(pD|1;^jr-F@-f(O5n`xh;&70^N=@=|?)` zTrKpIFi2=vzyAGV_x=w%Pi)<~wfftena1ho+WF<JDr{9$)@-r<qNJ+IDwTWh-n}2j z*>%nf1N`RMfbL32JuPNxx^=+<h0@Zme6wQ%Vs@2q9y{IiU#r#d;#+5Z1_tMLK2u|3 z=PE%xJ-szWhjNv)w6e0Yyp{%i>@9zP&(ze^^<bs$w9gs(W@cvR&Yinpalqir-Me>B zpXO$8SbllFZS}R?<?q20YbXl_wy90>b>dKV>$!04+PBZ2t&5-ew72G8%rLRH-~Zsj zgDF#{Y}#a$n3yOjIrHV^<)G7mB_$swmn~ho^y9vIO-)T#S65}_#e1v2e|vXV+AJ+C zO-oywU&4Ulf33TPm6e=z*_k_cWNO_dctD3_A8zN5-&e!=In2FRYHP{MAZzR0eU{~) zzI}6JZ+H<}z~1ZT?3(iL*K7US+S)kr>&Y3L&%E7L`uf}3+w6C@`_Hra`S-VZ|E|!O z4J9ux-Q1jh`{vEcs;VRjHfhjAn5d{IJ3DAeVWIkbe~zM}qJrIOF9TWF*cRRX{jK(g zoK3}s!pCl!nkzdxI2tQfU0WBsdclH(jEohF7e9XavNL@9%V{2-o(s3IneBgPU;oeL z`rWGmYz#MU-mI*yK5TsF!i5WK*68dB&~DGUw`b<b05*m<H#RD3Yj+>Mkm&c~?rzWt zH=UiFH5IR~tzETBYlcElU|`|bSD{-zgVuCBJ3CuTODifW>d~V|U0q!%H&o|GY|op! zbm`O8;p<CEN~{ugy?K-4?0mRi-kyn}jaS;N^i>E~>!+`;uQ!0fL}hn3S69QUY?m%v zm@s9^5rZ`=R=l{oyL{!!mEYgr@9*w@e6FCeya9Cg>qpSVN7t`EfA(yiZS}S5*Uhc0 zs_yJ4blKxo=Hcn7_`B`+mo=L<8Kv0wN||=qNGeQFbZ!HkOlM^!#Sjo5FE1mrC-HDw zU%cD>eYLhWHrLifCTC`LYB68AeqG+C;={kczs>XS&9SL0I=-HZ!Jzh6$*0fPm^=IW z+}et)ZL}v&oVYrC{j!T0l9G~4Hs|LWCc9NtSv4OtFg4}n=NG?XWCfbv@XcPfWXY1P zdA5pQm>3Mo-bB<$s_SVzXJk<Gowa1yvSU|%JUu;K*}d<>77MX!Yops=9W{{XnJ{6( zo;`bJ8mGtYF3bJ;>go#Vsa|)t<x0=><LBV`@afZ|JsFms)sY*MT-V3#wR%?Y^Yiob zQBhIy_H{9PDhi{bZf(!MFC--N=<(zIH9tSSysU0*y<6VCZbRMT{Cj&OK}VxY&YV44 zT3A?FS9k8ji4WhrIdfOafamhXi=3RCE7z<!bK(T(QmS@-`DOm|<AQ<?Nv%20-_zTB z@!L_iyOV@>Z4cRVAaZjW@2-92@8>au8`b~&)6>JFt*yN$S;Nw@@)!6d+2^q-6IxoN zE!O)wExwrWvfZ-y*`FUDn>&is?*s$}ZcI4H#L%#FXQi`q^MYv?4MB@B%ZyD;P1lx6 zTa{@1xi2Cnw(Rmt*`0^3DQqy3pEqTS$dg$@Vq$gw|JjN+$Luca{o1Xdqcdm5438^T ziWREQ4mPu2zj}3RdCTVX^NR%U+__T`>C?aVOuKu0eEow1jlRCVG5zNgQ|(qv-C6bZ z)wO;7^X+Pzriv(TkYf)v^au|RXE?B<@bLp@iI;W>2O7lKZAxE>+z}1*?w-H>{CxYv z{h)=(H`dIbaH>X8Q8DB9qr&yay#Ijq-x!H-YCnDYRO8d`@bz(`qM}W4f@0$0?JO?^ zC#(5-d3n8R0NwxHt7d9s^r}J9(@Ofsf}dp-Q(RqK^kR3FJUunFrKM%2aeC3ZD;^3v zckX=k>Q!K1V2IY$%a<p+C!Rak-`3U^XPlFi9{8el%a)Sw)6UPfD%IlhUvl~79m}cL z*TtUJYIIqCSyxxLbFJ^zoSR0htgID(A0OwhudiofSAW<vRXcpz^y$Zs9QjZ>?Z}ZM zH*Q1}7J}9$ZU0sq5)hEFd6Lo;WepvjCwY>(UGw^+Ou6obZ?;^h)MZ)xY{6UakdP_U z7k;>8vc!F60UM8mLH)m)in}*%oQPeknso8X6(3(;+lmhgc2<_g#=*|c$^ZZT)kvM$ zvV=LKyx~uuto5-z+1*uNvwVGh>+Apb`K+q=_~_^?)9e=|pjGC_Uo7GVU0S8`J|H6E z#v+cx@9*!QSb4qov7W%UZ{Iu)zj$|dw|k#VVyVrB<@xvbX@{-(@cDBy|3Yawh6xiV zg0`|VB^$J~wys^XX2OQKyLbP7c6PQU&$S~4IyN>o>gw!UQ_|Cmb?;p<4BR<){?pUb zyE{5q*w{XO<!w|1wSk@)CNj945S)E1BsiG)_1yzX6Kh<(y}3nYR<2p|WNDYF-Ipfc zC}Ym+YvT6)`uX|!hhP8x{*K<3BlxC@f#K1kN9WF+TOGFc)}~bNsw%6TC6`~OpP$Fe z$@%fi7nME7Bso313pTT!pKot(Xju63(^ED!Ha$JPZQHiB=yy*&JzYOKI{NhK)9r0- zi&o9PJ1_V4wuR9Xuhpe}m|0_cFkqfxa@)Ll_a>|RGcc4`{e5zBa!f_m_MDqdJgJ_Z zCttnFs(Ab2#g442T1=nknC0Kg$<OC!<ux)gQc_k<d=N0(rutjX%}uF4rb(OU-Puvt z+-=GuZT9B$bp05cjlT^O4m4O6KWk}eiP=*zvHt4*dwZ+d83IB>pBBnw6fFMrwx!_d zsi|#kZIPSPHfkogh=3OSA9%WPnYiTQGx=d_A~x3kF4G8=l$4AN3~XF)r0Wn88hU?U z?dvyh-aL8Ik~F2bxOnT<t>y3TynOS<Cqc*z6cR622ZV(9cz8r?PV1dJH@2+o+t%#s zKg3xb7hnAL<|cD1lblV(h2Buvh=Zp;ZgcZDp2En$$H#XefQ^BLg+<1yWX0a2&$n#d z>fSHM``C2#>ecQ&5{$klfBpKEdwZL!tE*tD>(`f;j~_b36zWk}T>SV#z_+hoMa9MW zd3m3@nr_>$p#ap=xV~O}X}aL)X}X&?ZuIo_4h{@_cv-|?=d`QyZf{EE{@0g!a*}HG z_jjqaR<L6s8V`P9`SmR*I9OIjrlqxYr{2xWdfW2udR+-fdBM8><@NRVn^?I)D^wU3 zT+BFl@Zj8oD|rM(MYnF>9=|cEb?45?pdg`~6a$Irda+#G+@C*uh}coEFd^LI(%FZq zH)gsmetU1PG{b_WOOG!9vTwe9{h=G0-rlE=9doPmxj6IU#fuYjmTi49JN^8;h5yg? zN}HFxVTx|`KUveU)O-4(kGwoQJ0u=HIXO9_WoPm8N6DVHHZ~kFS&oj3aVp)KiK|`f z{{Pd})XYpuI`sI8pPwH?!<H>3JwDH}pIu(=->^Ami`YYznfzXR;&+$zuAKXF3Df+F z>$fT|PScH+;9+}n`Cf{h+P<2fO6uzD3<ag7xt~*oCY@QnYE{>^^`}mqGW)%mF<#kN zQL%CQLXRlsti8@+`f)lho^Ejn4!%6oIDN*YQVo$)({!Wj{{C`(;F4k_&pG{M&6<vm zj+i|aA8&2V22K112WPMSBXNCC{r|YwSX(QrTW3L+OEe!ZnXvA}qodttXJz_s+`niM zQ*8O9!<+aXTv-`>Vhb-Pr>CD^-g^#d^E?-26%~~SbHD%o{$5{CPc3CG14CtH<&vy+ z@KW$wHC$7y%#Dp77iPuB-`5OY#^F`JrFhYT1qOA0DoT_N@;^ApmmMjfa_P~tXV)GH zo0^zBJ3oJa!a*ix=H}Yp-;N$VT3K1S@Gx&hqv>f*W5(Oj)|{HUy0IG)90LO<u3h`~ z-{0Rmt0Lc~-Q1L#@%z!o6BBv%zF^?t<-ORSlaiv+llne{gPZ&IP0;0BeDZU95^tQH zZLaP&M?y+U>%i8DCCMBC9yhk--rk&kzV83OwJTS8F5r6e`T2QM6O+0>KLo|Zj)kP` zc)p?j|G(_BThdq_JL^~VEnKr`QBqD$kKDG3mb3E&mE9I-bR0Y8=IZKN!X6nJ$=UDK z!StUIba~6a{cBunYHQp1WKShdaLe9P`T5!3-{1E{-@e=L+AXH4s`~LfUxTA;{{4Nq z?}Zoked-q1Z)$EnKi7Ks%9Wk}wWnk`ZhbeS!Re^(J-`2deik2WV%@oO=k4wJ$GQ1F z&3=5mfAQPy<4x0-EWE?`<^6qm)t#3zO!U0wm+>SY@9XXB+gI{Z$j<8L@9*!~*x1z6 z)PD4LIapME(P(v&G*0VDnC__4FK-{WEoWv=PtT4WKTb?k-kx*w&@n?Pd3n%r8oIh` z8^K#0zP-O69~^x7=1s}>R}O#v_qUoWIv~`~(XnUBjK02eudlC9O-<$H<>kED0lK_l zMa8TM6F}qg_t@VoS`)oJug3WP{`&ll{^!4FFf;hgx9fGvPfkkO5zumg{o&8wzvK7S zNZQu?_>fpoVDP@XB_ktaLD%d0e}6uH_>ge<=j-e10|NtJUt9Y)@ZEv)=lvrhBtqF$ z9ACV8WoByX>gM(-vu4G~&tG0%HhbT3>*$4r&aN&lQc_Y~U0o6Xxx5(;J>aql4GrD3 zYuC(~Gt<)2!q>$p7TD%)DtQ^SrFLOW<U0q$Q;!}!l8sN;`FumxRjm@xx-rIeEw)Zh zPWAu)mb<yNty`zJ@9(?2yM@*LcsMu&t{mE>YF+;B%Ju8~+}zsWdyh4AbiRE1HqWkh zSARc$_H)hPWf@snpdI<Y-|x4t|Cf`M_3P{F@S2)G4-dCboiZgUIoZ1I?;htF&)&^2 zOcoLn64Q&>QUCv+?`$(u6O#gSzFxQ6`)YSjoqF}n&CSBX!kuS-yes07w~JXg^V*qZ za@iXFa<)?QbyKplxkK++9KUcuU~lrwnKPZ+`Huegdvj|m_o`5VrruuOU7+32vxS1o z-`z<xkg%z0$~3w4|KIohtlVNJPMr8)<hR`=Iy&0LrKP^@)0)W5X=i7J#>ek3e(uNC z?098^kdTmZ+8G807B)7czbhd#hYSr~i=DHwvc7!z5)>2^wl3!7+qY-!7xs%yoIKh2 zwz&SUcfGy6FJ^n}n*6hC;X=hvc6%y5GP(UJk8<mi`T6B#u)e;2W@hG<h_JA2WpAUL zot>EuKd&-cw{D$*W#Yv}phonsKR=5D0xq<03QI{#>+0w{dh}?<FXy}8Hzpsyb?X-B zramR5LzgcL@3Qvx_Wt_yYv5VsuyyhK=Uou0{q^PHbMG8!B~h#2-`+~g$?eO(zmHp7 zujbbmO#_1sdz+?<ii)ldTieyub!%7Y>!se)s}cf?grA?Ao4wh;-*gHi!?$nW_U+sE z;lj_u?fm9>cW!{rPw^KQ7B)6C+*$HcsHWocv$LQTfUM8IzP-JD)v7M>kKs1jVQU<2 zNrA4MDt|w3<;u)%+uGV%b>CSgg^ygStE<;7KYjM>-;a;mTUuCTTDW<SA3Js|dV3x# zE9=swOaK1)85|Q66Bt<d@6XR^)296h2CaAc^R{KmlqriAEt)v-;=R4qi4qQJX`s9J z8W@=)lFJww`uh5g_sJTYn}7fE#ijS;$&>nVdpx{A;Mcde(hQ(Q?G8JoJv}{nd3#r^ z&{)Kla`gCd&=qIj-`!okZe3Yn;lcLK`aeHDK0Q7C4sUjD?%W9z9z1xkV9gqv{ChSx zA_4<97Ct`q>Q$Dm?%J1^moGl|_3KwQHa1R9PVZ?tAHRQpU-4A9=99esl<CtKpUAls z@Jt(YvW0Ov-=8cK6O)$q_Vv-*{VsGTv7dPoy{F=1pRDx<L(ojDp`jsY+0K^CVD|g+ zW$Iqu-igyb{Q3F$@ZrM`<&3hfXz1$drlvlfG-(p(2o6~x&?O`}IXN?0UY$LARzGG( zLT+wsY;5hPCnuw~=WVr^J86=TRn^;=56aO3YWGf_K7F{Pn(ySfb8^OMJslk<TDir4 zY+SXjW9rnaO=(`<-qCNG!*ADqe&)-*euL?cwN;G;1qBYmU*F&V|NZ^_w^dbMUR+gG z_xIIiXJ_Z%*`cVa`t;eeW8MoNiyd@VUAb!2rpW%)t5>gIuOE2-W2dls(%X=|C(fL4 zF=c)F^y$_uTbB9GX3KvKT7<Mp;e#7f+QPH5&HV!d1^?-2YHNpwh9<^jno3AW80>A? zw8@As{NA!xtStBJYySV6dpJ>0X4-~h209bl4=;wT1+cZ1-P?Y+oqxV*_O&^d#TR}n z>+8!43N~78k2ti;HQ?{8jmgJfy?S+SmMM4J|2{d}T?-a4+!tQj>o?OVRgHUn+}<Q( zZpBsNVq#($zgMkXnYp<>8#L-&DXtrJ<g10soCX<ohKA79VVqT#Zx7tRKmV&;*2P7x zKKHr(9=LvGnzkk=ARy!YlDPum;o(2jmMmFf@b~4Tqun3;J3BjN`q=)mI=As8X1SMc zO-fGYj4*K&SXOmWYTC4E=jYq6U%B$)l`BsS6d%02yj)63>gm&`iHV6}VPStFLc^CV zTC`}%%xjOhsy4YxG9+YYcW3p4hJ-jA?k-;DH`l84RY(PB^_YZ2Mq=W`xpQ-KbA5NP zpF4DjDRsVzy88AAkDJ%8hkJOOICA93`SbY~7C26wI`ygz!}5%L5%;>fe{XJX{`~1v zLQb}$qhn4^PEk=&Wo2bRz=a)!k1t)iG-Dpq<_k*7%Ag5_q@*QFml_)x&6zuQXVv_M zwzjstd;czW@AvZZ`tgMQ>a}ZZ4121+cF8w8PnkNkxTwg+)^@sHY*%7LQPHQ{+w)(R z?Ao|-<Np2id3kzTQg`(3+`TJrUzd}R@ZtCO_bXPc=;`56Rb4t+-GAAVB_~dweq6oi z!@Mn9wp`cQGA+Vc;px+-8NVN?vb3Ig^4f!kmp5?EnaS$@y8<@8D1Ch`H8pi@>~699 zKi=FlPECEP9llOLg5{{`(W6Jp%F0fiI#v7Y%fu;DWUR~Hfbx}}s#=E0tm)II@7wop zL*n5hM~<}dNE+4tDzUb<R-3A>tE<b^nv|G$(8|fzm-kG2^@j(Hm*)!!3!go6#;spY zS5Z+>Q&Ur4fBnLRg%1ue?rE2>dMPR@dU3r9)86xQ)N2o&*eGvbSJL*s``bbL>OYs4 z`+IqLIXOA$>FK?x-nDx-H#fJomR8ZD0L4V6_~jcGEpobW`lr3snZ-ScGq!IxpS3!C zecaBSJKt7W?J{O#c=6)Jhs=orduFh$Ufkrim{-=S<lmp4_xIPgKV4rr=iISlZXO;h zR;=L2?su0wwo@;9+ZxyTRiHgt>jGZ1OPJ@$9NYX$-((8sca7`Uu61>Fy}Pxw`*5P* zoa!mNO<F3ZbUr#a*E$1qDR9}1J+@+?Jz{O_?CjiCmT%Q8EM{!|@MMl<vBm$)2kq{l z^9gjt1Vuzezkd3ZB&%+2zJ05V?y`dmZTz}-Yj`c%TmRo~-`%@+_ipVF37Ot-Fu`Hz zTwZCjEkC?OMMW7IcJ10Fv(MPg&5fa9|G!_|D`npY>S<_r`1$?&@iE!inYsP&C(eD< zQ!f5bt*QC5$h8}E>_y_?HnGdUGfZw>TN}N0?b_$(=FXlm!=mm_#lN-7wTpgz$^4ua zb$WrjR=AUslMCyWGF`Kr8w~lOj10ZKy>F*>MencMJ7b2#p1!`mw8>cqtU-OjXkjU- zS<|Pt|9{;ruJ7gLb>_^OKY#zeEUC(NJR@`I%9WDu?_y<TW&eop^NrhI_xIJ+)p2{P zDl00syf`Z=E}nl`)T;QI&xJWxFIrBVGG&fcsaCcc7iZ&v7sV&$+t=@qYyYBAZg62i zBMU1lXMyUS#Ds(iGiQRXa@?aUAuMd1dTL7X^K)CxpU)OBGBWDv=>cup=aGE$_HArT zOiWyyoP@-R6)QXzU)0gjnP*?$_dY%<YSr4cb0<$$R#V$n|Nq~WD_2gPI<;xjrttM~ zvK9pk_TG&5dGZ`|gL#s~vcLatSbwfqwe5Sq{%4U%m0H1{8%#WmRE;LH@$PDsKD*1a z)O+zdw%+Y|%I313c^*Y6847}uLavK=W^$dhnSWpY_4TT^?`O_A8Sw2l|N7c%an)~2 z`-;ESecL?m`DY2qgR}jrtE)>(zdk<Rzjx1`3;t{N?2-AGbM?xV6@80Wt>R+P(9ubG zvP4%`*Wlp{pRYpQQ>RYt>gww2JNM(`WA}c!+~nlRvu2%ovs;>j4b(U8=<arRcYl9l zW3z+8j2SboT)UR^{o3{GvuDmc$g;-0U#|DShQ_+eZ*L-h@Mql3oUl+wPw$*x*3a|Y z=H}+#e*SoPxZTI|Xs@I(+tt5H6Fs(k;<0FIYh&xueE;_CQLD|23@l8JE-oo2C#kY9 zC0<(MdE!@k?~x-e8D++$A>d6ockaaa`Sm>wKKx#5_NOxD-g)!l;^O3VBa>vld3ty_ zOke-*UEZ|g2VaNt8$~$(e0aFs*4Eb9Lh{;#2@^PYcxt}95Hyopxp!~v^>wjTRaN=- z_sKr_`R2yP#hW)5Us(}&^5n_a*Vay+JbChzDJ2C39-f|oK|x9rJ!YHdA7fK_b7$w~ zRjazDPoM6gGS8wAw1Yb)CdSL_)PVyJetdi!a`>Bsx}o95<;&e8B4&7~%$hap)TvWk zTwJG4o}4*j#vH5CSHHf#-XQI@f7;ZkORe~Fmd{%1d-7m2`@-tke!Wa<PI*;V|32K# zFRl}Dpol}BU9w<zS9|;OGc%2^ua7@}=FA-9biS^<n(y!K-q~3k925k)8{_nJ{l`q= zA)WpG`F|LfI%zR1Si6>2ZxR!Oii!$bdD&qOTid-?SBEd2etUbqyF9n@n)v;4x$La0 zycsN_Ve0Sh*Z+5&BzX4h*}%ZSZ{NPnx2rvM_Uu!SNV~+u#1N4QjtP%MW}8`U+19>u z+v7Qw#Z}eSkt<$y99Gbe-{&%2zf5fR2DwzT+Dwa{1q&A%*8VEl5gQ+Wep#eh%Z!;b zpFVw>dut2lYo}8uPF%Qn(X#4`hBbe_PheCO*HMk~H#ZCu53#(_la!pP?B2(~(AC{N z-?o}fdVkGNAt@;?h8sHym4&t+JhY6T$?j)IR~Hu-*QJ+jb2zr_I%x7}+qP|W?`OCu zeY~@?m;rQ&12;GKroA>cHrK9Q+n#s#)alc|zrBsFuCA`D+t=UUzhFT^eEfaT^>xqB z$sW6R)4|Y#&tu1?3)ikKTfe^k|G&RCZbaDH?rm#hQ<}H~bZFAd!{<F591bj$54if} zXxJLblWj%}51yQyyh7;U@qYQ4RXnj<GA`cOnEbG;veGg}QA&Y_mlrhWwte>O*%Ni7 zVoVCk%J}&Bo;`bZ?ABkM3wQ3+{Qp;5U0t0fs<E)T!L+!=1#}J3k6*vyb{4Vz$+w-J z2x<T_Fr?gavygdmX{q;%Pe-pbYfDzVIQpPj>?VWp&8Wiyfq@sjr|a3){t{uk;$&rQ z%`anNQ2i}u&8v$C8lCoRu=>m4?(V)rrmL&arurMtv3AgrMGDUruUyH=06G?BN1^gs zr!*NAef|8eudafw;C%HeLNegUkB^TRFJ3%%?%d_Rv(2i$WH>q=Tp7Gv&ay~FQ<Ia; z6Lfcy2UD6r^LDeWjEouc=H+E)zkculbl=drb^jh5Z0;0R=exyH=f=dubm`Ki2iv;4 zxtE5sMYol=NT2%j=~FwOtP|rYb-y_gd#g-uKDO4~v2$nr@3-blW(Z11c!YiE++0yr zwMhHH{rdlVYk!x;#l<l-D(LIy=j6P3^e8DI!C_g#0i6?X{{8z~T~$?8R%T}HxahCb zOPjL7!ou=$`5nfWo}Zg*oquo7!{^VRKYpzIySr9XN2lidyI4<8&r^9@_*fXeeEH(= z@?E^dG;O!Uoi#s=q8{Jdn9N@LT~zkNh3<pL@7<G&<zKvL(TgW_e?=rDB%VBbCTCIb z;LXj=C!U--dv>mM`MY(oyZK}+4t$zESukK5o7rbMMK!f$Yu3cX#r5r4ct>>2KP&c& zmoFC=6m<0T?5X<tYHRlOEnBvH`SOK_m-p<MGaJm<L=LetT&OzL+NE*m(4mqfyYBAp zvuDmoNlS~0h&+1ycxKmzxyI?|`ed#9WUa#j11HX#2ReJ^$HqCS0nHp&uUrA$5U?)h z=GCjBVq#{8c&*($JUkrsXRYGgyklVxXwy9-BjbmAcW&O?nRu9O%Z#h~udl8B{PnBq zL=W(`Hggjbo|p@Nx<s`>L!a_bPoFxaq^w+8S~}mh`q|5uGp9@m2@Pcxe;l!R*Dfn- z>)$UfE^caSdiU<#h9jH|4F`YAta;t|^`YURJ(Zt9W0E^|xVX5On3%}O$OIf?v(bO) zevrc`!TR|7gS@=F>*M#od-v|$t5;6-E7xsFOG`6)`g7XEiH(li=gfYifBN)v{fo17 ztha70=>PO!@!GY$Tq;wiPTgDgcUF+Xo99j*9xtw}3_d+gx45+Q?D_NUy|%Wtb#-+U zCr)H!WMocnKX@a?(a|wVQTW64MiH%q73&%r80_u;Gx)v)?W;LAXJ^gNqR7ZdKM**# z)O$J$3rkE)OrN|x-}SV>UiD3Hj97A}Pnoi$blRp(n_9WWuU)y~;pwTWrgq7TYf1Cg ztzS1LA7@|y)eD+!Mhp%<K00b@%a$)spP~MXM|hrHZP&iV+F@%JY?-=oq4*7>dCJ0Z zara(dU;p~`>%+H;gMu!doo#-7ZS?UI8?&;q+}zw&sDL)q9$;nt`1y1Gtu2}V|NZ^& z^ycRD+}zy6o3D)1&v9^a2F5N-ZgKCITgb5?aIxFj8HS+a?TqUWoZyIxiUKVp{`yKZ z_&F~R&l1&0=IiG&o}O{Mu2U3S&~<jUxx8J?j5QNolqSwR*Ul&V>-+us%a<-yR95cZ zwQIxfjK6ye9v+&a87w0!Yia%PA)AT94H*?XJ2~5v*4EaWYp%PuZCbEEfouIC$$%X@ zcI4#d27a8#&M)`pO`B25xjCSdEcWf&cf4QT-Tz!`Yb&>y&W&BAuQw(ix2V%)vVZfS zua7Tk8vh=iMeUwDF5bL(^Xk>3C)T(1Ep7}`Qj?dz|L!j6GPS?|{{Ega1+>9IX<|fp zc=^vyPv^{;Bd#A;P+Y97t<BBJxpn*Y%Rx=QzPuD>Xt*|Y|E5isjvQICXHSfekB**R z-_|LjPu4m43pyXZu=2{oFZ^NWH?3LIvx#|I-EGi@h5EKF5<ar2MrJuT8vJxMH9c1^ zQdGUc9~>QB?V$ei>+A3gN2e=Su7I}vUAhG7d6%VBB_0i&F?+`3$<23<F6cAj`TFqr z^YtrNCO+x;J8epKc6RgkwojjmPVF_=fA9YN`PUvYFImj9v-GuC!m>>woYw>YF)CY~ z0`1KC3)+Y@+cP~~eG~JPsZ%4RS=w2JgoG~a&wqAirn9Xq-{)Unvt?yvjfy>decyh3 zd|buvHE3n#;f441R65_>zj7sKo1Tnajl{M)d#lC2rEj>hZskf%hJ>rDLZ|GW@P3Yu z`^-t_&Ye3u+x+mk!otE0f>wp<J~JF{%Covmj8#=IH8nNZ5Ec<JV{TQoM*NHW_5V{V z1+Gb`$fnAC<K*Kz_rGz^9-CD5JBxizb_%OsNDqyVKVPx%xT^QG1su9Nq)Zh)GczzS zoDKW(>GARYbLY+p3JU7#>b`jqd*Z|i&_-Zo<>XsiGNYoRbb1zm#{=$Pz54XwLr2r; zI~OlzPAf3(m@q-$%7u=O4u#uSZ`|Nm-@M_-$p_53I}cv(l2W|0zg|9e-wL-rL#7(H zrrQ;FjwU2!WOS@+zj^cKeEWL2TR#iLe0|SO*N^}8^Jn7X$@X=BGP1H>y?b{q@~VeO zyPIi>wvCNUT;|uWUlY40P82NBJD~ovt}$rd`+IvA-#NM?NXy>-KWJsu_WS$m`OO#1 z$P5e=Of|i>VS~Yr#PoFcZL9a~yLaWvm%qQiGchp*1_r*U-RR)q!EtAXX|~wu<_-LL z8vNO>UcFjX=V1C;IDGGdlrJwXM$|sqUH;x^#Y}x2ogUr#F5MsI0y+2h$?i@`%gB(B zk<n3BPR`3acW%SYn>X|C?@K*?YhUH(Wjl8K`1||&+O=ypir;_t?%nV2?_a-oVG!MX z;PPc*hJqIt7OMNt18p8)OHY6P>{(iE?casFIQ-_>fTqLEr?7YE$M0KkTD;S3Le>5U zw+ftD<>lqWPHJ0ORdJ{r3w-$W>D|rE>gww3(to^^Ca#O!?H0y9+czX6Bw^w*<MeYP z;^N8WSFc``wW$zbSg>$mVoC~&bZSb<0~WpbeLZ{Aluj^dB{VL-zCM0(XT_8$Qw}Jt zOsV<xrE`@qORDgq7f(uGUpqMUeC+PBME=Z-jE04Wt*op*yy;lBOs%;1^PfL8adG!H zY%qB7b{W6!--8DZI8=yVyLN4!S#H$+$2>9>3qEW(cKNdK%D5?0rz&z9vs??1o0<A^ z1!%}wTU&d@Gj4{0;$mYXqogN}hmIb7`s(WH0?=Wm%l+m$x&F2|_vGYcas4<MX=(2G zdwVKBgANnkmXl~)Gv7gB!%Ds7>(<SiGbblMUtSsnR<ByMYRQr-*RF+ygj~6E=gjfr z$8+8uetLTP@@32B+1J-SJk$!=xsjQfdGh4R%>4ZD;9%$1lRDV><rMC-)!4=CsWAK# zVU~aI%)Ub=P97c}p`lX`o(j^raQCjPj7-h{e}CV+0pB9?;lqj3r_awaJ$(=a7^Q11 zs=j2b`S*B5;9}4+%m)uX9G9<Ou;ApMKYw0cUheJX6%_j8``53nQ$K8A@(EuT<LT#j z?&QgpD_3Ub=1!e9?b_9=cXyR)?^5O0kT%P4;8)b~)(lSjWNK!{x1!~=e%po(2B*7w z&CJXKE{HP-$ja_5eC&4aZ1xU=%u7q2pP&E#@Avz^zrA(tOIdS)%iW^-+nYBxH_x6u zJMpMMadEMo-M;7N=T}!(PoF-$xUdkkYJ0V8;jb?*)&1v%goZ{&MdjV!SNrtT)K#li z+1lE^zrVk~v$OKso15L@`T@_Mo}T{x-rn77*Pgw0?c0}^mv`;j_4e&sPEO8!`}S?x zwCP4qy@|y{kC?qxTUV}p`Q+r}qeqUsdi^>$C@A5=i5C|ai|fbnRG&I|^6l;I`S<tL za&mHByLPSm`@7Jvu)5FB&d#%~UbT94Wo700IhM_(-@knM5)mEkT=+{jdfSN;C-&6* zob>7Pg=^Q2-HSMT=FF9=S95c7w{G2<C^&i2q)8h#Y}m2Gf}x?I;lNVw={NrIZ#b^d zUi-knH7ZK#BYSRc?iAUW*w~BvjnvfAKBRkeEA|Aog@lGCrfRP^mLJIa`-zm4l+}XC zN2ckvK6=Z;w)Enn-Mgd1!meGpGNnko+3-|HUtizp>H4#0&OCYY<j0R6JG;6-Cji~t zynM+LmfLIZOQf<)^w_d_^Wna-Cnp3~EnL0AL`1c&uI|hELm@pzoi3X;Y`AdYLc#{) z&Y2QVx98tidKt#b${HJcxAOC|g_|6;w6s90^5#A~Zjpa)59r3K88dq1?d9&(E%TrM z@9*#O8|wP{{0s`3nx0=v0?wSidR6o`NBh(X6F?_~fsVO6Tl?yY=C8@mJ$j7J2sD+H zmw&%KUu%2*eK+;LkNfRC{#Rc-{BpYQY_pBuPEPh%($>cI<&K_$!i8J6PJIeEJb%(8 zp;cFHZ}bPY9XWC&fY;&C<Hx%TAG1BR09|gbn(t*<;o!iK>NS7WDy=;k4gY%s-8P<> z($Uqmh#}P@K3?AJE&IXq=jA0NJj{5#7SGX=x2riI=XzXPRax2DY`!D2^x_`rQ-^0= zx^$^YzNEA?u<XDo<vkT2lfV&SKE+cdC?G)KoBH*9Gv+5>US4Kk;N<LldwaXMxH$g{ zp5MQIZOAjTw6rv1R}t!DXjrg7;Z*DysZ;ivXQWQu-j@6L>C?Aw-lS}>Z*D8{^yG{= z7_q0KFfUK9EWEJr<GR@0Ev>DWFI@^ye){>?=jZ1aDhoue){!_>{_f7tA0L&gZeF`q z_Tqvf0|P%l|J5f6Ebcs@i_aFF4$sfe-~V@I5$FV>pdT|@q)+)A>|5xgG;u>W|HO$C z-|(3JdHMSF?y9d^H!ra-=Sdan42g_<`SdBPd}M1&i;0QJox6AM@2~gIUUBZ+xzp42 z^<#DfL`BW&@Avof%e%HFa(DTAwcSe`Km(3v&#J1Zu&}Uz%8Ivd?_Rjja71at_WieR zMKLvQsQX)$o11HCX&Dz6XJ}|BD?4{@_4lx_u%Nk~B_&&?PMx}DjgGzj{~H^V>wmx9 z4mvlYEct8zXtU}(+iD&jo{-Q`R~MHbKYymCri$xEm3(^>Ic;*oO?LAs^-{k=_a0zl zc>4S~|398gLm7}KSc(M|f`Wp0d3pcJeR_I&`u~5w*Dqh5F0kzT-{0k%zLuM@$LuUx z+R@=rTx|SQ?@qw7Q}^rt^D<~?X(fRo;8PPTHv_}z>H5o46~x5F!$U$AOb?O$Y_PB3 z(UHy%s@!cxp$}$xMnp)=wL9$o_0f6z|1QleEG!cWoM(F8Fq!iA@8637JvF=Q7`3#t z0)ELR+zUAN)AWGUsfwOY8`8eNyX)aTGwUyRPK>hLVxN;+vq2kZ!{X!lK^J02Z%#XV zxSjvy^z9R-O-qYBHY4)T;lsOk?Q-jvyL<KO)qD5OrM-N!Y}v9ED?n|kX`*ay3R+rP z3=RJC?Gm-`Y&v;*y8e9odOZz|ikFvEqfA#{xq9`tEPL9?1w7JzjN(@8>*n!2dT_A$ z#9<cY?udrEtrIzg)gENoPpSF+?d{=qei_MAw{G1!eVRMZ-Q9h%$CCNGT;!ECG&Sd1 z7PslR^QNmbOP@L{Ra{)GQr0;2u4@8s>Yp~p&N*{p0s<y%-D+CnsUoyKW~Wh2P*B3n zR}&^oxVk$0{9NnlLDwg12Hec@^UYUT03LnQ)qVP4hCz49$45ssD(9FcoRVVCHvanc z>)joN%GTD^mzH?4v9ZmOnkIJb+BJ<}xv>9fjK99FK0n{SU#P!805WVNBboK+NT-yP zRKRM`q0XQK=(Ml2HXHwV@TuUil(6vRK>HOdRxEhaz>_K`H?L7c^9y%EYU<Xsvr=C! zoj%>Y>fVzF!Etf(rcBwAb#>Lcb?a8G(t4v3$vo?B&xwx@6c)HkivD`Wu|9r38^ea5 zH<;@r?yL#ja_G_9x4BtaR`vgCSXo<rXPe!;b<3{q&y3X5Qw!71&+FBTH&AI~EvVd~ z$Zjchp#QxJ-?<K^?;8Bs*Vaa}e-&qB2+7{A<Q(ba^JaRyVDijvaece`e=>TB+qZ6A z8?)2M)>d}4vAxQjeYMil)_(i`J$-qO>5@f@lr%LnkN3%Lm?F#9+|kj|(cN9VZ3fen z{KX~pyLRkI&@J-z_Fm;wts(@v5X{lhF??N2qx(k7`ETCjgebek#KgF|rWQ06-?@8t zqWz52nXbPUid!@{Or3glXYum~W)nSHcI~&YP-edqAa^ZbYN}&NS=pgE(hckymtA9G zY%DBpoH)U8Y~!BF&uQ1!#Ts;Hb8zzUf!0m!+Eta3vSjXD*{ke|#~jnlmoGCf3J3{V zvuc%8i}2EA%N9)vf6#6$&As&VjiX18-n~2b?B_!_zQ4b}|M~Oh@|HzU{`~yxKhLJ} z>#M5=GFzOt<=s8?PvJy>n23mosAy`vgG`#Cfq{UaV58LGnKNg`@2fc&``bd9%jQc` zL~>%H;#X#`<>F<g!sb(6E?&RB-*^80ijPXEu1^oDXluW|ySx0{xpNaIPF%Zo?HlPG zEKG^NzPy|@YnIH+dmT=Wj*Xk!HZ57A^5bFnv7m?uj?XJMPD+nh(6?#j%9V5H-aR+h zIyfk3!>1Jm%`S_N$mT5HyhcS&&#wO8pBXb|SQbBf^7Lu!?y}yW<;P?`KR@q2UH_Vs z{;plSE-aFK=O+ic+*DjQie>xnU%wJ0Ib`NcWPTSYckRW?mkZO&Pi-m8QIntj@5ICd z3!U2^J$hthWc1+l*6i!AUcBg$HqY}=b5;<jsHg~A8?`m>u2p1Yq_MGanOk$(_mh*= zElu|dw)XV+<mKtHvtPM%sjIW|<e4)m-wqr&aKLl2+QnPkmn#E58W=@IMKLh^Rnz+L z>62dkJ{x6a(Ak*Ea`9FYQc_tN84EsdGB|X7ef-5{#&<_$oOE<`-{0TQzrE8%X<t#R z@uCB~PZN9l`}ybYjZo*!)A-J9ZvMUg|KH`{BZe6mGIDcEOG;`!K04~4?+|?H^5x>^ z=VXP2g@ro5JUJ=cH)U;Gn)&iHi$xPAPF%Tu{rk6XpN3RyU%K?^#^mF2wpAhV@%(#F z%P>x3d-3MYniVS?ME?l{@bzq(E41U+m3#Njt?azWbX753(#D~3MnYz0=Qd#>A&Vc$ z2M<dAd$3Ua@T}6;*E0Vv*tKg`j=o&gL7n8|eS0fEt3}N_ck0xqFJEN-WF9$swD#wx zqc?9_s;Dg4zJ0pycOQR$|BQ?kot>SF7Ab*UHf`Fw*RPxV{bd~eL16vj#mSeK`FhCr z*0!{?q@AC~%dntFDk~?arSU{tTif40VQFb@h8aewT+7wmxZT~|OMW_u9-7g#&rIgr zf`uzqSU?AW`>#DMC~#xivCw9Xps487AB?F_neHq;!(?V=W?lZyMSp$F&Z5`X);i8( zZggN^u&Mj=<H^a%lhysxg57oO?DlQjCKk_=DjAVI>)@l+JVGKODtdZyVq$5zxqqLX zm4361U*689@>9#!#D~RqK{xj)o0ynbSX-N$o4<bjI@vU@)8*0D?CTpCqrQIqYF+-W zr=#P+<Hyn4^JKG&6J!bt3$3Q>yB)ASb(nok+*i=4ods$Z7R@|zRfcO8u0PWteQM3a z=g+6loA>X}&*C4K4j)eb_2uQQTeq5vzdd{QY{w1@M#e_#@^>YrrHO0Ht|<s`C@CrN z@$t#byvM;3_9`<c=gET^Qc_Z&8zl=0I(k7MG$cgEJWobnKfkoJw6M^y(?!ZW@6M^I z+B0R#etvqoa^=eB=jX@oD0tYyDI5|K5)~CyR8(~9_U)@zt~50_Z=Ab&)v7yp@0#b` zS+Q=No}yx4czAeV;KjRlbrlo@1OyBc53!uv8~cidjct=9linhmnut9WA6Eu1fADRw zdq1DNoz0fd2M<b`%&0z9URL((5eP(WN;%2k%fa5gef##e{2RXWaFm$}i~A+X#Kqma za3MfN_4d7c`%+E{MX=@y2#AXQ{q{B*bP>wh=$$)vhOdu1doH8mX4W@J4rfWw67?@l zO-<6~c@fdklRcKKS+nNMnKSq9-Ag?^t+k~^L{wC<vt*e?`8yeKb2>XKtLoR6%(%FF zQ#6D7WGpAm{in8E9kv!=uJ5#&pwl_y<Kvl`o4<Ygc71*P^($9;<n2M-iJd!l=G48G zNI5-C_r)hyZT5+FJKQUOELgw3U(aaf%$YJgp@$AiJ~Mb2n3tK^+0)Z<+tAREmzOvB zSkJ>NHhcE$@tbdFTl(tCpFcHzy+64FqN1V#0vNXM-o5+ew?}fR(*gqmIyyTIO-)<p zXJuxt+`6^2rpAW-H0$N%+qavms2n+QqGRG4ZL2%G%h&JSE8D+2fxY5)`q@c~`Q_#3 zuLgna{kAiWF6)BM71#OocG;38TR@$n-`@=FjM6nVH5-@qGcv3>n)TT5)M56zy1L}# z$1h%FFgV1=$Jf;CnLPP&fIk;|x<SIB#WHeo_io)fb@uGp)2DA=y(%gtWmRzbP-9DL zYweH5rY5GFacdU#R8N_C^uPgzwJJ=Y^UA!o`rKleIAcacZ0y;(o39=`NML1Uy;d{X zLO^4snLyho4s|m#vDkePyB!`b6hF*WTwJ_GR$kt|#rWVsNuL=X<{zA+o}8S_$jGQP z5wynwbV$;TBLxf$55By-3_6eLwtK&vYVpIz3&nFb)&BnW=1tC&GvB}8ulJvA)>~)l z;^(L!(9+r}D=T|ba><@OHkz6*`|bZ7SUT%Q<32k>zSBQ`{Zd*h7qNWqO~DTrx)WLR zG#>Z(NPN`@4+#NHRxoTRdg`Tdeb&5raUmfl1;&RN89*nd9^+4NWYmdpmgL`?(7QT( z{kfUO?0fHQO65M?#nN;6(xsyR|7!iFu1&kIYkR2C=~irPth4RJ_}&j63QB$^y?rdZ z<wEzmb?dUPuZxU`(9qD};Na-!?mj=uH2eFzyBZ}dMpa)j^rRBLx}Dh7+0}J!j^*bU z7Z=}X-?(qzz9mbR<lf%qJKN0H0|YWoP0_q}@7|Q18i(0=d3i<H<0B(~etUa+;aq;c za}N%zSSTYSbLaZ?<LA%6fAJzCKcBzWAmC}2kh!_Jo}ON8Y%GI=qvOF{rLU(<nZm-t zGG)q?YwP3xzr3uT^Sqs3zNoNpu3fDZ!-sEgqu+czdh3?d?ajM)|9*9Kb+?%Aq^VP% zK7T%a+O)Vm6(8qXm#?^1lA6V*^MxHWt8wJmvAmm`T;1K>ot&Eb`}5DtFs!PoQumva z;A=8v(vl@h&dK-4T2IT-e;1f0@922�d`#0XBvg@7~4jD#?tDyxB0%L`kVBICXXU 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 diff --git a/RTCP/Cobalt/GPUProc/doc/cobalt-data-flow/cobalt-data-flow.eap b/RTCP/Cobalt/GPUProc/doc/cobalt-data-flow/cobalt-data-flow.eap index 167741ee5ed1163610fc0029a95869683ed2726e..2a67d0f69a127cc34f6f84e18e85aa4bb00b702f 100644 GIT binary patch delta 90891 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%0DhAv2J*&;S8%*fIg9V-aMxLJo&E0_USD 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 delta 16988 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 diff --git a/RTCP/Cobalt/GPUProc/doc/pipeline-buffers.txt b/RTCP/Cobalt/GPUProc/doc/pipeline-buffers.txt index be7f8a9a82b..28b06061862 100644 --- a/RTCP/Cobalt/GPUProc/doc/pipeline-buffers.txt +++ b/RTCP/Cobalt/GPUProc/doc/pipeline-buffers.txt @@ -68,7 +68,7 @@ FFT-shift {inplace} FFT-64 {inplace} | [station][pol][sample][channel] [48][2][3072][64] = 144 MiB B V -Delay compensation + Transpose {I/O: delays} +Delay compensation + Transpose (implicit, DO_TRANSPOSE not defined) {I/O: delays} | [station][pol][channel][sample] [48][2][64][3072] = 144 MiB A V FFT-shift {inplace} -- GitLab From 7f1c2d679c80f487e3dc1c47d57734eccec04538 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 12:59:07 +0000 Subject: [PATCH 671/933] Task #9127: '#error' msg fix in Beamformer GPU kernel --- RTCP/Cobalt/GPUProc/share/gpu/kernels/BeamFormer.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/GPUProc/share/gpu/kernels/BeamFormer.cu b/RTCP/Cobalt/GPUProc/share/gpu/kernels/BeamFormer.cu index cb9a3a12e7e..a293c15b113 100644 --- a/RTCP/Cobalt/GPUProc/share/gpu/kernels/BeamFormer.cu +++ b/RTCP/Cobalt/GPUProc/share/gpu/kernels/BeamFormer.cu @@ -29,7 +29,7 @@ #define NR_STATIONS_PER_PASS ((NR_STATIONS + NR_PASSES - 1) / NR_PASSES) #endif #if NR_STATIONS_PER_PASS > 32 -#error "need more passes to beam for this number of stations" +#error "need more passes to beamform this number of stations" #endif #ifdef FLYS_EYE -- GitLab From a0c016a602cab3112c68e2e2e0ffbfa80af565b3 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 13:23:19 +0000 Subject: [PATCH 672/933] Task #9127: update DRAGNET variants file --- CMake/variants/variants.dragnet | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/CMake/variants/variants.dragnet b/CMake/variants/variants.dragnet index 1bcded7ad77..de81cc7cd9a 100644 --- a/CMake/variants/variants.dragnet +++ b/CMake/variants/variants.dragnet @@ -5,26 +5,32 @@ option(USE_MPI "Use MPI" ON) option(USE_CUDA "Use CUDA" ON) -# Specify versions, such that ABI incompat updates of these don't break already installed LOFAR binaries. Matters when we have to roll-back. -set(LOG4CPLUS_ROOT_DIR /opt/log4cplus-1.1.2) +# Default search path for LOFAR deps (see CMake/variants/variants): /opt/lofar/external:/usr/local +# The dirs thereunder must be lower case, e.g. unittest++/ or dal/ + +# For the RPMs to make it work under CentOS 7, see https://support.astron.nl/lofar_issuetracker/issues/8161 + +# Specify versioned paths, such that ABI incompat updates of these don't break already installed LOFAR binaries. Matters when we have to roll-back. +set(LOG4CPLUS_ROOT_DIR /opt/log4cplus-1.1.2) # RHEL/CentOS 7 has log4cxx in the repo, but LOFAR log4cxx is dodgy, so install log4cplus from CentOS 6 rpm pkgs. set(BLITZ_ROOT_DIR /opt/blitz-0.10) -set(CUDA_TOOLKIT_ROOT_DIR /opt/cuda-7.0) -set(CASACORE_ROOT_DIR /opt/casacore-2.0.3) -set(CASAREST_ROOT_DIR /opt/casarest) # pkg has no releases +set(CUDA_TOOLKIT_ROOT_DIR /opt/cuda-7.5) # libcuda.so on CentOS 7 w/ CUDA driver from ElRepo resides under /usr/lib64/nvidia/ +set(CASACORE_ROOT_DIR /opt/casacore-2.1.0) +set(CASAREST_ROOT_DIR /opt/casarest-1.4.1) set(CASA_ROOT_DIR /opt/casasynthesis) # for awimager2; pkg has no releases; it's a chunk of CASA, so var name is misleading, since it'll fail on the real CASA root dir set(DAL_ROOT_DIR /opt/lofardal-2.5.0) +set(AOFLAGGER_ROOT_DIR /opt/aoflagger-2.8.0) -# Avoid using unnecessary custom installed packages in /usr/local (NFS). -# This may clash with libs other deps (e.g. casacore) linked to, and deps on NFS (possible latency spike for COBALT). +# Force using /usr libs over custom (redundant) libs in /usr/local (NFS). +# They may clash with libs that other deps (e.g. casacore) linked to, and are on NFS (may cause latency spikes in COBALT). set(FFTW3_ROOT_DIR /usr) set(CFITSIO_ROOT_DIR /usr) set(GSL_ROOT_DIR /usr) # RHEL/CentOS 7 has openmpi in /usr/lib64/openmpi and mpich in /usr/lib64/mpich -set(MPI_ROOT_DIR /usr/lib64/openmpi) +set(MPI_ROOT_DIR /usr/lib64/openmpi) -# By default and on RHEL/CentOS 7, the GCC linker does not opt out overlinking. -# Make it so. It removes some ghost deps, but still leaves mysterious lib deps in place... +# By default and on RHEL/CentOS 7, the GCC linker does not opt out overlinking. To find overlinking: ldd -u -r <binary> +# Avoid overlinking with this (option must go before the libs). Still, some (e.g. mpi libs) are apparently "needed"(?) set(GNU_EXE_LINKER_FLAGS "-Wl,--as-needed") set(GNU_SHARED_LINKER_FLAGS "-Wl,--as-needed") set(CLANG_EXE_LINKER_FLAGS "-Wl,--as-needed") -- GitLab From 7da518c928d1382fe371ecf48d4ad24929670ca9 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 13:24:02 +0000 Subject: [PATCH 673/933] Task #9127: update dop256 variants file --- CMake/variants/variants.dop256 | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/CMake/variants/variants.dop256 b/CMake/variants/variants.dop256 index 957ff6a3497..6d8e7a91e43 100644 --- a/CMake/variants/variants.dop256 +++ b/CMake/variants/variants.dop256 @@ -11,26 +11,7 @@ option(USE_CUDA "Use CUDA" ON) # Default search path for LOFAR deps (see CMake/variants/variants): /opt/lofar/external:/usr/local # The dirs thereunder must be lower case, e.g. unittest++/ or dal/ -# For the RPMs to make it work under CentOS 7, see https://support.astron.nl/lofar_issuetracker/issues/8161 - -# RHEL/CentOS 7 has log4cxx in the repo, but LOFAR log4cxx is dodgy, so install log4cplus from CentOS 6 rpm pkgs. -#option(USE_LOG4CPLUS "Use log4cplus" OFF) -#option(USE_LOG4CXX "Use log4cxx" ON) - -# RHEL/CentOS 7: blitz/blitz-devel N/A, so installed from two CentOS 6 rpm pkgs. (Idem for UnitTest++, but from Fedora 22 pkgs) -# blitz includes end up in /usr/include, but an arch specific one under /usr/lib64/blitz/include. -# Set the latter as INCLUDE_DIR, since /usr/include is already in the std search path. -set(BLITZ_INCLUDE_DIR /usr/lib64/blitz/include CACHE PATH "blitz include path") - -# RHEL/CentOS 7 has openmpi in /usr/lib64/openmpi and mpich in /usr/lib64/mpich -set(MPI_ROOT_DIR /usr/lib64/openmpi) - -# By default and on RHEL/CentOS 7, the GCC linker does not opt out overlinking. -# Make it so. It removes some ghost deps, but still leaves mysterious lib deps in place... -set(GNU_EXE_LINKER_FLAGS "-Wl,--as-needed") -set(GNU_SHARED_LINKER_FLAGS "-Wl,--as-needed") -set(CLANG_EXE_LINKER_FLAGS "-Wl,--as-needed") -set(CLANG_SHARED_LINKER_FLAGS "-Wl,--as-needed") +set(QPID_ROOT_DIR /opt/qpid) # Enable ccache symlinks to accelerate recompilation (/usr/bin/ccache). #set(GNU_C /usr/lib64/ccache/gcc) -- GitLab From d5a601c50805fbfdb54ba5cd9ad3036276dfee9f Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 13:25:44 +0000 Subject: [PATCH 674/933] Task #9127: update compiler default cmake files: uniformize OPTARCH, add -stdlib=libc++ to CLANG (acked-by Tammo Jan Dijkema) --- CMake/variants/CLANG.cmake | 10 ++++++++-- CMake/variants/GNU.cmake | 2 +- CMake/variants/GNUCXX11.cmake | 2 +- CMake/variants/GNU_JUROPA.cmake | 8 +++++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CMake/variants/CLANG.cmake b/CMake/variants/CLANG.cmake index 84a397c478f..4a4a3bc1976 100644 --- a/CMake/variants/CLANG.cmake +++ b/CMake/variants/CLANG.cmake @@ -7,7 +7,7 @@ set(LOFAR_COMPILER_SUITES CLANG) # Build variants -set(LOFAR_BUILD_VARIANTS DEBUG OPT OPT3) +set(LOFAR_BUILD_VARIANTS DEBUG OPT OPT3 OPTARCH) # CLANG compiler suite set(CLANG_COMPILERS CLANG_C CLANG_CXX CLANG_Fortran CLANG_ASM) @@ -20,21 +20,27 @@ set(CLANG_C_FLAGS "-W -Wall -Wno-unknown-pragmas") set(CLANG_C_FLAGS_DEBUG "-g") set(CLANG_C_FLAGS_OPT "-g -O2") set(CLANG_C_FLAGS_OPT3 "-g -O3") -set(CLANG_CXX_FLAGS "-W -Wall -Woverloaded-virtual -Wno-unknown-pragmas") +set(CLANG_C_FLAGS_OPTARCH "-g -O3 -march=native") +set(CLANG_CXX_FLAGS "-W -Wall -Woverloaded-virtual -Wno-unknown-pragmas -stdlib=libc++") set(CLANG_CXX_FLAGS_DEBUG "-g") set(CLANG_CXX_FLAGS_OPT "-g -O2") set(CLANG_CXX_FLAGS_OPT3 "-g -O3") +set(CLANG_CXX_FLAGS_OPTARCH "-g -O3 -march=native") set(CLANG_EXE_LINKER_FLAGS) set(CLANG_EXE_LINKER_FLAGS_DEBUG) set(CLANG_EXE_LINKER_FLAGS_OPT) set(CLANG_EXE_LINKER_FLAGS_OPT3) +set(CLANG_EXE_LINKER_FLAGS_OPTARCH) set(CLANG_SHARED_LINKER_FLAGS) set(CLANG_SHARED_LINKER_FLAGS_DEBUG) set(CLANG_SHARED_LINKER_FLAGS_OPT) set(CLANG_SHARED_LINKER_FLAGS_OPT3) +set(CLANG_SHARED_LINKER_FLAGS_OPTARCH) set(CLANG_COMPILE_DEFINITIONS) set(CLANG_COMPILE_DEFINITIONS_DEBUG "-DLOFAR_DEBUG -DENABLE_DBGASSERT -DENABLE_TRACER") set(CLANG_COMPILE_DEFINITIONS_OPT) set(CLANG_COMPILE_DEFINITIONS_OPT3 "-DNDEBUG -DDISABLE_DEBUG_OUTPUT") +set(CLANG_COMPILE_DEFINITIONS_OPTARCH + "-DNDEBUG -DDISABLE_DEBUG_OUTPUT") diff --git a/CMake/variants/GNU.cmake b/CMake/variants/GNU.cmake index 2b68de7493f..0077dcd50ac 100644 --- a/CMake/variants/GNU.cmake +++ b/CMake/variants/GNU.cmake @@ -35,7 +35,7 @@ set(GNU_SHARED_LINKER_FLAGS) set(GNU_SHARED_LINKER_FLAGS_DEBUG) set(GNU_SHARED_LINKER_FLAGS_OPT) set(GNU_SHARED_LINKER_FLAGS_OPT3) -set(GNU_SHARED_LINKER_FLAGS_OPTARCH3) +set(GNU_SHARED_LINKER_FLAGS_OPTARCH) set(GNU_COMPILE_DEFINITIONS) set(GNU_COMPILE_DEFINITIONS_DEBUG "-DLOFAR_DEBUG -DENABLE_DBGASSERT -DENABLE_TRACER") diff --git a/CMake/variants/GNUCXX11.cmake b/CMake/variants/GNUCXX11.cmake index a87d417a256..66f112039ee 100644 --- a/CMake/variants/GNUCXX11.cmake +++ b/CMake/variants/GNUCXX11.cmake @@ -35,7 +35,7 @@ set(GNUCXX11_SHARED_LINKER_FLAGS) set(GNUCXX11_SHARED_LINKER_FLAGS_DEBUG) set(GNUCXX11_SHARED_LINKER_FLAGS_OPT) set(GNUCXX11_SHARED_LINKER_FLAGS_OPT3) -set(GNUCXX11_SHARED_LINKER_FLAGS_OPTARCH3) +set(GNUCXX11_SHARED_LINKER_FLAGS_OPTARCH) set(GNUCXX11_COMPILE_DEFINITIONS) set(GNUCXX11_COMPILE_DEFINITIONS_DEBUG "-DLOFAR_DEBUG -DENABLE_DBGASSERT -DENABLE_TRACER") diff --git a/CMake/variants/GNU_JUROPA.cmake b/CMake/variants/GNU_JUROPA.cmake index da072fe0f10..de3b884dea5 100644 --- a/CMake/variants/GNU_JUROPA.cmake +++ b/CMake/variants/GNU_JUROPA.cmake @@ -7,7 +7,7 @@ set(LOFAR_COMPILER_SUITES GNU_JUROPA) # Build variants -set(LOFAR_BUILD_VARIANTS DEBUG OPT OPT3) +set(LOFAR_BUILD_VARIANTS DEBUG OPT OPT3 OPTARCH) # GNU compiler suite set(GNU_JUROPA_COMPILERS GNU_C GNU_CXX GNU_Fortran GNU_ASM) @@ -20,21 +20,27 @@ set(GNU_C_FLAGS "-W -Wall -Wno-unknown-pragmas") set(GNU_C_FLAGS_DEBUG "-g") set(GNU_C_FLAGS_OPT "-g -O2") set(GNU_C_FLAGS_OPT3 "-g -O3") +set(GNU_C_FLAGS_OPTARCH "-g -O3 -march=native") set(GNU_CXX_FLAGS "-W -Wall -Woverloaded-virtual -Wno-unknown-pragmas") set(GNU_CXX_FLAGS_DEBUG "-g") set(GNU_CXX_FLAGS_OPT "-g -O2") set(GNU_CXX_FLAGS_OPT3 "-g -O3") +set(GNU_CXX_FLAGS_OPTARCH "-g -O3 -march=native") set(GNU_EXE_LINKER_FLAGS) set(GNU_EXE_LINKER_FLAGS_DEBUG) set(GNU_EXE_LINKER_FLAGS_OPT) set(GNU_EXE_LINKER_FLAGS_OPT3) +set(GNU_EXE_LINKER_FLAGS_OPTARCH) set(GNU_SHARED_LINKER_FLAGS) set(GNU_SHARED_LINKER_FLAGS_DEBUG) set(GNU_SHARED_LINKER_FLAGS_OPT) set(GNU_SHARED_LINKER_FLAGS_OPT3) +set(GNU_SHARED_LINKER_FLAGS_OPTARCH) set(GNU_COMPILE_DEFINITIONS) set(GNU_COMPILE_DEFINITIONS_DEBUG "-DLOFAR_DEBUG -DENABLE_DBGASSERT -DENABLE_TRACER") set(GNU_COMPILE_DEFINITIONS_OPT) set(GNU_COMPILE_DEFINITIONS_OPT3 "-DNDEBUG -DDISABLE_DEBUG_OUTPUT") +set(GNU_COMPILE_DEFINITIONS_OPTARCH + "-DNDEBUG -DDISABLE_DEBUG_OUTPUT") -- GitLab From 5fa6c031e27c4b82653f1168fe53e12fa77d4008 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 13:26:15 +0000 Subject: [PATCH 675/933] Task #9127: remove obsolete variant files --- .gitattributes | 5 ----- CMake/variants/variants.b7015 | 1 - CMake/variants/variants.node501 | 1 - CMake/variants/variants.node502 | 1 - CMake/variants/variants.node503 | 1 - CMake/variants/variants.node521 | 1 - 6 files changed, 10 deletions(-) delete mode 120000 CMake/variants/variants.b7015 delete mode 120000 CMake/variants/variants.node501 delete mode 120000 CMake/variants/variants.node502 delete mode 120000 CMake/variants/variants.node503 delete mode 120000 CMake/variants/variants.node521 diff --git a/.gitattributes b/.gitattributes index 3b0f7668512..cc8cc45e316 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2329,7 +2329,6 @@ CMake/testscripts/checkfloat -text CMake/testscripts/default.debug -text CMake/testscripts/timeout -text CMake/variants/variants.MacRenting -text -CMake/variants/variants.b7015 -text CMake/variants/variants.buildhostcentos7 -text CMake/variants/variants.cbt001 -text CMake/variants/variants.cbt002 -text @@ -2353,10 +2352,6 @@ CMake/variants/variants.lexar001 -text CMake/variants/variants.lexar002 -text CMake/variants/variants.lhn002 -text CMake/variants/variants.lotar -text -CMake/variants/variants.node501 -text -CMake/variants/variants.node502 -text -CMake/variants/variants.node503 -text -CMake/variants/variants.node521 -text CMake/variants/variants.phi -text CMake/variants/variants.sharkbay -text Docker/docker-build-all.sh -text diff --git a/CMake/variants/variants.b7015 b/CMake/variants/variants.b7015 deleted file mode 120000 index ce73d06cf96..00000000000 --- a/CMake/variants/variants.b7015 +++ /dev/null @@ -1 +0,0 @@ -variants.fs5 \ No newline at end of file diff --git a/CMake/variants/variants.node501 b/CMake/variants/variants.node501 deleted file mode 120000 index ce73d06cf96..00000000000 --- a/CMake/variants/variants.node501 +++ /dev/null @@ -1 +0,0 @@ -variants.fs5 \ No newline at end of file diff --git a/CMake/variants/variants.node502 b/CMake/variants/variants.node502 deleted file mode 120000 index ce73d06cf96..00000000000 --- a/CMake/variants/variants.node502 +++ /dev/null @@ -1 +0,0 @@ -variants.fs5 \ No newline at end of file diff --git a/CMake/variants/variants.node503 b/CMake/variants/variants.node503 deleted file mode 120000 index ce73d06cf96..00000000000 --- a/CMake/variants/variants.node503 +++ /dev/null @@ -1 +0,0 @@ -variants.fs5 \ No newline at end of file diff --git a/CMake/variants/variants.node521 b/CMake/variants/variants.node521 deleted file mode 120000 index ce73d06cf96..00000000000 --- a/CMake/variants/variants.node521 +++ /dev/null @@ -1 +0,0 @@ -variants.fs5 \ No newline at end of file -- GitLab From f15c47d2ac89c71bec2798019173fe2f1d01b11b Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 13:34:59 +0000 Subject: [PATCH 676/933] Task #9127: add SubSystems pkg: Dragnet. Update LofarPacakgeList.cmake (also removes already disabled AOFlagger entry from pkg list) --- CMake/LofarPackageList.cmake | 4 ++-- SubSystems/CMakeLists.txt | 1 + SubSystems/Dragnet/CMakeLists.txt | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 SubSystems/Dragnet/CMakeLists.txt diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index 88247a72744..d5db2f0d8fa 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -1,7 +1,7 @@ # - Create for each LOFAR package a variable containing the absolute path to # its source directory. # -# Generated by gen_LofarPackageList_cmake.sh at do 23 jun 2016 12:19:31 CEST +# Generated by gen_LofarPackageList_cmake.sh at Mon Aug 22 15:36:01 CEST 2016 # # ---- DO NOT EDIT ---- # @@ -37,7 +37,6 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(PythonDPPP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/PythonDPPP) set(DPPP_AOFlag_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/DPPP_AOFlag) set(SPW_Combine_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/SPWCombine) - set(AOFlagger_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/AOFlagger) set(LofarFT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Imager/LofarFT) set(AWImager2_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Imager/AWImager2) set(Laps-GRIDInterface_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/LAPS/GRIDInterface) @@ -175,4 +174,5 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(LAPS_CEP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SubSystems/LAPS_CEP) set(RAServices_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SubSystems/RAServices) set(DataManagement_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SubSystems/DataManagement) + set(Dragnet_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SubSystems/Dragnet) endif(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) diff --git a/SubSystems/CMakeLists.txt b/SubSystems/CMakeLists.txt index 98ec1dfc379..b654ea67287 100644 --- a/SubSystems/CMakeLists.txt +++ b/SubSystems/CMakeLists.txt @@ -14,3 +14,4 @@ lofar_add_package(PVSS_DB) lofar_add_package(LAPS_CEP) lofar_add_package(RAServices) lofar_add_package(DataManagement) +lofar_add_package(Dragnet) diff --git a/SubSystems/Dragnet/CMakeLists.txt b/SubSystems/Dragnet/CMakeLists.txt new file mode 100644 index 00000000000..f70557fc764 --- /dev/null +++ b/SubSystems/Dragnet/CMakeLists.txt @@ -0,0 +1,5 @@ +# $Id$ + +# Dragnet: LOFAR software install for the LOFAR DRAGNET cluster +lofar_package(Dragnet DEPENDS Online_Cobalt Offline ObservationStartListener) + -- GitLab From b9c0e025a6ecf484b504dfbbcef031288210fe8d Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 22 Aug 2016 14:25:35 +0000 Subject: [PATCH 677/933] Task #9127: ObservationStartListener: improve fmt of lofarlogger for shell scripts --- .../ObservationStartListener/src/lofarlogger.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/LCS/MessageDaemons/ObservationStartListener/src/lofarlogger.sh b/LCS/MessageDaemons/ObservationStartListener/src/lofarlogger.sh index 032accef1d6..cb1c9b595c4 100755 --- a/LCS/MessageDaemons/ObservationStartListener/src/lofarlogger.sh +++ b/LCS/MessageDaemons/ObservationStartListener/src/lofarlogger.sh @@ -1,6 +1,6 @@ # lofarlogger.sh # -# Copyright (C) 2015 +# Copyright (C) 2015-2016 # ASTRON (Netherlands Institute for Radio Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands # @@ -22,12 +22,17 @@ # Usage: source lofarlogger.sh # then e.g.: log INFO "foo bar" -# logs e.g.: 2015-10-16 16:00:46,186 INFO foo bar +# logs e.g.: 2015-10-16 16:00:46,186 INFO - foo bar log() { - loglevel=$1 # one of: DEBUG INFO WARNING ERROR CRITICAL + loglevel=$1 # one of: DEBUG INFO WARN ERROR FATAL message=$2 ts=`date --utc '+%F %T,%3N'` # e.g. 2015-10-16 16:00:46,186 - echo "$ts $loglevel $message" >&2 + echo "$ts $loglevel - $message" >&2 +} + +fatal() { + log $1 + exit 1 } -- GitLab From b41e250b63d80c56d569eecf8680d03bc87b46fe Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Tue, 23 Aug 2016 09:30:44 +0000 Subject: [PATCH 678/933] Task #9127: update r35149 to micro improve setcap_cobalt sudoers file --- RTCP/Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt b/RTCP/Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt index 97ab1655685..df16728cb6d 100644 --- a/RTCP/Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt +++ b/RTCP/Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt @@ -1,5 +1,5 @@ ## Allows lofarbuild to add the listed capabilities to any single writable file for automated roll-out. ## Attempts to disallow adding another set of capabilities. ## Does not attempt to disallow adding the listed capabilities to other files, which would be trivial to bypass. -Cmnd_Alias SETCAP_COBALT = /sbin/setcap cap_net_raw\,cap_sys_nice\,cap_ipc_lock+ep *, ! /sbin/setcap cap_net_raw\,cap_sys_nice\,cap_ipc_lock+ep * * +Cmnd_Alias SETCAP_COBALT = /sbin/setcap cap_net_raw\,cap_sys_nice\,cap_ipc_lock=ep *, ! /sbin/setcap cap_net_raw\,cap_sys_nice\,cap_ipc_lock=ep * * lofarbuild ALL = (root) NOPASSWD: SETCAP_COBALT -- GitLab From 5ac1b8102d64f43d8b05b4edbfac281731f318fc Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 23 Aug 2016 09:57:41 +0000 Subject: [PATCH 679/933] Task #9522: Added more logging --- RTCP/Cobalt/OutputProc/src/GPUProcIO.cc | 5 +++++ RTCP/Cobalt/OutputProc/src/SubbandWriter.cc | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc index b28d52d12b4..0b1ecbc0f94 100644 --- a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc +++ b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc @@ -163,6 +163,8 @@ bool process(Stream &controlStream) && file.location.host != "localhost") continue; + LOG_INFO_STR("starting with fileIdx " << fileIdx); + mdLogger.log(mdKeyPrefix + PN_COP_LOCUS_NODE + '[' + lexical_cast<string>(fileIdx) + ']', formatDataPointLocusName(myHostName)); @@ -172,7 +174,10 @@ bool process(Stream &controlStream) LOG_INFO_STR("Setting up writer for " << file.location.filename); SubbandWriter *writer = new SubbandWriter(parset, fileIdx, mdLogger, mdKeyPrefix, logPrefix); + LOG_INFO_STR("push_back()"); subbandWriters.push_back(writer); + + LOG_INFO_STR("done with fileIdx " << fileIdx); } } diff --git a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc index d2a1e12dda5..a986dc3095d 100644 --- a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc +++ b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc @@ -41,8 +41,14 @@ namespace LOFAR itsInputThread(parset, streamNr, itsOutputPool, logPrefix), itsOutputThread(parset, streamNr, itsOutputPool, mdLogger, mdKeyPrefix, logPrefix) { - for (unsigned i = 0; i < maxReceiveQueueSize; i++) - itsOutputPool.free.append(new CorrelatedData(parset.settings.correlator.stations.size(), parset.settings.correlator.nrChannels, parset.settings.correlator.nrSamplesPerIntegration(), heapAllocator, 512)); + for (unsigned i = 0; i < maxReceiveQueueSize; i++) { + LOG_INFO_STR("posix_memalign() " << i); + CorrelatedData *data = new CorrelatedData(parset.settings.correlator.stations.size(), parset.settings.correlator.nrChannels, parset.settings.correlator.nrSamplesPerIntegration(), heapAllocator, 512); + LOG_INFO_STR("append() " << i); + itsOutputPool.free.append(data); + LOG_INFO_STR("done adding " << i); + //itsOutputPool.free.append(new CorrelatedData(parset.settings.correlator.stations.size(), parset.settings.correlator.nrChannels, parset.settings.correlator.nrSamplesPerIntegration(), heapAllocator, 512)); + } } -- GitLab From fe5ff72c220981ce3f275960e98cede89124d2d0 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Tue, 23 Aug 2016 16:43:56 +0000 Subject: [PATCH 680/933] Task #9127: update r35159: adjust script (unused) --- RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh index 3d246001f8e..8c76ec8818b 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh @@ -45,14 +45,14 @@ for HOST in ${HOSTS:-cbm001 cbm002 cbm003 cbm004 cbm005 cbm006 cbm007 cbm008 cbm ln -sfT /localhome/lofarsystem/lofar/var var # Set capabilities so our soft real-time programs can elevate prios. + # Requires Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt to be in place. # - # cap_sys_nice: allow real-time priority for threads - # cap_ipc_lock: allow app to lock in memory (prevent swap) - # cap_net_raw: allow binding sockets to NICs - OUTPUTPROC_CAPABILITIES='cap_sys_nice,cap_ipc_lock' - sudo /sbin/setcap \"${OUTPUTPROC_CAPABILITIES}\"=ep bin/outputProc || true - RTCP_CAPABILITIES='cap_net_raw,cap_sys_nice,cap_ipc_lock' - sudo /sbin/setcap \"${RTCP_CAPABILITIES}\"=ep bin/rtcp || true + # cap_net_raw: allow binding sockets to NICs (in addition to IPs) + # cap_sys_nice: allow process to increase priority (but not to real-time class) + # cap_ipc_lock: allow process to lock in memory (prevent swap) + sudo /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep bin/rtcp || true + sudo /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep bin/outputProc || true + sudo /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep bin/TBB_Writer || true " || exit 1 done -- GitLab From 95364ce7d0a4753263231f2decf75d484e4a4067 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Tue, 23 Aug 2016 16:46:27 +0000 Subject: [PATCH 681/933] Task #9127: revert r35161 --- RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh index 8c76ec8818b..3d246001f8e 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh @@ -45,14 +45,14 @@ for HOST in ${HOSTS:-cbm001 cbm002 cbm003 cbm004 cbm005 cbm006 cbm007 cbm008 cbm ln -sfT /localhome/lofarsystem/lofar/var var # Set capabilities so our soft real-time programs can elevate prios. - # Requires Cobalt/OutputProc/etc/sudoers.d/setcap_cobalt to be in place. # - # cap_net_raw: allow binding sockets to NICs (in addition to IPs) - # cap_sys_nice: allow process to increase priority (but not to real-time class) - # cap_ipc_lock: allow process to lock in memory (prevent swap) - sudo /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep bin/rtcp || true - sudo /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep bin/outputProc || true - sudo /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep bin/TBB_Writer || true + # cap_sys_nice: allow real-time priority for threads + # cap_ipc_lock: allow app to lock in memory (prevent swap) + # cap_net_raw: allow binding sockets to NICs + OUTPUTPROC_CAPABILITIES='cap_sys_nice,cap_ipc_lock' + sudo /sbin/setcap \"${OUTPUTPROC_CAPABILITIES}\"=ep bin/outputProc || true + RTCP_CAPABILITIES='cap_net_raw,cap_sys_nice,cap_ipc_lock' + sudo /sbin/setcap \"${RTCP_CAPABILITIES}\"=ep bin/rtcp || true " || exit 1 done -- GitLab From 65e4150f3be251e92657b4163463361530036fb0 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Wed, 24 Aug 2016 09:33:24 +0000 Subject: [PATCH 682/933] Task #9127: add new Dragnet LOFAR and IERS install and activate scripts to SubSystems/Dragnet/scripts. Still some hard-coded version numbers. --- .gitattributes | 6 ++ .../Dragnet/scripts/LOFAR-Dragnet-activate.sh | 36 ++++++++ .../Dragnet/scripts/LOFAR-Dragnet-deploy.sh | 88 ++++++++++++++++++ .../casacore-measures-tables/CheckIERS.cc | 89 +++++++++++++++++++ .../scripts/casacore-measures-tables/Makefile | 36 ++++++++ .../apply_casacore_measures_data.sh | 40 +++++++++ .../casacore_measures_common.sh | 55 ++++++++++++ .../cron-update-IERS-DRAGNET.sh | 82 +++++++++++++++++ .../get_casacore_measures_data.sh | 84 +++++++++++++++++ 9 files changed, 516 insertions(+) create mode 100755 SubSystems/Dragnet/scripts/LOFAR-Dragnet-activate.sh create mode 100755 SubSystems/Dragnet/scripts/LOFAR-Dragnet-deploy.sh create mode 100644 SubSystems/Dragnet/scripts/casacore-measures-tables/CheckIERS.cc create mode 100644 SubSystems/Dragnet/scripts/casacore-measures-tables/Makefile create mode 100755 SubSystems/Dragnet/scripts/casacore-measures-tables/apply_casacore_measures_data.sh create mode 100755 SubSystems/Dragnet/scripts/casacore-measures-tables/casacore_measures_common.sh create mode 100755 SubSystems/Dragnet/scripts/casacore-measures-tables/cron-update-IERS-DRAGNET.sh create mode 100755 SubSystems/Dragnet/scripts/casacore-measures-tables/get_casacore_measures_data.sh diff --git a/.gitattributes b/.gitattributes index cc8cc45e316..e8eba5c3e36 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5464,6 +5464,12 @@ SAS/XML_generator/test/test_regression.py -text SAS/XML_generator/test/test_regression.sh -text SubSystems/DataManagement/CMakeLists.txt -text SubSystems/DataManagement/DataManagement.ini -text +SubSystems/Dragnet/scripts/LOFAR-Dragnet-activate.sh eol=lf +SubSystems/Dragnet/scripts/LOFAR-Dragnet-deploy.sh eol=lf +SubSystems/Dragnet/scripts/casacore-measures-tables/apply_casacore_measures_data.sh eol=lf +SubSystems/Dragnet/scripts/casacore-measures-tables/casacore_measures_common.sh eol=lf +SubSystems/Dragnet/scripts/casacore-measures-tables/cron-update-IERS-DRAGNET.sh eol=lf +SubSystems/Dragnet/scripts/casacore-measures-tables/get_casacore_measures_data.sh eol=lf SubSystems/LAPS_CEP/test/startPythonFromMsg.py eol=lf SubSystems/LAPS_CEP/test/startPythonFromMsg.run eol=lf SubSystems/LAPS_CEP/test/startPythonFromMsg.sh eol=lf diff --git a/SubSystems/Dragnet/scripts/LOFAR-Dragnet-activate.sh b/SubSystems/Dragnet/scripts/LOFAR-Dragnet-activate.sh new file mode 100755 index 00000000000..ee636ea743a --- /dev/null +++ b/SubSystems/Dragnet/scripts/LOFAR-Dragnet-activate.sh @@ -0,0 +1,36 @@ +#!/bin/bash -ex +# LOFAR-Dragnet-activate.sh +# +# LOFAR DRAGNET software activation. Repoint 'current' symlink to already deployed LOFAR release. +# +# Jenkins shell command: +# svn export https://svn.astron.nl/LOFAR/trunk/SubSystems/Dragnet/scripts/LOFAR-Dragnet-activate.sh && \ +# ./LOFAR-Dragnet-activate.sh $JENKINS_RELEASE && rm LOFAR-Dragnet-activate.sh +# +# $Id$ + +# config: version, paths, hostnames +lofar_version=2.17.5 # "x.y.z" (release tag version) +lofar_release=LOFAR-Release-$(echo $lofar_version | tr . _) # svn tag name example: LOFAR-Release-2_17_5 +lofar_versions_root=/opt/lofar_versions +prefix=$lofar_versions_root/$lofar_release +#nodelist="dragnet dragproc $(seq -s ' ' -f drg%02g 1 23)" +nodelist=dragnet + +# repoint 'current' symlink to release version +declare -a status_arr3 +declare arr3_i=0 +for host in $nodelist; do + # Escape double quotes below the following line! + ssh -q -o BatchMode=yes -o NoHostAuthenticationForLocalhost=yes -o StrictHostKeyChecking=no $host " + hostname && + cd $lofar_versions_root && + ln -sfT $lofar_release current_tmp && mv -fT current_tmp current && # atomically replace symlink + sync + " >&2 & + status_arr3[$arr3_i]=$! + ((arr3_i++)) || true +done +for ((i = 0; i < $arr3_i; i++)); do + wait ${status_arr3[$i]} +done diff --git a/SubSystems/Dragnet/scripts/LOFAR-Dragnet-deploy.sh b/SubSystems/Dragnet/scripts/LOFAR-Dragnet-deploy.sh new file mode 100755 index 00000000000..c777e1aa588 --- /dev/null +++ b/SubSystems/Dragnet/scripts/LOFAR-Dragnet-deploy.sh @@ -0,0 +1,88 @@ +#!/bin/bash -ex +# LOFAR-Dragnet-deploy.sh +# +# LOFAR software build and deploy for the LOFAR DRAGNET cluster. +# Does not activate deployed software (i.e. does not repoint 'current' symlink). See LOFAR-Dragnet-activate.sh +# +# Jenkins shell command: +# svn export https://svn.astron.nl/LOFAR/trunk/SubSystems/Dragnet/scripts/LOFAR-Dragnet-deploy.sh && \ +# ./LOFAR-Dragnet-deploy.sh $JENKINS_RELEASE && rm LOFAR-Dragnet-deploy.sh +# +# $Id$ + +# unload all loaded env modules to avoid accidentally depending on pkgs in PATH, LD_LIBRARY_PATH, ... +module purge || true + +# config: version, paths, hostnames +lofar_version=2.17.5 # "x.y.z" (release tag version) +lofar_release=LOFAR-Release-$(echo $lofar_version | tr . _) # svn tag name example: LOFAR-Release-2_17_5 +lofar_versions_root=/opt/lofar_versions +prefix=$lofar_versions_root/$lofar_release +#nodelist="dragnet dragproc $(seq -s ' ' -f drg%02g 1 23)" +nodelist=dragnet +tmpdir=`mktemp -d 2>/dev/null || mktemp -d -t tempdir` # GNU/Linux and Mac OS X compat mktemp usage +buildtype=gnu_optarch # optarch enables -O3 -march=native + +# check out release. +cd "$tmpdir" +svn checkout https://svn.astron.nl/LOFAR/tags/$lofar_release LOFAR > /dev/null + +# build, install into DESTDIR, and create deploy archive +mkdir -p $buildtype && cd $_ +cmake -DBUILD_PACKAGES=Dragnet -DCMAKE_INSTALL_prefix=$prefix ../LOFAR +make -j 8 install DESTDIR="$tmpdir"/destdir +chgrp -R dragnet destdir/* +archive=$lofar_release-dragnet.tgz +tar zcf $archive destdir/* # whole $prefix path ends up in archive entries + +# create environment module file +envmodfilename=$lofar_version +echo '#%Module 1.0' >> $envmodfilename +echo 'module-whatis "Adds the ASTRON LOFAR tree (NDPPP, BBS, awimager, pybdsm, ...) release '$lofar_version' to your environment (do not mix with sourcing lofarinit.sh)"' >> $envmodfilename +echo 'conflict lofar' >> $envmodfilename +echo 'prepend-path PATH '$prefix'/bin:'$prefix'/sbin' >> $envmodfilename +echo 'prepend-path LD_LIBRARY_PATH '$prefix'/lib' >> $envmodfilename +echo 'prepend-path PYTHONPATH '$prefix'/lib64/python2.7/site-packages' >> $envmodfilename +echo 'setenv LOFARROOT '$prefix >> $envmodfilename +echo '#setenv LOFARDATAROOT /opt/lofar/data' >> $envmodfilename + +# create archive and copy across cluster +declare -a status_arr1 +declare arr1_i=0 +for host in $nodelist; do + scp -p -q -o BatchMode=yes -o NoHostAuthenticationForLocalhost=yes -o StrictHostKeyChecking=no $archive $host:$lofar_versions_root/ & + status_arr1[$arr1_i]=$! + ((arr1_i++)) || true +done +for ((i = 0; i < $arr1_i; i++)); do + wait ${status_arr1[$i]} +done + +# unpack and set up across cluster (requires $prefix and /etc/modulefiles/lofar/ to be writable) +declare -a status_arr2 +declare arr2_i=0 +for host in $nodelist; do + # Escape double quotes below the following line! + ssh -q -o BatchMode=yes -o NoHostAuthenticationForLocalhost=yes -o StrictHostKeyChecking=no $host " + hostname && + cd $lofar_versions_root && + chgrp dragnet $archive $envmodfilename && + mv $envmodfilename /etc/modulefiles/lofar/ && + rm -rf $lofar_release && + cd / && tar zxfm $lofar_versions_root/$archive && # full pathname in tar file, so unpack from root dir; -m: don't warn on timestamping /opt + rm -rf $prefix/var && # replace created var/ subdirs by symlink to common var/ dir + ln -sfT /home/lofarsys/lofar/var $prefix/var && + sudo -n /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep $prefix/bin/rtcp && # reqs sudoers.d/ file in place + sudo -n /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep $prefix/bin/outputProc && + sudo -n /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep $prefix/bin/TBB_Writer && + sync + " >&2 & + status_arr2[$arr2_i]=$! + ((arr2_i++)) || true +done +for ((i = 0; i < $arr2_i; i++)); do + wait ${status_arr2[$i]} +done + +cd +rm -rf "$tmpdir" diff --git a/SubSystems/Dragnet/scripts/casacore-measures-tables/CheckIERS.cc b/SubSystems/Dragnet/scripts/casacore-measures-tables/CheckIERS.cc new file mode 100644 index 00000000000..6e5879794cc --- /dev/null +++ b/SubSystems/Dragnet/scripts/casacore-measures-tables/CheckIERS.cc @@ -0,0 +1,89 @@ +// CheckIERS program to find age of IERS tables. +// +// $Id$ +// +// Program CheckIERS checks the last entry in the IERSpredict table and +// compares this with the current day (all in MJD). +// The predict table should have entries for the coming period. If it only has +// entries in the past, the table is outdated. +// +// Usage: CheckIERS dir=<root dir of IERS tables> +// +// Options: all: age of last entry in all IERS tables +// verbose: Show readable text, instead of just numbers +// +// The root directory is default set to /localhome/IERS; the +// default location for the IERS tables is in directory 'geodetic' under +// the IERS root directory. +// +// If only the age of the table must be extracted, parse the output of this +// program like this: +// +// > CheckIERS | tail -1 | awk '{print $6}' +// +// Author: A.P. Schoenmakers +// + +#include <iostream> +#include <casacore/casa/Exceptions.h> +#include <casacore/casa/Inputs/Input.h> +#include <casacore/tables/Tables.h> +#include <casacore/casa/OS/Time.h> + +using namespace casa; + +int main(int argc, char *argv[]) { + try + { + Input inputs; + inputs.version(""); + inputs.create("dir","/localhome/IERS","root dir of IERS (default: /localhome/IERS)","String"); + inputs.create("table","IERSpredict","IERS table to check","String"); + inputs.create("verbose","False","Verbose output","Bool","verbose"); + inputs.create("all","False","Use all IERS tables","Bool","all"); + inputs.readArguments(argc, argv); + + String dir = inputs.getString("dir"); + String table = inputs.getString("table"); + Bool verbose = inputs.getBool("verbose"); + Bool all_tables = inputs.getBool("all"); + std::vector<String> tables; + if (all_tables == True) { + tables.push_back("IERSeop2000"); + tables.push_back("IERSeop97"); + tables.push_back("IERSpredict2000"); + tables.push_back("IERSpredict"); + } else { + tables.push_back(table); + } + + for (std::vector<String>::iterator it = tables.begin() ; it != tables.end(); ++it) { + String fullname = dir + "/geodetic/" + *it; + + if (verbose) cout << "Checking table " << fullname << endl; + Table ierstable(fullname); + + uint lastLine = ierstable.nrow(); + //cout << "Number of lines: " << lastLine << endl; + + ROTableColumn MJDcol(ierstable,"MJD"); + + int eopMJD = (int)MJDcol.asdouble(lastLine-1); + Time curMJDtime; + int curMJD = (int)curMJDtime.modifiedJulianDay(); + if (verbose) { + cout << "Current MJD is : " << curMJD << endl; + cout << "Last MJD in " << *it << " : " << eopMJD << endl; + cout << "Nr of days remaining: " << eopMJD - curMJD << endl; + } else { + cout << eopMJD - curMJD << endl; + } + } + } + catch(AipsError& err) + { + cerr << "Aips++ error detected: " << err.getMesg() << endl; + return 1; + }; + +} diff --git a/SubSystems/Dragnet/scripts/casacore-measures-tables/Makefile b/SubSystems/Dragnet/scripts/casacore-measures-tables/Makefile new file mode 100644 index 00000000000..81eb7e41ca9 --- /dev/null +++ b/SubSystems/Dragnet/scripts/casacore-measures-tables/Makefile @@ -0,0 +1,36 @@ +# Stupid Makefile for program CheckIERS +# +# Valid for kis001 +# +# $Id$ +# +# Uses weekly casacore. +# +CFLAGS = -g -Wall +CC = g++ +AIPSFLAGS = -DAIPS -DAIPS_LINUX -DAIPS_LITTLE_ENDIAN -DAIPS_AUTO_STL -DAIPS_NO_TEMPLATE_SRC +#AIPSLIBD = -L/data/casacore/weekly/casacore/lib +AIPSLIBD = -L/opt/casacore/lib -Wl,-rpath=/opt/casacore/lib +AIPSLIBS = -lcasa_measures -lcasa_tables -lcasa_casa +#AIPSINCD = -I/data/casacore/weekly/casacore/include/casacore +AIPSINCD = -I/opt/casacore/include + +INCLUDES = -I. $(AIPSINCD) +LIBS = $(AIPSLIBD) $(AIPSLIBS) -ldl +FLAGS = $(AIPSFLAGS) +OBJS = CheckIERS.o +SRCS = CheckIERS.cc + +all: CheckIERS + +CheckIERS: $(OBJS) ${HDRS} + ${CC} ${CFLAGS} ${FLAGS} ${INCLUDES} -o $@ $(OBJS) ${LIBS} + +.cc.o: + ${CC} ${CFLAGS} ${FLAGS} ${INCLUDES} -c $< + +depend: + makedepend ${SRCS} + +clean: + rm *.o diff --git a/SubSystems/Dragnet/scripts/casacore-measures-tables/apply_casacore_measures_data.sh b/SubSystems/Dragnet/scripts/casacore-measures-tables/apply_casacore_measures_data.sh new file mode 100755 index 00000000000..44892177d58 --- /dev/null +++ b/SubSystems/Dragnet/scripts/casacore-measures-tables/apply_casacore_measures_data.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# apply_casacore_measures_tables.sh +# +# Install downloaded casacore measures tables atomically and verify it is in use. +# +# $Id$ + +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/casacore_measures_common.sh" + + +module add casacore # add casacore bin dir to PATH to run the findmeastable program + +update_id=$(get_latest) + +# If not already in use, switch current symlink to the latest $dir_prefix/ *atomically* with an mv. Avoids race with a reader. +if [ "`readlink current`" != "$update_id" ]; then + log INFO "Applying $update_id" + ln -s "$update_id" "$working_dir/current_${update_id}" && mv -Tf "$working_dir/current_${update_id}" "$working_dir/current" +else + log INFO "No new table to apply." +fi + +# Check if casacore uses the just set up tables by extracting the path (token(s) 6,...) from findmeastable. +# If ok, findmeastable prints: "Measures table Observatories found as /home/jenkins/root/share/casacore/data/geodetic/Observatories" +if ! findmeastable > /dev/null; then exit 1; fi +used_dir=`findmeastable | cut -d' ' -f 6-` +if [ $? -ne 0 ]; then exit 1; fi +used_path=`readlink -f "$used_dir/../../.."` +if [ $? -ne 0 ]; then exit 1; fi +update_id_path="$working_dir/$update_id" +if [ "$update_id_path" != "$used_path" ]; then + # potential improvement: revert if applied (and if it used to work) (e.g. empty $update_id/) + fatal "It appears that the most recently set up measures tables are not in use. Most recent on the system is: '$update_id'." +fi + +remove_old + +log INFO "Done. The most recently retrieved measures data is (now) in use." diff --git a/SubSystems/Dragnet/scripts/casacore-measures-tables/casacore_measures_common.sh b/SubSystems/Dragnet/scripts/casacore-measures-tables/casacore_measures_common.sh new file mode 100755 index 00000000000..85f70072cac --- /dev/null +++ b/SubSystems/Dragnet/scripts/casacore-measures-tables/casacore_measures_common.sh @@ -0,0 +1,55 @@ +# casacore_measures_common.sh +# Source this file, don't execute it! +# +# $Id$ + +working_dir=/opt/IERS +dir_prefix=IERS- +measures_ftp_path=ftp://ftp.astron.nl/outgoing/Measures +measures_data_filename=WSRT_Measures.ztar +#measures_md5sum_filename=$measures_data_filename.md5sum +hostname=$(hostname) + +# Example log() usage: log INFO "foo bar" +# writes to stderr something like: node42 2015-10-16 16:00:46,186 INFO - foo bar +log() { + loglevel=$1 # one of: DEBUG INFO WARN ERROR FATAL + message=$2 + ts=`date --utc '+%F %T,%3N'` # e.g. 2015-10-16 16:00:46,186 + echo "$hostname $ts $loglevel - $message" >&2 +} + +fatal() { + log FATAL "$1" + exit 1 +} + +# echo the directory component name of the most recent measures table. +# 'Most recent' is from name, not from last modified date! +get_latest() { + if ! cd "$working_dir"; then fatal "Failed to cd to $working_dir"; fi + local update_id + update_id=`ls -d "$dir_prefix"* 2> /dev/null | tail -n 1` + if [ -z "$update_id" ]; then + # fatal since this function is to be called after an update has been retrieved and + # unpacked (but not yet activated via the symlink). + log FATAL "No existing casacore measures install recognized."; + fi + cd "$OLDPWD" # 'cd -' echos the path altering func output :| + echo "$update_id" +} + +# Remove earlier downloaded entries beyond the 4 latest. ('ls' also sorts.) +remove_old() { + if ! cd "$working_dir"; then fatal "Failed to cd to $working_dir"; fi + old_update_ids=`ls -d "$dir_prefix"* 2> /dev/null | head -n -4` + if [ ! -z "$old_update_ids" ]; then + rm -rf $old_update_ids + if [ $? -ne 0 ]; then + log WARN "Failed to remove old measure tables dir(s)." # not fatal + else + log INFO "Removed old measure tables dir(s): $old_update_ids" # var contains newlines... + fi + fi + cd "$OLDPWD" # 'cd -' echos the path altering func output :| +} diff --git a/SubSystems/Dragnet/scripts/casacore-measures-tables/cron-update-IERS-DRAGNET.sh b/SubSystems/Dragnet/scripts/casacore-measures-tables/cron-update-IERS-DRAGNET.sh new file mode 100755 index 00000000000..1499e22e2ab --- /dev/null +++ b/SubSystems/Dragnet/scripts/casacore-measures-tables/cron-update-IERS-DRAGNET.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# cron-update-IERS-DRAGNET.sh +# +# Updates casacore measures tables on the DRAGNET cluster under /opt/IERS/ +# To be started once per week (or so) via cron as 'lofarsys:dragnet' on any single node. +# lofarsys' crontab (every Mon, 04:00 AM): +# 0 4 * * 1 /opt/IERS/cron-update-IERS-DRAGNET.sh 2> /home/lofarsys/lofar/var/log/IERS/cron-update-IERS-DRAGNET.log +# +# Caveats: If any cluster node is unreachable, the update is not applied. +# This is intentional, but when at least one node is down often, this is unworkable. +# The solution is *not* a partial install, but to create a proper software package +# that every node pulls on a fixed time in the week and upon boot before starting slurmd. +# +# $Id$ + +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/casacore_measures_common.sh" + + +nodelist="dragnet dragproc $(seq -s ' ' -f drg%02g 1 23)" +log INFO "Started. Node list: $nodelist" + +log INFO "Retrieving fresh copy of casacore measures archive file" +"$working_dir/get_casacore_measures_data.sh" || exit 1 + +latest=$(get_latest) +path="$working_dir/$latest/data/$measures_data_filename" +log INFO "Transferring archive '$path' across the cluster" +declare -a status_arr1 +declare arr1_i=0 +for host in $nodelist; do + scp -p -q -o BatchMode=yes -o NoHostAuthenticationForLocalhost=yes -o StrictHostKeyChecking=no "$path" "$host:$working_dir" & + status_arr1[$arr1_i]=$! + ((arr1_i++)) || true +done +for ((i = 0; i < $arr1_i; i++)); do + log DEBUG "Waiting for pid ${status_arr1[$i]}" + wait ${status_arr1[$i]} || exit 1 +done + +log INFO "Putting archive content in place on other nodes" +declare -a status_arr2 +declare arr2_i=0 +for host in $nodelist; do + # Escape double quotes below the following line! + ssh -q -o BatchMode=yes -o NoHostAuthenticationForLocalhost=yes -o StrictHostKeyChecking=no $host " + hostname && + cd /opt/IERS && + mkdir -p \"$latest/data\" && + mv \"$measures_data_filename\" \"$latest/data\" && + cd \"$latest/data\" && + tar zxf \"$measures_data_filename\" && + cd ../.. && + chgrp -R dragnet \"$latest\" + " >&2 & + status_arr2[$arr2_i]=$! + ((arr2_i++)) || true +done +for ((i = 0; i < $arr2_i; i++)); do + log DEBUG "Waiting for pid ${status_arr2[$i]}" + wait ${status_arr2[$i]} || exit 1 +done + +log INFO "Applying update across the cluster" +declare -a status_arr3 +declare arr3_i=0 +for host in $nodelist; do + # Escape double quotes below the following line! + ssh -q -o BatchMode=yes -o NoHostAuthenticationForLocalhost=yes -o StrictHostKeyChecking=no $host " + hostname && + \"$working_dir/apply_casacore_measures_data.sh\" + " >&2 & + status_arr3[$arr3_i]=$! + ((arr3_i++)) || true +done +for ((i = 0; i < $arr3_i; i++)); do + log DEBUG "Waiting for pid ${status_arr3[$i]}" + wait ${status_arr3[$i]} || exit 1 +done + +log INFO "Done" diff --git a/SubSystems/Dragnet/scripts/casacore-measures-tables/get_casacore_measures_data.sh b/SubSystems/Dragnet/scripts/casacore-measures-tables/get_casacore_measures_data.sh new file mode 100755 index 00000000000..c53b3266583 --- /dev/null +++ b/SubSystems/Dragnet/scripts/casacore-measures-tables/get_casacore_measures_data.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# get_casacore_measures_tables.sh +# +# Retrieve new casacore measures tables under $working_dir and extract. Written for jenkins@fs5 (DAS-4). +# If it works out, remove very old download dirs starting with $dir_prefix. +# +# $Id$ + +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/casacore_measures_common.sh" + + +# Get the data (~12 MB) from the server. This may take up to 10s of seconds for a slow FTP server. +# By default, when wget downloads a file, the timestamp is set to match the timestamp from the remote file. +download() +{ + wget -N --tries=4 \ + $measures_ftp_path/$measures_data_filename #\ + #$measures_ftp_path/$measures_md5sum_filename +} + +# Verify that md5 hash is equal to hash in $measures_md5sum_filename (no longer used, was used for measures tables from CSIRO) +# No need to compare the filename. (If from CSIRO, note that the .md5sum contains a CSIRO path.) +check_md5() +{ + local md5sum=`cut -f 1 -d ' ' $measures_md5sum_filename` + if [ $? -ne 0 ]; then return 1; fi + local data_md5=`md5sum $measures_data_filename | cut -f 1 -d ' '` + if [ -z "$data_md5" ]; then return 1; fi + + if [ "$md5sum" != "$data_md5" ]; then + log ERROR "Computed and downloaded MD5 sums do not match." + return 1 + fi + + return 0 +} + +# Check that the measures tables under tmp_$update_id/data are up-to- date. +check_uptodate() +{ + # Only check last int, but set options all and verbose as example cmd to manually run on error. + check_out=$("$working_dir"/CheckIERS dir="$update_id/data" all=true verbose=true) || return 1 + nrdays=$(echo "$check_out" | tail -1 | awk '{print $5}') || return 1 + [ $nrdays -ge 7 ] # IERS prediction for at least 7 more days? +} + + +update_id=$dir_prefix`date --utc +%FT%T.%N` # e.g. IERS-2015-09-26T01:58:30.098006623 +if [ $? -ne 0 ]; then exit 1; fi + +# Use a tmp_ name until written and checked, then move into place. +if ! cd "$working_dir" || ! mkdir -p "tmp_$update_id/data" || ! cd "tmp_$update_id/data"; then + fatal "Failed to create and cd to tmp_$update_id/data" +fi +if ! download; then # || ! check_md5; then + rm -f $measures_data_filename + log ERROR "Download failed. Retrying once." + sleep 2 + if ! download; then # || ! check_md5; then + rm -f $measures_data_filename #$measures_md5sum_filename + cd ../.. && rm -rf "tmp_$update_id" + fatal "Download failed again." + exit 1 + fi +fi + +if ! tar zxf $measures_data_filename; then + cd ../.. && rm -rf "tmp_$update_id" + exit 1 +fi + +cd ../.. || exit 1 # back to $working_dir + +# Make it available to the apply script to install/move it at an opportune moment. +if ! mv "tmp_$update_id" "$update_id"; then + # On error, leave the tmp_ dir in place for manual inspection. + fatal "Failed to prepare measures tables update 'tmp_$update_id'. Manual intervention required." +fi + +if ! check_uptodate; then fatal "Verification of downloaded measures tables failed."; fi + +log INFO "Done. Measures tables update '$update_id' ready to be applied." -- GitLab From a003d2034339116255ef7dc1c0fe7aaf13b02325 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Wed, 24 Aug 2016 12:23:02 +0000 Subject: [PATCH 683/933] Task #9798: create task branch -- GitLab From 90329ab34a07c7999150132b674a65c543ce6c0d Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Wed, 24 Aug 2016 12:24:25 +0000 Subject: [PATCH 684/933] Task #9798: fix symlinks in pipeline installation --- .gitattributes | 2 ++ CEP/Pipeline/recipes/sip/CMakeLists.txt | 20 ++----------------- CEP/Pipeline/recipes/sip/bin/CMakeLists.txt | 19 ++++++++++++++++++ .../bad_station_detection/CMakeLists.txt | 6 ++++++ 4 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 CEP/Pipeline/recipes/sip/bin/CMakeLists.txt create mode 100644 CEP/Pipeline/recipes/sip/external/bad_station_detection/CMakeLists.txt diff --git a/.gitattributes b/.gitattributes index e8eba5c3e36..c94fae351e0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1521,6 +1521,7 @@ CEP/Pipeline/recipes/examples/master/example.py eol=lf CEP/Pipeline/recipes/examples/master/example_parallel.py eol=lf CEP/Pipeline/recipes/examples/nodes/example_parallel.py eol=lf CEP/Pipeline/recipes/sip/CMakeLists.txt eol=lf +CEP/Pipeline/recipes/sip/bin/CMakeLists.txt -text CEP/Pipeline/recipes/sip/bin/genericpipeline.py -text CEP/Pipeline/recipes/sip/bin/imaging_pipeline.py -text CEP/Pipeline/recipes/sip/bin/loader.py -text @@ -1545,6 +1546,7 @@ CEP/Pipeline/recipes/sip/demixing/bbs_TauA.parset eol=lf CEP/Pipeline/recipes/sip/demixing/bbs_TauA_smoothcal.parset eol=lf CEP/Pipeline/recipes/sip/demixing/bbs_VirA.parset eol=lf CEP/Pipeline/recipes/sip/demixing/bbs_VirA_smoothcal.parset eol=lf +CEP/Pipeline/recipes/sip/external/bad_station_detection/CMakeLists.txt -text CEP/Pipeline/recipes/sip/helpers/ComplexArray.py -text CEP/Pipeline/recipes/sip/helpers/MultipartPostHandler.py -text CEP/Pipeline/recipes/sip/helpers/WritableParmDB.py -text diff --git a/CEP/Pipeline/recipes/sip/CMakeLists.txt b/CEP/Pipeline/recipes/sip/CMakeLists.txt index 297a997162b..ed77327d735 100644 --- a/CEP/Pipeline/recipes/sip/CMakeLists.txt +++ b/CEP/Pipeline/recipes/sip/CMakeLists.txt @@ -64,24 +64,8 @@ python_install( plugins/PipelineStep_combineParsets.py DESTINATION lofarpipe/recipes) -lofar_add_bin_scripts( - bin/calibration_pipeline.py - bin/msss_calibrator_pipeline.py - bin/msss_imager_pipeline.py - bin/msss_target_pipeline.py - bin/preprocessing_pipeline.py - bin/imaging_pipeline.py - bin/pulsar_pipeline.py - bin/long_baseline_pipeline.py - bin/selfcal_imager_pipeline.py - bin/runPipeline.sh - bin/startPython.sh - bin/startPythonVersion.sh - bin/stopPython.sh - bin/genericpipeline.py - bin/loader.py - external/bad_station_detection/asciistats.py - external/bad_station_detection/statsplot.py) +add_subdirectory(bin) +add_subdirectory(external/bad_station_detection) install(FILES demixing/bbs_CasA.parset diff --git a/CEP/Pipeline/recipes/sip/bin/CMakeLists.txt b/CEP/Pipeline/recipes/sip/bin/CMakeLists.txt new file mode 100644 index 00000000000..53d89911854 --- /dev/null +++ b/CEP/Pipeline/recipes/sip/bin/CMakeLists.txt @@ -0,0 +1,19 @@ +# $Id: CMakeLists.txt 34753 2016-06-20 10:43:42Z schaap $ + +lofar_add_bin_scripts( + calibration_pipeline.py + msss_calibrator_pipeline.py + msss_imager_pipeline.py + msss_target_pipeline.py + preprocessing_pipeline.py + imaging_pipeline.py + pulsar_pipeline.py + long_baseline_pipeline.py + selfcal_imager_pipeline.py + runPipeline.sh + startPython.sh + startPythonVersion.sh + stopPython.sh + genericpipeline.py + loader.py + ) diff --git a/CEP/Pipeline/recipes/sip/external/bad_station_detection/CMakeLists.txt b/CEP/Pipeline/recipes/sip/external/bad_station_detection/CMakeLists.txt new file mode 100644 index 00000000000..bca19e5255f --- /dev/null +++ b/CEP/Pipeline/recipes/sip/external/bad_station_detection/CMakeLists.txt @@ -0,0 +1,6 @@ +# $Id: CMakeLists.txt 34753 2016-06-20 10:43:42Z schaap $ + +lofar_add_bin_scripts( + asciistats.py + statsplot.py + ) -- GitLab From c6bb56e3a2753951b502cc6d2484caa8c57fed8a Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Wed, 24 Aug 2016 12:37:51 +0000 Subject: [PATCH 685/933] Task #9127: supplement to r35163: update Dragnet deploy and activate scripts --- .../Dragnet/scripts/LOFAR-Dragnet-activate.sh | 13 ++++--- .../Dragnet/scripts/LOFAR-Dragnet-deploy.sh | 36 +++++++++---------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/SubSystems/Dragnet/scripts/LOFAR-Dragnet-activate.sh b/SubSystems/Dragnet/scripts/LOFAR-Dragnet-activate.sh index ee636ea743a..50e391b4bec 100755 --- a/SubSystems/Dragnet/scripts/LOFAR-Dragnet-activate.sh +++ b/SubSystems/Dragnet/scripts/LOFAR-Dragnet-activate.sh @@ -14,18 +14,17 @@ lofar_version=2.17.5 # "x.y.z" (release tag version) lofar_release=LOFAR-Release-$(echo $lofar_version | tr . _) # svn tag name example: LOFAR-Release-2_17_5 lofar_versions_root=/opt/lofar_versions prefix=$lofar_versions_root/$lofar_release -#nodelist="dragnet dragproc $(seq -s ' ' -f drg%02g 1 23)" -nodelist=dragnet +nodelist="dragnet dragproc $(seq -s ' ' -f drg%02g 1 23)" -# repoint 'current' symlink to release version +# repoint 'current' symlink to release version atomically (using mv -T) declare -a status_arr3 declare arr3_i=0 for host in $nodelist; do - # Escape double quotes below the following line! + # Escape double quotes below the following line! And use \ at newline and don't use '#' comments across ssh as lofarbuild uses tcsh... ssh -q -o BatchMode=yes -o NoHostAuthenticationForLocalhost=yes -o StrictHostKeyChecking=no $host " - hostname && - cd $lofar_versions_root && - ln -sfT $lofar_release current_tmp && mv -fT current_tmp current && # atomically replace symlink + hostname && \ + cd $lofar_versions_root && \ + ln -sfT $lofar_release current_tmp && mv -fT current_tmp current && \ sync " >&2 & status_arr3[$arr3_i]=$! diff --git a/SubSystems/Dragnet/scripts/LOFAR-Dragnet-deploy.sh b/SubSystems/Dragnet/scripts/LOFAR-Dragnet-deploy.sh index c777e1aa588..5231e35f0da 100755 --- a/SubSystems/Dragnet/scripts/LOFAR-Dragnet-deploy.sh +++ b/SubSystems/Dragnet/scripts/LOFAR-Dragnet-deploy.sh @@ -18,8 +18,7 @@ lofar_version=2.17.5 # "x.y.z" (release tag version) lofar_release=LOFAR-Release-$(echo $lofar_version | tr . _) # svn tag name example: LOFAR-Release-2_17_5 lofar_versions_root=/opt/lofar_versions prefix=$lofar_versions_root/$lofar_release -#nodelist="dragnet dragproc $(seq -s ' ' -f drg%02g 1 23)" -nodelist=dragnet +nodelist="dragnet dragproc $(seq -s ' ' -f drg%02g 1 23)" tmpdir=`mktemp -d 2>/dev/null || mktemp -d -t tempdir` # GNU/Linux and Mac OS X compat mktemp usage buildtype=gnu_optarch # optarch enables -O3 -march=native @@ -29,9 +28,8 @@ svn checkout https://svn.astron.nl/LOFAR/tags/$lofar_release LOFAR > /dev/null # build, install into DESTDIR, and create deploy archive mkdir -p $buildtype && cd $_ -cmake -DBUILD_PACKAGES=Dragnet -DCMAKE_INSTALL_prefix=$prefix ../LOFAR +cmake -DBUILD_PACKAGES=Dragnet -DCMAKE_INSTALL_PREFIX=$prefix ../LOFAR make -j 8 install DESTDIR="$tmpdir"/destdir -chgrp -R dragnet destdir/* archive=$lofar_release-dragnet.tgz tar zcf $archive destdir/* # whole $prefix path ends up in archive entries @@ -58,23 +56,25 @@ for ((i = 0; i < $arr1_i; i++)); do wait ${status_arr1[$i]} done -# unpack and set up across cluster (requires $prefix and /etc/modulefiles/lofar/ to be writable) +# Unpack and set up across cluster (requires $prefix and /etc/modulefiles/lofar/ to be writable). +# The archived files all have full pathname, so unpack from root dir; -m: don't warn on timestamping /opt +# Need to replace created var/ subdirs by symlink to common var/ dir. +# The sudo setcap cmds reqs a sudoers.d/ file in place to allow lofarbuild to do this without auth. declare -a status_arr2 declare arr2_i=0 for host in $nodelist; do - # Escape double quotes below the following line! - ssh -q -o BatchMode=yes -o NoHostAuthenticationForLocalhost=yes -o StrictHostKeyChecking=no $host " - hostname && - cd $lofar_versions_root && - chgrp dragnet $archive $envmodfilename && - mv $envmodfilename /etc/modulefiles/lofar/ && - rm -rf $lofar_release && - cd / && tar zxfm $lofar_versions_root/$archive && # full pathname in tar file, so unpack from root dir; -m: don't warn on timestamping /opt - rm -rf $prefix/var && # replace created var/ subdirs by symlink to common var/ dir - ln -sfT /home/lofarsys/lofar/var $prefix/var && - sudo -n /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep $prefix/bin/rtcp && # reqs sudoers.d/ file in place - sudo -n /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep $prefix/bin/outputProc && - sudo -n /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep $prefix/bin/TBB_Writer && + # Escape double quotes below the following line! And use \ at newline and don't use '#' comments across ssh as lofarbuild uses tcsh... + ssh -t -q -o BatchMode=yes -o NoHostAuthenticationForLocalhost=yes -o StrictHostKeyChecking=no $host " + hostname && \ + cd $lofar_versions_root && \ + mv $envmodfilename /etc/modulefiles/lofar/ && \ + rm -rf $lofar_release && \ + cd / && tar zxfm $lofar_versions_root/$archive && \ + rm -rf $prefix/var && \ + ln -sfT /home/lofarsys/lofar/var $prefix/var && \ + sudo -n /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep $prefix/bin/rtcp && \ + sudo -n /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep $prefix/bin/outputProc && \ + sudo -n /sbin/setcap cap_net_raw,cap_sys_nice,cap_ipc_lock=ep $prefix/bin/TBB_Writer && \ sync " >&2 & status_arr2[$arr2_i]=$! -- GitLab From ccd3166c5620fe2b607b81037600a479ae69c0c5 Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Thu, 25 Aug 2016 07:53:49 +0000 Subject: [PATCH 686/933] Task #9655: last changes for SNMP powerunit monitorring --- .gitattributes | 5 + MAC/Navigator2/config/config .navigator.3.10 | 29 + MAC/Navigator2/config/config.navigator.3.14 | 35 ++ .../panels/Hardware/Station_PowerUnits.pnl | 194 +++++++ .../panels/Test/Navigator_testPanel.pnl | 72 +++ .../objects/Hardware/Station_PowerUnit.pnl | 504 ++++++++++++++++++ .../objects/Hardware/powerUnit_small.pnl | 19 +- MAC/Navigator2/scripts/libs/navFunct.ctl | 10 + MAC/Navigator2/scripts/libs/navigator.ctl | 1 + 9 files changed, 866 insertions(+), 3 deletions(-) create mode 100644 MAC/Navigator2/config/config .navigator.3.10 create mode 100644 MAC/Navigator2/config/config.navigator.3.14 create mode 100644 MAC/Navigator2/panels/Hardware/Station_PowerUnits.pnl create mode 100644 MAC/Navigator2/panels/Test/Navigator_testPanel.pnl create mode 100644 MAC/Navigator2/panels/objects/Hardware/Station_PowerUnit.pnl diff --git a/.gitattributes b/.gitattributes index e8eba5c3e36..f59eaa0795a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3848,6 +3848,7 @@ MAC/Navigator2/bin/linux-64/WebView.ewo -text MAC/Navigator2/bin/windows-64/WebView.ewo -text MAC/Navigator2/bin/windows/WebView.ewo -text MAC/Navigator2/colorDB/Lofar[!!-~]colors -text +MAC/Navigator2/config/config[!!-~].navigator.3.10 -text MAC/Navigator2/config/config.ccu -text MAC/Navigator2/config/config.dist.station -text MAC/Navigator2/config/config.dist_test.station -text @@ -3856,6 +3857,7 @@ MAC/Navigator2/config/config.level.ccu -text MAC/Navigator2/config/config.level.maincu -text MAC/Navigator2/config/config.level.station -text MAC/Navigator2/config/config.maincu -text +MAC/Navigator2/config/config.navigator.3.14 -text MAC/Navigator2/config/config.sas099 -text MAC/Navigator2/config/config.standalone.station -text MAC/Navigator2/config/progs.ccu -text @@ -3890,6 +3892,7 @@ MAC/Navigator2/panels/Hardware/RemoteOverview.pnl -text MAC/Navigator2/panels/Hardware/Station.pnl -text MAC/Navigator2/panels/Hardware/Station_Cabinet.pnl -text MAC/Navigator2/panels/Hardware/Station_Cabinet_detailed.pnl -text +MAC/Navigator2/panels/Hardware/Station_PowerUnits.pnl -text MAC/Navigator2/panels/Hardware/Station_RCU.pnl -text MAC/Navigator2/panels/Hardware/Station_RSPBoard.pnl -text MAC/Navigator2/panels/Hardware/Station_Subrack.pnl -text @@ -3948,6 +3951,7 @@ MAC/Navigator2/panels/Settings/mail.pnl.bak -text MAC/Navigator2/panels/Test/Claim_Viewer.pnl -text MAC/Navigator2/panels/Test/CobaltTestStub.pnl -text MAC/Navigator2/panels/Test/Event_Viewer.pnl -text +MAC/Navigator2/panels/Test/Navigator_testPanel.pnl -text MAC/Navigator2/panels/Test/stringTest.pnl -text MAC/Navigator2/panels/Test/test[!!-~]colorrange.pnl -text MAC/Navigator2/panels/Test/test.pnl -text @@ -4025,6 +4029,7 @@ MAC/Navigator2/panels/objects/Hardware/Station_Cabinet_top.pnl -text MAC/Navigator2/panels/objects/Hardware/Station_Clock.pnl -text MAC/Navigator2/panels/objects/Hardware/Station_HBA.pnl -text MAC/Navigator2/panels/objects/Hardware/Station_LBA.pnl -text +MAC/Navigator2/panels/objects/Hardware/Station_PowerUnit.pnl -text MAC/Navigator2/panels/objects/Hardware/Station_Subrack_small.pnl -text MAC/Navigator2/panels/objects/Hardware/Station_TempAndHumidity.pnl -text MAC/Navigator2/panels/objects/Hardware/Station_mainView.pnl -text diff --git a/MAC/Navigator2/config/config .navigator.3.10 b/MAC/Navigator2/config/config .navigator.3.10 new file mode 100644 index 00000000000..5cade6015bd --- /dev/null +++ b/MAC/Navigator2/config/config .navigator.3.10 @@ -0,0 +1,29 @@ +[general] +pvss_path = "D:/Siemens/Automation/WinCC_OA/3.10" +proj_path = "D:/Data/TRUNK-MCU001" +proj_path = "D:/Data/TRUNK-Navigator2" +proj_version = "3.10" +langs = "en_US.iso88591" +distributed = 1 +ctrlMaxPendings = 2000 +messageCompression = "zlib-bzip2" +useValueArchive = 1 + + +#local via putty from home or if working with local instantations of mcu/ccc/cs001 +#data = "localhost" +#event = "localhost" + +#mcu001 +#data = "10.149.96.3" +#event = "10.149.96.3" +data = "mcu001.control.lofar" +event = "mcu001.control.lofar" + +#mcu099 +#data = "10.149.96.23" +#event = "10.149.96.23" + +#eventPort = 5997 +#dataPort = 5998 +pmonPort = 2025 diff --git a/MAC/Navigator2/config/config.navigator.3.14 b/MAC/Navigator2/config/config.navigator.3.14 new file mode 100644 index 00000000000..f7828eebc79 --- /dev/null +++ b/MAC/Navigator2/config/config.navigator.3.14 @@ -0,0 +1,35 @@ +[general] +pvss_path = "D:/Siemens/Automation/WinCC_OA/3.10" +proj_path = "D:/Data/TRUNK-MCU001" +proj_path = "D:/Data/TRUNK-Navigator2" +proj_version = "3.10" +langs = "en_US.iso88591" +distributed = 1 +ctrlMaxPendings = 2000 +# avoid proxyserver to be used for contacts between 3.12 onwards and 3.10 +# can be removed as soon as all installations are updated to 3.14 +mxProxy = "None" + +#Set maximum connect messages size to unlimmited (needed for version 3.12 onwards because of the huge amount of Cobalt processes to connect to +maxConnectMessageSize = 0 +messageCompression = "zlib-bzip2" +useValueArchive = 1 + + +#local via putty from home or if working with local instantations of mcu/ccc/cs001 +#data = "localhost" +#event = "localhost" + +#mcu001 +#data = "10.149.96.3" +#event = "10.149.96.3" +data = "mcu001.control.lofar" +event = "mcu001.control.lofar" + +#mcu099 +#data = "10.149.96.23" +#event = "10.149.96.23" + +#eventPort = 5997 +#dataPort = 5998 +pmonPort = 2025 diff --git a/MAC/Navigator2/panels/Hardware/Station_PowerUnits.pnl b/MAC/Navigator2/panels/Hardware/Station_PowerUnits.pnl new file mode 100644 index 00000000000..4c25fd1195d --- /dev/null +++ b/MAC/Navigator2/panels/Hardware/Station_PowerUnits.pnl @@ -0,0 +1,194 @@ +V 11 +1 +LANG:1 17 PowerUnits detail +PANEL,-1 -1 1200 748 N "_3DFace" 0 +"main() +{ + // Initialise the Panel + navPanel_initPanel(\"fw_viewBox\"); + baseDP = g_currentDatapoint; + + reload(); +} + + +void prepareHardwareList() +{ + dynAppend(g_stationList,navFunct_bareDBName(sysName)); + // For this panel PowerUnits should be selectable so we get them for the treelist + + + if (dpGet(sysName+\"LOFAR_PIC_StationInfo.nrOfPowerUnits\",nrOfPowerUnits) == -1) + { + LOG_TRACE(\"Station_PowerUnits.pnl:prepareHardwareList|failed to get \"+sysName+\"LOFAR_PIC_StationInfo.nrOfPowerUnits\"); + return; + } + + + for(int i=0;i<nrOfPowerUnits;i++) + { + dynAppend(g_powerUnitList,i); + } +} + +// +// Callback for dpConnect to action point. +// If there is an action required this point will tell so +// +void doAction(string aDP, string anAction) { + LOG_DEBUG(\"Station_PowerUnits.pnl:doAction| Action required. found: \" + anAction); + // split action into essentials + dyn_string actionString; + if (!navFunct_splitAction(anAction,actionString)) { + return; + } + + LOG_DEBUG(\"Station_PowerUnits.pnl:doAction|found actionString: \" + actionString); + + // Reload + if (actionString[1] == \"Reload\") { + reload(); + } else if (actionString[1] == \"DistChanged\") { + // for dist system bound hardware only, if the distsystem went offline we need to replace + // the screen with a broken connection screen. + if (!g_initializing) { + + // check if this syst is in the connectionlist and down + int iPos=dynContains(g_connections[\"NAME\"],sysName); + if (iPos > 0) { + if (!g_connections[\"UP\"][iPos]) { + navPanel_setEvent(\"invalid DP\",\"ChangePanel\"); + } + } + } + return; + } +} + +void reload() { + + navFunct_clearGlobalLists(); + + // set the hardware selectable items for this screen + prepareHardwareList(); + + if (nrOfPowerUnits == 1) + { + setValue(\"unitsText\",\"text\", sysName + \" has 1 PowerModule\"); + } + else + { + setValue(\"unitsText\",\"text\", sysName + \" has \" + nrOfPowerUnits + \" PowerModules\"); + } + + // set panel to ready + g_objectReady=true; + + // trigger that the panel values are calculated and ready + navPanel_setEvent(\"Station_PowerUnits.pnl\",\"Update\"); + +}" 0 + E E E E 1 0 0 0 107 694 +""0 1 +E "#uses \"navPanel.ctl\" + +string baseDP = \"\"; +int nrOfPowerUnits = 0; +" 0 + 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 6 Layer1 +1 16 1 "" 924 +0 +1 17 1 "" 916 +0 +1 18 1 "1" 1 +0 +1 19 1 "" 917 +0 +1 20 1 "" 918 +0 +1 21 1 "" 920 +0 +1 22 1 "" 921 +0 +1 23 1 "" 922 +0 +1 24 2 "" 924 +0 +1 25 2 "" 916 +0 +1 26 2 "1" 1 +0 +1 27 2 "" 917 +0 +1 28 2 "" 918 +0 +1 29 2 "" 920 +0 +1 30 2 "" 921 +0 +1 31 2 "" 922 +0 +2 15 +"unitsText" +"" +1 430 40 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +129 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 430 40 600 56 +0 2 2 "0s" 0 0 0 192 0 0 430 40 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 23 CS001 has 1 PowerModule +0 +LAYER, 1 +1 +LANG:1 6 Layer2 +0 +LAYER, 2 +1 +LANG:1 6 Layer3 +0 +LAYER, 3 +1 +LANG:1 6 Layer4 +0 +LAYER, 4 +1 +LANG:1 6 Layer5 +0 +LAYER, 5 +1 +LANG:1 6 Layer6 +0 +LAYER, 6 +1 +LANG:1 6 Layer7 +0 +LAYER, 7 +1 +LANG:1 6 Layer8 +0 +3 1 "powerunit0" -1 +"objects\\Hardware\\Station_PowerUnit.pnl" 7 213 T 128 U +1 +"$unitNumber""0" +3 2 "powerunit1" -1 +"objects\\Hardware\\Station_PowerUnit.pnl" 7 513 T 129 U +1 +"$unitNumber""1" +0 diff --git a/MAC/Navigator2/panels/Test/Navigator_testPanel.pnl b/MAC/Navigator2/panels/Test/Navigator_testPanel.pnl new file mode 100644 index 00000000000..021f0661b49 --- /dev/null +++ b/MAC/Navigator2/panels/Test/Navigator_testPanel.pnl @@ -0,0 +1,72 @@ +V 11 +1 +LANG:1 0 +PANEL,-1 -1 1205 862 N "_3DFace" 0 +E E E E E 1 -1 -1 0 150 150 +""0 1 +E E 2 +"CBRef" "1" +"EClose" E +"" +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 0 +28 0 +"TAB1" +"" +1 0 0 E E E 1 E 1 E N "_3DText" E N "_3DFace" E E + E E +0 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 -2 -2 1212 872 +E2 "Events" 1 +LANG:1 6 Events +1 "Test/Event_Viewer.pnl" 1 +LANG:1 0 +0 + +"Claims" 1 +LANG:1 6 Claims +1 "Test/Claim_Viewer.pnl" 1 +LANG:1 0 +0 + + +0 +LAYER, 1 +1 +LANG:1 0 +0 +LAYER, 2 +1 +LANG:1 0 +0 +LAYER, 3 +1 +LANG:1 0 +0 +LAYER, 4 +1 +LANG:1 0 +0 +LAYER, 5 +1 +LANG:1 0 +0 +LAYER, 6 +1 +LANG:1 0 +0 +LAYER, 7 +1 +LANG:1 0 +0 +0 diff --git a/MAC/Navigator2/panels/objects/Hardware/Station_PowerUnit.pnl b/MAC/Navigator2/panels/objects/Hardware/Station_PowerUnit.pnl new file mode 100644 index 00000000000..3883d19d67e --- /dev/null +++ b/MAC/Navigator2/panels/objects/Hardware/Station_PowerUnit.pnl @@ -0,0 +1,504 @@ +V 11 +1 +LANG:1 8 (NoName) +PANEL,-1 -1 1063 427 N "_3DFace" 1 +"$unitNumber" +"main() +{ + powerunitNr = $unitNumber; + baseDP = sysName+\"LOFAR_PIC_POWEC\" + powerunitNr; + + if (dpGet(sysName+\"LOFAR_PIC_StationInfo.nrOfPowerUnits\",nrOfPowerUnits) == -1) + { + LOG_TRACE(\"Station_PowerUnits.pnl:prepareHardwareList|failed to get \"+sysName+\"LOFAR_PIC_StationInfo.nrOfPowerUnits\"); + return; + } + + // check if this powerunit is available in the system and needs to be filled needs to be filled + if (powerunitNr >= nrOfPowerUnits ) + { + this.enabled(false); + border.visible(false); + setValue(\"selfState\", \"visible\", false); + setValue(\"powerunit_highlight\", \"visible\", false); + unitname.visible(false); + valueText.visible(false); + alarmText.visible(false); + powerValueTable.visible(false); + powerAlarmTable.visible(false); + return; + } + + if(dpExists(baseDP +\".nrOfModules:_online.._value\")) + { + dpConnect(\"setPUvalues\", baseDP +\".nrOfModules:_online.._value\", + baseDP +\".voltage:_online.._value\", + baseDP +\".current:_online.._value\", + baseDP +\".temperature:_online.._value\", + baseDP +\".OK:_online.._value\", + baseDP +\".nrOfModules:_online.._invalid\"); + dpConnect(\"setPUalarms\", baseDP +\".nrOfAlarms:_online.._value\", + baseDP +\".alarmTime:_online.._value\", + baseDP +\".alarmText:_online.._value\", + baseDP +\".alarmType:_online.._value\", + baseDP +\".alarmReason:_online.._value\", + baseDP +\".nrOfAlarms:_online.._invalid\"); + if (!navFunct_dpReachable(baseDP)) + { + setPUvalues(\"\",0, + \"\",makeDynInt(), + \"\",makeDynInt(), + \"\",makeDynInt(), + \"\",makeDynInt(), + \"\",true); + setPUalarms(\"\",0, + \"\",makeDynString(), + \"\",makeDynString(), + \"\",makeDynInt(), + \"\",makeDynInt(), + \"\",true); + } + } + + + setValue(\"unitname\",\"text\",\"POWEC: \" + powerunitNr); + + // pass baseDP to selfstate Object to work with + setValue(\"selfState.light\",\"toolTipText\",baseDP); + + if (dpExists(baseDP) ){ + // connect for selfUpdates + showSelfState(baseDP); + } +} + +void setPUvalues(string dp1, int nrOfModules, + string dp2, dyn_int voltage, + string dp3, dyn_int current, + string dp4, dyn_int temperature, + string dp5, dyn_int OK, + string dp6, bool invalid) +{ + // clear the table + powerValueTable.deleteAllLines(); + if (!invalid) + { + powerValueTable.backCol(\"_Transparent\"); + // fill the table + for (int i = 1; i <= nrOfModules; i++) + { + // sometimes the number of values for one or more single entries isn't the same as the number of available modules. + // we want to be able to see the remaining correct values, so we have to skip those + float v = 0.0; + float c = 0.0; + int t = 0; + int s = 0; + if (i <= dynlen(voltage)) v = voltage[i]/10.; + if (i <= dynlen(current)) c = current[i]/100.; + if (i <= dynlen(temperature)) t = temperature[i]; + if (i <= dynlen(OK)) s = OK[i]; + + + powerValueTable.appendLine(\"Voltage\",v,\"Current\",c,\"Temperature\",t,\"Status\",s); + } + } + else + { + setValue(\"unitname\",\"text\",\"POWEC: \" + powerunitNr + \" INVALID\"); + powerValueTable.foreCol(\"Lofar_invalid\"); + } +} + +void setPUalarms(string dp1, int nrOfAlarms, + string dp2, dyn_string alarmtime, + string dp3, dyn_string alarmtext, + string dp4, dyn_int alarmtype, + string dp5, dyn_int alarmreason, + string dp6, bool invalid) +{ + // clear the table + powerAlarmTable.deleteAllLines(); + if (!invalid) + { + powerAlarmTable.backCol(\"_Transparent\"); + // fill the table + for (int i = 1; i <= nrOfAlarms; i++) + { + // sometimes the number of values for one or more single entries isn't the same as the number of available modules. + // we want to be able to see the remaining correct values, so we have to skip those + string ti = \"\"; + string tx = \"\"; + int tp = 0; + int r = 0; + if (i <= dynlen(alarmtime)) ti = alarmtime[i]; + if (i <= dynlen(alarmtext)) tx = alarmtext[i]; + if (i <= dynlen(alarmtype)) tp = alarmtype[i]; + if (i <= dynlen(alarmreason)) r = alarmreason[i]; + + powerAlarmTable.appendLine(\"Time\",ti,\"Text\",tx,\"Type\",tp,\"Reason\",r); + } + } + else + { + powerAlarmTable.foreCol(\"Lofar_invalid\"); + } +}" 0 + E "main() +{ + click(); +}" 0 + "main() +{ + rClick(); +}" 0 + "main() +{ + dblClick(); +}" 0 + 1 0 0 0 17 191 +""0 1 +E "#uses \"navPanel.ctl\" + +string baseDP = \"\"; +int powerunitNr = -1; +bool bDoubleClicked = false; +int nrOfPowerUnits = 1; + +// routine for single mouse click +void click() { + // set delay in case double click was meant + delay(0, 100); + if (!bDoubleClicked) { + navPanel_setEvent(\"POWEC\"+powerunitNr,\"EventClick\"); + } +} + +// routine for double mouse click +void dblClick() { + // indicate this is a doubleClick + bDoubleClicked = true; + + if (dpExists(baseDP) ) { + LOG_DEBUG(\"Station_PowerUnit:DoubleClick|Setting currentDatapoint from : \"+g_currentDatapoint+\" to \"+baseDP); + g_currentDatapoint=baseDP; + navPanel_setEvent(\"POWEC\"+powerunitNr,\"ChangePanel\"); + } + + // set delay to avoid click event will be triggered + delay(0, 500); + bDoubleClicked = false; +} + +// routine for right mouse click +void rClick() { + navPanel_setEvent(\"POWEC\"+powerunitNr,\"EventRightClick\"); +}" 0 + 2 +"CBRef" "1" +"EClose" E +"" +1 +DISPLAY_LAYER, 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 +LAYER, 0 +1 +LANG:1 6 Layer1 +6 924 +"powerunit_highlight" +"" +1 110 90 E E E 1 E 0 E N "_Transparent" E N "Lofar_highLight" E E + "main() +{ + rClick(); +}" 0 + "main() +{ + dblClick(); +}" 0 + +97 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +"main() +{ + dpConnect( \"PowerunitCallback\",true,DPNAME_NAVIGATOR + g_navigatorID +\".objectTrigger\" ); +} + +void PowerunitCallback(string dp1, bool aTrig) { + + LOG_DEBUG(\"Station_Powerunit.pnl:PowerunitCallback| ObjectTrigger Callback on: \"+dp1+\" trigger: \"+aTrig); + LOG_DEBUG(\"Station_Powerunit.pnl:PowerunitCallback|Found highlight : \" + highlight + \" Looking for: POWEC\" + powerunitNr); + bool bHighlight=false; + if (dynContains(highlight,\"POWEC\"+powerunitNr)) { + bHighlight=true; + } + LOG_DEBUG(\"Station_Powerunit.pnl:PowerunitCallback|Highlight request: \"+bHighlight); + powerunit_highlight.visible = bHighlight; +}" 0 + "main() +{ + click(); +}" 0 + 0 1 1 2 1 E U 1 E 127 97 957 307 +6 916 +"border" +"" +1 110 100 E E E 1 E 1 E N {0,0,0} E N "_3DFace" E E + "main() +{ + rClick(); +}" 0 + "main() +{ + dblClick(); +}" 0 + +91 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E "main() +{ + click(); +}" 0 + 0 1 1 2 1 E 1.138888888888889 0 1 6.7222222222223 2 1 E 110 100 830 300 +1 923 1 "" 1 +0 +2 917 +"unitname" +"" +1 132 82 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + E E +92 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E E 0 1 1 2 1 E U 0 E 132 82 214 98 +0 2 2 "0s" 0 0 0 192 0 0 132 82 1 +1 +LANG:1 35 MS Shell Dlg 2,10,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 12 Power Unit 0 +25 918 +"powerValueTable" +"" +1 142 142 E E E 1 E 1 E N "_WindowText" E N "_Transparent" E E + "main(int row, string column) +{ + rClick(); +}" 0 + "main(int row, string column) +{ + dblClick(); +}" 0 + +93 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 34 MS Shell Dlg 2,8,-1,5,50,0,0,0,0,0 +0 140 140 424 284 +E"main(int row, string column, string value) +{ + click(); + +}" 0 + 1 0 1 4 0 "Voltage" 6 1 0 "[3.2f,False,False,ALIGNMENT_END,False]" 1 +LANG:1 7 Voltage +E +1 +LANG:1 0 + +60 "Current" 6 1 0 "[3.2f,False,False,ALIGNMENT_END,False]" 1 +LANG:1 7 Current +E +1 +LANG:1 0 + +60 "Temperature" 8 1 0 "[3.2f,False,False,ALIGNMENT_END,False]" 1 +LANG:1 11 Temperature +E +1 +LANG:1 0 + +74 "Status" 6 1 0 "[5s,,,ALIGNMENT_CENTER]" 1 +LANG:1 6 Status +E +1 +LANG:1 0 + +60 +15 15 10 10 +1 +LANG:1 34 MS Shell Dlg 2,8,-1,5,50,0,0,0,0,0 +0 0 1 1 1 7 +1 0 +25 920 +"powerAlarmTable" +"" +1 472 142 E E E 1 E 1 E N "_WindowText" E N "_Transparent" E E + "main(int row, string column) +{ + rClick(); +}" 0 + "main(int row, string column) +{ + dblClick(); +}" 0 + +94 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +0 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 470 140 934 284 +E"main(int row, string column, string value) +{ + click(); + +}" 0 + 1 0 1 4 0 "Time" 11 1 0 "[0s,,,ALIGNMENT_CENTER]" 1 +LANG:1 4 Time +E +1 +LANG:1 0 + +100 "Text" 30 1 0 "[0s,,,ALIGNMENT_BEGINNING]" 1 +LANG:1 4 Text +E +1 +LANG:1 0 + +250 "Type" 2 1 0 "[0d,False,False,ALIGNMENT_BEGINNING,False]" 1 +LANG:1 4 Type +E +1 +LANG:1 0 + +25 "Reason" 5 1 0 "[0d,False,False,ALIGNMENT_BEGINNING,False]" 1 +LANG:1 6 Reason +E +1 +LANG:1 0 + +50 +15 15 10 10 +1 +LANG:1 37 MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0 +0 0 1 1 1 7 +1 0 +2 921 +"valueText" +"" +1 220 120 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + "main() +{ + rClick(); +}" 0 + "main() +{ + dblClick(); +}" 0 + +95 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E "main() +{ + click(); +}" 0 + 0 1 1 2 1 E U 0 E 220 120 257 133 +0 2 2 "0s" 0 0 0 192 0 0 220 120 1 +1 +LANG:1 34 MS Shell Dlg 2,8,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 6 Values +2 922 +"alarmText" +"" +1 662 122 E E E 1 E 1 E N "_WindowText" E N "_Window" E E + "main() +{ + rClick(); +}" 0 + "main() +{ + dblClick(); +}" 0 + +96 0 0 0 0 0 +E E E +0 +1 +LANG:1 0 + +1 +"dashclr"N "_Transparent" +E "main() +{ + click(); +}" 0 + 0 1 1 2 1 E U 0 E 662 122 702 135 +0 2 2 "0s" 0 0 0 192 0 0 662 122 1 +1 +LANG:1 34 MS Shell Dlg 2,8,-1,5,75,0,0,0,0,0 +0 1 +LANG:1 6 Alarms +0 +LAYER, 1 +1 +LANG:1 6 Layer2 +0 +LAYER, 2 +1 +LANG:1 6 Layer3 +0 +LAYER, 3 +1 +LANG:1 6 Layer4 +0 +LAYER, 4 +1 +LANG:1 6 Layer5 +0 +LAYER, 5 +1 +LANG:1 6 Layer6 +0 +LAYER, 6 +1 +LANG:1 6 Layer7 +0 +LAYER, 7 +1 +LANG:1 6 Layer8 +0 +3 1 "selfState" -1 +"objects\\lofar_self_state.pnl" 363 291 T 87 1 0 1 -223 -182 +0 +0 diff --git a/MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl b/MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl index d8281fed8cb..41ac46b34f6 100644 --- a/MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl +++ b/MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl @@ -8,6 +8,7 @@ PANEL,-1 -1 388 166 N "_3DFace" 1 dyn_dyn_anytype tab; station = $station+\":\"; + baseDP=station+\"LOFAR_PIC_POWEC0\"; if (dpExists(station+\"LOFAR_PIC_StationInfo.nrOfPowerUnits\")) { @@ -165,7 +166,19 @@ void update2Powecs(string dp1, int nrOfModules1, } " 0 - E E E E 1 -1 -1 0 0 0 + E "main(int x, int y) +{ + click(); +}" 0 + "main() +{ + rClick(); +}" 0 + "main(int x, int y) +{ + dblClick(); +}" 0 + 1 -1 -1 0 0 0 ""0 1 E "#uses \"navPanel.ctl\" string station = \"\"; @@ -188,8 +201,8 @@ void dblClick() { // indicate this is a doubleClick bDoubleClicked = true; - if (dpExists(baseDP) ) { - LOG_DEBUG(\"BPTemp_small.pnl:DoubleClick|Setting currentDatapoint from : \"+g_currentDatapoint+\" to \"+baseDP); + if (dpExists(baseDP) ) { + LOG_DEBUG(\"powerUnit_small.pnl:DoubleClick|Setting currentDatapoint from : \"+g_currentDatapoint+\" to \"+baseDP); g_currentDatapoint=baseDP; navPanel_setEvent(station,\"ChangePanel\"); } diff --git a/MAC/Navigator2/scripts/libs/navFunct.ctl b/MAC/Navigator2/scripts/libs/navFunct.ctl index f05a3584db7..bb2f74ad007 100644 --- a/MAC/Navigator2/scripts/libs/navFunct.ctl +++ b/MAC/Navigator2/scripts/libs/navFunct.ctl @@ -1545,6 +1545,15 @@ void navFunct_fillHardwareTree() { } else { + // add PowerUnits + if (dynlen(g_powerUnitList) > 0) { + for (int i = 1; i <= dynlen(g_powerUnitList); i++) { + dp = station+":LOFAR_PIC_POWEC"+g_powerUnitList[i]; + dynAppend(result,connectTo+",POWEC"+g_powerUnitList[i]+","+dp); + } + lvl="PowerUnit"; + } + // add Cabinets if (dynlen(g_cabinetList) > 0) { for (int i = 1; i <= dynlen(g_cabinetList); i++) { @@ -1825,6 +1834,7 @@ void navFunct_clearGlobalLists() { dynClear(g_subrackList); dynClear(g_uriBoardList); dynClear(g_uniBoardList); + dynClear(g_powerUnitList); dynClear(g_FPGAList); dynClear(g_RSPList); dynClear(g_TBBList); diff --git a/MAC/Navigator2/scripts/libs/navigator.ctl b/MAC/Navigator2/scripts/libs/navigator.ctl index d868b5b3d0a..0c38275bb24 100644 --- a/MAC/Navigator2/scripts/libs/navigator.ctl +++ b/MAC/Navigator2/scripts/libs/navigator.ctl @@ -80,6 +80,7 @@ global dyn_int g_TBBList; // holds valid TBB's for choices in the v global dyn_int g_RCUList; // holds valid RCU's for choices in the viewBox global dyn_int g_HBAList; // holds valid HBAAntenna's for choices in the viewBox global dyn_int g_LBAList; // holds valid LBAAntenna's for choices in the viewBox +global dyn_int g_powerUnitList; // holds valid PowerUnits for choices in the viewBox // CEP based globals global dyn_int g_OSRackList; // holds valid Offline/Storageracks for choices in view global dyn_int g_locusNodeList; // holds valid storagenodes for choices in view -- GitLab From bd74530ac3a6551a28a9c4430ee75c0055903dfc Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 25 Aug 2016 08:37:23 +0000 Subject: [PATCH 687/933] Task #9682: Fix SLURM job status if failed, and avoid requeueing of pipeline job on node failure because SLURM messes up its administation (mixes old and new job, makes job unfindable by jobname, triggers abort-trigger job anyway) --- MAC/Services/src/PipelineControl.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 851c0701aa3..ffee99a53ac 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -424,9 +424,6 @@ class PipelineControl(OTDBBusListener): # Enforce the dependencies, instead of creating lingering jobs "--kill-on-invalid-dep=yes", - # Restart job if a node fails - "--requeue", - # Maximum run time for job (31 days) "--time=31-0", @@ -471,11 +468,11 @@ function runcmd {{ PID=$! wait $PID # returns the exit status of "wait" if interrupted wait $PID # returns the exit status of $PID - RESULT=$? + CMDRESULT=$? trap - SIGTERM SIGINT - return $RESULT + return $CMDRESULT }} # print some info -- GitLab From 20b3651d999871ae5fa90b46d84905d8d72e3936 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 25 Aug 2016 08:46:14 +0000 Subject: [PATCH 688/933] Task #9607: fix in getMaxPredecessorEndTime. upload auto-generated start/end time to otdb --- .../ResourceAssigner/lib/assignment.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index ce5f20e942d..7d615b1c329 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -140,6 +140,13 @@ class ResourceAssigner(): logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s one hour from now', startTime, endTime, otdb_id) + try: + logger.info('uploading auto-generated start/end time (%s, %s) to otdb for otdb_id=%s', startTime, endTime, otdb_id) + self.otdbrpc.taskSetSpecification(otdb_id, { 'LOFAR.ObsSW.Observation.startTime': startTime.strftime('%Y-%m-%d %H:%M:%S'), + 'LOFAR.ObsSW.Observation.stopTime': endTime.strftime('%Y-%m-%d %H:%M:%S')}) + except Exception as e: + logger.error(e) + # insert new task and specification in the radb # any existing specification and task with same otdb_id will be deleted automatically logger.info('doAssignment: insertSpecification momId=%s, otdb_id=%s, status=%s, taskType=%s, startTime=%s, endTime=%s' % @@ -276,7 +283,7 @@ class ResourceAssigner(): def getMaxPredecessorEndTime(self, specification_tree): try: - predecessor_specs = [tree['specification'] for tree in specification_tree['predecessors']] + predecessor_specs = [parameterset(tree['specification']) for tree in specification_tree['predecessors']] predecessor_endTimes = [datetime.strptime(spec.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') for spec in predecessor_specs] if predecessor_endTimes: -- GitLab From 844cb932af343c1476d9aa1272c369e83b1b16da Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 25 Aug 2016 08:48:13 +0000 Subject: [PATCH 689/933] Task #9607: derive all pipeline estimators from common base_pipeline_estimator. return default pipeline duration of 1 hour in case start/end time are not filled in --- .gitattributes | 1 + .../resource_estimators/CMakeLists.txt | 1 + .../base_pipeline_estimator.py | 51 +++++++++++++++++++ .../calibration_pipeline.py | 6 +-- .../resource_estimators/image_pipeline.py | 6 +-- .../longbaseline_pipeline.py | 6 +-- .../resource_estimators/pulsar_pipeline.py | 6 +-- 7 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_pipeline_estimator.py diff --git a/.gitattributes b/.gitattributes index d6a225875dd..95ee64a43be 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5295,6 +5295,7 @@ SAS/ResourceAssignment/ResourceAssignmentEstimator/raestimatorservice -text SAS/ResourceAssignment/ResourceAssignmentEstimator/raestimatorservice.ini -text SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/CMakeLists.txt -text SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/__init__.py -text +SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_pipeline_estimator.py -text SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_resource_estimator.py -text SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py -text SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py -text diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/CMakeLists.txt index 6c42b9e1eed..ccfd9a6d16a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/CMakeLists.txt @@ -3,6 +3,7 @@ set(_py_files __init__.py base_resource_estimator.py + base_pipeline_estimator.py observation.py longbaseline_pipeline.py calibration_pipeline.py diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_pipeline_estimator.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_pipeline_estimator.py new file mode 100644 index 00000000000..1d666829ccb --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/base_pipeline_estimator.py @@ -0,0 +1,51 @@ +# base_resource_estimator.py +# +# Copyright (C) 2016 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: base_resource_estimator.py 33534 2016-02-08 14:28:26Z schaap $ + +import logging +import pprint +from math import ceil +from base_resource_estimator import BaseResourceEstimator + +logger = logging.getLogger(__name__) + +DATAPRODUCTS = "Observation.DataProducts." +PIPELINE = "Observation.ObservationControl.PythonControl." + +#Observation.DataProducts.Output_Correlated.storageClusterName= + +class BasePipelineResourceEstimator(BaseResourceEstimator): + """ base ResourceEstimator for all Pipelines + """ + def __init__(self, name): + logger.info("init BasePipelineResourceEstimator") + super(BasePipelineResourceEstimator, self).__init__(name=name) + + def _getDuration(self, start, end): + # pipelines could be prescheduled for the resource assigner + # without a proper start/end time + # just return a default duration in that case, because slurm will handle the start/end time + try: + return super(BasePipelineResourceEstimator, self)._getDuration(start, end) + except Exception as e: + logger.error(e) + logger.info("Could not get duration from parset, returning default pipeline duration of 1 hour") + return 3600 diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py index 7dac29c602c..fe87a4eab80 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/calibration_pipeline.py @@ -23,7 +23,7 @@ import logging import pprint from math import ceil -from base_resource_estimator import BaseResourceEstimator +from base_pipeline_estimator import BasePipelineResourceEstimator logger = logging.getLogger(__name__) @@ -32,12 +32,12 @@ PIPELINE = "Observation.ObservationControl.PythonControl." #Observation.DataProducts.Output_Correlated.storageClusterName= -class CalibrationPipelineResourceEstimator(BaseResourceEstimator): +class CalibrationPipelineResourceEstimator(BasePipelineResourceEstimator): """ ResourceEstimator for Calibration Pipelines """ def __init__(self): logger.info("init CalibrationPipelineResourceEstimator") - BaseResourceEstimator.__init__(self, name='pipeline') #FIXME name='calibration_pipeline' + BasePipelineResourceEstimator.__init__(self, name='pipeline') #FIXME name='calibration_pipeline' self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_Correlated.enabled', diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py index 3dfaaa10eea..d09c30d9d1b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/image_pipeline.py @@ -22,7 +22,7 @@ import logging from math import ceil -from base_resource_estimator import BaseResourceEstimator +from base_pipeline_estimator import BasePipelineResourceEstimator from lofar.parameterset import parameterset logger = logging.getLogger(__name__) @@ -33,12 +33,12 @@ PIPELINE = "Observation.ObservationControl.PythonControl." #Observation.DataProducts.Output_Correlated.storageClusterName= #Observation.ObservationControl.PythonControl.AWimager -class ImagePipelineResourceEstimator(BaseResourceEstimator): +class ImagePipelineResourceEstimator(BasePipelineResourceEstimator): """ ResourceEstimator for Imaging Pipelines """ def __init__(self): logger.info("init ImagePipelineResourceEstimator") - BaseResourceEstimator.__init__(self, name='pipeline') #FIXME name='imaging_pipeline' + BasePipelineResourceEstimator.__init__(self, name='pipeline') #FIXME name='imaging_pipeline' self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_Correlated.enabled', diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py index 2a6678c0f5a..100dc061d2b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/longbaseline_pipeline.py @@ -22,7 +22,7 @@ import logging from math import ceil -from base_resource_estimator import BaseResourceEstimator +from base_pipeline_estimator import BasePipelineResourceEstimator logger = logging.getLogger(__name__) @@ -31,12 +31,12 @@ PIPELINE = "Observation.ObservationControl.PythonControl." #Observation.DataProducts.Output_Correlated.storageClusterName= -class LongBaselinePipelineResourceEstimator(BaseResourceEstimator): +class LongBaselinePipelineResourceEstimator(BasePipelineResourceEstimator): """ ResourceEstimator for Long Baseline Pipelines """ def __init__(self): logger.info("init LongBaselinePipelineResourceEstimator") - BaseResourceEstimator.__init__(self, name='pipeline') #FIXME name='longbaseline_pipeline' + BasePipelineResourceEstimator.__init__(self, name='pipeline') #FIXME name='longbaseline_pipeline' self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_Correlated.enabled', diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py index c6ae0475d83..a36a1d718db 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/resource_estimators/pulsar_pipeline.py @@ -22,7 +22,7 @@ import logging from math import ceil -from base_resource_estimator import BaseResourceEstimator +from base_pipeline_estimator import BasePipelineResourceEstimator logger = logging.getLogger(__name__) @@ -31,12 +31,12 @@ PIPELINE = "Observation.ObservationControl.PythonControl." #Observation.DataProducts.Output_Correlated.storageClusterName= -class PulsarPipelineResourceEstimator(BaseResourceEstimator): +class PulsarPipelineResourceEstimator(BasePipelineResourceEstimator): """ ResourceEstimator for Pulsar Pipelines """ def __init__(self): logger.info("init PulsarPipelineResourceEstimator") - BaseResourceEstimator.__init__(self, name='pipeline') #FIXME name='pulsar_pipeline' + BasePipelineResourceEstimator.__init__(self, name='pipeline') #FIXME name='pulsar_pipeline' self.required_keys = ('Observation.startTime', 'Observation.stopTime', DATAPRODUCTS + 'Input_CoherentStokes.enabled', -- GitLab From bb260d47a9b4fe3e09ad8c47750d7941c2eda13d Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Thu, 25 Aug 2016 08:56:50 +0000 Subject: [PATCH 690/933] Task #9127: revert part of r35157 that removes AOFlagger_SOURCE_DIR from LofarPackageList.cmake to restore daily LofIm build on lhn002. This is not the real solution, the daily build must be fixed to take AOFlagger externally. This will break again on the next run+commit of gen_LofarPackageList_cmake.sh. --- CMake/LofarPackageList.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index d5db2f0d8fa..de4b618b209 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -37,6 +37,7 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(PythonDPPP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/PythonDPPP) set(DPPP_AOFlag_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/DPPP_AOFlag) set(SPW_Combine_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/SPWCombine) + set(AOFlagger_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/AOFlagger) set(LofarFT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Imager/LofarFT) set(AWImager2_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Imager/AWImager2) set(Laps-GRIDInterface_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/LAPS/GRIDInterface) -- GitLab From 33ac876343b31cd6b223eb3b29a4cd6b822eaccd Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 25 Aug 2016 09:36:00 +0000 Subject: [PATCH 691/933] Task #9607: switch status for CEP4 pipelines via webscheduler --- .../static/app/controllers/datacontroller.js | 9 ++- .../static/app/controllers/gridcontroller.js | 55 +++++++++++++++++-- .../lib/webservice.py | 13 +++-- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 72026b03b07..7a007f76a42 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -161,9 +161,12 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }; self.convertLocalUTCDateToISOString = function(local_utc_date) { - //reverse trick to offset the timestamp with the utcOffset, see explanation above. - var real_utc = new Date(local_utc_date.getTime() + self.utcOffset) - return real_utc.toISOString(); + if(local_utc_date) { + //reverse trick to offset the timestamp with the utcOffset, see explanation above. + var real_utc = new Date(local_utc_date.getTime() + self.utcOffset) + return real_utc.toISOString(); + } + return undefined; }; //local client time offset to utc in milliseconds diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 290e0ff68d4..a9fd470e597 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -481,10 +481,7 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do contextmenuElement.append(ulElement); var selected_tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); - var selected_cep4_tasks = selected_tasks.filter(function(t) { - var task_claims = dataService.resourceClaims.filter(function(rc) { return rc.task_id == t.id;}); - return task_claims.length > 0; - }); + var selected_cep4_tasks = selected_tasks.filter(function(t) { return t['cluster'] == 'CEP4'; }); // var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); // ulElement.append(liElement); @@ -538,6 +535,56 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } + var approved_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return t.status == 'approved' && t.type == 'pipeline'; }); + + if(approved_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Schedule approved CEP4 pipelines</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + for(var pl of approved_selected_cep4_pipelines) { + var newTask = { id: pl.id, status: 'prescheduled' }; + dataService.putTask(newTask); + } + }); + } + + var scheduled_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'prescheduled' || t.status == 'scheduled' || t.status == 'queued') && t.type == 'pipeline'; }); + + if(scheduled_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Unschedule (pre)scheduled CEP4 pipelines</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + for(var pl of scheduled_selected_cep4_pipelines) { + if(pl.status == 'queued') { + var newTask = { id: pl.id, status: 'aborted' }; + dataService.putTask(newTask); + } + + var newTask = { id: pl.id, status: 'approved' }; + dataService.putTask(newTask); + } + }); + } + + var active_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return t.status == 'active' && t.type == 'pipeline'; }); + + if(active_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Abort active CEP4 pipelines</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + for(var pl of active_selected_cep4_pipelines) { + var newTask = { id: pl.id, status: 'aborted' }; + dataService.putTask(newTask); + } + }); + } + var closeContextMenu = function(cme) { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index ca90ee0ef90..740070bec6b 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -280,9 +280,6 @@ def getTasksByMoMGroupId(mom_group_id): @app.route('/rest/tasks/<int:task_id>', methods=['PUT']) def putTask(task_id): - if isProductionEnvironment(): - abort(403, 'Editing of tasks is by users is not yet approved') - if 'Content-Type' in request.headers and \ request.headers['Content-Type'].startswith('application/json'): updatedTask = json.loads(request.data) @@ -292,12 +289,18 @@ def putTask(task_id): abort(404) if 'starttime' in updatedTask: + if isProductionEnvironment(): + abort(403, 'Editing of startime of tasks by users is not yet approved') + try: updatedTask['starttime'] = asDatetime(updatedTask['starttime']) except ValueError: abort(400, 'timestamp not in iso format: ' + updatedTask['starttime']) if 'endtime' in updatedTask: + if isProductionEnvironment(): + abort(403, 'Editing of endtime of tasks by users is not yet approved') + try: updatedTask['endtime'] = asDatetime(updatedTask['endtime']) except ValueError: @@ -305,8 +308,8 @@ def putTask(task_id): logger.info('putTask: ' + str(updatedTask)) rarpc.updateTaskAndResourceClaims(task_id, - starttime=updatedTask.get('starttime', None), - endtime=updatedTask.get('endtime', None), + #starttime=updatedTask.get('starttime', None), + #endtime=updatedTask.get('endtime', None), task_status=updatedTask.get('status', None)) return "", 204 -- GitLab From 127358fdec37e5d4d3ac9125957d8f26a75e84e5 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 25 Aug 2016 10:09:10 +0000 Subject: [PATCH 692/933] Task #9607: fix in switch status for CEP4 pipelines via webscheduler: put status in otdb, and let RA respond to that --- .../static/app/controllers/gridcontroller.js | 2 +- .../angular-gantt-contextmenu-plugin.js | 59 +++++++++++++++++-- .../lib/webservice.py | 24 ++++++-- 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index a9fd470e597..5a90e5ac225 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -553,7 +553,7 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do var scheduled_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'prescheduled' || t.status == 'scheduled' || t.status == 'queued') && t.type == 'pipeline'; }); if(scheduled_selected_cep4_pipelines.length > 0) { - var liContent = '<li><a href="#">Unschedule (pre)scheduled CEP4 pipelines</a></li>' + var liContent = '<li><a href="#">Unschedule (pre)scheduled/queued CEP4 pipelines</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); liElement.on('click', function() { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index 7f4c7306f32..00f6a2d53a3 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -58,10 +58,7 @@ contextmenuElement.append(ulElement); var selected_tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); - var selected_cep4_tasks = selected_tasks.filter(function(t) { - var task_claims = dataService.resourceClaims.filter(function(rc) { return rc.task_id == t.id;}); - return task_claims.length > 0; - }); + var selected_cep4_tasks = selected_tasks.filter(function(t) { return t['cluster'] == 'CEP4'; }); // var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); // ulElement.append(liElement); @@ -114,6 +111,60 @@ }); } + var approved_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return t.status == 'approved' && t.type == 'pipeline'; }); + + if(approved_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Schedule approved CEP4 pipelines</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + for(var pl of approved_selected_cep4_pipelines) { + var newTask = { id: pl.id, status: 'prescheduled' }; + dataService.putTask(newTask); + } + }); + } + + var scheduled_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'prescheduled' || t.status == 'scheduled' || t.status == 'queued') && t.type == 'pipeline'; }); + + if(scheduled_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Unschedule (pre)scheduled/queued CEP4 pipelines</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + for(var pl of scheduled_selected_cep4_pipelines) { + if(pl.status == 'queued') { + var newTask = { id: pl.id, status: 'aborted' }; + dataService.putTask(newTask); + } + + var newTask = { id: pl.id, status: 'approved' }; + dataService.putTask(newTask); + } + }); + } + + var active_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return t.status == 'active' && t.type == 'pipeline'; }); + + if(active_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Abort active CEP4 pipelines</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + for(var pl of active_selected_cep4_pipelines) { + var newTask = { id: pl.id, status: 'aborted' }; + dataService.putTask(newTask); + } + }); + } + + + + + var closeContextMenu = function() { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 740070bec6b..4d440585730 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -59,6 +59,8 @@ from lofar.sas.datamanagement.cleanup.config import DEFAULT_SERVICENAME as DEFAU from lofar.sas.datamanagement.storagequery.rpc import StorageQueryRPC from lofar.sas.datamanagement.storagequery.config import DEFAULT_BUSNAME as DEFAULT_STORAGEQUERY_BUSNAME from lofar.sas.datamanagement.storagequery.config import DEFAULT_SERVICENAME as DEFAULT_STORAGEQUERY_SERVICENAME +from lofar.sas.otdb.otdbrpc import OTDBRPC +from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME from lofar.common import isProductionEnvironment, isTestEnvironment from lofar.common.util import humanreadablesize #from lofar.sas.resourceassignment.resourceassigner. import updateTaskMomDetails @@ -104,6 +106,7 @@ app.json_encoder = CustomJSONEncoder rarpc = None momrpc = None +otdbrpc = None curpc = None sqrpc = None momqueryrpc = None @@ -307,11 +310,18 @@ def putTask(task_id): abort(400, 'timestamp not in iso format: ' + updatedTask['endtime']) logger.info('putTask: ' + str(updatedTask)) - rarpc.updateTaskAndResourceClaims(task_id, - #starttime=updatedTask.get('starttime', None), - #endtime=updatedTask.get('endtime', None), - task_status=updatedTask.get('status', None)) + if 'status' in updatedTask: + task = rarpc.getTask(task_id) + + if not task: + abort(404, "unknown task %s" % str(updatedTask)) + otdbrpc.taskSetStatus(task['otdb_id'], updatedTask['status']) + + #rarpc.updateTaskAndResourceClaims(task_id, + #starttime=updatedTask.get('starttime', None), + #endtime=updatedTask.get('endtime', None)) + return "", 204 except KeyError: abort(404) @@ -682,6 +692,8 @@ def main(): parser.add_option('--radb_servicename', dest='radb_servicename', type='string', default=DEFAULT_RADB_SERVICENAME, help='Name of the radbservice, default: %default') parser.add_option('--radb_notification_busname', dest='radb_notification_busname', type='string', default=DEFAULT_RADB_CHANGES_BUSNAME, help='Name of the notification bus exchange on the qpid broker on which the radb notifications are published, default: %default') parser.add_option('--radb_notification_subjects', dest='radb_notification_subjects', type='string', default=DEFAULT_RADB_CHANGES_SUBJECTS, help='Subject(s) to listen for on the radb notification bus exchange on the qpid broker, default: %default') + parser.add_option('--otdb_busname', dest='otdb_busname', type='string', default=DEFAULT_OTDB_SERVICE_BUSNAME, help='Name of the bus exchange on the qpid broker on which the otdbservice listens, default: %default') + parser.add_option('--otdb_servicename', dest='otdb_servicename', type='string', default=DEFAULT_OTDB_SERVICENAME, help='Name of the otdbservice, default: %default') parser.add_option('--mom_busname', dest='mom_busname', type='string', default=DEFAULT_MOM_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momservice listens, default: %default') parser.add_option('--mom_servicename', dest='mom_servicename', type='string', default=DEFAULT_MOM_SERVICENAME, help='Name of the momservice, default: %default') parser.add_option('--mom_broker', dest='mom_broker', type='string', default=None, help='Address of the qpid broker for the mom service, default: localhost') @@ -701,6 +713,8 @@ def main(): rarpc = RARPC(busname=options.radb_busname, servicename=options.radb_servicename, broker=options.broker) global momrpc momrpc = MoMRPC(busname=options.mom_busname, servicename=options.mom_servicename, timeout=2.5, broker=options.broker) + global otdbrpc + otdbrpc = OTDBRPC(busname=options.otdb_busname, servicename=options.otdb_servicename, broker=options.broker) global curpc curpc = CleanupRPC(busname=options.cleanup_busname, servicename=options.cleanup_servicename, broker=options.broker) global sqrpc @@ -710,7 +724,7 @@ def main(): global radbchangeshandler radbchangeshandler = RADBChangesHandler(options.radb_notification_busname, subjects=options.radb_notification_subjects, broker=options.broker, momqueryrpc=momqueryrpc, radbrpc=rarpc) - with radbchangeshandler, rarpc, curpc, sqrpc, momrpc, momqueryrpc: + with radbchangeshandler, rarpc, otdbrpc, curpc, sqrpc, momrpc, momqueryrpc: '''Start the webserver''' app.run(debug=options.verbose, threaded=True, host='0.0.0.0', port=options.port) -- GitLab From 841d1aed5777b1b9c72ef807962550a4e1bcf66a Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 25 Aug 2016 10:30:01 +0000 Subject: [PATCH 693/933] Task #9607: minor fix, filter out undefined tasks --- .../lib/static/app/controllers/gridcontroller.js | 1 + .../static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js | 1 + 2 files changed, 2 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 5a90e5ac225..5d3a03db921 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -481,6 +481,7 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do contextmenuElement.append(ulElement); var selected_tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); + selected_tasks = selected_tasks.filter(function(t) { return t != undefined; }); var selected_cep4_tasks = selected_tasks.filter(function(t) { return t['cluster'] == 'CEP4'; }); // var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index 00f6a2d53a3..864d971d400 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -58,6 +58,7 @@ contextmenuElement.append(ulElement); var selected_tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); + selected_tasks = selected_tasks.filter(function(t) { return t != undefined; }); var selected_cep4_tasks = selected_tasks.filter(function(t) { return t['cluster'] == 'CEP4'; }); // var liElement = angular.element('<li><a href="#">Copy Task</a></li>'); -- GitLab From f1fd58a7adb9763c61c13f3bbb7a2c2e6c6fc2dd Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 25 Aug 2016 10:37:58 +0000 Subject: [PATCH 694/933] Task #9607: default zoom 1 day --- .../lib/static/app/controllers/datacontroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 7a007f76a42..0ebfd32d4f2 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -882,7 +882,7 @@ dataControllerMod.controller('DataController', $scope.openViewFromDatePopup = function() { $scope.viewFromDatePopupOpened = true; }; $scope.openViewToDatePopup = function() { $scope.viewToDatePopupOpened = true; }; $scope.zoomTimespans = [{value:30, name:'30 Minutes'}, {value:60, name:'1 Hour'}, {value:3*60, name:'3 Hours'}, {value:6*60, name:'6 Hours'}, {value:12*60, name:'12 Hours'}, {value:24*60, name:'1 Day'}, {value:2*24*60, name:'2 Days'}, {value:3*24*60, name:'3 Days'}, {value:5*24*60, name:'5 Days'}, {value:7*24*60, name:'1 Week'}, {value:14*24*60, name:'2 Weeks'}, {value:28*24*60, name:'4 Weeks'}, {value:1, name:'Custom (1 min)'}]; - $scope.zoomTimespan = $scope.zoomTimespans[4]; + $scope.zoomTimespan = $scope.zoomTimespans[5]; $scope.jumpToNow = function() { var floorLofarTime = dataService.floorDate(dataService.lofarTime, 1, 5); dataService.viewTimeSpan = { -- GitLab From 9b1c1f08ffb5b0d9cfcb5a7265a5d4c2f0a75439 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Thu, 25 Aug 2016 11:25:17 +0000 Subject: [PATCH 695/933] Task #9783: Reintegrate task branch, DPPP averaging can now take the resolution --- CEP/DP3/DPPP/include/DPPP/Averager.h | 6 +++ CEP/DP3/DPPP/src/Averager.cc | 81 +++++++++++++++++++++++++--- CEP/DP3/DPPP/test/tAverager.cc | 37 +++++++++++++ 3 files changed, 116 insertions(+), 8 deletions(-) diff --git a/CEP/DP3/DPPP/include/DPPP/Averager.h b/CEP/DP3/DPPP/include/DPPP/Averager.h index c9bdec28fd0..5b43c5ef46d 100644 --- a/CEP/DP3/DPPP/include/DPPP/Averager.h +++ b/CEP/DP3/DPPP/include/DPPP/Averager.h @@ -91,6 +91,10 @@ namespace LOFAR { const casa::Cube<bool>& flags, int timeIndex); + // Get the value in Hertz of a string like "1000 MHz". If unit is + // omitted it defaults to Hertz + double getFreqHz(const string& freqstr); + //# Data members. DPInput* itsInput; string itsName; @@ -101,6 +105,8 @@ namespace LOFAR { casa::Cube<casa::Complex> itsAvgAll; casa::Cube<float> itsWeightAll; casa::Cube<bool> itsFullResFlags; + double itsFreqResolution; + double itsTimeResolution; uint itsNChanAvg; uint itsNTimeAvg; uint itsMinNPoint; diff --git a/CEP/DP3/DPPP/src/Averager.cc b/CEP/DP3/DPPP/src/Averager.cc index f761308d6ff..b1f739fa3b6 100644 --- a/CEP/DP3/DPPP/src/Averager.cc +++ b/CEP/DP3/DPPP/src/Averager.cc @@ -28,6 +28,8 @@ #include <Common/ParameterSet.h> #include <Common/LofarLogger.h> #include <casa/Arrays/ArrayMath.h> +#include <Common/StringUtil.h> + #include <iostream> #include <iomanip> @@ -41,22 +43,35 @@ namespace LOFAR { const string& prefix) : itsInput (input), itsName (prefix), - itsNChanAvg (parset.getUint (prefix+"freqstep", 1)), - itsNTimeAvg (parset.getUint (prefix+"timestep", 1)), itsMinNPoint (parset.getUint (prefix+"minpoints", 1)), itsMinPerc (parset.getFloat (prefix+"minperc", 0.) / 100.), itsNTimes (0), - itsTimeInterval (0) + itsTimeInterval (0), + itsNoAvg (true) { - if (itsNChanAvg <= 0) itsNChanAvg = 1; - if (itsNTimeAvg <= 0) itsNTimeAvg = 1; - itsNoAvg = (itsNChanAvg == 1 && itsNTimeAvg == 1); + string freqResolutionStr = parset.getString(prefix+"freqresolution","0"); + itsFreqResolution = getFreqHz(freqResolutionStr); + + if (itsFreqResolution > 0) { + itsNChanAvg = 0; // Will be set later in updateinfo + } else { + itsNChanAvg = parset.getUint (prefix+"freqstep", 1); + } + + itsTimeResolution = parset.getFloat(prefix+"timeresolution", 0.); + if (itsTimeResolution > 0) { + itsNTimeAvg = 0; // Will be set later in updateInfo + } else { + itsNTimeAvg = parset.getUint(prefix+"timestep", 1); + } } Averager::Averager (DPInput* input, const string& stepName, uint nchanAvg, uint ntimeAvg) : itsInput (input), itsName (stepName), + itsFreqResolution (0), + itsTimeResolution (0), itsNChanAvg (nchanAvg), itsNTimeAvg (ntimeAvg), itsMinNPoint (1), @@ -79,7 +94,27 @@ namespace LOFAR { info().setWriteData(); info().setWriteFlags(); info().setMetaChanged(); + + if (itsNChanAvg <= 0) { + if (itsFreqResolution > 0) { + double chanwidth = infoIn.chanWidths()[0]; + itsNChanAvg = std::max(1, (int)(itsFreqResolution / chanwidth + 0.5)); + } else { + itsNChanAvg = 1; + } + } + itsTimeInterval = infoIn.timeInterval(); + if (itsNTimeAvg <= 0) { + if (itsTimeResolution > 0) { + itsNTimeAvg = std::max(1, (int)(itsTimeResolution / itsTimeInterval + 0.5)); + } else { + itsNTimeAvg = 1; + } + } + + itsNoAvg = (itsNChanAvg == 1 && itsNTimeAvg == 1); + // Adapt averaging to available nr of channels and times. itsNTimeAvg = std::min (itsNTimeAvg, infoIn.ntime()); itsNChanAvg = info().update (itsNChanAvg, itsNTimeAvg); @@ -88,8 +123,15 @@ namespace LOFAR { void Averager::show (std::ostream& os) const { os << "Averager " << itsName << std::endl; - os << " freqstep: " << itsNChanAvg << std::endl; - os << " timestep: " << itsNTimeAvg << std::endl; + os << " freqstep: " << itsNChanAvg; + if (itsFreqResolution>0) { + os << " (set by freqresolution: " << itsFreqResolution << " Hz)" << std::endl; + } + os << " timestep: " << itsNTimeAvg; + if (itsTimeResolution>0) { + os << " (set by timeresolution: " << itsTimeResolution << ")"; + } + os << std::endl; os << " minpoints: " << itsMinNPoint << std::endl; os << " minperc: " << 100*itsMinPerc << std::endl; } @@ -339,5 +381,28 @@ namespace LOFAR { } } + double Averager::getFreqHz(const string& freqstr) { + String unit; + // See if a unit is given at the end. + String v(freqstr); + // Remove possible trailing blanks. + rtrim(v); + Regex regex("[a-zA-Z]+$"); + string::size_type pos = v.index (regex); + if (pos != String::npos) { + unit = v.from (pos); + v = v.before (pos); + } + // Set value and unit. + + double value = strToDouble(v); + if (unit.empty()) { + return value; + } else { + Quantity q(value, unit); + return q.getValue("Hz", true); + } + } + } //# end namespace } diff --git a/CEP/DP3/DPPP/test/tAverager.cc b/CEP/DP3/DPPP/test/tAverager.cc index 62e18028951..fa8d6856e6b 100644 --- a/CEP/DP3/DPPP/test/tAverager.cc +++ b/CEP/DP3/DPPP/test/tAverager.cc @@ -30,6 +30,8 @@ #include <casa/Arrays/ArrayMath.h> #include <casa/Arrays/ArrayLogical.h> #include <casa/Arrays/ArrayIO.h> + +#include <casa/Quanta/Quantum.h> #include <iostream> using namespace LOFAR; @@ -471,6 +473,37 @@ void test1(int ntime, int nbl, int nchan, int ncorr, execute (step1); } +// Like test 1, but specify target resolution +void test1resolution(int ntime, int nbl, int nchan, int ncorr, + double timeresolution, double freqresolution, + string frequnit, bool flag) +{ + cout << "test1: ntime=" << ntime << " nrbl=" << nbl << " nchan=" << nchan + << " ncorr=" << ncorr << " timeresolution=" << timeresolution + << " freqresolution=" << freqresolution << endl; + // Create the steps. + TestInput* in = new TestInput(ntime, nbl, nchan, ncorr, flag); + DPStep::ShPtr step1(in); + ParameterSet parset; + parset.add ("freqresolution", toString(freqresolution)+frequnit); + parset.add ("timeresolution", toString(timeresolution)); + DPStep::ShPtr step2(new Averager(in, parset, "")); + + if (!frequnit.empty()) { + Quantity q(freqresolution, frequnit); + freqresolution = q.getValue("Hz", true); + } + + int navgchan = std::max(1, int(freqresolution / 100000 + 0.5)); + int navgtime = std::max(1, int(timeresolution / 5. + 0.5)); + + DPStep::ShPtr step3(new TestOutput(ntime, nbl, nchan, ncorr, + navgtime, navgchan, flag)); + step1->setNextStep (step2); + step2->setNextStep (step3); + execute (step1); +} + // Like test1, but the averaging is done in two steps. void test2(int ntime, int nbl, int nchan, int ncorr, bool flag) { @@ -572,6 +605,10 @@ int main() test1(11, 3, 30, 2, 3, 3, false); test1(10, 3, 32, 4, 1, 32, false); test1(10, 3, 32, 1, 1, 1, false); + + test1resolution(10, 3, 32, 4, 10., 100000, "Hz", false); + test1resolution(11, 3, 32, 4, 1., 800, "kHz", false); + test1resolution(11, 3, 32, 4, 15., 0.4, "MHz", false); test2(10, 3, 32, 2, true); test2(10, 3, 32, 2, false); test3(1, 1); -- GitLab From ecc2d1f2b8c1451f5c6354fdc6e6c26774126bf5 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 26 Aug 2016 11:26:08 +0000 Subject: [PATCH 696/933] Task #9522: Reduce malloc() of big blocks a factor 60 for uv and 10 for bf data --- RTCP/Cobalt/CoInterface/src/CorrelatedData.cc | 15 ++++++++++++++ RTCP/Cobalt/CoInterface/src/CorrelatedData.h | 7 +++++++ RTCP/Cobalt/OutputProc/src/GPUProcIO.cc | 12 +++++++++-- RTCP/Cobalt/OutputProc/src/SubbandWriter.cc | 20 ++++++++++++++----- RTCP/Cobalt/OutputProc/src/SubbandWriter.h | 3 +++ 5 files changed, 50 insertions(+), 7 deletions(-) diff --git a/RTCP/Cobalt/CoInterface/src/CorrelatedData.cc b/RTCP/Cobalt/CoInterface/src/CorrelatedData.cc index d55d4474e1b..83ec3a1c148 100644 --- a/RTCP/Cobalt/CoInterface/src/CorrelatedData.cc +++ b/RTCP/Cobalt/CoInterface/src/CorrelatedData.cc @@ -108,6 +108,21 @@ namespace LOFAR } + size_t CorrelatedData::size(unsigned nrStations, + unsigned nrChannels, + unsigned maxNrValidSamples, + unsigned alignment) + { + const size_t nrBaselines = nrStations * (nrStations + 1) / 2; + const size_t nrBytesPerNrValidSamples = + maxNrValidSamples < 256 ? 1 : maxNrValidSamples < 65536 ? 2 : 4; + + return nrBaselines * nrChannels * NR_POLARIZATIONS * NR_POLARIZATIONS * sizeof(float) + alignment + + nrBaselines * nrChannels * nrBytesPerNrValidSamples + 2 * alignment; + } + + + void CorrelatedData::init(unsigned nrChannels, Allocator &allocator) { switch (itsNrBytesPerNrValidSamples) { diff --git a/RTCP/Cobalt/CoInterface/src/CorrelatedData.h b/RTCP/Cobalt/CoInterface/src/CorrelatedData.h index edec287e41b..b0f177da676 100644 --- a/RTCP/Cobalt/CoInterface/src/CorrelatedData.h +++ b/RTCP/Cobalt/CoInterface/src/CorrelatedData.h @@ -49,6 +49,13 @@ namespace LOFAR size_t nrVisibilities, Allocator & = heapAllocator, unsigned alignment = 1); + // Return the maximal memory required for these settings, given a certain alignment. + // Use alignment=0 to get the unaligned memory size required. + static size_t size(unsigned nrStations, unsigned nrChannels, + unsigned maxNrValidSamples, + unsigned alignment = 0); + + CorrelatedData &operator += (const CorrelatedData &); // Fast access to weights; T = uint32_t, uint16_t, or uint8_t, diff --git a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc index 0b1ecbc0f94..848bf6a9f34 100644 --- a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc +++ b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc @@ -181,6 +181,8 @@ bool process(Stream &controlStream) } } + map<size_t, SmartPtr<Arena> > arenas; + map<size_t, SmartPtr<Allocator> > allocators; map<size_t, SmartPtr<Pool<TABTranspose::BeamformedData> > > outputPools; TABTranspose::Receiver::CollectorMap collectors; @@ -208,14 +210,20 @@ bool process(Stream &controlStream) const size_t nrSubbands = file.lastSubbandIdx - file.firstSubbandIdx; const size_t nrChannels = stokes.nrChannels; const size_t nrSamples = stokes.nrSamples; + const size_t alignment = TABTranspose::BeamformedData::alignment; + const size_t poolSize = 10; + + arenas[fileIdx] = new MallocedArena(poolSize * (MultiDimArray<float,3>::nrElements(boost::extents[nrSamples][nrSubbands][nrChannels]) * sizeof(float) + alignment), alignment); + allocators[fileIdx] = new SparseSetAllocator(*arenas[fileIdx]); outputPools[fileIdx] = new Pool<TABTranspose::BeamformedData>(str(format("process::outputPool [file %u]") % fileIdx), parset.settings.realTime); // Create and fill an outputPool for this fileIdx - for (size_t i = 0; i < 10; ++i) { + for (size_t i = 0; i < poolSize; ++i) { outputPools[fileIdx]->free.append(new TABTranspose::BeamformedData( boost::extents[nrSamples][nrSubbands][nrChannels], - boost::extents[nrSubbands][nrChannels] + boost::extents[nrSubbands][nrChannels], + *allocators[fileIdx] ), false); } diff --git a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc index a986dc3095d..13162480783 100644 --- a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc +++ b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc @@ -23,6 +23,7 @@ #include "SubbandWriter.h" #include <CoInterface/CorrelatedData.h> +#include <CoInterface/Allocator.h> #include <CoInterface/OMPThread.h> #include <boost/format.hpp> @@ -37,17 +38,26 @@ namespace LOFAR const std::string &logPrefix) : itsStreamNr(streamNr), + itsArena(0), + itsAllocator(0), itsOutputPool(str(format("SubbandWriter::itsOutputPool [stream %u]") % streamNr), parset.settings.realTime), itsInputThread(parset, streamNr, itsOutputPool, logPrefix), itsOutputThread(parset, streamNr, itsOutputPool, mdLogger, mdKeyPrefix, logPrefix) { + const unsigned alignment = 512; + const unsigned nrStations = parset.settings.correlator.stations.size(); + const unsigned nrChannels = parset.settings.correlator.nrChannels; + const unsigned nrSamples = parset.settings.correlator.nrSamplesPerIntegration(); + + // We alloc all memory at once to avoid maxReceiveQueueSize malloc() calls, which occasionally stall on CEP4 + LOG_INFO_STR("Allocating memory..."); + itsArena = new MallocedArena(maxReceiveQueueSize * CorrelatedData::size(nrStations, nrChannels, nrSamples, alignment), alignment); + LOG_INFO_STR("Allocated " << itsArena->size() << " bytes"); + + itsAllocator = new SparseSetAllocator(*itsArena); for (unsigned i = 0; i < maxReceiveQueueSize; i++) { - LOG_INFO_STR("posix_memalign() " << i); - CorrelatedData *data = new CorrelatedData(parset.settings.correlator.stations.size(), parset.settings.correlator.nrChannels, parset.settings.correlator.nrSamplesPerIntegration(), heapAllocator, 512); - LOG_INFO_STR("append() " << i); + CorrelatedData *data = new CorrelatedData(nrStations, nrChannels, nrSamples, *itsAllocator, alignment); itsOutputPool.free.append(data); - LOG_INFO_STR("done adding " << i); - //itsOutputPool.free.append(new CorrelatedData(parset.settings.correlator.stations.size(), parset.settings.correlator.nrChannels, parset.settings.correlator.nrSamplesPerIntegration(), heapAllocator, 512)); } } diff --git a/RTCP/Cobalt/OutputProc/src/SubbandWriter.h b/RTCP/Cobalt/OutputProc/src/SubbandWriter.h index cc3b43767cb..6ba94d811a2 100644 --- a/RTCP/Cobalt/OutputProc/src/SubbandWriter.h +++ b/RTCP/Cobalt/OutputProc/src/SubbandWriter.h @@ -26,6 +26,7 @@ #include <CoInterface/OutputTypes.h> #include <CoInterface/Parset.h> #include <CoInterface/Pool.h> +#include <CoInterface/Allocator.h> #include <CoInterface/SmartPtr.h> #include <CoInterface/StreamableData.h> #include <CoInterface/FinalMetaData.h> @@ -65,6 +66,8 @@ namespace LOFAR const unsigned itsStreamNr; + SmartPtr<Arena> itsArena; + SmartPtr<Allocator> itsAllocator; Pool<StreamableData> itsOutputPool; InputThread itsInputThread; -- GitLab From 3025c54fab6fd51eccbe203cd155671b59711514 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 26 Aug 2016 11:40:36 +0000 Subject: [PATCH 697/933] Task #9273: ApplyCal: make 'Common' prefix optional for solution names --- CEP/DP3/DPPP/include/DPPP/phasefitter.h | 1 + CEP/DP3/DPPP/src/ApplyCal.cc | 6 ++--- CEP/DP3/DPPP/src/GainCal.cc | 34 +++++++++++++++---------- CEP/DP3/DPPP/src/phasefitter.cc | 13 ++++++++++ 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/CEP/DP3/DPPP/include/DPPP/phasefitter.h b/CEP/DP3/DPPP/include/DPPP/phasefitter.h index 82bff267fc1..f09a2db8eb6 100644 --- a/CEP/DP3/DPPP/include/DPPP/phasefitter.h +++ b/CEP/DP3/DPPP/include/DPPP/phasefitter.h @@ -285,6 +285,7 @@ class PhaseFitter void bruteForceSearchTEC2Model(double& lowerAlpha, double& upperAlpha, double& beta) const; double ternarySearchTEC2ModelAlpha(double startAlpha, double endAlpha, double& beta) const; void fillDataWithTEC2Model(double alpha, double beta); + void fillDataWithTEC1Model(double alpha); void bruteForceSearchTEC1Model(double& lowerAlpha, double& upperAlpha) const; double ternarySearchTEC1ModelAlpha(double startAlpha, double endAlpha) const; diff --git a/CEP/DP3/DPPP/src/ApplyCal.cc b/CEP/DP3/DPPP/src/ApplyCal.cc index 08f0dc16ea9..6ac751d0ebd 100644 --- a/CEP/DP3/DPPP/src/ApplyCal.cc +++ b/CEP/DP3/DPPP/src/ApplyCal.cc @@ -173,13 +173,13 @@ namespace LOFAR { itsParmExprs.push_back("Clock:1"); } } else if (itsCorrectType == "commonrotationangle") { - itsParmExprs.push_back("CommonRotationAngle"); + itsParmExprs.push_back("{Common,}RotationAngle"); } else if (itsCorrectType == "commonscalarphase") { - itsParmExprs.push_back("CommonScalarPhase"); + itsParmExprs.push_back("{Common,}ScalarPhase"); } else if (itsCorrectType == "rotationmeasure") { itsParmExprs.push_back("RotationMeasure"); } else if (itsCorrectType == "commonscalaramplitude") { - itsParmExprs.push_back("CommonScalarAmplitude"); + itsParmExprs.push_back("{Common,}ScalarAmplitude"); } else { THROW (Exception, "Correction type " + itsCorrectType + diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index e863212c633..8bfb2148014 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -124,7 +124,7 @@ namespace LOFAR { ASSERT(itsMode=="diagonal" || itsMode=="phaseonly" || itsMode=="fulljones" || itsMode=="scalarphase" || itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || - itsMode=="tec"); + itsMode=="tecandphase" || itsMode=="tec"); } GainCal::~GainCal() @@ -172,7 +172,7 @@ namespace LOFAR { itsSols.reserve(itsTimeSlotsPerParmUpdate); // Initialize phase fitters, set their frequency data - if (itsMode=="tec") { + if (itsMode=="tecandphase" || itsMode=="tec") { itsTECSols.reserve(itsTimeSlotsPerParmUpdate); itsPhaseFitters.reserve(itsNFreqCells); // TODO: could be numthreads instead @@ -260,7 +260,7 @@ namespace LOFAR { FlagCounter::showPerc1 (os, itsTimerSolve.getElapsed(), totaltime); os << " of it spent in estimating gains and computing residuals" << endl; - if (itsMode == "tec") { + if (itsMode == "tec" || itsMode == "tecandphase") { os << " "; FlagCounter::showPerc1 (os, itsTimerPhaseFit.getElapsed(), totaltime); os << " of it spent in fitting phases" << endl; @@ -451,7 +451,7 @@ namespace LOFAR { continue; } - if (itsMode=="tec") { + if (itsMode=="tec" || itsMode=="tecandphase") { iS[ch/itsNChan].incrementWeight(weight[bl*nCr*nCh+ch*nCr]); } @@ -540,7 +540,7 @@ namespace LOFAR { uint iter=0; - casa::Matrix<double> tecsol(2, info().antennaNames().size(), 0); + casa::Matrix<double> tecsol(itsMode=="tecandphase"?2:1, info().antennaNames().size(), 0); std::vector<StefCal::Status> converged(itsNFreqCells,StefCal::NOTCONVERGED); for (;iter<itsMaxIter;++iter) { @@ -556,7 +556,7 @@ namespace LOFAR { } } - if (itsMode=="tec") { + if (itsMode=="tec" || itsMode=="tecandphase") { itsTimerSolve.stop(); itsTimerPhaseFit.start(); casa::Matrix<casa::DComplex> sols_f(itsNFreqCells, info().antennaNames().size()); @@ -624,7 +624,11 @@ namespace LOFAR { if (numpoints>1) { // TODO: limit should be higher //cout<<"tecsol(0,"<<st<<")="<<tecsol(0,st)<<", tecsol(1,"<<st<<")="<<tecsol(1,st)<<endl; - cost=itsPhaseFitters[st]->FitDataToTEC2Model(tecsol(0, st), tecsol(1,st)); + if (itsMode=="tecandphase") { + cost=itsPhaseFitters[st]->FitDataToTEC2Model(tecsol(0, st), tecsol(1,st)); + } else { // itsMode=="tec" + cost=itsPhaseFitters[st]->FitDataToTEC1Model(tecsol(0, st)); + } // Update solution in stefcal for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { ASSERT(isFinite(phases[freqCell])); @@ -632,7 +636,9 @@ namespace LOFAR { } } else { tecsol(0, st) = 0; //std::numeric_limits<double>::quiet_NaN(); - tecsol(1, st) = 0; //std::numeric_limits<double>::quiet_NaN(); + if (itsMode=="tecandphase") { + tecsol(1, st) = 0; //std::numeric_limits<double>::quiet_NaN(); + } } if (st==34 && itsDebugLevel>0) { @@ -706,7 +712,7 @@ namespace LOFAR { } } itsSols.push_back(sol); - if (itsMode=="tec") { + if (itsMode=="tec" || itsMode=="tecandphase") { itsTECSols.push_back(tecsol); } @@ -793,7 +799,7 @@ namespace LOFAR { name=string("CommonScalarPhase:"); } else if (itsMode=="scalaramplitude") { name=string("CommonScalarAmplitude:"); - } else if (itsMode=="tec") { + } else if (itsMode=="tec" || itsMode=="tecandphase") { name=string("TEC:"); } else { @@ -881,9 +887,9 @@ namespace LOFAR { itsMode=="tec") && pol>0) { continue; } - int realimmax; // For tec, this functions as dummy between tec and commonscalarphase + int realimmax; // For tecandphase, this functions as dummy between tec and commonscalarphase if (itsMode=="phaseonly" || itsMode=="scalarphase" || - itsMode=="amplitudeonly" || itsMode=="scalaramplitude") { + itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || itsMode=="tec") { realimmax=1; } else { realimmax=2; @@ -901,7 +907,7 @@ namespace LOFAR { name=name+strri[realim]; } } - if (itsMode=="tec") { + if (itsMode=="tecandphase" || itsMode=="tec") { if (realim==0) { name="TEC:"; } else { @@ -930,7 +936,7 @@ namespace LOFAR { values(freqCell, ts) = arg(itsSols[ts](pol/3,st,freqCell)); } else if (itsMode=="scalaramplitude" || itsMode=="amplitudeonly") { values(freqCell, ts) = abs(itsSols[ts](pol/3,st,freqCell)); - } else if (itsMode=="tec") { + } else if (itsMode=="tec" || itsMode=="tecandphase") { if (realim==0) { values(freqCell, ts) = itsTECSols[ts](realim,st) / 8.44797245e9; } else { diff --git a/CEP/DP3/DPPP/src/phasefitter.cc b/CEP/DP3/DPPP/src/phasefitter.cc index 07c171dd743..2391ada1996 100644 --- a/CEP/DP3/DPPP/src/phasefitter.cc +++ b/CEP/DP3/DPPP/src/phasefitter.cc @@ -114,6 +114,12 @@ void PhaseFitter::fillDataWithTEC2Model(double alpha, double beta) _phases[ch] = TEC2ModelFunc(_frequencies[ch], alpha, beta); } +void PhaseFitter::fillDataWithTEC1Model(double alpha) +{ + for(size_t ch=0; ch!=Size(); ++ch) + _phases[ch] = TEC1ModelFuncWrapped(_frequencies[ch], alpha); +} + void PhaseFitter::FitTEC2ModelParameters(double& alpha, double& beta) const { double lowerAlpha = -40000.0e6, upperAlpha = 40000.0e6; @@ -130,6 +136,13 @@ double PhaseFitter::FitDataToTEC2Model(double& alpha, double& beta) return TEC2ModelCost(alpha, beta); } +double PhaseFitter::FitDataToTEC1Model(double& alpha) +{ + FitTEC1ModelParameters(alpha); + fillDataWithTEC1Model(alpha); + return TEC1ModelCost(alpha); +} + void PhaseFitter::FitTEC1ModelParameters(double& alpha) const { double lowerAlpha = -40000.0e6, upperAlpha = 40000.0e6; -- GitLab From 916665e439ca997d95de2d3c8fcc96895bd5edc4 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 26 Aug 2016 11:42:12 +0000 Subject: [PATCH 698/933] Task #9273: revert accidental checkins --- CEP/DP3/DPPP/include/DPPP/phasefitter.h | 1 - CEP/DP3/DPPP/src/GainCal.cc | 34 ++++++++++--------------- CEP/DP3/DPPP/src/phasefitter.cc | 13 ---------- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/CEP/DP3/DPPP/include/DPPP/phasefitter.h b/CEP/DP3/DPPP/include/DPPP/phasefitter.h index f09a2db8eb6..82bff267fc1 100644 --- a/CEP/DP3/DPPP/include/DPPP/phasefitter.h +++ b/CEP/DP3/DPPP/include/DPPP/phasefitter.h @@ -285,7 +285,6 @@ class PhaseFitter void bruteForceSearchTEC2Model(double& lowerAlpha, double& upperAlpha, double& beta) const; double ternarySearchTEC2ModelAlpha(double startAlpha, double endAlpha, double& beta) const; void fillDataWithTEC2Model(double alpha, double beta); - void fillDataWithTEC1Model(double alpha); void bruteForceSearchTEC1Model(double& lowerAlpha, double& upperAlpha) const; double ternarySearchTEC1ModelAlpha(double startAlpha, double endAlpha) const; diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index 8bfb2148014..e863212c633 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -124,7 +124,7 @@ namespace LOFAR { ASSERT(itsMode=="diagonal" || itsMode=="phaseonly" || itsMode=="fulljones" || itsMode=="scalarphase" || itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || - itsMode=="tecandphase" || itsMode=="tec"); + itsMode=="tec"); } GainCal::~GainCal() @@ -172,7 +172,7 @@ namespace LOFAR { itsSols.reserve(itsTimeSlotsPerParmUpdate); // Initialize phase fitters, set their frequency data - if (itsMode=="tecandphase" || itsMode=="tec") { + if (itsMode=="tec") { itsTECSols.reserve(itsTimeSlotsPerParmUpdate); itsPhaseFitters.reserve(itsNFreqCells); // TODO: could be numthreads instead @@ -260,7 +260,7 @@ namespace LOFAR { FlagCounter::showPerc1 (os, itsTimerSolve.getElapsed(), totaltime); os << " of it spent in estimating gains and computing residuals" << endl; - if (itsMode == "tec" || itsMode == "tecandphase") { + if (itsMode == "tec") { os << " "; FlagCounter::showPerc1 (os, itsTimerPhaseFit.getElapsed(), totaltime); os << " of it spent in fitting phases" << endl; @@ -451,7 +451,7 @@ namespace LOFAR { continue; } - if (itsMode=="tec" || itsMode=="tecandphase") { + if (itsMode=="tec") { iS[ch/itsNChan].incrementWeight(weight[bl*nCr*nCh+ch*nCr]); } @@ -540,7 +540,7 @@ namespace LOFAR { uint iter=0; - casa::Matrix<double> tecsol(itsMode=="tecandphase"?2:1, info().antennaNames().size(), 0); + casa::Matrix<double> tecsol(2, info().antennaNames().size(), 0); std::vector<StefCal::Status> converged(itsNFreqCells,StefCal::NOTCONVERGED); for (;iter<itsMaxIter;++iter) { @@ -556,7 +556,7 @@ namespace LOFAR { } } - if (itsMode=="tec" || itsMode=="tecandphase") { + if (itsMode=="tec") { itsTimerSolve.stop(); itsTimerPhaseFit.start(); casa::Matrix<casa::DComplex> sols_f(itsNFreqCells, info().antennaNames().size()); @@ -624,11 +624,7 @@ namespace LOFAR { if (numpoints>1) { // TODO: limit should be higher //cout<<"tecsol(0,"<<st<<")="<<tecsol(0,st)<<", tecsol(1,"<<st<<")="<<tecsol(1,st)<<endl; - if (itsMode=="tecandphase") { - cost=itsPhaseFitters[st]->FitDataToTEC2Model(tecsol(0, st), tecsol(1,st)); - } else { // itsMode=="tec" - cost=itsPhaseFitters[st]->FitDataToTEC1Model(tecsol(0, st)); - } + cost=itsPhaseFitters[st]->FitDataToTEC2Model(tecsol(0, st), tecsol(1,st)); // Update solution in stefcal for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { ASSERT(isFinite(phases[freqCell])); @@ -636,9 +632,7 @@ namespace LOFAR { } } else { tecsol(0, st) = 0; //std::numeric_limits<double>::quiet_NaN(); - if (itsMode=="tecandphase") { - tecsol(1, st) = 0; //std::numeric_limits<double>::quiet_NaN(); - } + tecsol(1, st) = 0; //std::numeric_limits<double>::quiet_NaN(); } if (st==34 && itsDebugLevel>0) { @@ -712,7 +706,7 @@ namespace LOFAR { } } itsSols.push_back(sol); - if (itsMode=="tec" || itsMode=="tecandphase") { + if (itsMode=="tec") { itsTECSols.push_back(tecsol); } @@ -799,7 +793,7 @@ namespace LOFAR { name=string("CommonScalarPhase:"); } else if (itsMode=="scalaramplitude") { name=string("CommonScalarAmplitude:"); - } else if (itsMode=="tec" || itsMode=="tecandphase") { + } else if (itsMode=="tec") { name=string("TEC:"); } else { @@ -887,9 +881,9 @@ namespace LOFAR { itsMode=="tec") && pol>0) { continue; } - int realimmax; // For tecandphase, this functions as dummy between tec and commonscalarphase + int realimmax; // For tec, this functions as dummy between tec and commonscalarphase if (itsMode=="phaseonly" || itsMode=="scalarphase" || - itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || itsMode=="tec") { + itsMode=="amplitudeonly" || itsMode=="scalaramplitude") { realimmax=1; } else { realimmax=2; @@ -907,7 +901,7 @@ namespace LOFAR { name=name+strri[realim]; } } - if (itsMode=="tecandphase" || itsMode=="tec") { + if (itsMode=="tec") { if (realim==0) { name="TEC:"; } else { @@ -936,7 +930,7 @@ namespace LOFAR { values(freqCell, ts) = arg(itsSols[ts](pol/3,st,freqCell)); } else if (itsMode=="scalaramplitude" || itsMode=="amplitudeonly") { values(freqCell, ts) = abs(itsSols[ts](pol/3,st,freqCell)); - } else if (itsMode=="tec" || itsMode=="tecandphase") { + } else if (itsMode=="tec") { if (realim==0) { values(freqCell, ts) = itsTECSols[ts](realim,st) / 8.44797245e9; } else { diff --git a/CEP/DP3/DPPP/src/phasefitter.cc b/CEP/DP3/DPPP/src/phasefitter.cc index 2391ada1996..07c171dd743 100644 --- a/CEP/DP3/DPPP/src/phasefitter.cc +++ b/CEP/DP3/DPPP/src/phasefitter.cc @@ -114,12 +114,6 @@ void PhaseFitter::fillDataWithTEC2Model(double alpha, double beta) _phases[ch] = TEC2ModelFunc(_frequencies[ch], alpha, beta); } -void PhaseFitter::fillDataWithTEC1Model(double alpha) -{ - for(size_t ch=0; ch!=Size(); ++ch) - _phases[ch] = TEC1ModelFuncWrapped(_frequencies[ch], alpha); -} - void PhaseFitter::FitTEC2ModelParameters(double& alpha, double& beta) const { double lowerAlpha = -40000.0e6, upperAlpha = 40000.0e6; @@ -136,13 +130,6 @@ double PhaseFitter::FitDataToTEC2Model(double& alpha, double& beta) return TEC2ModelCost(alpha, beta); } -double PhaseFitter::FitDataToTEC1Model(double& alpha) -{ - FitTEC1ModelParameters(alpha); - fillDataWithTEC1Model(alpha); - return TEC1ModelCost(alpha); -} - void PhaseFitter::FitTEC1ModelParameters(double& alpha) const { double lowerAlpha = -40000.0e6, upperAlpha = 40000.0e6; -- GitLab From 5487c90a43b1e1bdf487bb17c775b0c71392b715 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 12:03:24 +0000 Subject: [PATCH 699/933] Task #9607: added timeout argument to constructor --- SAS/MoM/MoMQueryService/momqueryrpc.py | 6 ++++++ SAS/MoM/MoMQueryService/momrpc.py | 2 +- SAS/OTDB_Services/otdbrpc.py | 5 +++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/SAS/MoM/MoMQueryService/momqueryrpc.py b/SAS/MoM/MoMQueryService/momqueryrpc.py index 385ee6cd18d..4e3c31c439b 100644 --- a/SAS/MoM/MoMQueryService/momqueryrpc.py +++ b/SAS/MoM/MoMQueryService/momqueryrpc.py @@ -13,6 +13,12 @@ logger = logging.getLogger(__file__) class MoMQueryRPC(RPCWrapper): + def __init__(self, busname=DEFAULT_MOMQUERY_BUSNAME, + servicename=DEFAULT_MOMQUERY_SERVICENAME, + broker=None, + timeout=120): + super(MoMQueryRPC, self).__init__(busname, servicename, broker, timeout=timeout) + def getProjectDetails(self, ids): '''get the project details for one or more mom ids :param ids single or list of mom ids diff --git a/SAS/MoM/MoMQueryService/momrpc.py b/SAS/MoM/MoMQueryService/momrpc.py index 52d59c4cf8e..d8800f2d2bf 100644 --- a/SAS/MoM/MoMQueryService/momrpc.py +++ b/SAS/MoM/MoMQueryService/momrpc.py @@ -10,7 +10,7 @@ from lofar.mom.momqueryservice.config import DEFAULT_MOM_BUSNAME, DEFAULT_MOM_SE logger = logging.getLogger(__file__) class MoMRPC(RPCWrapper): - def __init__(self, busname=DEFAULT_MOM_BUSNAME, servicename=DEFAULT_MOM_SERVICENAME, broker=None, timeout=10, verbose=False): + def __init__(self, busname=DEFAULT_MOM_BUSNAME, servicename=DEFAULT_MOM_SERVICENAME, broker=None, timeout=120, verbose=False): super(MoMRPC, self).__init__(busname=busname, servicename=servicename, broker=broker, timeout=timeout, verbose=verbose) def copyTask(self, mom2id, newTaskName=None, newTaskDescription=None): diff --git a/SAS/OTDB_Services/otdbrpc.py b/SAS/OTDB_Services/otdbrpc.py index 8b5383b1073..a7d60c8d4a4 100644 --- a/SAS/OTDB_Services/otdbrpc.py +++ b/SAS/OTDB_Services/otdbrpc.py @@ -20,8 +20,9 @@ class OTDBPRCException(Exception): class OTDBRPC(RPCWrapper): def __init__(self, busname=DEFAULT_OTDB_SERVICE_BUSNAME, servicename=DEFAULT_OTDB_SERVICENAME, - broker=None): - super(OTDBRPC, self).__init__(busname, servicename, broker) + broker=None, + timeout=120): + super(OTDBRPC, self).__init__(busname, servicename, broker, timeout=timeout) def taskGetIDs(self, otdb_id=None, mom_id=None): if otdb_id: -- GitLab From 1637f657d7dd804028251706979cc92ce8052124 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 12:04:04 +0000 Subject: [PATCH 700/933] Task #9607: use longer timeouts on rpc's. added cluster column on specification --- .../ResourceAssigner/lib/assignment.py | 82 ++++++++++--------- .../ResourceAssignmentDatabase/radb.py | 58 +++---------- .../sql/create_database.sql | 3 +- .../ResourceAssignmentService/rpc.py | 5 +- 4 files changed, 57 insertions(+), 91 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 7d615b1c329..13e912d31b8 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -76,10 +76,10 @@ class ResourceAssigner(): :param re_servicename: servicename of the resource estimator service (default: ResourceEstimation) :param broker: Valid Qpid broker host (default: None, which means localhost) """ - self.radbrpc = RARPC(servicename=radb_servicename, busname=radb_busname, broker=broker) - self.rerpc = RPC(re_servicename, busname=re_busname, broker=broker, ForwardExceptions=True) - self.otdbrpc = OTDBRPC(busname=otdb_busname, servicename=otdb_servicename, broker=broker) ## , ForwardExceptions=True hardcoded in RPCWrapper right now - self.momrpc = MoMQueryRPC(servicename=mom_servicename, busname=mom_busname, broker=broker) + self.radbrpc = RARPC(servicename=radb_servicename, busname=radb_busname, broker=broker, timeout=180) + self.rerpc = RPC(re_servicename, busname=re_busname, broker=broker, ForwardExceptions=True, timeout=180) + self.otdbrpc = OTDBRPC(busname=otdb_busname, servicename=otdb_servicename, broker=broker, timeout=180) ## , ForwardExceptions=True hardcoded in RPCWrapper right now + self.momrpc = MoMQueryRPC(servicename=mom_servicename, busname=mom_busname, broker=broker, timeout=180) self.ra_notification_bus = ToBus(address=ra_notification_busname, broker=broker) self.ra_notification_prefix = ra_notification_prefix @@ -147,11 +147,14 @@ class ResourceAssigner(): except Exception as e: logger.error(e) + clusterIsCEP4 = self.checkClusterIsCEP4(mainParset) + clusterName = 'CEP4' if clusterIsCEP4 else 'CEP2' + # insert new task and specification in the radb # any existing specification and task with same otdb_id will be deleted automatically - logger.info('doAssignment: insertSpecification momId=%s, otdb_id=%s, status=%s, taskType=%s, startTime=%s, endTime=%s' % - (momId, otdb_id, status, taskType, startTime, endTime)) - result = self.radbrpc.insertSpecificationAndTask(momId, otdb_id, status, taskType, startTime, endTime, str(mainParset)) + logger.info('doAssignment: insertSpecification momId=%s, otdb_id=%s, status=%s, taskType=%s, startTime=%s, endTime=%s cluster=%s' % + (momId, otdb_id, status, taskType, startTime, endTime, clusterName)) + result = self.radbrpc.insertSpecificationAndTask(momId, otdb_id, status, taskType, startTime, endTime, str(mainParset), clusterName) if not result['inserted']: logger.error('could not insert specification and task') @@ -161,8 +164,12 @@ class ResourceAssigner(): taskId = result['task_id'] logger.info('doAssignment: inserted specification (id=%s) and task (id=%s)' % (specificationId,taskId)) + task = self.radbrpc.getTask(taskId) + self.processPredecessors(task) + self.processSuccessors(task) + # do not assign resources to task for other clusters than cep4 - if not self.checkClusterIsCEP4(mainParset): + if not clusterIsCEP4: return if status != 'prescheduled': @@ -185,7 +192,6 @@ class ResourceAssigner(): # and if not enough resources are available, then they are put to conflict status # also, if any claim is in conflict state, then the task is put to conflict status as well main_needed = needed[str(otdb_id)] - task = self.radbrpc.getTask(taskId) if 'errors' in main_needed and main_needed['errors']: for error in main_needed['errors']: @@ -205,12 +211,11 @@ class ResourceAssigner(): (len(conflictingClaims), conflictingClaims)) self._sendNotification(task, 'conflict') else: - logger.info('doAssignment: all claims for task %s were succesfully claimed. Setting task status to scheduled' % (taskId,)) - self.radbrpc.updateTaskAndResourceClaims(taskId, task_status='scheduled', claim_status='allocated') + logger.info('doAssignment: all claims for task %s were succesfully claimed. Setting claim statuses to allocated' % (taskId,)) + self.radbrpc.updateTaskAndResourceClaims(taskId, claim_status='allocated') + # send notification that the task was scheduled, + # another service sets the parset spec in otdb, and updated otdb task status to scheduled, which is then synced to radb self._sendNotification(task, 'scheduled') - - self.processPredecessors(specification_tree) - self.processSuccessors(task) else: logger.warning('doAssignment: Not all claims could be inserted. Setting task %s status to conflict' % (taskId)) self.radbrpc.updateTask(taskId, status='conflict') @@ -227,33 +232,27 @@ class ResourceAssigner(): except Exception as e: logger.error(str(e)) - def processPredecessors(self, specification_tree): + def processPredecessors(self, task): try: - predecessor_trees = specification_tree['predecessors'] - - if predecessor_trees: - otdb_id = specification_tree['otdb_id'] - pred_otdb_ids = [pt['otdb_id'] for pt in predecessor_trees] - logger.info('proccessing predecessors otdb_ids=%s for otdb_id=%s', pred_otdb_ids, otdb_id) - - task = self.radbrpc.getTask(otdb_id=otdb_id) - - if task: - for predecessor_tree in predecessor_trees: - #first process the predecessor's predecessors - self.processPredecessors(predecessor_tree) - - #then check if the predecessor needs to be linked to this task - pred_otdb_id = predecessor_tree['otdb_id'] - predecessor_task = self.radbrpc.getTask(otdb_id=pred_otdb_id) - if predecessor_task: - if predecessor_task['id'] not in task['predecessor_ids']: - logger.info('connecting predecessor task with otdb_id=%s to it\'s successor with otdb_id=%s', pred_otdb_id, otdb_id) - self.radbrpc.insertTaskPredecessor(task['id'], predecessor_task['id']) - else: - logger.warning('could not find predecessor task with otdb_id=%s in radb for task otdb_id=%s', pred_otdb_id, otdb_id) + predecessor_mom_ids = self.momrpc.getPredecessorIds(task['mom_id'])[str(task['mom_id'])] + + if predecessor_mom_ids: + logger.info('proccessing predecessor mom_ids=%s for mom_id=%s otdb_id=%s', predecessor_mom_ids, task['mom_id'], task['otdb_id']) + + for predecessor_mom_id in predecessor_mom_ids: + #check if the predecessor needs to be linked to this task + predecessor_task = self.radbrpc.getTask(mom_id=predecessor_mom_id) + if predecessor_task: + if predecessor_task['id'] not in task['predecessor_ids']: + logger.info('connecting predecessor task with mom_id=%s otdb_id=%s to it\'s successor with mom_id=%s otdb_id=%s', predecessor_task['mom_id'], + predecessor_task['otdb_id'], + task['mom_id'], + task['otdb_id']) + self.radbrpc.insertTaskPredecessor(task['id'], predecessor_task['id']) + else: + logger.warning('could not find predecessor task with otdb_id=%s in radb for task otdb_id=%s', predecessor_task['otdb_id'], task['otdb_id']) else: - logger.info('no predecessors for otdb_id=%s', specification_tree['otdb_id']) + logger.info('no predecessors for otdb_id=%s', task['otdb_id']) except Exception as e: logger.error(e) @@ -270,7 +269,10 @@ class ResourceAssigner(): successor_task = self.radbrpc.getTask(mom_id=successor_mom_id) if successor_task: if successor_task['id'] not in task['successor_ids']: - logger.info('connecting successor task with otdb_id=%s to it\'s predecessor with otdb_id=%s', successor_task['otdb_id'], task['otdb_id']) + logger.info('connecting successor task with mom_id=%s otdb_id=%s to it\'s predecessor with mom_id=%s otdb_id=%s', successor_task['mom_id'], + successor_task['otdb_id'], + task['mom_id'], + task['otdb_id']) self.radbrpc.insertTaskPredecessor(successor_task['id'], task['id']) movePipelineAfterItsPredecessors(successor_task, self.radbrpc) else: diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index a3833bc4b62..9f4e158589d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -211,8 +211,6 @@ class RADatabase: tasks = list(self._executeQuery(query, qargs, fetch=_FETCH_ALL)) - self._addClusterToTasks(tasks) - return tasks @@ -242,48 +240,8 @@ class RADatabase: if task['successor_ids'] is None: task['successor_ids'] = [] - self._addClusterToTasks([task]) return task - def _addClusterToTasks(self, tasks): - #parse the parset content of the specification - #and retreive the storageClusterName for each task if possible - try: - spec_ids = [t['specification_id'] for t in tasks if t != None] - - specs = self.getSpecifications(specification_ids=spec_ids) - spec_dict = {s['id']:s for s in specs} - - keys = ['Output_Correlated', - 'Output_IncoherentStokes', - 'Output_CoherentStokes', - 'Output_InstrumentModel', - 'Output_SkyImage', - 'Output_Pulsar'] - - for task in tasks: - try: - spec = spec_dict.get(task['specification_id']) - if spec: - parset = [line.strip() for line in spec['content'].split('\n')] - parset = [line for line in parset if line.startswith('Observation.DataProducts')] - parset = [line.split('=') for line in parset] - parset = {items[0]:items[1] for items in parset} - - enabled_keys = [key for key in keys if parset.get('Observation.DataProducts.%s.enabled' % key, '').lower() in ['true', 't', 'yes', 'y', '1']] - clusters = [parset.get('Observation.DataProducts.%s.storageClusterName' % key, 'CEP2') for key in enabled_keys] - clusters = [c if c else 'CEP2' for c in clusters] - - #pick median cluster - clusters.sort() - task['cluster'] = clusters[len(clusters)/2] - else: - task['cluster'] = 'unknown' - except Exception as e: - task['cluster'] = 'unknown' - except Exception as e: - pass - def _convertTaskTypeAndStatusToIds(self, task_status, task_type): '''converts task_status and task_type to id's in case one and/or the other are strings''' if task_status and isinstance(task_status, basestring): @@ -469,14 +427,14 @@ class RADatabase: return list(self._executeQuery(query, [specification_id], fetch=_FETCH_ALL)) - def insertSpecification(self, starttime, endtime, content, commit=True): - logger.info('insertSpecification starttime=%s, endtime=%s' % (starttime, endtime)) + def insertSpecification(self, starttime, endtime, content, cluster, commit=True): + logger.info('insertSpecification starttime=%s, endtime=%s cluster=%s' % (starttime, endtime, cluster)) query = '''INSERT INTO resource_allocation.specification - (starttime, endtime, content) - VALUES (%s, %s, %s) + (starttime, endtime, content, cluster) + VALUES (%s, %s, %s, %s) RETURNING id;''' - id = self._executeQuery(query, (starttime, endtime, content), fetch=_FETCH_ONE)['id'] + id = self._executeQuery(query, (starttime, endtime, content, cluster), fetch=_FETCH_ONE)['id'] if commit: self.commit() return id @@ -491,7 +449,7 @@ class RADatabase: self.commit() return self.cursor.rowcount > 0 - def updateSpecification(self, specification_id, starttime=None, endtime=None, content=None, commit=True): + def updateSpecification(self, specification_id, starttime=None, endtime=None, content=None, cluster=None, commit=True): fields = [] values = [] @@ -507,6 +465,10 @@ class RADatabase: fields.append('content') values.append(content) + if cluster is not None : + fields.append('cluster') + values.append(cluster) + values.append(specification_id) query = '''UPDATE resource_allocation.specification diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql index 78fc4c4dac1..0dbaed1f40b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql @@ -135,6 +135,7 @@ CREATE TABLE resource_allocation.specification ( starttime timestamp, endtime timestamp, content text, + cluster text DEFAULT '', PRIMARY KEY (id) ) WITH (OIDS=FALSE); ALTER TABLE resource_allocation.specification @@ -304,7 +305,7 @@ ALTER TABLE resource_allocation.config CREATE OR REPLACE VIEW resource_allocation.task_view AS SELECT t.id, t.mom_id, t.otdb_id, t.status_id, t.type_id, t.specification_id, - ts.name AS status, tt.name AS type, s.starttime, s.endtime, extract(epoch from age(s.endtime, s.starttime)) as duration, + ts.name AS status, tt.name AS type, s.starttime, s.endtime, extract(epoch from age(s.endtime, s.starttime)) as duration, s.cluster, (SELECT array_agg(tp.predecessor_id) FROM resource_allocation.task_predecessor tp where tp.task_id=t.id) as predecessor_ids, (SELECT array_agg(tp.task_id) FROM resource_allocation.task_predecessor tp where tp.predecessor_id=t.id) as successor_ids FROM resource_allocation.task t diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py index 44834eed42e..f7373c5e3cd 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py @@ -21,8 +21,9 @@ class RARPCException(Exception): class RARPC(RPCWrapper): def __init__(self, busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, - broker=None): - super(RARPC, self).__init__(busname, servicename, broker) + broker=None, + timeout=120): + super(RARPC, self).__init__(busname, servicename, broker, timeout=timeout) def getResourceClaimStatuses(self): return self.rpc('GetResourceClaimStatuses') -- GitLab From aff8df4f8442f1b509f9f20572a07282962e3191 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 12:22:08 +0000 Subject: [PATCH 701/933] Task #9607: log canStart result --- MAC/Services/src/PipelineControl.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index ffee99a53ac..5a1eeab28ea 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -317,12 +317,9 @@ class PipelineDependencies(object): logger.error("canStart(%s): Error obtaining task states, not starting pipeline: %s", otdbId, e) return False - logger.info("canStart(%s)? state = %s, predecessors = %s", otdbId, myState, predecessorStates) - - return ( - myState == "scheduled" and - all([x == "finished" for x in predecessorStates.values()]) - ) + startable = (myState == "scheduled" and all([x == "finished" for x in predecessorStates.values()])) + logger.info("canStart(%s)? state = %s, predecessors = %s, canStart = %s", otdbId, myState, predecessorStates, startable) + return startable class PipelineControl(OTDBBusListener): def __init__(self, otdb_notification_busname=DEFAULT_OTDB_NOTIFICATION_BUSNAME, otdb_service_busname=DEFAULT_OTDB_SERVICE_BUSNAME, ra_service_busname=DEFAULT_RAS_SERVICE_BUSNAME, **kwargs): -- GitLab From c168d8f51fd763d495d54cc82c3cd9e04a497044 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 12:22:52 +0000 Subject: [PATCH 702/933] Task #9607: bug fix, do use empty lists in where clause --- SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 9f4e158589d..b5b1d75122c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -176,7 +176,7 @@ class RADatabase: if isinstance(task_ids, int): # just a single id conditions.append('id = %s') qargs.append(task_ids) - elif task_ids: #assume a list/enumerable of id's + else: #assume a list/enumerable of id's conditions.append('id in %s') qargs.append(tuple(task_ids)) @@ -184,7 +184,7 @@ class RADatabase: if isinstance(mom_ids, int): # just a single id conditions.append('mom_id = %s') qargs.append(mom_ids) - elif mom_ids: #assume a list/enumerable of id's + else: #assume a list/enumerable of id's conditions.append('mom_id in %s') qargs.append(tuple(mom_ids)) @@ -192,7 +192,7 @@ class RADatabase: if isinstance(otdb_ids, int): # just a single id conditions.append('otdb_id = %s') qargs.append(otdb_ids) - elif otdb_ids: #assume a list/enumerable of id's + else: #assume a list/enumerable of id's conditions.append('otdb_id in %s') qargs.append(tuple(otdb_ids)) -- GitLab From e9516d435f9c19a2b2ae611db498f728f77ef166 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 12:51:24 +0000 Subject: [PATCH 703/933] Task #9607: added cluster arg to methods --- .../ResourceAssignmentDatabase/radb.py | 4 ++-- .../ResourceAssignmentService/rpc.py | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index b5b1d75122c..8ab5103dd13 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -1278,7 +1278,7 @@ class RADatabase: if commit: self.commit() - def insertSpecificationAndTask(self, mom_id, otdb_id, task_status, task_type, starttime, endtime, content, commit=True): + def insertSpecificationAndTask(self, mom_id, otdb_id, task_status, task_type, starttime, endtime, content, cluster, commit=True): ''' Insert a new specification and task in one transaction. Removes existing task with same otdb_id if present in the same transaction. @@ -1301,7 +1301,7 @@ class RADatabase: # delete old specification, task, and resource claims using cascaded delete self.deleteSpecification(task['specification_id'], False) - specId = self.insertSpecification(starttime, endtime, content, False) + specId = self.insertSpecification(starttime, endtime, content, cluster, False) taskId = self.insertTask(mom_id, otdb_id, task_status, task_type, specId, False) if specId >= 0 and taskId >= 0: diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py index f7373c5e3cd..eb8e61c0b5f 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py @@ -205,7 +205,7 @@ class RARPC(RPCWrapper): specification['endtime'] = specification['endtime'].datetime() return specification - def insertSpecificationAndTask(self, mom_id, otdb_id, task_status, task_type, starttime, endtime, content): + def insertSpecificationAndTask(self, mom_id, otdb_id, task_status, task_type, starttime, endtime, content, cluster): return self.rpc('InsertSpecificationAndTask', mom_id=mom_id, otdb_id=otdb_id, @@ -213,23 +213,26 @@ class RARPC(RPCWrapper): task_type=task_type, starttime=starttime, endtime=endtime, - content=content) + content=content, + cluster=cluster) - def insertSpecification(self, starttime, endtime, content): + def insertSpecification(self, starttime, endtime, content, cluster): return self.rpc('InsertSpecification', starttime=starttime, endtime=endtime, - content=content) + content=content, + cluster=cluster) def deleteSpecification(self, id): return self.rpc('DeleteSpecification', id=id) - def updateSpecification(self, id, starttime=None, endtime=None, content=None): + def updateSpecification(self, id, starttime=None, endtime=None, content=None, cluster=None): return self.rpc('UpdateSpecification', id=id, starttime=starttime, endtime=endtime, - content=content) + content=content, + cluster=cluster) def getSpecifications(self): specifications = self.rpc('GetSpecifications') -- GitLab From 90105b565c0fa78f76aabd176c3cc0d0024efd83 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 12:52:44 +0000 Subject: [PATCH 704/933] Task #9607: check for None --- SAS/MoM/MoMQueryService/momqueryservice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index eb719155679..ce8b6842c83 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -184,7 +184,7 @@ class MoMDatabaseWrapper: for row in rows: mom2id = row['mom2id'] pred_string = row['predecessor'] - pred_id_list = [y[1:] for y in [x.strip() for x in pred_string.split(',')] if y[0] == 'M'] + pred_id_list = [y[1:] for y in [x.strip() for x in pred_string.split(',')] if y[0] == 'M'] if pred_string else [] pred_id_list = [int(x) for x in pred_id_list if x.isdigit()] result[str(mom2id)] = pred_id_list @@ -221,7 +221,7 @@ class MoMDatabaseWrapper: for row in rows: suc_mom2id = row['mom2id'] pred_string = row['predecessor'] - pred_id_list = [y[1:] for y in [x.strip() for x in pred_string.split(',')] if y[0] == 'M'] + pred_id_list = [y[1:] for y in [x.strip() for x in pred_string.split(',')] if y[0] == 'M'] if pred_string else [] for mom2id in ids_str.split(','): if mom2id in pred_id_list: result[str(mom2id)].append(suc_mom2id) -- GitLab From 28ac303ed484ca5e9693ab78869756d487cf5bad Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 13:27:23 +0000 Subject: [PATCH 705/933] Task #9607: getTasks by cluster --- SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py | 6 +++++- SAS/ResourceAssignment/ResourceAssignmentService/rpc.py | 4 ++-- SAS/ResourceAssignment/ResourceAssignmentService/service.py | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 8ab5103dd13..014ff430b7e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -155,7 +155,7 @@ class RADatabase: raise KeyError('No such status: %s. Valid values are: %s' % (status_name, ', '.join(self.getResourceClaimStatusNames()))) - def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None): + def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None, cluster=None): if len([x for x in [task_ids, mom_ids, otdb_ids] if x != None]) > 1: raise KeyError("Provide either task_ids or mom_ids or otdb_ids, not multiple kinds.") @@ -206,6 +206,10 @@ class RADatabase: conditions.append('type_id = %s') qargs.append(task_type) + if cluster is not None: + conditions.append('cluster = %s') + qargs.append(cluster) + if conditions: query += ' WHERE ' + ' AND '.join(conditions) diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py index eb8e61c0b5f..7b4496abd62 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py @@ -179,8 +179,8 @@ class RARPC(RPCWrapper): otdb_id=otdb_id, status=status) - def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None): - tasks = self.rpc('GetTasks', lower_bound=lower_bound, upper_bound=upper_bound, task_ids=task_ids, task_status=task_status, task_type=task_type, mom_ids=mom_ids, otdb_ids=otdb_ids) + def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None, cluster=None): + tasks = self.rpc('GetTasks', lower_bound=lower_bound, upper_bound=upper_bound, task_ids=task_ids, task_status=task_status, task_type=task_type, mom_ids=mom_ids, otdb_ids=otdb_ids, cluster=cluster) for task in tasks: task['starttime'] = task['starttime'].datetime() task['endtime'] = task['endtime'].datetime() diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/service.py b/SAS/ResourceAssignment/ResourceAssignmentService/service.py index 7e01393bee2..d33fadea6f7 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/service.py @@ -217,7 +217,8 @@ class RADBHandler(MessageHandlerInterface): task_status=kwargs.get('task_status'), task_type=kwargs.get('task_type'), mom_ids=kwargs.get('mom_ids'), - otdb_ids=kwargs.get('otdb_ids')) + otdb_ids=kwargs.get('otdb_ids'), + cluster=kwargs.get('cluster')) def _getTask(self, **kwargs): logger.info('GetTask: %s' % dict({k:v for k,v in kwargs.items() if v != None})) -- GitLab From 90de1dd06b9298e4e4ce27ca6659b1fd65528305 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 13:28:08 +0000 Subject: [PATCH 706/933] Task #9607: move CEP4 pipelines way in the future to now --- .../ResourceAssigner/lib/schedulechecker.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index 007210019bd..26512725637 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -39,8 +39,8 @@ logger = logging.getLogger(__name__) def movePipelineAfterItsPredecessors(task, radbrpc, min_start_timestamp=None): try: - #only reschedule pipelines which have resourceclaims and run on cep4 - if task and task['type'] == 'pipeline' and task.get('cluster') == 'CEP4' and radbrpc.getResourceClaims(task_ids=task['id']): + #only reschedule pipelines which run on cep4 + if task and task['type'] == 'pipeline' and task.get('cluster') == 'CEP4': logger.info("checking pipeline starttime radb_id=%s otdb_id=%s", task['id'], task['otdb_id']) predecessor_tasks = radbrpc.getTasks(task_ids=task['predecessor_ids']) @@ -50,9 +50,9 @@ def movePipelineAfterItsPredecessors(task, radbrpc, min_start_timestamp=None): max_pred_endtime = max(predecessor_endtimes) - if task['starttime'] < max_pred_endtime: + if (task['starttime'] < max_pred_endtime) or (min_start_timestamp and task['starttime'] > min_start_timestamp): shift = max_pred_endtime - task['starttime'] - logger.info("Moving ahead %s pipeline radb_id=%s otdb_id=%s by %s", task['status'], task['id'], task['otdb_id'], shift) + logger.info("Moving %s pipeline radb_id=%s otdb_id=%s by %s", task['status'], task['id'], task['otdb_id'], shift) radbrpc.updateTaskAndResourceClaims(task['id'], starttime=task['starttime']+shift, endtime=task['endtime']+shift) updated_task = radbrpc.getTask(task['id']) if updated_task['status'] not in [u'scheduled', u'queued']: @@ -123,8 +123,8 @@ class ScheduleChecker(): now = datetime.utcnow() min_start_timestamp = now + timedelta(seconds=PIPELINE_CHECK_INTERVAL) - scheduled_pipelines = self._radbrpc.getTasks(task_status='scheduled', task_type='pipeline') - queued_pipelines = self._radbrpc.getTasks(task_status='queued', task_type='pipeline') + scheduled_pipelines = self._radbrpc.getTasks(task_status='scheduled', task_type='pipeline', cluster='CEP4') + queued_pipelines = self._radbrpc.getTasks(task_status='queued', task_type='pipeline', cluster='CEP4') sq_pipelines = scheduled_pipelines + queued_pipelines if sq_pipelines: -- GitLab From f609b13dc51dad9821a96ee103b9beb941fbae9d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 14:15:45 +0000 Subject: [PATCH 707/933] Task #9607: assign empty list for pred/suc ids --- SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 014ff430b7e..b51921f2ba6 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -215,6 +215,13 @@ class RADatabase: tasks = list(self._executeQuery(query, qargs, fetch=_FETCH_ALL)) + for task in tasks: + if task['predecessor_ids'] is None: + task['predecessor_ids'] = [] + + if task['successor_ids'] is None: + task['successor_ids'] = [] + return tasks -- GitLab From b73672be26d29bfbd53afe09cf5700b366cc0523 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 14:16:03 +0000 Subject: [PATCH 708/933] Task #9607: cluster arg for specification --- .../ResourceAssignmentService/service.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/service.py b/SAS/ResourceAssignment/ResourceAssignmentService/service.py index d33fadea6f7..25b8cbd8ba2 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/service.py @@ -284,13 +284,15 @@ class RADBHandler(MessageHandlerInterface): kwargs['task_type'], kwargs.get('starttime').datetime() if kwargs.get('starttime') else None, kwargs.get('endtime').datetime() if kwargs.get('endtime') else None, - kwargs['content']) + kwargs['content'], + kwargs['cluster']) def _insertSpecification(self, **kwargs): logger.info('InsertSpecification: %s' % dict({k:v for k,v in kwargs.items() if v != None})) specification_id = self.radb.insertSpecification(kwargs.get('starttime').datetime() if kwargs.get('starttime') else None, kwargs.get('endtime').datetime() if kwargs.get('endtime') else None, - kwargs['content']) + kwargs['content'], + kwargs['cluster']) return {'id':specification_id} def _deleteSpecification(self, **kwargs): @@ -305,7 +307,8 @@ class RADBHandler(MessageHandlerInterface): updated = self.radb.updateSpecification(id, starttime=kwargs['starttime'].datetime() if 'starttime' in kwargs else None, endtime=kwargs['endtime'].datetime() if 'endtime' in kwargs else None, - content=kwargs.get('content')) + content=kwargs.get('content'), + cluster=kwargs.get('cluster')) return {'id': id, 'updated': updated} def _getUnits(self): -- GitLab From 0c78f4b25be9aa4f4040e13f131cf7d9cd4e31db Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 14:16:31 +0000 Subject: [PATCH 709/933] Task #9607: move scheduled/queued cep4 pipelines to now --- .../ResourceAssigner/lib/assignment.py | 2 +- .../ResourceAssigner/lib/schedulechecker.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 13e912d31b8..84432456013 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -274,7 +274,7 @@ class ResourceAssigner(): task['mom_id'], task['otdb_id']) self.radbrpc.insertTaskPredecessor(successor_task['id'], task['id']) - movePipelineAfterItsPredecessors(successor_task, self.radbrpc) + movePipelineAfterItsPredecessors(successor_task, self.radbrpc, datetime.utcnow()) else: logger.warning('could not find predecessor task with otdb_id=%s in radb for task otdb_id=%s', successor_task['otdb_id'], task['otdb_id']) else: diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index 26512725637..d309b2a229c 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -44,6 +44,7 @@ def movePipelineAfterItsPredecessors(task, radbrpc, min_start_timestamp=None): logger.info("checking pipeline starttime radb_id=%s otdb_id=%s", task['id'], task['otdb_id']) predecessor_tasks = radbrpc.getTasks(task_ids=task['predecessor_ids']) + predecessor_endtimes = [t['endtime'] for t in predecessor_tasks] if min_start_timestamp: predecessor_endtimes.append(min_start_timestamp) @@ -52,11 +53,11 @@ def movePipelineAfterItsPredecessors(task, radbrpc, min_start_timestamp=None): if (task['starttime'] < max_pred_endtime) or (min_start_timestamp and task['starttime'] > min_start_timestamp): shift = max_pred_endtime - task['starttime'] - logger.info("Moving %s pipeline radb_id=%s otdb_id=%s by %s", task['status'], task['id'], task['otdb_id'], shift) + logger.info("Moving %s pipeline radb_id=%s otdb_id=%s by %s from \'%s\' to \'%s\'", task['status'], task['id'], task['otdb_id'], shift, task['starttime'], max_pred_endtime) radbrpc.updateTaskAndResourceClaims(task['id'], starttime=task['starttime']+shift, endtime=task['endtime']+shift) updated_task = radbrpc.getTask(task['id']) - if updated_task['status'] not in [u'scheduled', u'queued']: - logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change to %s", updated_task['id'], updated_task['otdb_id'], updated_task['status']) + if updated_task['status'] != task['status']: + logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change from %s to %s", updated_task['id'], updated_task['otdb_id'], task['status'], updated_task['status']) #TODO: automatically resolve conflict status by moved pipeline in first free time slot. except Exception as e: logger.error("Error while checking pipeline starttime: %s", e) @@ -125,12 +126,13 @@ class ScheduleChecker(): scheduled_pipelines = self._radbrpc.getTasks(task_status='scheduled', task_type='pipeline', cluster='CEP4') queued_pipelines = self._radbrpc.getTasks(task_status='queued', task_type='pipeline', cluster='CEP4') - sq_pipelines = scheduled_pipelines + queued_pipelines + pipelines = scheduled_pipelines + queued_pipelines - if sq_pipelines: - logger.info('checking starttime of %s scheduled/queued cep4 pipelines', len(sq_pipelines)) + if pipelines: + logger.info('checking starttime of %s scheduled/queued cep4 pipelines min_start_timestamp=%s', len(pipelines), min_start_timestamp) + pipelines.sort(key=lambda pl: pl['starttime'], reverse=True) - for task in sq_pipelines: + for task in pipelines: movePipelineAfterItsPredecessors(task, self._radbrpc, min_start_timestamp) except Exception as e: logger.error("Error while checking scheduled pipelines: %s", e) -- GitLab From f816b9af6b0576988cd0b150c87d9985b9792016 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 26 Aug 2016 18:22:51 +0000 Subject: [PATCH 710/933] Task #9607: run in UTC timezone --- .../RATaskSpecifiedService/lib/RATaskSpecified.py | 4 ++++ SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py | 4 ++++ SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py | 4 ++++ SAS/ResourceAssignment/ResourceAssignmentService/service.py | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py b/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py index 4847a364f2d..3c61fedb012 100755 --- a/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py +++ b/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py @@ -298,6 +298,10 @@ class RATaskSpecified(OTDBBusListener): logger.info("Result sent") def main(): + # make sure we run in UTC timezone + import os + os.environ['TZ'] = 'UTC' + import logging import sys from optparse import OptionParser diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py index 775d484b03e..0fd5d28fe2e 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py @@ -74,6 +74,10 @@ class SpecifiedTaskListener(RATaskSpecifiedBusListener): __all__ = ["SpecifiedTaskListener"] def main(): + # make sure we run in UTC timezone + import os + os.environ['TZ'] = 'UTC' + from optparse import OptionParser from lofar.messaging import setQpidLogLevel from lofar.common.util import waitForInterrupt diff --git a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py index fd1f1fd6e65..a0f720bb348 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEstimator/service.py @@ -124,6 +124,10 @@ def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, brok verbose=True) def main(): + # make sure we run in UTC timezone + import os + os.environ['TZ'] = 'UTC' + from optparse import OptionParser from lofar.messaging import setQpidLogLevel from lofar.common.util import waitForInterrupt diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/service.py b/SAS/ResourceAssignment/ResourceAssignmentService/service.py index 25b8cbd8ba2..b205c500b86 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/service.py @@ -325,6 +325,10 @@ def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, brok verbose=verbose) def main(): + # make sure we run in UTC timezone + import os + os.environ['TZ'] = 'UTC' + # Check the invocation arguments parser = OptionParser("%prog [options]", description='runs the resourceassignment database service') -- GitLab From f4ba80d2d43fe48a3179bb382652a26f099fb777 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 27 Aug 2016 14:32:33 +0000 Subject: [PATCH 711/933] Task #9522: Ask SLURM which nodes are available on CEP4 --- .../GPUProc/src/scripts/cobalt_functions.sh | 11 +++++++---- RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh | 4 ++++ RTCP/Cobalt/GPUProc/src/scripts/startBGL.sh | 15 --------------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh b/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh index 301a14087ab..0d73077f3da 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh @@ -52,9 +52,11 @@ function read_cluster_model { case "${CLUSTER_NAME}" in CEP4) HEADNODE=head01.cep4.control.lofar - #COMPUTENODES="`seq -f "cpu%02.0f.cep4" 1 50`" - COMPUTENODES="`seq -f "cpu%02.0f.cep4" 1 26` `seq -f "cpu%02.0f.cep4" 30 35` `seq -f "cpu%02.0f.cep4" 37 39`" - NRCOMPUTENODES=50 + COMPUTENODES="`ssh $HEADNODE sinfo --responding --states=idle,mixed,alloc --format=%n --noheader --partition=cobalt --sort=N | awk '{ print $1 ".cep4"; }'`" + if [ -z "$COMPUTENODES" ]; then + echo "ERROR: Could not obtain list of available CEP4 nodes. Defaulting to all." + COMPUTENODES="`seq -f "cpu%02.0f.cep4" 1 50`" + fi GLOBALFS_DIR=/data @@ -66,11 +68,12 @@ function read_cluster_model { *) HEADNODE=lhn001.cep2.lofar COMPUTENODES="`seq -f "locus%02.0f" 1 94`" - NRCOMPUTENODES=94 SLURM=false GLOBALFS=false DOCKER=false ;; esac + + NRCOMPUTENODES=`echo $COMPUTENODES | wc -w` } diff --git a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh index 2333b3230c1..dd0c8b1fc6c 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh @@ -252,6 +252,10 @@ read_cluster_model # Determine list of outputProc hosts for various purposes if $SLURM; then + # TODO: Start SLURM job, wait for resources, record node list + echo "ERROR: SLURM resource allocation is not supported" + exit 1 + # Expand node list into something usable NODE_LIST="`ssh $HEADNODE scontrol show hostnames $SLURM_JOB_NODELIST`" else diff --git a/RTCP/Cobalt/GPUProc/src/scripts/startBGL.sh b/RTCP/Cobalt/GPUProc/src/scripts/startBGL.sh index 99e90f9aae2..5103ddc82f6 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/startBGL.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/startBGL.sh @@ -63,21 +63,6 @@ mkfifo -m 0660 "$COMMANDPIPE" || true # Construct command line COMMAND="env LOFARENV=$LOFARENV runObservation.sh -P $PIDFILE -o Cobalt.commandStream=file:$COMMANDPIPE $PARSET" -# Process cluster requirements -read_cluster_model - -if $SLURM; then - # We need to issue "salloc" on the target cluster, and once the resources - # are available, the job should first SSH back here to start the observation. - - # Note that we need to marshall some SLURM environment variables as well, hence the use of bash. - for s in SLURM_JOB_ID SLURM_JOB_NODELIST SLURM_JOB_NUM_NODES; do - SLURM_VARS+=" $s=\$$s" - done - - COMMAND="ssh -tt $HEADNODE salloc -N $NRCOMPUTENODES -J $OBSID bash -c 'ssh `hostname -f` -tt $SLURM_VARS $COMMAND'" -fi - # Start observation in the background echo "Starting $COMMAND" $COMMAND > $LOGFILE 2>&1 </dev/null & -- GitLab From 988ae82c17b9519e5bede4adcc2b81d8e3df76a0 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 27 Aug 2016 20:44:50 +0000 Subject: [PATCH 712/933] Task #9522: Fix possibility of overlapping allocation in SparseSetAllocator --- RTCP/Cobalt/CoInterface/src/Allocator.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RTCP/Cobalt/CoInterface/src/Allocator.cc b/RTCP/Cobalt/CoInterface/src/Allocator.cc index ba185a4b90a..a6b80b9a1d7 100644 --- a/RTCP/Cobalt/CoInterface/src/Allocator.cc +++ b/RTCP/Cobalt/CoInterface/src/Allocator.cc @@ -124,6 +124,11 @@ namespace LOFAR for (SparseSet<void *>::const_iterator it = freeList.getRanges().begin(); it != freeList.getRanges().end(); it++) { void *begin = align(it->begin, alignment); + if ((char *) begin >= (char *) it->end ) { + // alignment shift already results in out of bounds + continue; + } + if ((char *) it->end - (char *) begin >= (ptrdiff_t) size) { // enough space -- reserve it freeList.exclude(begin, (void *) ((char *) begin + size)); -- GitLab From c5e8cb7ea33a99755824b6af5aa6504fbdeaffc0 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 27 Aug 2016 20:45:34 +0000 Subject: [PATCH 713/933] Task #9522: Fix CorrelatedData::size. Introduced CorrelatedData::nrBytesPerNrValidSamples. --- RTCP/Cobalt/CoInterface/src/CorrelatedData.cc | 17 +++++++++-------- RTCP/Cobalt/CoInterface/src/CorrelatedData.h | 2 ++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/RTCP/Cobalt/CoInterface/src/CorrelatedData.cc b/RTCP/Cobalt/CoInterface/src/CorrelatedData.cc index 83ec3a1c148..c207e63c260 100644 --- a/RTCP/Cobalt/CoInterface/src/CorrelatedData.cc +++ b/RTCP/Cobalt/CoInterface/src/CorrelatedData.cc @@ -76,8 +76,7 @@ namespace LOFAR itsAlignment, allocator, true), - itsNrBytesPerNrValidSamples( - maxNrValidSamples < 256 ? 1 : maxNrValidSamples < 65536 ? 2 : 4) + itsNrBytesPerNrValidSamples(nrBytesPerNrValidSamples(maxNrValidSamples)) { init(nrChannels, allocator); } @@ -100,8 +99,7 @@ namespace LOFAR [NR_POLARIZATIONS], visibilities, false), - itsNrBytesPerNrValidSamples( - maxNrValidSamples < 256 ? 1 : maxNrValidSamples < 65536 ? 2 : 4) + itsNrBytesPerNrValidSamples(nrBytesPerNrValidSamples(maxNrValidSamples)) { ASSERT(this->visibilities.num_elements() == nrVisibilities); init(nrChannels, allocator); @@ -114,11 +112,14 @@ namespace LOFAR unsigned alignment) { const size_t nrBaselines = nrStations * (nrStations + 1) / 2; - const size_t nrBytesPerNrValidSamples = - maxNrValidSamples < 256 ? 1 : maxNrValidSamples < 65536 ? 2 : 4; - return nrBaselines * nrChannels * NR_POLARIZATIONS * NR_POLARIZATIONS * sizeof(float) + alignment - + nrBaselines * nrChannels * nrBytesPerNrValidSamples + 2 * alignment; + return nrBaselines * nrChannels * NR_POLARIZATIONS * NR_POLARIZATIONS * sizeof (fcomplex) + alignment + + nrBaselines * nrChannels * nrBytesPerNrValidSamples(maxNrValidSamples) + 2 * alignment; // nrValidSamples are aligned and padded + } + + + size_t CorrelatedData::nrBytesPerNrValidSamples(size_t maxNrValidSamples) { + return maxNrValidSamples < 256 ? 1 : maxNrValidSamples < 65536 ? 2 : 4; } diff --git a/RTCP/Cobalt/CoInterface/src/CorrelatedData.h b/RTCP/Cobalt/CoInterface/src/CorrelatedData.h index b0f177da676..0efe2c42eee 100644 --- a/RTCP/Cobalt/CoInterface/src/CorrelatedData.h +++ b/RTCP/Cobalt/CoInterface/src/CorrelatedData.h @@ -93,6 +93,8 @@ namespace LOFAR Matrix<uint32_t> itsNrValidSamples4; // [nrBaselines][nrChannels] Matrix<uint16_t> itsNrValidSamples2; // [nrBaselines][nrChannels] Matrix<uint8_t> itsNrValidSamples1; // [nrBaselines][nrChannels] + + static size_t nrBytesPerNrValidSamples(size_t maxNrValidSamples); }; -- GitLab From 17f3f83c85864631c507668e8a067c14f3ef92f7 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sun, 28 Aug 2016 11:06:32 +0000 Subject: [PATCH 714/933] Task #9522: Strengthened test to detect CorrelatedData::size errors --- RTCP/Cobalt/OutputProc/test/tSubbandWriter.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/RTCP/Cobalt/OutputProc/test/tSubbandWriter.cc b/RTCP/Cobalt/OutputProc/test/tSubbandWriter.cc index 24eee255d26..d51f882ab71 100644 --- a/RTCP/Cobalt/OutputProc/test/tSubbandWriter.cc +++ b/RTCP/Cobalt/OutputProc/test/tSubbandWriter.cc @@ -57,15 +57,21 @@ SUITE(SubbandWriter) Parset ps; OneBeam() { - ps.add("Observation.VirtualInstrument.stationList", "[CS001]"); + ps.add("Observation.VirtualInstrument.stationList", "[CS001,15*CS002]"); ps.add("Observation.Dataslots.CS001LBA.RSPBoardList", "[0]"); ps.add("Observation.Dataslots.CS001LBA.DataslotList", "[0]"); + ps.add("Observation.Dataslots.CS002LBA.RSPBoardList", "[0]"); + ps.add("Observation.Dataslots.CS002LBA.DataslotList", "[0]"); ps.add("PIC.Core.CS001LBA.position", "[0,0,0]"); ps.add("PIC.Core.CS001LBA.phaseCenter", "[0,0,0]"); + ps.add("PIC.Core.CS002LBA.position", "[0,0,0]"); + ps.add("PIC.Core.CS002LBA.phaseCenter", "[0,0,0]"); ps.add("Observation.ObsID", boost::lexical_cast<string>(obsId)); ps.add("Observation.startTime", "2013-01-01 00:00"); ps.add("Observation.stopTime", "2013-01-01 01:00"); + ps.add("Cobalt.Correlator.nrChannelsPerSubband", "64"); + ps.add("Cobalt.blockSize", "196608"); ps.add("Observation.nrBeams", "1"); ps.add("Observation.Beam[0].subbandList", "[0]"); -- GitLab From 5a751303f6eeec1e22874be8ef4e9a3e72cc5a15 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 29 Aug 2016 07:34:45 +0000 Subject: [PATCH 715/933] Task #9607: fixed race condition. When otdb listener is triggered by onTaskScheduled, then you need to get the task status from otdb and not radb where the status still may need to be propagated to --- MAC/Services/src/PipelineControl.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 5a1eeab28ea..5679178f5d1 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -246,14 +246,17 @@ class PipelineDependencies(object): """ Raised when a task cannot be found in the RADB. """ pass - def __init__(self, ra_service_busname=DEFAULT_RAS_SERVICE_BUSNAME): + def __init__(self, ra_service_busname=DEFAULT_RAS_SERVICE_BUSNAME, otdb_service_busname=DEFAULT_OTDB_SERVICE_BUSNAME): self.rarpc = RARPC(busname=ra_service_busname) + self.otdbrpc = OTDBRPC(busname=otdb_service_busname) def open(self): self.rarpc.open() + self.otdbrpc.open() def close(self): self.rarpc.close() + self.otdbrpc.close() def __enter__(self): self.open() @@ -266,9 +269,7 @@ class PipelineDependencies(object): """ Return the status of a single `otdb_id'. """ - - radb_task = self.rarpc.getTask(otdb_id=otdb_id) - return radb_task["status"] + return self.otdbrpc.taskGetStatus(otdb_id=otdb_id) def getPredecessorStates(self, otdb_id): """ @@ -327,7 +328,7 @@ class PipelineControl(OTDBBusListener): self.otdb_service_busname = otdb_service_busname self.otdbrpc = OTDBRPC(busname=otdb_service_busname) - self.dependencies = PipelineDependencies(ra_service_busname=ra_service_busname) + self.dependencies = PipelineDependencies(ra_service_busname=ra_service_busname, otdb_service_busname=DEFAULT_OTDB_SERVICE_BUSNAME) self.slurm = Slurm() def _setStatus(self, otdb_id, status): -- GitLab From 68d6c9620d154afc5d40c81323d83c232cd93647 Mon Sep 17 00:00:00 2001 From: Stefan Froehlich <s.froehlich@fz-juelich.de> Date: Mon, 29 Aug 2016 08:05:59 +0000 Subject: [PATCH 716/933] Task #9800: support for remote clusters in Hertfordshire and Juelich. changes in command construction and special handling of node setup. --- .../lofarpipe/support/remotecommand.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index 9bde0ca9846..cc200680806 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -22,6 +22,7 @@ from lofarpipe.support.lofarexceptions import PipelineQuit from lofarpipe.support.jobserver import job_server import lofarpipe.support.lofaringredient as ingredient from lofarpipe.support.xmllogging import add_child +from subprocess import Popen, PIPE # By default, Linux allocates lots more memory than we need(?) for a new stack # frame. When multiplexing lots of threads, that will cause memory issues. @@ -58,9 +59,28 @@ def run_remote_command(config, logger, host, command, env, arguments = None, res return run_via_slurm_srun_cep3(logger, command, arguments, host) elif method == "custom_cmdline": return run_via_custom_cmdline(logger, host, command, env, arguments, config, resources) + # Hertfordshire cluster + elif method == "pbs_ssh": + return run_via_ssh(logger, host, command, env, arguments) + # Jureca HPC + elif method == "slurm_srun": + return run_via_slurm_srun(logger, command, arguments, host) else: return run_via_ssh(logger, host, command, env, arguments) + +def run_via_slurm_srun(logger, command, arguments, host): + for arg in arguments: + command = command + " " + str(arg) + commandarray = ["srun", "-N 1", "--cpu_bind=map_cpu:none", "-w", host, "/bin/sh", "-c", "hostname && " + command] + # we have a bug that crashes jobs when too many get startet at the same time + # temporary NOT 100% reliable workaround + from random import randint + time.sleep(randint(1, 20)/2.) + ########################## + return commandarray + + def run_via_slurm_srun_cep3(logger, command, arguments, host): logger.debug("Dispatching command to %s with srun" % host) for arg in arguments: @@ -365,6 +385,62 @@ class RemoteCommandRecipeMixIn(object): if max_per_node: self.logger.info("Limiting to %d simultaneous jobs/node" % max_per_node) + # External cluster stuff + try: + method = self.config.get('remote', 'method') + except: + method = None + # JURECA SLURM + if method == 'slurm_srun': + nodeliststr = [] + hargs = ['srun','hostname'] + proc = Popen(hargs, False, stdout=PIPE, stderr=None) + tup = proc.communicate() + nodeliststr = tup[0].rstrip('\n').split('\n') + # remove duplicates. order not important + nodeliststr = list(set(nodeliststr)) + + # equal distribution + total = len(jobs) + # when nodes crash? length of slurm_nodelist and env slurm_nnodes dont match anymore + nnodes = len(nodeliststr) + # round robin + nodelist = [] + for i in range(total): + nodelist.append(nodeliststr[i%nnodes]) + + for i, job in enumerate(jobs): + job.host = nodelist[i] + + # Hertfordshire cluster + if method == 'pbs_ssh': + # special case - get the list of nodes from the pbs job + nodeliststr = [] + + try: + filename = os.environ['PBS_NODEFILE'] + except KeyError: + self.logger.error('PBS_NODEFILE not found.') + raise PipelineQuit() + + with open(filename, 'r') as file: + for line in file: + node_name = line.split()[0] + if node_name not in nodeliststr: + nodeliststr.append(node_name) + + # equal distribution + total = len(jobs) + # when nodes crash? length of slurm_nodelist and env slurm_nnodes dont match anymore + nnodes = len(nodeliststr) + # round robin + nodelist = [] + for i in range(total): + nodelist.append(nodeliststr[i%nnodes]) + + for i, job in enumerate(jobs): + job.host = nodelist[i] + with job_server(self.logger, jobpool, self.error) as (jobhost, jobport): self.logger.debug("Job dispatcher at %s:%d" % (jobhost, jobport)) for job_id, job in enumerate(jobs): -- GitLab From f3be35f8fe0631d1cd461758282f7c6209cb8929 Mon Sep 17 00:00:00 2001 From: Stefan Froehlich <s.froehlich@fz-juelich.de> Date: Mon, 29 Aug 2016 08:07:48 +0000 Subject: [PATCH 717/933] Task #9800: better output in case of errors. --- CEP/Pipeline/recipes/sip/master/executable_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/master/executable_args.py b/CEP/Pipeline/recipes/sip/master/executable_args.py index e418b7e399c..5fd1472b9f7 100644 --- a/CEP/Pipeline/recipes/sip/master/executable_args.py +++ b/CEP/Pipeline/recipes/sip/master/executable_args.py @@ -391,7 +391,7 @@ class executable_args(BaseRecipe, RemoteCommandRecipeMixIn): if job.results['returncode'] != 0: outp.skip = True if not self.inputs['error_tolerance']: - self.logger.error("A job has failed and error_tolerance is not set. Bailing out!") + self.logger.error("A job has failed with returncode %d and error_tolerance is not set. Bailing out!" % job.results['returncode']) return 1 for k, v in job.results.items(): if not k in jobresultdict: -- GitLab From 4f205f1432158d6ab652797904b5a6a84ce8829c Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 29 Aug 2016 10:28:04 +0000 Subject: [PATCH 718/933] Task #9279: remove obsolete variant files from DAS-4 and the one from Sven Duscha who left years ago. DAS-4 variant file removal checked with Bram to ensure they are not used on DAS-5. --- .gitattributes | 4 --- CMake/variants/variants.Macbook-Pro | 17 ----------- CMake/variants/variants.fs0 | 22 -------------- CMake/variants/variants.fs5 | 47 ----------------------------- CMake/variants/variants.gpu01 | 1 - CMake/variants/variants.gpu1 | 1 - CMake/variants/variants.sharkbay | 20 ------------ 7 files changed, 112 deletions(-) delete mode 100644 CMake/variants/variants.Macbook-Pro delete mode 100644 CMake/variants/variants.fs0 delete mode 100644 CMake/variants/variants.fs5 delete mode 120000 CMake/variants/variants.gpu01 delete mode 120000 CMake/variants/variants.gpu1 delete mode 100644 CMake/variants/variants.sharkbay diff --git a/.gitattributes b/.gitattributes index deda4072f96..c82c37c84cc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2345,9 +2345,6 @@ CMake/variants/variants.cbt010 -text CMake/variants/variants.dop282 -text CMake/variants/variants.dop320 -text CMake/variants/variants.dragproc -text -CMake/variants/variants.fs0 -text -CMake/variants/variants.gpu01 -text -CMake/variants/variants.gpu1 -text CMake/variants/variants.lcs157 -text CMake/variants/variants.lexar -text CMake/variants/variants.lexar001 -text @@ -2355,7 +2352,6 @@ CMake/variants/variants.lexar002 -text CMake/variants/variants.lhn002 -text CMake/variants/variants.lotar -text CMake/variants/variants.phi -text -CMake/variants/variants.sharkbay -text Docker/docker-build-all.sh -text Docker/dynspec/Dockerfile -text Docker/dynspec/bashrc -text diff --git a/CMake/variants/variants.Macbook-Pro b/CMake/variants/variants.Macbook-Pro deleted file mode 100644 index 832ac78323b..00000000000 --- a/CMake/variants/variants.Macbook-Pro +++ /dev/null @@ -1,17 +0,0 @@ -option(USE_LOG4CXX "log4cxx is used" OFF) -option(USE_SHMEM "No shmem" OFF) -option(USE_LOG4CPLUS "Do not use LOG4CPLUS" ON) -set(BOOST_ROOT_DIR /usr/local) -set(BOOST_INCLUDE_DIR /usr/local/include/boost) -set(HDF5_ROOT_DIR /usr/local/hdf5) -set(HDF5_INCLUDE_DIR /usr/local/hdf5/include) -set(PQ_LIBRARY /usr/local/pgsql/lib/libpq.a) -set(PQXX_LIBRARIES /usr/local/lib) -set(CASACORE_ROOT_DIR /usr/local) -set(CMAKE_INSTALL_PREFIX /opt/lofar) - -# GNU compiler suite -set(GNU_COMPILERS GNU_C GNU_CXX GNU_ASM) -set(GNU_C /opt/local/bin/gcc ) # GNU C compiler -set(GNU_CXX /opt/local/bin/g++ ) # GNU C++ compiler -set(GNU_ASM /opt/local/bin/gcc ) # GNU assembler diff --git a/CMake/variants/variants.fs0 b/CMake/variants/variants.fs0 deleted file mode 100644 index bc655c68bf5..00000000000 --- a/CMake/variants/variants.fs0 +++ /dev/null @@ -1,22 +0,0 @@ -## -*- CMake -*- -## -## Host specific variants file. -## Settings in this file extend or override those in the global variants file. -## -## $Id: variants.fs5 25763 2013-07-25 09:33:20Z mol $ - -option(USE_LOG4CPLUS "Use Log4Cplus" ON) -option(USE_MPI "Use MPI" ON) - -set(CUDADRIVER_ROOT_DIR $ENV{CUDA_ROOT}) -set(CUDA_TOOLKIT_ROOT_DIR $ENV{CUDA_PATH}) -set(LOG4CPLUS_ROOT_DIR /home/mol/root) -set(CASACORE_ROOT_DIR /home/mol/root) -set(LIBSSH2_ROOT_DIR /home/mol/root) -set(DAL_ROOT_DIR /home/mol/root) -set(UNITTEST++_ROOT_DIR /home/mol/root/UnitTest++) - -# The file /usr/lib64/boost/BoostConfig.cmake in the package boost-devel-1.41 -# that ships with CentOS 6.3 seems to be broken. Setting Boost_NO_BOOST_CMAKE -# will ignore it. -set(Boost_NO_BOOST_CMAKE ON) diff --git a/CMake/variants/variants.fs5 b/CMake/variants/variants.fs5 deleted file mode 100644 index c8144443380..00000000000 --- a/CMake/variants/variants.fs5 +++ /dev/null @@ -1,47 +0,0 @@ -## -*- CMake -*- -## -## Host specific variants file. -## Settings in this file extend or override those in the global variants file. -## -## $Id$ - -option(USE_LOG4CPLUS "Use Log4Cplus" ON) -option(USE_MPI "Use MPI" ON) -# To build Cobalt, you must also enable USE_CUDA or USE_OPENCL (not both). - -set(CUDADRIVER_ROOT_DIR $ENV{CUDA_ROOT}) -set(CUDA_TOOLKIT_ROOT_DIR $ENV{CUDA_PATH}) -set(LOG4CPLUS_ROOT_DIR /home/jenkins/root) -set(CASACORE_ROOT_DIR /home/jenkins/root) -set(CASAREST_ROOT_DIR /home/jenkins/root) -set(CFITSIO_ROOT_DIR /home/jenkins/root) -set(PYRAP_ROOT_DIR /home/jenkins/root) -set(LIBSSH2_ROOT_DIR /home/jenkins/root) -set(BLITZ_ROOT_DIR /home/jenkins/root) -set(VALGRIND_ROOT_DIR /home/jenkins/root) # also in /usr, but ours has --with-mpicc=... (and includes, but we don't use them) -set(PQ_ROOT_DIR /usr/pgsql-8.4) -set(DAL_ROOT_DIR /home/jenkins/root) -set(UNITTEST++_ROOT_DIR /cm/shared/apps/UnitTest++) - -# To build MAC without deps on PVSS, pass -DBUILD_GCFPVSS=OFF. Run cmake a 2nd time to propagate. -#set(BUILD_GCFPVSS OFF) - -# The file /usr/lib64/boost/BoostConfig.cmake in the package boost-devel-1.41 -# that ships with CentOS 6.3 seems to be broken. Setting Boost_NO_BOOST_CMAKE -# will ignore it. -set(Boost_NO_BOOST_CMAKE ON) - - -# To use gcc 4.8.1 also use a newer boost. -#set(BOOST_ROOT_DIR /cm/shared/package/boost/1_54_0-gcc-4.8.1) -#set(Boost_ADDITIONAL_VERSIONS "1.54" "1.54.0") -#set(Boost_NO_SYSTEM_PATHS ON) - -# Enable gcc 4.8.1. If gcc does not have libbfd built (also) as a shared library, -# you get linker errors about static libs and -fPIC. Work around by passing to -# CMake -DUSE_BACKTRACE=OFF to get it into the CMake cache. -#set(GNU_C /cm/shared/package/gcc/4.8.1/bin/gcc) -#set(GNU_CXX /cm/shared/package/gcc/4.8.1/bin/g++) -#set(GNU_Fortran /cm/shared/package/gcc/4.8.1/bin/gfortran) -#set(GNU_ASM /cm/shared/package/gcc/4.8.1/bin/gcc) - diff --git a/CMake/variants/variants.gpu01 b/CMake/variants/variants.gpu01 deleted file mode 120000 index ce73d06cf96..00000000000 --- a/CMake/variants/variants.gpu01 +++ /dev/null @@ -1 +0,0 @@ -variants.fs5 \ No newline at end of file diff --git a/CMake/variants/variants.gpu1 b/CMake/variants/variants.gpu1 deleted file mode 120000 index ce73d06cf96..00000000000 --- a/CMake/variants/variants.gpu1 +++ /dev/null @@ -1 +0,0 @@ -variants.fs5 \ No newline at end of file diff --git a/CMake/variants/variants.sharkbay b/CMake/variants/variants.sharkbay deleted file mode 100644 index 25b6b562669..00000000000 --- a/CMake/variants/variants.sharkbay +++ /dev/null @@ -1,20 +0,0 @@ -## -*- CMake -*- -## -## Host specific variants file. -## Settings in this file extend or override those in the global variants file. -## -## $Id: variants.fs5 24757 2013-05-01 10:50:49Z loose $ - -option(USE_LOG4CPLUS "Use Log4Cplus" ON) -option(USE_MPI "Use MPI" ON) - -# Refer to symlink that go may through ccache. -# Force GCC 4.7 for CUDA 5.5 -set(GNU_C /usr/lib/ccache/gcc-4.7 ) -set(GNU_CXX /usr/lib/ccache/g++-4.7 ) -set(GNU_Fortran /usr/bin/gfortran ) -set(GNU_ASM /usr/lib/ccache/gcc-4.7 ) - -#set(CUDADRIVER_ROOT_DIR /usr/lib/nvidia-319/) -set(CUDA_TOOLKIT_ROOT_DIR /usr/local/cuda/) -set(UNITTEST++_ROOT_DIR /usr/include/unittest++) -- GitLab From 59c23541ec2bb27aef4847b165aa81f403155d2d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 29 Aug 2016 10:58:57 +0000 Subject: [PATCH 719/933] Task #9607: fix for zero length lists --- SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index b51921f2ba6..f64e69bf157 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -176,7 +176,7 @@ class RADatabase: if isinstance(task_ids, int): # just a single id conditions.append('id = %s') qargs.append(task_ids) - else: #assume a list/enumerable of id's + elif len(task_ids) > 0: #assume a list/enumerable of id's conditions.append('id in %s') qargs.append(tuple(task_ids)) @@ -184,7 +184,7 @@ class RADatabase: if isinstance(mom_ids, int): # just a single id conditions.append('mom_id = %s') qargs.append(mom_ids) - else: #assume a list/enumerable of id's + elif len(mom_ids) > 0: #assume a list/enumerable of id's conditions.append('mom_id in %s') qargs.append(tuple(mom_ids)) @@ -192,7 +192,7 @@ class RADatabase: if isinstance(otdb_ids, int): # just a single id conditions.append('otdb_id = %s') qargs.append(otdb_ids) - else: #assume a list/enumerable of id's + elif len(otdb_ids) > 0: #assume a list/enumerable of id's conditions.append('otdb_id in %s') qargs.append(tuple(otdb_ids)) -- GitLab From 064c53fcb8458405a7b46848637b79daf9373516 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 29 Aug 2016 11:44:10 +0000 Subject: [PATCH 720/933] Task #9607: added an RAScripts package which will hold scripts using ra/otdb/mom rpcs --- .gitattributes | 1 + CMake/LofarPackageList.cmake | 4 ++-- SAS/ResourceAssignment/CMakeLists.txt | 1 + SAS/ResourceAssignment/RAScripts/CMakeLists.txt | 7 +++++++ SubSystems/RAServices/CMakeLists.txt | 3 ++- 5 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 SAS/ResourceAssignment/RAScripts/CMakeLists.txt diff --git a/.gitattributes b/.gitattributes index 95ee64a43be..a0273d1b4f2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5054,6 +5054,7 @@ SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/__init__.py -text SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/otdbtorataskstatuspropagator -text SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/otdbtorataskstatuspropagator.ini -text SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py -text +SAS/ResourceAssignment/RAScripts/CMakeLists.txt -text SAS/ResourceAssignment/RATaskSpecifiedService/bin/CMakeLists.txt -text SAS/ResourceAssignment/RATaskSpecifiedService/bin/rataskspecifiedservice -text SAS/ResourceAssignment/RATaskSpecifiedService/bin/rataskspecifiedservice.ini -text diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index 88247a72744..de8392dd784 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -1,7 +1,7 @@ # - Create for each LOFAR package a variable containing the absolute path to # its source directory. # -# Generated by gen_LofarPackageList_cmake.sh at do 23 jun 2016 12:19:31 CEST +# Generated by gen_LofarPackageList_cmake.sh at ma 29 aug 2016 12:57:19 CEST # # ---- DO NOT EDIT ---- # @@ -37,7 +37,6 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(PythonDPPP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/PythonDPPP) set(DPPP_AOFlag_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/DPPP_AOFlag) set(SPW_Combine_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/SPWCombine) - set(AOFlagger_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/AOFlagger) set(LofarFT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Imager/LofarFT) set(AWImager2_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Imager/AWImager2) set(Laps-GRIDInterface_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/LAPS/GRIDInterface) @@ -161,6 +160,7 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(SystemStatusDatabase_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/ResourceAssignment/SystemStatusDatabase) set(SystemStatusService_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/ResourceAssignment/SystemStatusService) set(OTDBtoRATaskStatusPropagator_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator) + set(RAScripts_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SAS/ResourceAssignment/RAScripts) set(CCU_MAC_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SubSystems/CCU_MAC) set(LCU_MAC_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SubSystems/LCU_MAC) set(MCU_MAC_SOURCE_DIR ${CMAKE_SOURCE_DIR}/SubSystems/MCU_MAC) diff --git a/SAS/ResourceAssignment/CMakeLists.txt b/SAS/ResourceAssignment/CMakeLists.txt index 92b15a3a3f9..a391f0f99f4 100644 --- a/SAS/ResourceAssignment/CMakeLists.txt +++ b/SAS/ResourceAssignment/CMakeLists.txt @@ -10,5 +10,6 @@ lofar_add_package(ResourceAssignmentService) lofar_add_package(SystemStatusDatabase) lofar_add_package(SystemStatusService) lofar_add_package(OTDBtoRATaskStatusPropagator) +lofar_add_package(RAScripts) diff --git a/SAS/ResourceAssignment/RAScripts/CMakeLists.txt b/SAS/ResourceAssignment/RAScripts/CMakeLists.txt new file mode 100644 index 00000000000..54845a3409a --- /dev/null +++ b/SAS/ResourceAssignment/RAScripts/CMakeLists.txt @@ -0,0 +1,7 @@ +# $Id$ + +lofar_package(RAScripts 1.0 DEPENDS PyMessaging ResourceAssignmentService OTDB_Services) + +lofar_find_package(Python 2.6 REQUIRED) +include(PythonInstall) + diff --git a/SubSystems/RAServices/CMakeLists.txt b/SubSystems/RAServices/CMakeLists.txt index 9bc5419dca4..26b4cea0e0d 100644 --- a/SubSystems/RAServices/CMakeLists.txt +++ b/SubSystems/RAServices/CMakeLists.txt @@ -13,7 +13,8 @@ lofar_package(RAServices ResourceAssignmentService SystemStatusDatabase SystemStatusService - DataManagement) + DataManagement + RAScripts) # supervisord config files install(FILES -- GitLab From 35320e11872cd917892eabea0a5161209519dde3 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 29 Aug 2016 12:01:13 +0000 Subject: [PATCH 721/933] Task #9607: added preliminary PoverO script --- .gitattributes | 1 + .../RAScripts/CMakeLists.txt | 1 + SAS/ResourceAssignment/RAScripts/povero | 80 +++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100755 SAS/ResourceAssignment/RAScripts/povero diff --git a/.gitattributes b/.gitattributes index a0273d1b4f2..1c572c907c2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5055,6 +5055,7 @@ SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/otdbtorataskstatuspropagator SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/otdbtorataskstatuspropagator.ini -text SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py -text SAS/ResourceAssignment/RAScripts/CMakeLists.txt -text +SAS/ResourceAssignment/RAScripts/povero -text SAS/ResourceAssignment/RATaskSpecifiedService/bin/CMakeLists.txt -text SAS/ResourceAssignment/RATaskSpecifiedService/bin/rataskspecifiedservice -text SAS/ResourceAssignment/RATaskSpecifiedService/bin/rataskspecifiedservice.ini -text diff --git a/SAS/ResourceAssignment/RAScripts/CMakeLists.txt b/SAS/ResourceAssignment/RAScripts/CMakeLists.txt index 54845a3409a..0aafc91e4e1 100644 --- a/SAS/ResourceAssignment/RAScripts/CMakeLists.txt +++ b/SAS/ResourceAssignment/RAScripts/CMakeLists.txt @@ -5,3 +5,4 @@ lofar_package(RAScripts 1.0 DEPENDS PyMessaging ResourceAssignmentService OTDB_S lofar_find_package(Python 2.6 REQUIRED) include(PythonInstall) +lofar_add_bin_scripts(povero) diff --git a/SAS/ResourceAssignment/RAScripts/povero b/SAS/ResourceAssignment/RAScripts/povero new file mode 100755 index 00000000000..a6310f57a47 --- /dev/null +++ b/SAS/ResourceAssignment/RAScripts/povero @@ -0,0 +1,80 @@ +#!/usr/bin/python + +# Copyright (C) 2012-2015 ASTRON (Netherlands Institute for Radio Astronomy) +# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. + +# $Id: webservice.py 35176 2016-08-25 10:09:10Z schaap $ + +'''ResourceAssignmentEditor webservice serves a interactive html5 website for +viewing and editing lofar resources.''' + +import sys +from optparse import OptionParser +from datetime import datetime +import logging +import subprocess + +from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as DEFAULT_RADB_BUSNAME +from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as DEFAULT_RADB_SERVICENAME + +from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC +from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME + +from lofar.sas.otdb.otdbrpc import OTDBRPC +from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME + +logger = logging.getLogger(__name__) + +def main(): + # make sure we run in UTC timezone + import os + os.environ['TZ'] = 'UTC' + + # Check the invocation arguments + parser = OptionParser('%prog [options]', + description='compute P over O for CEP4 pipelines') + parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') + parser.add_option('--radb_busname', dest='radb_busname', type='string', default=DEFAULT_RADB_BUSNAME, help='Name of the bus exchange on the qpid broker on which the radbservice listens, default: %default') + parser.add_option('--radb_servicename', dest='radb_servicename', type='string', default=DEFAULT_RADB_SERVICENAME, help='Name of the radbservice, default: %default') + parser.add_option('--otdb_busname', dest='otdb_busname', type='string', default=DEFAULT_OTDB_SERVICE_BUSNAME, help='Name of the bus exchange on the qpid broker on which the otdbservice listens, default: %default') + parser.add_option('--otdb_servicename', dest='otdb_servicename', type='string', default=DEFAULT_OTDB_SERVICENAME, help='Name of the otdbservice, default: %default') + parser.add_option('--mom_query_busname', dest='mom_query_busname', type='string', default=DEFAULT_MOMQUERY_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momqueryservice listens, default: %default') + parser.add_option('--mom_query_servicename', dest='mom_query_servicename', type='string', default=DEFAULT_MOMQUERY_SERVICENAME, help='Name of the momqueryservice, default: %default') + (options, args) = parser.parse_args() + + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.INFO) + + ra = RARPC(busname=options.radb_busname, servicename=options.radb_servicename, broker=options.broker) + otdb = OTDBRPC(busname=options.otdb_busname, servicename=options.otdb_servicename, broker=options.broker) + mom = MoMQueryRPC(busname=options.mom_query_busname, servicename=options.mom_query_servicename, broker=options.broker) + + with ra, otdb, mom: + pipelines = ra.getTasks(cluster='CEP4', task_type='pipeline', task_status='finished') + + print "OBS otdb_id, OBS duration [s], PL otdb_id, PL duration [s], P/O" + + for pl in pipelines: + pred_observations = ra.getTasks(task_ids=pl['predecessor_ids'], task_type='observation', task_status='finished') + + for pred_obs in pred_observations: + print "%s, %s, %s, %s, %.2f" % (pred_obs['otdb_id'], pred_obs['duration'], pl['otdb_id'], pl['duration'], pl['duration']/pred_obs['duration']) + + +if __name__ == '__main__': + main() -- GitLab From 3395691a05f174b490a7460d33f58cf2d27054ea Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 29 Aug 2016 12:35:00 +0000 Subject: [PATCH 722/933] Task #9522: Avoid DRAINING nodes --- RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh b/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh index 0d73077f3da..c9f3c01b7a0 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh @@ -52,7 +52,7 @@ function read_cluster_model { case "${CLUSTER_NAME}" in CEP4) HEADNODE=head01.cep4.control.lofar - COMPUTENODES="`ssh $HEADNODE sinfo --responding --states=idle,mixed,alloc --format=%n --noheader --partition=cobalt --sort=N | awk '{ print $1 ".cep4"; }'`" + COMPUTENODES="`ssh $HEADNODE sinfo --responding --states=idle,mixed,alloc --format=%n.cep4,%T --noheader --partition=cobalt --sort=N | fgrep -v ,draining | cut -f1 -d,`" if [ -z "$COMPUTENODES" ]; then echo "ERROR: Could not obtain list of available CEP4 nodes. Defaulting to all." COMPUTENODES="`seq -f "cpu%02.0f.cep4" 1 50`" -- GitLab From 8a3964a20e3e7f071b14b32c961688e105a70c39 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 29 Aug 2016 16:29:15 +0000 Subject: [PATCH 723/933] Task #9127: update INSTALL wrt external AOFlagger, and URLs of casarest, IERS tables --- INSTALL | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/INSTALL b/INSTALL index c96d99c9399..90de95b38ac 100644 --- a/INSTALL +++ b/INSTALL @@ -40,8 +40,6 @@ For Debian/Ubuntu (likely incomplete list, but goes a long way): apt-get install libboost-dev libblitz0-dev libfftw3-dev libcfitsio3-dev libxml2-dev liblog4cplus-dev apt-get install libpng12-dev # or latest version; for AOFlagger apt-get install libreadline-dev libpqxx-dev doxygen # optional - apt-get install libgsl0-dev # optional, for AOFlagger's RayleighFitter - apt-get install libgtkmm-[2.4|3.0]-dev libsigc++-2.0-dev # optional, for AOFlagger's rficonsole apt-get install libunittest++-dev # optional, for tests that use UnitTest++ @@ -49,10 +47,7 @@ For CentOS/Fedora (likely incomplete list, but goes a long way): yum install subversion cmake make gcc python-devel numpy yum install boost-devel fftw-devel cfitsio-devel libxml2-devel libpng-devel - yum install readline-devel libpqxx-devel doxygen # optional - yum install gsl-devel # optional, for AOFlagger's RayleighFitter - yum install gtkmm[24|30]-devel libsigc++20-devel # optional, for AOFlagger's rficonsole The blitz and blitz-devel pkgs are available on some distros, but not on e.g. CentOS 7. If required for your build, but not in your repo, take the two RPMs from CentOS 6 (or build from source). @@ -62,14 +57,17 @@ For CentOS/Fedora (likely incomplete list, but goes a long way): -NOTE (any OS): For manual builds, the most prominent dependency you likely need to build is casacore (+ friends): +NOTE (any OS): For manual builds, the most prominent dependencies you likely need to build are casacore (+ friends) and AOFlagger: casacore: https://github.com/casacore/casacore python-casacore ('pyrap'): https://github.com/casacore/python-casacore - IERS/measures tables: https://github.com/casacore/casacore-data-update - casarest: https://svn.astron.nl/casarest + IERS/measures tables: ftp://ftp.astron.nl/outgoing/Measures/WSRT_Measures.ztar (auto-updated weekly), + also see https://github.com/casacore/casacore-data-update + casarest: https://github.com/casacore/casarest - AOFlagger: http://sourceforge.net/projects/aoflagger/ + AOFlagger: https://sourceforge.net/projects/aoflagger/ + which may need: Debian/Ubuntu: libgsl0-dev libgtkmm-[2.4|3.0]-dev libsigc++-2.0-dev + CentOS/Fedora: gsl-devel gtkmm[24|30]-devel libsigc++20-devel Instructions for Manual Build from Source -- GitLab From f20050c4de00814eddc8486b7abdb0af072a85c6 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 29 Aug 2016 19:40:14 +0000 Subject: [PATCH 724/933] Task #9522: Replace std::list by faster std::deque (and made test larger to test difference) --- LCS/Common/include/Common/Thread/Queue.h | 4 ++-- LCS/Common/test/tQueue.cc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LCS/Common/include/Common/Thread/Queue.h b/LCS/Common/include/Common/Thread/Queue.h index 963978a51a5..dd8ad576fff 100644 --- a/LCS/Common/include/Common/Thread/Queue.h +++ b/LCS/Common/include/Common/Thread/Queue.h @@ -28,7 +28,7 @@ #include <Common/Thread/Condition.h> #include <Common/Thread/Mutex.h> -#include <list> +#include <deque> #include <time.h> @@ -61,7 +61,7 @@ template <typename T> class Queue mutable Mutex itsMutex; Condition itsNewElementAppended; - std::list<T> itsQueue; + std::deque<T> itsQueue; }; diff --git a/LCS/Common/test/tQueue.cc b/LCS/Common/test/tQueue.cc index 4bed1d72f5d..aebb45a20ad 100644 --- a/LCS/Common/test/tQueue.cc +++ b/LCS/Common/test/tQueue.cc @@ -63,7 +63,7 @@ public: void mainLoop() { sleep(1); // make "sure" B blocks on q.remove() - for (int i = 0; i < 10; i++) + for (int i = 0; i < 1024*1024; i++) q.append(i); } }; @@ -71,7 +71,7 @@ public: class B { public: void mainLoop() { - for (int i = 0; i < 10; i++) + for (int i = 0; i < 1024*1024; i++) ASSERT( q.remove() == i ); } }; -- GitLab From 5e28fa32548318d771db415dd25a83f6eee46bdc Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 29 Aug 2016 19:41:25 +0000 Subject: [PATCH 725/933] Task #9522: Added more logging --- RTCP/Cobalt/OutputProc/src/SubbandWriter.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc index 13162480783..2d5900eb69a 100644 --- a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc +++ b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc @@ -56,9 +56,13 @@ namespace LOFAR itsAllocator = new SparseSetAllocator(*itsArena); for (unsigned i = 0; i < maxReceiveQueueSize; i++) { + LOG_INFO_STR("new CorrelatedData " << i); CorrelatedData *data = new CorrelatedData(nrStations, nrChannels, nrSamples, *itsAllocator, alignment); + LOG_INFO_STR("itsOutputPool.free.append " << i); itsOutputPool.free.append(data); } + + LOG_INFO_STR("End of constructor"); } -- GitLab From 903275f2155ef1a08fbf37dd5460f9c5bd302aad Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 30 Aug 2016 07:58:00 +0000 Subject: [PATCH 726/933] Task #9607: added some parameters and fixes to PoverO script --- SAS/ResourceAssignment/RAScripts/povero | 73 +++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/SAS/ResourceAssignment/RAScripts/povero b/SAS/ResourceAssignment/RAScripts/povero index a6310f57a47..5c17c7e8c7b 100755 --- a/SAS/ResourceAssignment/RAScripts/povero +++ b/SAS/ResourceAssignment/RAScripts/povero @@ -28,6 +28,8 @@ from datetime import datetime import logging import subprocess +from lofar.parameterset import parameterset + from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as DEFAULT_RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as DEFAULT_RADB_SERVICENAME @@ -40,13 +42,31 @@ from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SER logger = logging.getLogger(__name__) +def getSlurmStats(otdb_id): + cmd = ['ssh', 'lofarsys@head01.cep4.control.lofar', 'sacct', '-o', 'cputimeraw,nnodes', '--name=%s' % otdb_id, '-S', '2016-01-01', '-X', '--parsable2', '-n'] + logger.debug(' '.join(cmd)) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + + if proc.returncode == 0: + try: + parts = out.split('|') + cputimeraw = int(parts[0]) + nnodes = int(parts[1]) + clusterusage = nnodes / 50.0 + return cputimeraw, nnodes, clusterusage + except Exception: + pass + + return 0, 0, 0 + def main(): # make sure we run in UTC timezone import os os.environ['TZ'] = 'UTC' # Check the invocation arguments - parser = OptionParser('%prog [options]', + parser = OptionParser('povero [options] <output_filename.csv>', description='compute P over O for CEP4 pipelines') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') parser.add_option('--radb_busname', dest='radb_busname', type='string', default=DEFAULT_RADB_BUSNAME, help='Name of the bus exchange on the qpid broker on which the radbservice listens, default: %default') @@ -57,6 +77,10 @@ def main(): parser.add_option('--mom_query_servicename', dest='mom_query_servicename', type='string', default=DEFAULT_MOMQUERY_SERVICENAME, help='Name of the momqueryservice, default: %default') (options, args) = parser.parse_args() + if len(args) != 1: + print parser.usage + exit(1) + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO) @@ -66,15 +90,52 @@ def main(): with ra, otdb, mom: pipelines = ra.getTasks(cluster='CEP4', task_type='pipeline', task_status='finished') + #pipelines = [ra.getTask(otdb_id=531911)] + + with open(args[0], 'w') as csv_file: + line = "Project, OBS otdb_id, OBS name, #demix_sources, demix_sources, antennaArray, antennaSet, OBS duration [s], PL otdb_id, PL name, #subbands, type, cluster usage [%], PL duration [s], PL duration_norm [s], P/O" + csv_file.write(line + "\n") + print line + + for pl in pipelines: + cputimeraw, nnodes, clusterusage = getSlurmStats(pl['otdb_id']) + + if nnodes == 0: + continue + + pl_full_cluster_duration = int(clusterusage * pl['duration']) + + pl_parset = parameterset(otdb.taskGetSpecification(otdb_id=pl['otdb_id'])['specification']) + pl_name = pl_parset.getString('ObsSW.Observation.Scheduler.taskName', 'unknown') + pl_sub_type = pl_parset.getString('ObsSW.Observation.processSubtype') + dp_types = ['Correlated', 'CoherentStokes', 'IncoherentStokes'] + dp_type = [dp_type for dp_type in dp_types if pl_parset.getBool('ObsSW.Observation.DataProducts.Input_%s.enabled' % dp_type)][0] + pl_nrofsubbands = len(pl_parset.getString('ObsSW.Observation.DataProducts.Input_%s.filenames' % dp_type).split(',')) + pl_demix_sources = pl_parset.getString('ObsSW.Observation.ObservationControl.PythonControl.PreProcessing.demix_always', '') + if not pl_demix_sources: + pl_demix_sources = pl_parset.getString('ObsSW.Observation.ObservationControl.PythonControl.PreProcessing.demix_if_needed', '') + + if pl_demix_sources: + pl_demix_sources = pl_demix_sources.replace('[','').replace(']','') + pl_demix_sources = [x.strip() for x in pl_demix_sources.split(',')] if pl_demix_sources else [] + + pred_observations = ra.getTasks(task_ids=pl['predecessor_ids'], task_type='observation', task_status='finished') - print "OBS otdb_id, OBS duration [s], PL otdb_id, PL duration [s], P/O" + for pred_obs in pred_observations: + pred_obs_parset = parameterset(otdb.taskGetSpecification(otdb_id=pred_obs['otdb_id'])['specification']) - for pl in pipelines: - pred_observations = ra.getTasks(task_ids=pl['predecessor_ids'], task_type='observation', task_status='finished') + obs_antennaArray = pred_obs_parset.getString('ObsSW.Observation.antennaArray') + obs_antennaSet = pred_obs_parset.getString('ObsSW.Observation.antennaSet') + projectName = pred_obs_parset.getString('ObsSW.Observation.Campaign.name', 'unknown') + obs_name = pred_obs_parset.getString('ObsSW.Observation.Scheduler.taskName', 'unknown') - for pred_obs in pred_observations: - print "%s, %s, %s, %s, %.2f" % (pred_obs['otdb_id'], pred_obs['duration'], pl['otdb_id'], pl['duration'], pl['duration']/pred_obs['duration']) + values = [projectName, pred_obs['otdb_id'], obs_name, len(pl_demix_sources), ';'.join(pl_demix_sources), obs_antennaArray, obs_antennaSet, pred_obs['duration'], + pl['otdb_id'], pl_name, pl_nrofsubbands, pl_sub_type, 100.0*clusterusage, pl['duration'], pl_full_cluster_duration, '%.2f' % (pl_full_cluster_duration/pred_obs['duration'])] + line = ', '.join([str(x) for x in values]) + csv_file.write(line + '\n') + print line + print "Done!" if __name__ == '__main__': main() -- GitLab From 8e1438da5aaf449198d631a2ec86c89bf7cd67ab Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 30 Aug 2016 08:33:42 +0000 Subject: [PATCH 727/933] Task #9607: cmdline option otdb_id to compute P/O for the given pipeline otdb_id --- SAS/ResourceAssignment/RAScripts/povero | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/RAScripts/povero b/SAS/ResourceAssignment/RAScripts/povero index 5c17c7e8c7b..fd4d9a3f933 100755 --- a/SAS/ResourceAssignment/RAScripts/povero +++ b/SAS/ResourceAssignment/RAScripts/povero @@ -75,6 +75,7 @@ def main(): parser.add_option('--otdb_servicename', dest='otdb_servicename', type='string', default=DEFAULT_OTDB_SERVICENAME, help='Name of the otdbservice, default: %default') parser.add_option('--mom_query_busname', dest='mom_query_busname', type='string', default=DEFAULT_MOMQUERY_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momqueryservice listens, default: %default') parser.add_option('--mom_query_servicename', dest='mom_query_servicename', type='string', default=DEFAULT_MOMQUERY_SERVICENAME, help='Name of the momqueryservice, default: %default') + parser.add_option('-o', '--otdb_id', dest='otdb_id', type='int', default=None, help='compute P/O for the given pipeline otdb_id') (options, args) = parser.parse_args() if len(args) != 1: @@ -89,8 +90,7 @@ def main(): mom = MoMQueryRPC(busname=options.mom_query_busname, servicename=options.mom_query_servicename, broker=options.broker) with ra, otdb, mom: - pipelines = ra.getTasks(cluster='CEP4', task_type='pipeline', task_status='finished') - #pipelines = [ra.getTask(otdb_id=531911)] + pipelines = [ra.getTask(otdb_id=options.otdb_id)] if options.otdb_id else ra.getTasks(cluster='CEP4', task_type='pipeline', task_status='finished') with open(args[0], 'w') as csv_file: line = "Project, OBS otdb_id, OBS name, #demix_sources, demix_sources, antennaArray, antennaSet, OBS duration [s], PL otdb_id, PL name, #subbands, type, cluster usage [%], PL duration [s], PL duration_norm [s], P/O" -- GitLab From 98c3901bc976dc21e62151e502b2b28d3de6ff8b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 30 Aug 2016 09:31:37 +0000 Subject: [PATCH 728/933] Task #9607: dependency on pyprameterset --- SAS/ResourceAssignment/RAScripts/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/RAScripts/CMakeLists.txt b/SAS/ResourceAssignment/RAScripts/CMakeLists.txt index 0aafc91e4e1..910b85a4f05 100644 --- a/SAS/ResourceAssignment/RAScripts/CMakeLists.txt +++ b/SAS/ResourceAssignment/RAScripts/CMakeLists.txt @@ -1,6 +1,6 @@ # $Id$ -lofar_package(RAScripts 1.0 DEPENDS PyMessaging ResourceAssignmentService OTDB_Services) +lofar_package(RAScripts 1.0 DEPENDS PyMessaging ResourceAssignmentService OTDB_Services pyparameterset) lofar_find_package(Python 2.6 REQUIRED) include(PythonInstall) -- GitLab From c522f0a9396cd42a8ffb9c4509649416f0c0d806 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 30 Aug 2016 09:31:57 +0000 Subject: [PATCH 729/933] Task #9607: removed unused momqueryrpc --- SAS/ResourceAssignment/RAScripts/povero | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/SAS/ResourceAssignment/RAScripts/povero b/SAS/ResourceAssignment/RAScripts/povero index fd4d9a3f933..c98253a363a 100755 --- a/SAS/ResourceAssignment/RAScripts/povero +++ b/SAS/ResourceAssignment/RAScripts/povero @@ -34,9 +34,6 @@ from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_BUSNAME as DEFAULT_RADB_BUSNAME from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAULT_SERVICENAME as DEFAULT_RADB_SERVICENAME -from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC -from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME - from lofar.sas.otdb.otdbrpc import OTDBRPC from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME @@ -73,8 +70,6 @@ def main(): parser.add_option('--radb_servicename', dest='radb_servicename', type='string', default=DEFAULT_RADB_SERVICENAME, help='Name of the radbservice, default: %default') parser.add_option('--otdb_busname', dest='otdb_busname', type='string', default=DEFAULT_OTDB_SERVICE_BUSNAME, help='Name of the bus exchange on the qpid broker on which the otdbservice listens, default: %default') parser.add_option('--otdb_servicename', dest='otdb_servicename', type='string', default=DEFAULT_OTDB_SERVICENAME, help='Name of the otdbservice, default: %default') - parser.add_option('--mom_query_busname', dest='mom_query_busname', type='string', default=DEFAULT_MOMQUERY_BUSNAME, help='Name of the bus exchange on the qpid broker on which the momqueryservice listens, default: %default') - parser.add_option('--mom_query_servicename', dest='mom_query_servicename', type='string', default=DEFAULT_MOMQUERY_SERVICENAME, help='Name of the momqueryservice, default: %default') parser.add_option('-o', '--otdb_id', dest='otdb_id', type='int', default=None, help='compute P/O for the given pipeline otdb_id') (options, args) = parser.parse_args() @@ -87,9 +82,8 @@ def main(): ra = RARPC(busname=options.radb_busname, servicename=options.radb_servicename, broker=options.broker) otdb = OTDBRPC(busname=options.otdb_busname, servicename=options.otdb_servicename, broker=options.broker) - mom = MoMQueryRPC(busname=options.mom_query_busname, servicename=options.mom_query_servicename, broker=options.broker) - with ra, otdb, mom: + with ra, otdb: pipelines = [ra.getTask(otdb_id=options.otdb_id)] if options.otdb_id else ra.getTasks(cluster='CEP4', task_type='pipeline', task_status='finished') with open(args[0], 'w') as csv_file: -- GitLab From e2174b7e7a18cd286aedec3b510f87d092e62596 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Tue, 30 Aug 2016 09:53:58 +0000 Subject: [PATCH 730/933] Task #9279: remove unused mars and phi variants files. Supplements r35214 --- .gitattributes | 1 - CMake/variants/variants.mars | 2 -- CMake/variants/variants.phi | 1 - 3 files changed, 4 deletions(-) delete mode 100644 CMake/variants/variants.mars delete mode 120000 CMake/variants/variants.phi diff --git a/.gitattributes b/.gitattributes index c82c37c84cc..9d299f5a903 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2351,7 +2351,6 @@ CMake/variants/variants.lexar001 -text CMake/variants/variants.lexar002 -text CMake/variants/variants.lhn002 -text CMake/variants/variants.lotar -text -CMake/variants/variants.phi -text Docker/docker-build-all.sh -text Docker/dynspec/Dockerfile -text Docker/dynspec/bashrc -text diff --git a/CMake/variants/variants.mars b/CMake/variants/variants.mars deleted file mode 100644 index a16ce348913..00000000000 --- a/CMake/variants/variants.mars +++ /dev/null @@ -1,2 +0,0 @@ -option(USE_LOG4CPLUS "log4cplus is used" OFF) -option(USE_LOG4CXX "log4cxx is used" OFF) diff --git a/CMake/variants/variants.phi b/CMake/variants/variants.phi deleted file mode 120000 index ce73d06cf96..00000000000 --- a/CMake/variants/variants.phi +++ /dev/null @@ -1 +0,0 @@ -variants.fs5 \ No newline at end of file -- GitLab From c549249c8717cae15cf9c100d5309ce5e5e1f3c3 Mon Sep 17 00:00:00 2001 From: Stefan Froehlich <s.froehlich@fz-juelich.de> Date: Tue, 30 Aug 2016 15:10:43 +0000 Subject: [PATCH 731/933] Task #9800: restructered text documentation of the generic pipeline. --- .gitattributes | 1 + .../docs/genericpipeline/gendocrestruct.rst | 822 ++++++++++++++++++ 2 files changed, 823 insertions(+) create mode 100644 CEP/Pipeline/docs/genericpipeline/gendocrestruct.rst diff --git a/.gitattributes b/.gitattributes index 9d299f5a903..bc18cd95282 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1389,6 +1389,7 @@ CEP/Pipeline/docs/examples/model_parsets/dppp4.parset eol=lf CEP/Pipeline/docs/examples/model_parsets/mwimager.parset eol=lf CEP/Pipeline/docs/examples/model_parsets/mwimager.pulsar.parset eol=lf CEP/Pipeline/docs/examples/model_parsets/ndppp.parset eol=lf +CEP/Pipeline/docs/genericpipeline/gendocrestruct.rst -text CEP/Pipeline/docs/notes/2010-11-15-grid.rst eol=lf CEP/Pipeline/docs/notes/2010-12-08-handover_discussion.rst eol=lf CEP/Pipeline/docs/notes/metadata.annotated eol=lf diff --git a/CEP/Pipeline/docs/genericpipeline/gendocrestruct.rst b/CEP/Pipeline/docs/genericpipeline/gendocrestruct.rst new file mode 100644 index 00000000000..4b196b054eb --- /dev/null +++ b/CEP/Pipeline/docs/genericpipeline/gendocrestruct.rst @@ -0,0 +1,822 @@ + +Generic Pipeline +================ + +This Chapter will explain how to define a pipeline for the execution with the Lofar Pipeline Framework. For this purpose a Generic Pipeline has been created which can be configured to integrate user programs in a flexible way. + +Introduction +------------ +The Pipeline Framework is used for automated processing on the CEP cluster systems for example for the MSSS pipelines. Writing these pipelines is complicated and requires a lot of knowledge of the framework itself and some programming skills. + +The generic pipeline is a pipeline based on that system. It helps users with the design and execution of their own workflow without the need to understand the underlying system. For a pipeline the user should organize the work into building blocks. Typically you want to use existing blocks that do standard operations like Calibrate-Stand-Alone or DPPP or the AWImager. Work that has to be done in preparation or post processing for these operations can and should be implemented as additional steps for the pipeline. + +The advantage is that the user does not need to manage any kind of multiprocessing for data parallel operations. Input and output filenames of steps are mostly hidden and can be managed in a consistent way. Parameters can be reduced to a minimum with different sets of default parsets for every step. Integration of other peoples steps and reusing existing work are one of the primary goals of the generic pipeline. A users pipeline can run on a single workstation and in a cluster environment without the need to change the pipeline parset or to program process management. + +In an ideal case there are enough predefined possible steps for the user to choose from so that defining a pipeline comes down configuring a minimal set of program arguments. + +Steps and arguments to the steps are defined in a parset file. This parset is then the argument for the ``genericpipeline.py``. The pipelines name will be the first part of the parsets name ``mypipeline.parset``. Log files will be tracked under that name and have to be deleted along with the working directory when the pipeline should be restartet from the beginning. + +Usage +----- +The generic pipeline is part of the Lofar Software Package and is basically a runnable python script. The pipeline definition is written in a parset file. After you have loaded the Lofar environment it can be run with the command: +:: + + > genericpipeline.py <parset-file> [options] + +Where possible options are ``-d`` for debug logging and ``-c <config-file>`` to use a specific pipeline configuration file. + +Quick Start +----------- + +Before you can use the genericpipeline you need to customize the configuration +file: + + * Copy ``$LOFARROOT/share/pipeline/pipeline.cfg`` to someplace in your ``$HOME`` and open it in an editor. + * It starts with a section **[DEFAULT]**, in there you need to edit two entries: + + #. **runtime\_directory**: This is the directory where the pipeline puts logfiles, parsets, the status of successful steps etc. Set this to a directory in your ``$HOME``, e.g. ``/home/<username>/PipelineExample/runtime`` + #. **working\_directory**: This is the directory where the processed data of the intermediate and final steps is put. Set this to a directory on your data disk, e.g. ``/data/scratch/<username>/PipelineExample`` + + * In case you do not run it on a cluster, you need to tell the pipeline to start the processes on the local machine: + + #. Search for the section **[cluster]**, set the entry **clusterdesc** to: ``(lofarroot)s/share/local.clusterdesc`` + #. Add a section ``[remote]`` by adding the following lines: + :: + + [remote] + method = local + max_per_node = <NumCPUCores> + + If there is already another section **[remote]**, then remove that. + + * The pipeline framework contains a number of parallel processing schemes for working on multi-node clusters. Ask you local sysadmin for advice. + +Once the pipeline is configured you need a pipeline-parset. A simple example +would be: +:: + + + # Pipeline for running NDPPP on all files in a directory + + #variable parameters + #path to the directory where we are looking for the input data + ! input_path = /data/scratch/dummyuser/test-in + # path to the parset + ! ndppp_parset = /home/dummyuser/parsets/NDPPP-preproc-parset.proto + + pipeline.steps=[createmap,ndppp] + + #Step 1: search for all measurement sets in one directory and generate a mapfile + createmap.control.kind = plugin + createmap.control.type = addMapfile + createmap.control.cmdline.create = mapfile_from_folder + createmap.control.mapfile_dir = input.output.mapfile_dir #this is the name that the mapfile will have + createmap.control.filename = input_data.mapfile + createmap.control.folder = {{ input_path }} #this references to the path we defined above + + #Step 2: run NDPPP with a given parset on all files that the previous step found + ndppp.control.type = dppp + ndppp.control.parset = {{ ndppp_parset }} #this references to the parset we defined above + ndppp.control.max_per_node = 4 #run 4 instances of NDPPP in parallel + ndppp.control.environment = {OMP_NUM_THREADS: 6} #tell NDPPP to use only 6 threads + ndppp.argument.msin = createmap.output.mapfile + + +Pipeline configuration +---------------------- + +The configuration of the Pipeline Framework is mainly done in the two files ``pipeline.cfg`` and ``tasks.cfg``. The ``pipeline.cfg`` contains general settings like job and working directories and where your pipeline installation and task configuration resides. The ``tasks.cfg`` contains the steps that can be used in a pipeline. If you want to use your own configuration the ``pipeline.cfg`` should be an argument to the pipeline call. + +Copy the file ``pipeline.cfg`` from your installation. In a Lofar Framework installation the file can be found in ``LOFARROOT/share/pipeline/pipeline.cfg``. In this file you can configure the basic setup of the Pipeline Framework. You might want to modify the ``runtime_directory`` and the ``working_directory`` especially if you are not the only one using that machine. Those are a little bit ambiguous but generally the ``runtime_directory`` contains parsets, mapfiles, logfiles automatically generated for running the pipeline system. The ``working_directory`` is where the actual data products are being placed and the directory from where the individual programs are called. So look there for any temporary data you would expect. It is fine to set both parameters to point to the same directory and have everything in one place. + +If you want to add your own operations to the generic pipeline some additional configuration is necessary. In the ``pipeline.cfg`` you have to add to the list of ``task_files`` your own ``tasks.cfg``. See the next section for a description of such a file. In the standard install only standard operations are defined and of course only programs that come with the Lofar Software Stack. + +If you want to run a pipeline on your local machine you need to add the following to the configuration: +:: + + [remote] + method = local + max_per_node = 1 + +The ``max_per_node`` value can be overwritten on a step by step bases and indicates how many subprocesses of that step are started concurrently. This number depends on the workload a specific step puts on the system. + +The more advanced user might need to modify the underlying scripts that execute the job. You can use your own master and node scripts without having access to the install directories of the pipeline. Edit the ``recipe_directories`` in the ``pipeline.cfg`` and point it to the folder where you have your ``master``, ``node`` and ``plugin`` directories. + +Task definitions +---------------- +Possible steps for a pipeline are configured as a task in the ``tasks.cfg`` file. The format is comparable to the python configparser class. That means the name of the task is given in brackets followed by parameters for this task. Example: +:: + + [dppp] + recipe = executable_args + executable = (lofarroot)s/bin/NDPPP + args_format = lofar + outputkey = msout + +This task is now accessible in a parset: +:: + + dppp_step.control.type = dppp + dppp_step.argument.msin... + +The ``recipe`` variable is mandatory and needs to be the name of a master script. Historically there are master and node scripts implementing functionality. The generic pipeline only uses one master script, the ``executable_args``. The old ones are still usable but you will most likely never use them. The ``outputkey = msout`` means that the automatically generated file name from the step will be mapped to the argument ``msout``. So now your dppp step has a default argument ``dppp_step.argument.msout=<inputfile>.dppp_step``. + +Node scripts handle the actual process call. Most of the time the node script version of ``executable_args`` will suffice and does not need to be specified. A second useful node script loads python files and runs their ``main()`` method. Meaning you have to have a function called ``main()``. In this way you can store parameters in the pipeline context and use them in later steps. Fore more information on python plugins please look at the feature section. A minimal task for your own python plugin would look like this: +:: + + [mypythontask] + recipe = executable_args + nodescript = python_plugin + executable =/path/to/my_python_script.py + +Now the task can be used in the parset by simply doing the following: +:: + + my_python_step.control.type = mypythontask + my_python_step.argument... + + +Mapfiles +-------- +Mapfiles are the data descriptors of the pipeline framework. Only primitive functions to create and manipulate mapfiles are available at the moment as it is subject of development (see keyword section). + +Contents of a mapfile is the hostname of the machine where your data is, the path to your input data and a field that lets you specify whether to skip the data or not (mark it as bad data). +A mapfile holds entries for all the measurement sets you want to use. + +For every entry in a mapfile a compute job will be created. With the ``max_per_node`` parameter in the step configuration you can specify how many of those should run at the same time on one node. The content of a mapfile is a python dictionary: +:: + + {'host': 'localhost', file: '/path/to/MS', 'skip': False} + +Only primitive functions to create and manipulate mapfiles are available at the moment as it is subject of development. Right now plugins are used (see keyword section on createMapfile plugin) + +Pipeline +-------- +Parset +^^^^^^ +The pipeline itself will be described in a parset. The steps are given as a list. Other general parameters for the pipeline are the optional path to a plugins directory and an existing mapfile. The beginning of a parset looks like this: +:: + + pipeline.steps = [step1,step2,step3,...] + pipeline.pluginpath = plugins # optional + pipeline.mapfile = /path/to/your_data.mapfile # optional + +This steplist determines the order of execution. In what order the steps are implemented in the parset does not matter. However to every rule there is an exception. When using more than one mapfile in a step, the file entries in the first one will be used for the automatic naming scheme. Make sure your first mapfile contains individual names (measurement sets for example). Otherwise you might overwrite your results. + +To prevent the steplist from getting too long you can use sublists like this: + +:: + + pipeline.steps = [step1,substeps,step3,...] + pipeline.steps.substeps = [substep1,substep2,...] + +Steps +^^^^^ +Step names are arbitrary but have to be unique in one pipeline. Step options are organized into two blocks. The first to configure the task and the second to give arguments to program that is called. The keyword for the task options is ``control`` and the step arguments is ``argument``. Any options given here overwrite the ones in the ``tasks.cfg``. If you already have configured all task options in your ``tasks.cfg`` you only need to specify what type of task this step should be. Minimum general usage: +:: + + step.control.type = taskname + step.argument.flags = [command,line,-flags] + +A more specific example: +:: + + step.control.type = dppp + step.control.args_format = lofar + step.control.max_per_node = 4 + step.control.environment = {OMP_NUM_THREADS: 8, SOME_OTHER: env_variable} + step.argument.steps = [c] + step.argument.c.type = gaincal + step.argument.c.usebeammodel = True + . + . + . + +Parameters specified after ``argument`` are passed to the program in a "long option" kind of way. In the example above ``args_format = lofar`` was specified which means the argument will be given as: +:: + + c.type gaincal + +Default is the posix way of giving long options which would look like: +:: + + --c.type=gaincal + +For more option descriptions look at the complete list of keywords for the ``executable_args`` task. There are more things a step could be other than a task. For this the keyword ``step.control.kind`` is used. Please see the "Features" section for details. + +Features +^^^^^^^^ + +**Plugins** + +For quick hacking of functionality plugins can also be used as steps. They are simple python scripts but in contrast to the ``pythontask`` are not tracked by the framework. Also plugins are only executed once and for every entry in a mapfile as this input is not mandatory. Their standard location is in the recipes directory. An extra ``pipelinepath`` can be specified in the pipeline parset. This location will then also be searched when trying to load the plugins. + +Self written plugins must contain the method called ``plugin_main`` which can have a dictionary as return value: +:: + + def plugin_main(args, **kwargs): + result = {} + <some code> + return result + +Plugins were introduced for development purposes and should be removed in the future. But for now they are used to create and manipulate mapfiles. + +**Internal dictionary** + +The pipeline holds an internal results dictionary. Entries are dictionaries themselves. Every step can put his own dictionary in there to pass variables down the pipeline. The default dictionary after starting a pipeline has the same name as the pipeline and has all key value pairs from the ``pipeline.cfg`` as entries. You can also access the following: ``job_dir``, ``parset_dir``, ``mapfile_dir`` and ``mapfile`` if it was given with ``pipeline.mapfile``. + +Normal steps chosen from the tasks list always put a dictionary in the results with their output mapfile. It either has the values of its input or the filenames of the computed output. The name of the dictionary is the steps name. + +A ``python_plugin`` task can write a dictionary to the results dictionary by simply returning an object of that type. + +This output dictionary can be accessed with a special notation ``stepname.output.variable`` useable in any argument. On the left is the stepname of which you want the output from and on the right the name of the variable that was chosen in that steps dictionary. Most of the time you only want to access mapfiles from previous steps. Lets say you created a mapfile in a step called ``createmap``. An example of getting that mapfile in a later step would be: +:: + + mystep.argument.inputfile = createmap.output.mapfile + +Notice that the mapfile represents the input file. If written like this the step will be executed for every "file" entry in the mapfile. + +**String replacement syntax** + +Since you want your step description ordered one after the other but maybe have used arguments that other users need to edit a Replacement Syntax as a convenience feature has been added. This means that you can use placeholders in your step arguments and have the real argument at the top of parset for easier access. The syntax for the placeholder is: +:: + + pipeline.replace.my_awimager = /path/to/my/awimager + . + . + . + myAWImagerStep.control.executable = {{ my_awimager }} + +Also possible is a partial string replacement in the argument like so: +:: + + pipeline.replace.my_awimager_path = /path/to/my/ + . + . + . + myAWImagerStep.control.executable = {{ my_awimager_path }}awimager + +Notice the trailing slash on the path and no space after the curly brackets. The space inside the brackets around the variable is necessary. +An alternative syntax to define the replacement string is to use an exclamation mark like in the popular python package jinja2: +:: + + ! my_awimager = /path/to/my/awimager + +This is just a different way of writing. The first version is more in line with the usual way of writing parsets. + +It is also possible to use environment variables in the replacement value (from version 2.18 onwards). +:: + + ! my_awimager = $HOME/local/bin/awimager + +**Python Plugins** + +A way of using your own scripts within the pipeline framework is taking advantage of the python plugin mechanism. The script only needs to have a function called ``main()``. Any arguments you define in the step have to be handled as arguments of this main function. Lets look at an example where we define a step to be a python plugin: +:: + + toystep.control.type = pythonplugin + toystep.control.executable = /path/to/my/script.py + toystep.argument.flags = previousstep.output.previousstep.mapfile # a positional argument with output filenames from a previous step + toystep.argument.optional = 6 # some more arguments + toystep.argument.threshold = 4 # we want to have in the script + +Now the script would look something like this: +:: + + def main(positional, optional=0, passthrough=0): + outdict = {} + print 'File name: ', str(positional) + + # cast to types is a good idea/needed because parsets only work on strings + derivedval = int(optional) / 2 + + # names in outdict get saved as 'optionalhalf.mapfile' and 'threspix.mapfile' + outdict['optionalhalf'] = derivedval + outdict['threshold'] = passthrough + + return outdict + +You can of course compute values depending on the input data. The different results will be saved in the output mapfiles and are associated to the data in this way for later use. A next step might use your output like this: +:: + + nextstep.control.type = another_task + nextstep.argument.needed_computed_value = toystep.output.optionalhalf.mapfile + nextstep.argument.some_other_value = toystep.output.threshold.mapfile + +This enables you to write simple scripts which can be tested on a single measurement set. Within the pipeline framework the script can then operate on the whole observation. + +**Loops** + +It is possible to loop steps. A loop is a different kind of step and contains a list of steps: +:: + + loopme.control.kind = loop + loopme.control.type = conditional + loopme.control.loopcount = [int] + loopme.control.loopsteps = [loopstep1,loopstep2,loopstep3,...] + +The steps will be looped until one of the steps puts in its output dictionary a ``'break': True`` or until the loop counter reaches the specified integer ``loopcount``. So maybe as last step of your list inside the loop you want a step that checks a break condition and outputs its value. + +**Subpipelines** + +A subpipeline is like the loop another kind of step. This construct is for the case that you have a working set of steps and do not want to clutter your parset or want to have a structure with individual parsets for individual functionality. You can hand over mapfiles to a subpipeline and access it with the output keyword mechanism ``subpipeline_name.output.mapfile``. Arguments you give to the subpipeline will be handled as string replacements (see paragraph above). This enables you to write parsets that can be run individually or as subpipelines without any change to the parset. + +The steps that will be added from the subpipeline will be prefixed with the subpipeline name. This makes it possible to have step definitions with the same name in the master parset and in the subpipeline parset without creating any conflicts. The following would be an example of a subpipeline step and the beginning of the subpipeline. The step: +:: + + subpipe.control.kind = pipeline + subpipe.control.type = my_subpipeline.parset + subpipe.control.mapfile_in = some_previous_step.output.mapfile + + +Syntax and Keywords +^^^^^^^^^^^^^^^^^^^ +ToDo: longish tables of possible keywords and their explanation for createMapfile plugin, executable\_args task, subpipeline controls etc. + +List of possible pipeline parameters + ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|Parameter |Type |Description | ++=========================+=============+=======================================================================================================+ +|**Pipeline** | | | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|pipeline.steps |list |The list of steps the pipeline will execute | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|pipeline.steps.<sublist> |list |This list will replace the <sublist> in the steplist defined in the argument above. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|pipeline.pluginpath |string |(optional) The folder that contains additional plugins. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|pipeline.mapfile |string |(optional) An existing mapfile that is then available in the input dictionary | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|pipeline.replace.<arg> |string |(optional) Will search the rest of the parset for {{ <arg> }} and will replace it with the value | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +| | |You can also use the jinja2 style with an exclamation mark (! <arg>) | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +| | |The value may contain environment variables that will be parsed from the system environment. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|**Steps** | | | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.control.type |string |What task should be executed by this step. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.control.<arg> | |Any control argument to configure the chosen type of step. | +| | |For executable_args see the list below. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.argument.flags |list |This optional list will be passed as command line arguments to the executable. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.argument.parset |string |You can specifiy a parset file here with parameters. Usually specified in tasks or the control block. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.argument.<arg> | |Any long option argument for the executable. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|**Loop Step** | |**<step>.control.kind = loop** | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.control.loopsteps |list |The list of steps for this loop. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.control.loopcount |int |Number of times the loop will run. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|**Subpipeline** | |**<step>.control.kind = pipeline** | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.control.type | |The parset of the pipeline to run. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.control.mapfile_in|string |If you want to start the subpipeline with a specific mapfile. | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.argument.<arg> | |The <arg> and <value> of the arguments will be used in the subpipeline string replacement mechanism | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|**Plugins** | |**<step>.control.kind = plugin** | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.control.arguments |list |List of arguments that will be passed as args to the plugin | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ +|<step>.control.<arg> | |Arguments that will passed as \*\*kwargs to the plugin | ++-------------------------+-------------+-------------------------------------------------------------------------------------------------------+ + +Defining a task with the master script ``executable_args``. The following are the possible control keys (<step>.control.<parameter>) + ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|Parameter |Type | Default |Description | ++======================+=============+===============+=======================================================================================+ +|**Mandatory** | | | | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|[<task_name>] |string | |The header that gives the step its name (the type value of the step in the parset). | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|recipe |string | |The master script we want to call. Needs to be ``executable_args`` | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|executable |string | |Path to the program that this task will execute. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|**Optional** | | | | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|nodescript |string |executable_args| | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|parsetasfile |boolean |False |Should the arguments of the step be passed as a parset file to the program? | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|nthreads |int |8 |Short argument for setting OMP_NUM_THREADS as environment variable. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|parset |string | |Path to a parset that contains the arguments for this task. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|arguments |list | |List of arguments for the task. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|mapfile_in |string | |The mapfile for this task. Usually contains the input files for the program | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|mapfiles_in |list | |A list of mapfiles if you have multiple sources of input for this task. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|inputkey |string | |The key in the arguments that gets the entries from the mapfile as value. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|inputkeys |string list | |The keys for multiple input mapfiles. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|mapfile_out |string | |Create your own mapfile with outputnames if you want tospecify them yourself. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|mapfiles_out |list | |Same as above for multiple outputs (imagers for example) | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|outputkey |string | |The key in the arguments that gets the entries from the output mapfile as value. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|outputkeys |string list | |The keys for multiple output mapfiles. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|skip_infile |boolean |False |Do not pass the input files to the program. Execute it for every entry in the mapfile | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|skip_outfile |boolean |False |Do not produce output files. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|inplace |boolean |False |Use input names for the output names. Manipulate files inplace. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|outputsuffixes |list | |List of suffixes that the program adds to the output filename (useful for imagers). | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|parsetasfile |boolean |False |Parset given to node scripts is a positional argument or content are named arguments. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|stepname |string | |For custom nameing the result. Default is the stepname from the pipeline parset. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|max_per_node |int | |How many times should this program run on one node. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|environment |dict | |Add environment variables formatted as a python dictionary (number of threads for ex.).| ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ +|error_tolerance |boolean |True |Controls if the program exits on the first error or continues with succeeded MS. | ++----------------------+-------------+---------------+---------------------------------------------------------------------------------------+ + +List of available tasks + ++----------------------+-------------+-------------+---------------------------------------------------------------------------------------+ +|Task | ++----------------------+-------------+-------------+---------------------------------------------------------------------------------------+ +|Parameter |Type | Default |Description | ++======================+=============+=============+=======================================================================================+ +|recipe |string | |Option only used in the tasks.cfg for defining the master recipe to use for the task. | ++----------------------+-------------+-------------+---------------------------------------------------------------------------------------+ +|kind |string |recipe |Tell the pipeline the kind of step [recipe, plugin, loop, pipeline]. | ++----------------------+-------------+-------------+---------------------------------------------------------------------------------------+ +|type |string | |If kind is recipe specify which task from the tasks.cfg are we going to use | ++----------------------+-------------+-------------+---------------------------------------------------------------------------------------+ + + +Cookbook example +^^^^^^^^^^^^^^^^ + +This is a pipeline version of the Lofar Imaging Cookbook practical example from chapter thirteen: +:: + + # Pipeline that runs the processing steps from the LOFAR Cookbook tutorial. + + pipeline.steps=[finddata, flag_compress, calibrate1, flagstation, calibrate2, image3c295, make_concatmap, combineMS, reCalibrate, imageCombined, subtract3C295, copySubtracted, parmDBmap, applycal, imageField] + + #variable parameters + pipeline.replace.input_path = /globaldata/COOKBOOK/Tutorial/3c295/data/L74759/ + pipeline.replace.flag_compress_parset = /globaldata/COOKBOOK/Tutorial/3c295/parsets/hba/NDPPP_HBA_preprocess.parset + pipeline.replace.bbs_calibration_parset = /globaldata/COOKBOOK/Tutorial/3c295/parsets/hba/bbs.parset + pipeline.replace.calibrator_skymodel = /globaldata/COOKBOOK/Tutorial/3c295/models/3C295TWO.skymodel + pipeline.replace.bbs_subtraction_parset = /globaldata/COOKBOOK/Tutorial/3c295/parsets/hba/bbs_subtract3c295.parset + pipeline.replace.bbs_correct_parset = /globaldata/COOKBOOK/Tutorial/3c295/parsets/hba/bbs_correct3c295.parset + + # 1st step + # find the data that we want to process and create a mapfile + finddata.control.kind = plugin # plugin -> short, non-parallel step + finddata.control.type = createMapfile # generate a new mapfile + finddata.control.method = mapfile_from_folder # look for all files in a given directory + finddata.control.folder = {{ input_path }} # directory in which to look for the data + finddata.control.mapfile_dir = input.output.mapfile_dir # put the mapfile into the runtime directory + finddata.control.filename = finddata.mapfile # name of the generated mapfile + finddata.control.pattern = L*_uv.MS # use only files that match this pattern + + # 2nd step: + # run NDPPP on all files, this will generate new files with the compressed data + flag_compress.control.type = dppp # run ndppp + flag_compress.control.parset = {{ flag_compress_parset }} # name of the parset to use + flag_compress.control.max_per_node = 4 # run at most 4 NDPPP processes per node + flag_compress.control.environment = {OMP_NUM_THREADS: 8} # tell NDPPP to use at most 8 threads per process + flag_compress.argument.msin = finddata.output.mapfile # process the data in the mapfile that was produced in the finddata step + + # 3rd step + # calibrate the data with BBS + calibrate1.control.type = python-calibrate-stand-alone # run BBS on single files + calibrate1.control.max_per_node = 20 # run at most 20 BBS processes per node + calibrate1.argument.force = True # force replaceing of parmDB and skyDB + calibrate1.argument.observation = flag_compress.output.mapfile # run on files generated by flag_compress step + calibrate1.argument.parset = {{ bbs_calibration_parset }} # which parset to use (path specified above) + calibrate1.argument.catalog = {{ calibrator_skymodel }} # which skymodel to use + + # 4th step: + # run NDPPP + # This time without an external parset, specifying everything in here. + flagstation.control.type = dppp # run ndppp + flagstation.control.max_per_node = 4 # run at most 4 NDPPP processes per node + flagstation.control.environment = {OMP_NUM_THREADS: 8} # tell NDPPP to use at most 8 threads per process + flagstation.control.outputkey = # no "outputkey" -> don't generate new outputfiles + flagstation.argument.msin = flag_compress.output.mapfile # run on files generated by flag_compress step + flagstation.argument.msout = . # set msout to "." in parset -> update input file + flagstation.argument.steps = [flag] + flagstation.argument.flag.type = preflagger + flagstation.argument.flag.baseline = [[RS508HBA,RS509HBA],[RS208HBA,RS509HBA],[CS302HBA*]] + + # 5th step + # re-calibrate the data with BBS, (exactly like step 3) + calibrate2.control.type = python-calibrate-stand-alone # run BBS on single files + calibrate2.control.max_per_node = 20 # run at most 20 BBS processes per node + calibrate2.argument.force = True # force replaceing of parmDB and skyDB + calibrate2.argument.observation = flag_compress.output.mapfile # run on files generated by flag_compress step + calibrate2.argument.parset = {{ bbs_calibration_parset }} # which parset to use (path specified above) + calibrate2.argument.catalog = {{ calibrator_skymodel }} # which skymodel to use + + # 6th step + # make a clean image of the calibrated data with awimager + # this will run one awimager process for each subband (so two for the tutorial data) + image3c295.control.type = awimager # run the awimager + image3c295.control.max_per_node = 2 # run at most 2 awimager processes per node + image3c295.control.environment = {OMP_NUM_THREADS: 10} # maximum number of parallel threads + image3c295.argument.ms = flag_compress.output.mapfile # run on files generated by flag_compress step + image3c295.argument.data = CORRECTED_DATA # read from the CORRECTED_DATA column + image3c295.argument.weight = briggs # further imaging parameters ... + image3c295.argument.robust = 0 + image3c295.argument.npix = 4096 + image3c295.argument.cellsize = 5.5arcsec + image3c295.argument.padding = 1.2 + image3c295.argument.stokes = I + image3c295.argument.operation = mfclark + image3c295.argument.wmax = 20000 + image3c295.argument.UVmin = 0.08 + image3c295.argument.UVmax = 18 + image3c295.argument.niter = 1000 + + # 7th step + # make a mapfile that will allow us to concatenate all data-files into one + make_concatmap.control.kind = plugin # plugin -> short, non-parallel step + make_concatmap.control.type = createMapfile # generate a new mapfile + make_concatmap.control.method = mapfile_all_to_one # put all files into one entry + make_concatmap.control.mapfile_in = flag_compress.output.mapfile # name of the input-mapfile + make_concatmap.control.mapfile_dir = input.output.mapfile_dir # put new mapfile into runtime directory + make_concatmap.control.filename = concat.mapfile # name of the new mapfile + + # 8th step + # now combine the MSs with the help of the mapfile from step 7 + combineMS.control.type = dppp # run ndppp + combineMS.control.max_per_node = 1 # run only one NDPPP process at a time + combineMS.control.environment = {OMP_NUM_THREADS: 10} # tell NDPPP to use at most 10 threads + combineMS.argument.msin = make_concatmap.output.mapfile # use the mapfile from the make_concatmap step + combineMS.argument.msin.datacolumn = DATA # read from the DATA column + combineMS.argument.steps = [] # don't really do anything with the data + combineMS.argument.msin.missingdata = True # fill missing data with flagged dummy values + + # 9th step + # calibrate the combined MS (we copied the uncorrected data) + # nearly the same as step 3 (and step 5), but this time we work on the combined MS and change "CellSize.Freq" + reCalibrate.control.type = python-calibrate-stand-alone # run BBS on single files + reCalibrate.control.max_per_node = 20 # run at most 20 BBS processes per node + reCalibrate.argument.force = True # force replaceing of parmDB and skyDB + reCalibrate.argument.observation = combineMS.output.mapfile # run on file generated by combineMS step + reCalibrate.argument.parset = {{ bbs_calibration_parset }} # which parset to use (path specified above) + reCalibrate.argument.catalog = {{ calibrator_skymodel }} # which skymodel to use + reCalibrate.argument.Step.solve.Solve.CellSize.Freq = 4 # overwrite one parameter in the BBS parset + + # 10th step + # make a clean image of the combined data with awimager + imageCombined.control.type = awimager # run the awimager + imageCombined.control.max_per_node = 1 # run only one process at a time + imageCombined.control.environment = {OMP_NUM_THREADS: 20} # maximum number of parallel threads + imageCombined.argument.ms = combineMS.output.mapfile # run on files generated by flag_compress step + imageCombined.argument.data = CORRECTED_DATA # read from the CORRECTED_DATA column + imageCombined.argument.weight = briggs # further imaging parameters ... + imageCombined.argument.robust = 0 + imageCombined.argument.npix = 4096 + imageCombined.argument.cellsize = 5.5arcsec + imageCombined.argument.padding = 1.2 + imageCombined.argument.stokes = I + imageCombined.argument.operation = mfclark + imageCombined.argument.wmax = 20000 + imageCombined.argument.UVmin = 0.08 + imageCombined.argument.UVmax = 18 + imageCombined.argument.niter = 5000 + imageCombined.argument.threshold = 0.1Jy + + # 11th step + # subtract 3C295 from the data + subtract3C295.control.type = python-calibrate-stand-alone # run BBS on single files + subtract3C295.control.max_per_node = 20 # run at most 20 BBS processes per node + subtract3C295.argument.force = False # keep old parmDB and skyDB + subtract3C295.argument.observation = combineMS.output.mapfile # run on file generated by combineMS step + subtract3C295.argument.parset = {{ bbs_subtraction_parset }} # which parset to use (path specified above) + subtract3C295.argument.catalog = {{ calibrator_skymodel }} # which skymodel to use + + # 12th step: + # run NDPPP on all files, this will generate new files with the compressed data + copySubtracted.control.type = dppp # run ndppp + copySubtracted.control.max_per_node = 4 # just my standard... + copySubtracted.control.environment = {OMP_NUM_THREADS: 8} # just my standard... + copySubtracted.argument.msin = combineMS.output.mapfile # data that was used in subtract3C295 step + copySubtracted.argument.msin.datacolumn = 3C295_SUBTRACTED # read data from the column it was written to + copySubtracted.argument.steps = [] # don't really do anything with the data + + # 13th step + # generate a mapfile that points to the parmdb generated in step 9 + # i.e. take the file-name(s) used in step 9 and add "/instrument" to them + parmDBmap.control.kind = plugin # plugin, -> short non-parallel step + parmDBmap.control.type = changeMapfile + parmDBmap.control.mapfile_in = combineMS.output.mapfile + parmDBmap.control.join_files = instrument + parmDBmap.control.newname = parmdb.mapfile + + # 14th step + # Apply old calibration to the data + applycal.control.type = python-calibrate-stand-alone # run BBS on single files + applycal.control.max_per_node = 20 # run at most 20 BBS processes per node + applycal.argument.observation = copySubtracted.output.mapfile # run on file generated by combineMS step + applycal.argument.parmdb = parmDBmap.output.mapfile # mapfile with the existing parmDB(s) to use + applycal.argument.parset = {{ bbs_correct_parset }} # which parset to use + applycal.argument.catalog = {{ calibrator_skymodel }} # which skymodel to use + + # 15th step + # make a clean image of the data where we subtracted 3C925 with awimager + imageField.control.type = awimager # run the awimager + imageField.control.max_per_node = 1 # run only one process at a time + imageField.control.environment = {OMP_NUM_THREADS: 20} # maximum number of parallel threads + imageField.argument.ms = copySubtracted.output.mapfile # read input MS here + imageField.argument.data = CORRECTED_DATA # read from the CORRECTED_DATA column + imageField.argument.weight = briggs # further imaging parameters ... + imageField.argument.robust = 0 + imageField.argument.npix = 4096 + imageField.argument.cellsize = 5.5arcsec + imageField.argument.padding = 1.2 + imageField.argument.stokes = I + imageField.argument.operation = mfclark + imageField.argument.wmax = 20000 + imageField.argument.UVmin = 0.08 + imageField.argument.UVmax = 18 + imageField.argument.niter = 1000 + imageField.argument.threshold = 30mJy + +Installation on Jureca +---------------------- +The installation process has changed over the past years and is now relying mostly on the system components. Only install what is not provided by the system. +The current local installation is in the home directory of user ``htb003``. For any mentioned script please look there. +To find out which modules to load use the ``module avail`` command. To get more information about a specific module or to search for specific software use ``module spider``. +The latest install is Lofar version 2.17 and it uses the following modules from the Jureca software stack 2016a: +:: + + module load GCC/5.3.0-2.26 ParaStationMPI/5.1.5-1 + module load Python/2.7.11 + module load CMake/3.4.3 + module load Boost/1.60.0-Python-2.7.11 + module load GSL/2.1 + module load HDF5/1.8.16 + module load flex/2.6.0 + module load XML-LibXML/2.0124-Perl-5.22.1 + module load SciPy-Stack/2016a-Python-2.7.11 + +In addition to these modules the Lofar software is depending on ``cfitsio``, ``wcslib``, ``casacore``, ``casacore-python``(pyrap), ``casarest``, ``aoflagger``. In preperation to compile the rest of the software the following paths are set with a a shell script. +For different versions of the software there are different scripts for loading the appropriate environment. This for example is what the Lofar version 2.17 with Jureca stack2016a looks like: +:: + + #!/bin/sh + export PYTHONPATH=/homea/htb00/htb003/local_jureca_stack2016a/lib/python2.7/site-packages + export PYTHONPATH=/homea/htb00/htb003/lofar_jureca_2.17_stack2016a/lib/python2.7/site-packages:$PYTHONPATH + #export PYTHONHOME=/homea/htb00/htb003/local_jureca_stack2016a + # + export PATH=/homea/htb00/htb003/local_jureca_stack2016a/bin:$PATH + export PATH=/homea/htb00/htb003/lofar_jureca_2.17_stack2016a/bin:$PATH + export PATH=/homea/htb00/htb003/lofar_jureca_2.17_stack2016a/sbin:$PATH + # + export LD_LIBRARY_PATH=/homea/htb00/htb003/lofar_jureca_2.17_stack2016a/lib:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=/homea/htb00/htb003/lofar_jureca_2.17_stack2016a/lib64:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=/homea/htb00/htb003/local_jureca_stack2016a/lib:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=/homea/htb00/htb003/local_jureca_stack2016a/lib64:$LD_LIBRARY_PATH + # + export LOFAR_BUILD_DIR=/homea/htb00/htb003 + export LOFAR_MAKER=release + export F77=gfortran + export FC=gfortran + export BLAS=/usr/local/software/jureca/Stages/2016a/software/OpenBLAS/0.2.15-GCC-5.3.0-2.26-LAPACK-3.6.0/lib/libopenblas.so + export LAPACK=/usr/local/software/jureca/Stages/2016a/software/OpenBLAS/0.2.15-GCC-5.3.0-2.26-LAPACK-3.6.0/lib/libopenblas.so + export LOFARROOT=${LOFAR_BUILD_DIR}/lofar_jureca_2.17_stack2016a + # + module load GCC/5.3.0-2.26 ParaStationMPI/5.1.5-1 + module load Python/2.7.11 + module load CMake/3.4.3 + module load Boost/1.60.0-Python-2.7.11 + module load GSL/2.1 + module load HDF5/1.8.16 + module load flex/2.6.0 + module load XML-LibXML/2.0124-Perl-5.22.1 + module load SciPy-Stack/2016a-Python-2.7.11 + export PYTHONSTARTUP=/homea/htb00/htb003/pythonstart + # + export CC=/usr/local/software/jureca/Stages/2016a/software/GCCcore/5.3.0/bin/gcc + export CXX=/usr/local/software/jureca/Stages/2016a/software/GCCcore/5.3.0/bin/g++ + # + export PKG_CONFIG_PATH=/homea/htb00/htb003/local_jureca_stack2016a/lib/pkgconfig:$PKG_CONFIG_PATH + # since Lofar 2.15. Flags for dependency building of aoflagger + # only for building aoflagger + export LDFLAGS=-L/homea/htb00/htb003/local_jureca_stack2016a/lib + export CPPFLAGS=-L/homea/htb00/htb003/local_jureca_stack2016a/include + # + export GSETTINGS_SCHEMA_DIR=/homea/htb00/htb003/local_jureca/share/glib-2.0/schemas + +In addition to this environment there have always been little changes that had to be made to ensure proper compiling and installation of the rest of the software. These changes differed from version to version so that no general solution can be presented. +The follwong only shows one possible guideline. + +cfitsio, wcslib +^^^^^^^^^^^^^^^ +Nothing special here. Just go the normal configure, make, make install way and add the ``prefix`` option to configure to install the libraries in userspace. + +Casacore, Python-Casacore, Casarest +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In general proceed as instructed on the project webpages for these packages. On Jureca the program ``ccmake`` in contrast to just calling ``cmake`` was used to configure the paths to all required libraries and the proper compiler version. +The cmake configuration files usualy do not find the correct libraries configured by the settings above as they are not in standard directories of fresh system installs. + +AOFlagger +^^^^^^^^^ +There were problems with the cmake project files. Because none of the software packages are in standard places the "FOUND" value for GTKMM and BOOST_ASIO might have to be set to true in the CMakeLists file in order to compile everything. +Not sure if this problem exists in the latest version. But without the graphical programs the compile should be straight forward. + +**RFIGUI, PGPLOT** + +Graphical programs are only available for the older Jureca software stack and can be loaded with ``env_lofar_2.15.sh`` and ``env_lofar_2.17.sh`` (authors comment: i recommend to not try to comile those in future versions if the system is not providing the gtkmm package. +compiling the gtk packages and integrating it in the environment is a major pain.). + +WSClean +^^^^^^^ +Nothing special. Take care of setting all options to the proper values for your locally (or not standard) installed libraries. + +Lofar Software Stack +^^^^^^^^^^^^^^^^^^^^ +To compile the Lofar software with a compiler in a custom location you have to edit the file ``CMake/variants/GNU.cmake``. This is also the place to change additional compiler options. This file gets read every time you use cmake for the Lofar software and overwrites changes you make in ccmake or in the CMakeCache.txt file. + +In the latest releases there was a weired problem with the ``shared`` option for the PyBDSM target. It only compiled when forcing the option via the environment variable ``LDFLAGS="-shared"``. + +Again, double check all paths you set in for the options in cmake. + + +Python Packages +^^^^^^^^^^^^^^^ +The least troublesome way to install additional python packages was to download them individually and use their recommended install process. Which is mostly callsing ``setup.py``. Give the option for the local install path you chose (can differ from package to package). +You can also use ``pip`` to install locally but it does not work in all cases. Sometimes it was not detecting that dependencies were already installed in the system paths and tried to overwrite them with diefferent version. +Installing with ``pip`` without dependencies the option is ``--no-deps``. Examples: +:: + + pip install APLpy --no-deps --target /homea/htb00/htb003/local_jureca_stack2016a/lib/python2.7/site-packages + + or using a setup.py in a local source directory: + + python setup.py install --prefix=/homea/htb00/htb003/local_jureca_stack2016a + +GSM Database +^^^^^^^^^^^^ +Log into head node jrl09 and run the script ``/homea/htb00/htb003/start_gsm_database.sh``. It has to be node 09 because that one is hardcoded in the ``gsm.py``. + +Notes for Developers +-------------------- +Some more deatiled information. + +Structure +^^^^^^^^^ +The structure of the generic pipeline has four levels. The parset, the pipeline parser, the master scripts and the node scripts. So very similar to the old pipeline structure. + +**Parset** + +The parset describes the pipeline as explained in the documentation. + +**Pipeline parser** + +The ``genericpipeline.py`` is the parser for any pipeline parset written in the generic pipeline way. Here the steps are created, keywords replaced and where the internal result dictionary is held. +This is pretty much complete. This is pretty much complete and is only extended when users find something limiting or bugs. Latest adddition for example was to enable the use of environment variables in the replacement mechanism. +Or to give the possibility to split the steplist in sublists so they are easier to read and to group by theme. + +There are some little remnants in the code from an attempt to make the construction of a pipeline interactive in a pythonconsole. Adding methods to add steps in specific places or getting output of all possible steps. Not sure if its worth to pursue this approach. + + +**master scripts** + +The master script for the generic pipeline is the ``executable_args.py``. But also the older framework master scripts can be used (Slightliy different calling convention. Please compare msss pipelines with prefactor.). There is only one master script because basically a step is one program that needs some input arguments and the return code needs to be tracked. This can in principle be done in a single script. Maybe a more basic script would better and then one could derive own versions of that class to handle outputs differently. + +**node scripts** + +There are only a few new node scripts. One general ``executable_args.py`` and for specific purpose ``python_plugin.py``, ``executable_casa.py`` and ``calibrate-stand-alone.py``. The casa and calibrate scripts needed too much special tinkering to put them into the executable_args as well. +All of these scripts can be called from the one new master script. + +Error reporting +^^^^^^^^^^^^^^^ +The most prominant thing missing from the generic pipeline framework are meaningful error messages. Because the structure of the redesign was not clear from the beginning error checking has not been top priority. +For validity checking of arguments one would need a reference. But there are no interface definitions for possible steps in a pipeline. Theese would have to be defined first to have meaningful argument checking. +For the control arguments there should be better checks. For example if there is no mapfile give out the message that there is no mapfile specified instead of the out of bounds on the mapfile array. + +Mapfiles +^^^^^^^^ +Another important thing is handling of mapfiles. The plugins mostly exist because of the lack of possibilities to create mapfiles with the standard framework. Now the utility code for mapfiles is copy pasted across different plugins... that is bad. +So there needs to be an overhaul of the mapfile class of the framework. The additional functionality from the dataproduct and multidataproduct classes should be merged into the framework. This way the plugins would contain less code or could be replaced with proper python steps in the pipelines. +What is mainly missing is the possibility to create mapfiles from given folders with file name patterns. Split and merge mapfiles and create proper file fields in the mapfiles for concat and split operations of ``dppp``. Other programs like ``wsclean`` might need different formatting of files lists (having external programs conform to interfaces would be better though). + +Parset +^^^^^^ +The framework allows to create two different kinds of parset objects. This caused some confusion more than once and the classes need to be unified or be made consistent some other way. +There is a class ``Parset`` in ``CEP/Pipeline/framework/lofarpipe/support/parset.py`` and one in ``CEP/Pipeline/framework/lofarpipe/cuisine/parset.py``. And then there is the ``pyparameterset`` class. Someone needs to take a look at all that. \ No newline at end of file -- GitLab From 5a0908244d5a7007f78b64c235edda52aa2ee980 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 31 Aug 2016 09:52:11 +0000 Subject: [PATCH 732/933] Task #9607: only guess start/end time for CEP4 pipelines, use duration from parset if given else 1hour --- .../ResourceAssigner/lib/assignment.py | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 84432456013..d0c66a8ab86 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -122,34 +122,39 @@ class ResourceAssigner(): mainParset = parameterset(specification_tree['specification']) momId = mainParset.getInt('Observation.momID', -1) - try: - startTime = datetime.strptime(mainParset.getString('Observation.startTime'), '%Y-%m-%d %H:%M:%S') - endTime = datetime.strptime(mainParset.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') - except ValueError: - logger.warning('cannot parse for start/end time from specification for otdb_id=%s. searching for sane defaults...', (otdb_id, )) - maxPredecessorEndTime = self.getMaxPredecessorEndTime(specification_tree) - - if maxPredecessorEndTime: - startTime = maxPredecessorEndTime + timedelta(minutes=1) - endTime = startTime + timedelta(hours=1) - logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s based on maxPredecessorEndTime (%s)', - startTime, endTime, otdb_id, maxPredecessorEndTime) - else: - startTime = datetime.utcnow() + timedelta(hours=1) + timedelta(minutes=1) - endTime = startTime + timedelta(hours=1) - logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s one hour from now', - startTime, endTime, otdb_id) - - try: - logger.info('uploading auto-generated start/end time (%s, %s) to otdb for otdb_id=%s', startTime, endTime, otdb_id) - self.otdbrpc.taskSetSpecification(otdb_id, { 'LOFAR.ObsSW.Observation.startTime': startTime.strftime('%Y-%m-%d %H:%M:%S'), - 'LOFAR.ObsSW.Observation.stopTime': endTime.strftime('%Y-%m-%d %H:%M:%S')}) - except Exception as e: - logger.error(e) clusterIsCEP4 = self.checkClusterIsCEP4(mainParset) clusterName = 'CEP4' if clusterIsCEP4 else 'CEP2' + if clusterIsCEP4: + try: + startTime = datetime.strptime(mainParset.getString('Observation.startTime'), '%Y-%m-%d %H:%M:%S') + endTime = datetime.strptime(mainParset.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') + except ValueError: + logger.warning('cannot parse for start/end time from specification for otdb_id=%s. searching for sane defaults...', (otdb_id, )) + maxPredecessorEndTime = self.getMaxPredecessorEndTime(specification_tree) + + taskDuration = mainParset.getInt('Observation.Scheduler.taskDuration', -1) + taskDuration = timedelta(seconds=taskDuration) if taskDuration > 0 else timedelta(hours=1) + + if maxPredecessorEndTime: + startTime = maxPredecessorEndTime + timedelta(minutes=1) + endTime = startTime + taskDuration + logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s based on maxPredecessorEndTime (%s)', + startTime, endTime, otdb_id, maxPredecessorEndTime) + else: + startTime = datetime.utcnow() + timedelta(hours=1) + timedelta(minutes=1) + endTime = startTime + taskDuration + logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s one hour from now', + startTime, endTime, otdb_id) + + try: + logger.info('uploading auto-generated start/end time (%s, %s) to otdb for otdb_id=%s', startTime, endTime, otdb_id) + self.otdbrpc.taskSetSpecification(otdb_id, { 'LOFAR.ObsSW.Observation.startTime': startTime.strftime('%Y-%m-%d %H:%M:%S'), + 'LOFAR.ObsSW.Observation.stopTime': endTime.strftime('%Y-%m-%d %H:%M:%S')}) + except Exception as e: + logger.error(e) + # insert new task and specification in the radb # any existing specification and task with same otdb_id will be deleted automatically logger.info('doAssignment: insertSpecification momId=%s, otdb_id=%s, status=%s, taskType=%s, startTime=%s, endTime=%s cluster=%s' % @@ -274,7 +279,7 @@ class ResourceAssigner(): task['mom_id'], task['otdb_id']) self.radbrpc.insertTaskPredecessor(successor_task['id'], task['id']) - movePipelineAfterItsPredecessors(successor_task, self.radbrpc, datetime.utcnow()) + movePipelineAfterItsPredecessors(successor_task, self.radbrpc) else: logger.warning('could not find predecessor task with otdb_id=%s in radb for task otdb_id=%s', successor_task['otdb_id'], task['otdb_id']) else: -- GitLab From 888413f2f6a93545cd2d7d2eee25308cc2f17188 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 31 Aug 2016 12:31:50 +0000 Subject: [PATCH 733/933] Task #9607: check for None --- SAS/MoM/MoMQueryService/momqueryservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index ce8b6842c83..566690a9721 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -132,7 +132,7 @@ class MoMDatabaseWrapper: rows = self._executeQuery(query) logger.info("Found %d results for mom id(s): %s" % - (len(rows), ids_str)) + (len(rows) if rows else 0, ids_str)) result = {} for row in rows: -- GitLab From 4321ca2749cb40f1ca4a052602bf06edc7e8835f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 31 Aug 2016 12:32:39 +0000 Subject: [PATCH 734/933] Task #9607: added Observation.Scheduler.taskDuration key --- .../RATaskSpecifiedService/lib/RATaskSpecified.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py b/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py index 3c61fedb012..272145aa4ff 100755 --- a/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py +++ b/SAS/ResourceAssignment/RATaskSpecifiedService/lib/RATaskSpecified.py @@ -108,6 +108,7 @@ def resourceIndicatorsFromParset( parsetDict ): add("Observation.VirtualInstrument.stationList", strvector) add("Observation.startTime") add("Observation.stopTime") + add("Observation.Scheduler.taskDuration") add("Observation.nrBeams") nrSAPs = int(get("Observation.nrBeams", 0)) -- GitLab From 7f61bea48dc0fc8c20417fc1ea059e151d301bbb Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 1 Sep 2016 06:28:19 +0000 Subject: [PATCH 735/933] Task #9522: Added missing dependency (due to modified dependencies in Ubuntu) --- Docker/lofar-outputproc/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index 7e67934e8c3..22a882cea6e 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -10,7 +10,7 @@ FROM lofar-base:${LOFAR_TAG} # # Run-time dependencies -RUN apt-get update && apt-get install -y liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libboost-regex${BOOST_VERSION}.0 +RUN apt-get update && apt-get install -y binutils liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libboost-regex${BOOST_VERSION}.0 # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH} \ -- GitLab From eb33d06a9d396583a47ac094045b22c38cc187ce Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Thu, 1 Sep 2016 07:23:14 +0000 Subject: [PATCH 736/933] Task #8680: updated dongels to 8 and 100 licenses again and placed the new shield parents for 3.14 --- .../PVSS/License/Astron_Central_1_shield.txt | 6 +- .../PVSS/License/Astron_Station_1_shield.txt | 55 ++++++++++--------- MAC/Deployment/data/PVSS/License/Station.rtu | 4 +- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt b/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt index 8beb1c99acb..2f8cdc681b7 100644 --- a/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt +++ b/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt @@ -1,8 +1,8 @@ [license] #hw = 00825320842 -code = "dongleHost 50202878741" -version = 31100002 -sn = "471_3031_1_Astron_Gen_I_2_311" +code = "dongleHost 10552290714" +version = 31400002 +sn = "471_3031_1_Astron_Gen_I_3_314" expire = 0000.00.00;00:00:00,000 redundancy = 1 ui = 15 diff --git a/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt b/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt index f69bf55da72..f0a7c1ba441 100644 --- a/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt +++ b/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt @@ -1,29 +1,30 @@ [license] -code = "dongleHost 30060581564" -version = 31100002 -sn = "471_3031_2_Astron_Gen_II_2_311" -expire = 0000.00.00;00:00:00,000 -redundancy = 0 -ui = 2 -para = 1 -dde = 5 -event = 1 -ios = 4000 -ssi = 0 -api = 80 -excelreport = 5 -http = 0 -infoserver = 1000 -comcenter = 5 -maintenance = 1 -scheduler = 1 -recipe = 1 -distributed = 255 -uifix = 0 -parafix = 0 -pararemote = 0 -ctrlext = 1 -update = 0 -licenseMax = 100 -licenseLeft = 83 +#hw = 12831493085 +code = "dongleHost 10388280866" +version = 31400002 +sn = "471_3031_2_Astron_Gen_II_3_314" +expire = 0000.00.00;00:00:00,000 +redundancy = 0 +ui = 2 +para = 1 +dde = 5 +event = 1 +api = 80 +excelreport = 5 +http = 0 +infoserver = 1000 +ios = 4000 +comcenter = 5 +maintenance = 1 +scheduler = 1 +ssi = 0 +recipe = 1 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 +licenseMax = 100 +licenseLeft = 100 diff --git a/MAC/Deployment/data/PVSS/License/Station.rtu b/MAC/Deployment/data/PVSS/License/Station.rtu index a35473f282a..01e8380c978 100644 --- a/MAC/Deployment/data/PVSS/License/Station.rtu +++ b/MAC/Deployment/data/PVSS/License/Station.rtu @@ -5,5 +5,5 @@ Version=1.00 [Contents] ; 1 command for WIBU-BOX with Serial Number 9-5510497: -N5qqe 010NG NNNG9 5NNNe FVB0A AG7yN -LTM15 LG +N5qqe 010NG NNNG9 5NNN0 L4K1A 8L87M +6ZXNA pX -- GitLab From 5f353d1090f4496c6bb9de808e3fbe8b5f9c392a Mon Sep 17 00:00:00 2001 From: Pieter Donker <donker@astron.nl> Date: Thu, 1 Sep 2016 08:36:13 +0000 Subject: [PATCH 737/933] Task #9770: Fixes for hba zero vs one sensitivity issue --- MAC/APL/PAC/Cal_Server/src/CalServer.cc | 61 +++++++++++++++-------- MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc | 3 ++ MAC/APL/PIC/RSP_Driver/src/RCUWrite.cc | 40 +++++++++++---- MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc | 4 +- MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc | 7 ++- 5 files changed, 80 insertions(+), 35 deletions(-) diff --git a/MAC/APL/PAC/Cal_Server/src/CalServer.cc b/MAC/APL/PAC/Cal_Server/src/CalServer.cc index db8c3ee063b..76fd4273374 100644 --- a/MAC/APL/PAC/Cal_Server/src/CalServer.cc +++ b/MAC/APL/PAC/Cal_Server/src/CalServer.cc @@ -718,22 +718,6 @@ GCFEvent::TResult CalServer::handle_cal_start(GCFEvent& e, GCFPortInterface &por itsSubArrays.scheduleAdd(subarray, &port); // calibration will start within one second - // set the spectral inversion right - RSPSetbypassEvent specInvCmd; - bool SIon(subarray->SPW().nyquistZone() == 2);// on or off? - specInvCmd.timestamp = Timestamp(0,0); - specInvCmd.rcumask = start.rcuMask; - specInvCmd.settings().resize(1); - specInvCmd.settings()(0).setXSI(SIon); - specInvCmd.settings()(0).setYSI(SIon); - LOG_DEBUG_STR("NyquistZone = " << subarray->SPW().nyquistZone() - << " setting spectral inversion " << ((SIon) ? "ON" : "OFF")); - itsRSPDriver->send(specInvCmd); - - //RCUSettings rcu_settings; - //rcu_settings().resize(1); - //rcu_settings()(0).setMode(start.rcumode); - _enableRCUs(subarray, SCHEDULING_DELAY + 4); } } @@ -902,16 +886,26 @@ void CalServer::_enableRCUs(SubArray* subarray, int delay) itsRSPDriver->send(enableCmd); } - // when the lbl inputs are selected swap the X and the Y. - //int rcumode(subarray->SPW().rcumode()); - //int rcumode(0); // TODO: add real rcumode or change all to antennaset - //if (rcumode == 1 || rcumode == 2) { // LBLinput used? + // set the spectral inversion right + bool SIon(subarray->SPW().nyquistZone() == 2);// on or off? + if (SIon) { + RSPSetbypassEvent specInvCmd; + specInvCmd.timestamp = Timestamp(0,0); + specInvCmd.rcumask = rcuMask; + specInvCmd.settings().resize(1); + specInvCmd.settings()(0).setXSI(SIon); + specInvCmd.settings()(0).setYSI(SIon); + LOG_INFO_STR("NyquistZone = " << subarray->SPW().nyquistZone() + << " setting spectral inversion " << ((SIon) ? "ON" : "OFF")); + itsRSPDriver->send(specInvCmd); + } + // when antennaSet = LBA_X or LBA_Y do not swap the X and the Y. if ((subarray->antennaSet() == "LBA_X") || (subarray->antennaSet() == "LBA_Y")) { LOG_INFO("LBL_X or LBA_Y used, swapping not needed"); } + // when the lbl inputs are selected swap the X and the Y. else { - RCUmask_t switchMask; switchMask.reset(); for (uint r = 0; r < m_n_rcus; r++) { @@ -968,6 +962,22 @@ void CalServer::_disableRCUs(SubArray* subarray) // LOG_INFO("No active rcu's anymore, forcing all units to mode 0 and disable"); } + // turn of rcu dataflow if not used anymore + RSPSetrcuEvent disableCmd; + disableCmd.timestamp = Timestamp(0,0); + disableCmd.rcumask = rcus2switchOff; + disableCmd.settings().resize(m_n_rcus); + + for (uint r = 0; r < m_n_rcus; r++) { + if (rcus2switchOff.test(r)) { + disableCmd.settings()(r).setEnable(false); + } + } + LOG_INFO_STR("disableCmd= " << disableCmd); + sleep (1); + LOG_INFO("Disable some rcu's because they are not used anymore"); + itsRSPDriver->send(disableCmd); + // when the lbl inputs are selected swap the X and the Y. LOG_INFO("Resetting swap of X and Y"); RSPSetswapxyEvent swapCmd; @@ -976,6 +986,15 @@ void CalServer::_disableRCUs(SubArray* subarray) swapCmd.antennamask = RCU2AntennaMask(rcus2switchOff); itsRSPDriver->send(swapCmd); + LOG_INFO("Resetting spectral inversion"); + RSPSetbypassEvent specInvCmd; + specInvCmd.timestamp = Timestamp(0,0); + specInvCmd.rcumask = rcus2switchOff; + specInvCmd.settings().resize(1); + specInvCmd.settings()(0).setXSI(false); + specInvCmd.settings()(0).setYSI(false); + itsRSPDriver->send(specInvCmd); + _updateDataStream(0); // asap if (!subarray) { // reset at startup of CalServer or stopping LBA's? diff --git a/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc b/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc index 3005237338d..f33330f7c64 100644 --- a/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc +++ b/MAC/APL/PAC/ITRFBeamServer/src/beamctl.cc @@ -379,6 +379,9 @@ GCFEvent::TResult beamctl::sendPointings(GCFEvent& event, GCFPortInterface& port IBSPointtoackEvent ack(event); if (ack.status != IBS_Protocol::IBS_NO_ERR) { cerr << "Error: " << errorName(ack.status) << endl; + if (ack.status == IBS_Protocol::IBS_UNKNOWN_BEAM_ERR) { + cerr << " Possible wrong bitmode ?" << endl; + } TRAN(beamctl::final); } else { TRAN(beamctl::sendPointings); // tran to myself to exec the ENTRY state again. diff --git a/MAC/APL/PIC/RSP_Driver/src/RCUWrite.cc b/MAC/APL/PIC/RSP_Driver/src/RCUWrite.cc index 3583b103428..beb524875de 100644 --- a/MAC/APL/PIC/RSP_Driver/src/RCUWrite.cc +++ b/MAC/APL/PIC/RSP_Driver/src/RCUWrite.cc @@ -35,8 +35,11 @@ using namespace LOFAR; using namespace RSP; using namespace EPA_Protocol; + +#define N_WRITES 2 // 2 writes, one for disable rcu and setdelay, one for enable rcu if requested + RCUWrite::RCUWrite(GCFPortInterface& board_port, int board_id) - : SyncAction(board_port, board_id, NR_BLPS_PER_RSPBOARD) + : SyncAction(board_port, board_id, NR_BLPS_PER_RSPBOARD * N_WRITES) { memset(&m_hdr, 0, sizeof(MEPHeader)); doAtInit(); // needed to enable/disable RCU's during initialization @@ -49,7 +52,7 @@ RCUWrite::~RCUWrite() void RCUWrite::sendrequest() { - uint8 global_blp = (getBoardId() * NR_BLPS_PER_RSPBOARD) + getCurrentIndex(); + uint8 global_blp = (getBoardId() * NR_BLPS_PER_RSPBOARD) + (getCurrentIndex() / N_WRITES); // skip update if the neither of the RCU's settings have been modified if (RTC::RegisterState::WRITE != Cache::getInstance().getState().rcusettings().get(global_blp * 2) @@ -68,12 +71,25 @@ void RCUWrite::sendrequest() LOG_DEBUG(formatString("%d.Y control=0x%08x", global_blp, y.getRaw())); EPARcuSettingsEvent rcusettings; - rcusettings.hdr.set(MEPHeader::RCU_SETTINGS_HDR, 1 << getCurrentIndex()); // also sets payload_length + rcusettings.hdr.set(MEPHeader::RCU_SETTINGS_HDR, 1 << (getCurrentIndex() / N_WRITES)); // also sets payload_length rcusettings.ap = EPA_Protocol::RCUHandler(); - rcusettings.ap.input_delay_x = x.getDelay(); - rcusettings.ap.enable_x = x.getEnable(); - rcusettings.ap.input_delay_y = y.getDelay(); - rcusettings.ap.enable_y = y.getEnable(); + + // new delay is active after datastream restart + switch (getCurrentIndex() % N_WRITES) { + case 0: { + rcusettings.ap.input_delay_x = x.getDelay(); + rcusettings.ap.enable_x = 0; + rcusettings.ap.input_delay_y = y.getDelay(); + rcusettings.ap.enable_y = 0; + } break; + + case 1: { + rcusettings.ap.input_delay_x = x.getDelay(); + rcusettings.ap.enable_x = x.getEnable(); + rcusettings.ap.input_delay_y = y.getDelay(); + rcusettings.ap.enable_y = y.getEnable(); + } break; + } m_hdr = rcusettings.hdr; getBoardPort().send(rcusettings); @@ -91,10 +107,10 @@ GCFEvent::TResult RCUWrite::handleack(GCFEvent& event, GCFPortInterface& /*port* LOG_WARN("RCUWrite::handleack:: unexpected ack"); return GCFEvent::NOT_HANDLED; } - + EPAWriteackEvent ack(event); - uint8 global_blp = (getBoardId() * NR_BLPS_PER_RSPBOARD) + getCurrentIndex(); + uint8 global_blp = (getBoardId() * NR_BLPS_PER_RSPBOARD) + (getCurrentIndex() / N_WRITES); if (!ack.hdr.isValidAck(m_hdr)) { @@ -104,8 +120,10 @@ GCFEvent::TResult RCUWrite::handleack(GCFEvent& event, GCFPortInterface& /*port* return GCFEvent::NOT_HANDLED; } - Cache::getInstance().getState().rcusettings().write_ack(global_blp * 2); - Cache::getInstance().getState().rcusettings().write_ack(global_blp * 2 + 1); + if ((getCurrentIndex() % N_WRITES) == 1) { + Cache::getInstance().getState().rcusettings().write_ack(global_blp * 2); + Cache::getInstance().getState().rcusettings().write_ack(global_blp * 2 + 1); + } return GCFEvent::HANDLED; } diff --git a/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc b/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc index 121ff5d3ffb..8415048f2b0 100644 --- a/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc +++ b/MAC/APL/PIC/RSP_Driver/src/SetHBACmd.cc @@ -62,7 +62,7 @@ void SetHBACmd::apply(CacheBuffer& cache, bool setModFlag) { // someone else using the I2C bus? I2Cuser busUser = cache.getI2Cuser(); - LOG_INFO_STR("SetHBA::apply : " << ((busUser == NONE) ? "NONE" : ((busUser == HBA) ? "HBA" : "RCU"))); + LOG_DEBUG_STR("SetHBA::apply : " << ((busUser == NONE) ? "NONE" : ((busUser == HBA) ? "HBA" : "RCU"))); if (busUser != NONE && busUser != HBA) { postponeExecution(true); return; @@ -94,7 +94,7 @@ void SetHBACmd::apply(CacheBuffer& cache, bool setModFlag) } } if (!delays_changed) { - LOG_INFO_STR("Skip updating rcu " << cache_rcu << ", value not changed"); + LOG_DEBUG_STR("Skip updating rcu " << cache_rcu << ", value not changed"); } //delays_changed = true; diff --git a/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc b/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc index fea50c24384..e95fb1f2679 100644 --- a/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc +++ b/MAC/APL/PIC/RSP_Driver/src/SetRCUCmd.cc @@ -90,7 +90,12 @@ void SetRCUCmd::apply(CacheBuffer& cache, bool setModFlag) // Apply delays and attenuation when mode was changed. if (m_event->settings()(eventRcu).isModeModified()) { - mode = m_event->settings()(eventRcu).getMode(); + + mode = m_event->settings()(eventRcu).getMode(); + if (m_event->settings()(eventRcu).getMode() == -1) { + LOG_INFO_STR("RCU " << eventRcu << ", mode = -1, bad RCU readout, use 0 instead"); + mode = 0; + } // clear HBA delays if mode < than 5 is selected if (mode < 5) { -- GitLab From 6fb95a9bcafc4be88c135a6cf1721c51e196681d Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Thu, 1 Sep 2016 10:05:56 +0000 Subject: [PATCH 738/933] Task #9273: improve test --- CEP/DP3/DPPP/test/tApplyCal2.run | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CEP/DP3/DPPP/test/tApplyCal2.run b/CEP/DP3/DPPP/test/tApplyCal2.run index 3e460f1ffa5..e270cb307db 100755 --- a/CEP/DP3/DPPP/test/tApplyCal2.run +++ b/CEP/DP3/DPPP/test/tApplyCal2.run @@ -13,6 +13,8 @@ if test ! -f ${srcdir}/tNDPPP-generic.in_MS.tgz; then exit 3 # untested fi +set -e # Stop on any error + rm -rf tApplyCal2_tmp mkdir -p tApplyCal2_tmp # Unpack the MS and other files and do the DPPP run. @@ -41,3 +43,22 @@ echo; echo "Testing with updateweights" $taqlexe 'select from tNDPPP-generic.MS where not(all(WEIGHTS_NEW~=81*WEIGHT_SPECTRUM))' > taql.out diff taql.out taql.ref || exit 1 +echo; echo "Testing CommonScalarPhase" +rm -rf tApplyCal.parmdb +../../../../ParmDB/src/parmdbm <<EOL +open table="tApplyCal.parmdb" +adddef CommonScalarPhase values=0 +EOL +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DATA3 steps=[applycal] applycal.parmdb=tApplyCal.parmdb applycal.correction=commonscalarphase showcounts=false +$taqlexe 'select from tNDPPP-generic.MS where not(all(DATA~=DATA3))' > taql.out +diff taql.out taql.ref || exit 1 + +echo; echo "Testing ScalarPhase" +rm -rf tApplyCal.parmdb +../../../../ParmDB/src/parmdbm <<EOL +open table="tApplyCal.parmdb" +adddef ScalarPhase values=0 +EOL +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DATA3 steps=[applycal] applycal.parmdb=tApplyCal.parmdb applycal.correction=commonscalarphase showcounts=false +$taqlexe 'select from tNDPPP-generic.MS where not(all(DATA~=DATA3))' > taql.out +diff taql.out taql.ref || exit 1 -- GitLab From 2384757b9134218da4d84c772e0aa5cf8eb558bd Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Thu, 1 Sep 2016 10:43:20 +0000 Subject: [PATCH 739/933] Task #9273: fix bug in ApplyCal CommonScalarPhase, add test --- CEP/DP3/DPPP/src/ApplyCal.cc | 12 ++++++++++-- CEP/DP3/DPPP/test/tApplyCal2.run | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CEP/DP3/DPPP/src/ApplyCal.cc b/CEP/DP3/DPPP/src/ApplyCal.cc index 6ac751d0ebd..104f9fbeb0f 100644 --- a/CEP/DP3/DPPP/src/ApplyCal.cc +++ b/CEP/DP3/DPPP/src/ApplyCal.cc @@ -330,8 +330,16 @@ namespace LOFAR { for (uint parmExprNum = 0; parmExprNum<itsParmExprs.size();++parmExprNum) { // parmMap contains parameter values for all antennas parmMap = itsParmDB->getValuesMap( itsParmExprs[parmExprNum] + "*", - minFreq, maxFreq, freqInterval, - bufStartTime, itsLastTime, itsTimeInterval, true); + minFreq, maxFreq, freqInterval, + bufStartTime, itsLastTime, itsTimeInterval, + true); + + // Resolve {Common,}Bla to CommonBla or Bla + if (!parmMap.empty() && + itsParmExprs[parmExprNum].find("{") != std::string::npos) { + uint colonPos = (parmMap.begin()->first).find(":"); + itsParmExprs[parmExprNum] = (parmMap.begin()->first).substr(0, colonPos); + } for (int ant = 0; ant < numAnts; ++ant) { parmIt = parmMap.find( diff --git a/CEP/DP3/DPPP/test/tApplyCal2.run b/CEP/DP3/DPPP/test/tApplyCal2.run index e270cb307db..54a2594dea1 100755 --- a/CEP/DP3/DPPP/test/tApplyCal2.run +++ b/CEP/DP3/DPPP/test/tApplyCal2.run @@ -62,3 +62,20 @@ EOL ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DATA3 steps=[applycal] applycal.parmdb=tApplyCal.parmdb applycal.correction=commonscalarphase showcounts=false $taqlexe 'select from tNDPPP-generic.MS where not(all(DATA~=DATA3))' > taql.out diff taql.out taql.ref || exit 1 + +echo; echo "Testing ScalarPhase values" +rm -rf tApplyCal.parmdb +../../../../ParmDB/src/parmdbm <<EOL +open table="tApplyCal.parmdb" +add ScalarPhase:CS001HBA0 values=[0.,0.,0.,0.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarPhase:CS002HBA0 values=[0.,0.,0.,0.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarPhase:CS002HBA1 values=[0.,0.,0.,0.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarPhase:CS004HBA1 values=[0.,0.,0.,0.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarPhase:RS106HBA values=[0.,0.,0.,0.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarPhase:RS208HBA values=[0.,0.,0.,0.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarPhase:RS305HBA values=[0.,0.,0.,0.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarPhase:RS307HBA values=[0.,0.,0.,0.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +EOL +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DATA3 steps=[applycal] applycal.parmdb=tApplyCal.parmdb applycal.correction=commonscalarphase showcounts=false +$taqlexe 'select from tNDPPP-generic.MS where not(all(DATA~=DATA3))' > taql.out +diff taql.out taql.ref || exit 1 -- GitLab From f138f76692bb4b971373b5816a2232ace955decc Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 1 Sep 2016 11:28:00 +0000 Subject: [PATCH 740/933] Task #9607: fix for MoM bug introduced before NV's holiday --- .../ResourceAssigner/lib/assignment.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index d0c66a8ab86..206e98a41dc 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -155,6 +155,18 @@ class ResourceAssigner(): except Exception as e: logger.error(e) + try: + # fix for MoM bug introduced before NV's holiday + # MoM sets ProcessingCluster.clusterName to CEP2 even when inputxml says CEP4 + # so, override it here if needed, and update to otdb + processingClusterName = mainParset.getString('Observation.Cluster.ProcessingCluster.clusterName', '') + if processingClusterName != clusterName: + logger.info('overwriting and uploading processingClusterName to otdb from %s to %s for otdb_id=%s', + processingClusterName, clusterName, otdb_id) + self.otdbrpc.taskSetSpecification(otdb_id, { 'LOFAR.ObsSW.Observation.Cluster.ProcessingCluster.clusterName': clusterName }) + except Exception as e: + logger.error(e) + # insert new task and specification in the radb # any existing specification and task with same otdb_id will be deleted automatically logger.info('doAssignment: insertSpecification momId=%s, otdb_id=%s, status=%s, taskType=%s, startTime=%s, endTime=%s cluster=%s' % -- GitLab From ba5bcf4b8f3598674710d3dab9badef83e6a606b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 1 Sep 2016 13:32:50 +0000 Subject: [PATCH 741/933] Task #9607: logging --- SAS/DataManagement/CleanupService/rpc.py | 4 +++- SAS/DataManagement/DataManagementCommon/path.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SAS/DataManagement/CleanupService/rpc.py b/SAS/DataManagement/CleanupService/rpc.py index 7a30f41cc30..28817acc778 100644 --- a/SAS/DataManagement/CleanupService/rpc.py +++ b/SAS/DataManagement/CleanupService/rpc.py @@ -46,7 +46,9 @@ def main(): path_result = rpc.getPathForOTDBId(otdb_id) if path_result['found']: path = path_result['path'] - print "This will delete everything in '%s'." % path + scratch_paths = path_result['scratch_paths'] + paths = scratch_paths + [path] + print "This will delete everything in '%s'." % ', '.join(paths) if raw_input("Are you sure? (y/n) ") == 'y': result = rpc.removeTaskData(otdb_id) print diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index 4cbf7afe621..17d6d9f89f9 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -80,7 +80,7 @@ class PathResolver: scratch_path = os.path.join(self.scratch_path, 'Observation%s' % task['otdb_id']) share_path = os.path.join(self.share_path, 'Observation%s' % task['otdb_id']) - logger.debug("Checking scratch paths %s %s for otdb_id=%s mom_id=%s radb_id=%s" % (scratch_path, share_path, task['otdb_id'], task['mom_id'], task['id'])) + logger.info("Checking scratch paths %s %s for otdb_id=%s mom_id=%s radb_id=%s" % (scratch_path, share_path, task['otdb_id'], task['mom_id'], task['id'])) if self.pathExists(scratch_path): path_result['scratch_paths'].append(scratch_path) -- GitLab From 300baf6d0b6525057f8732cc565ddd30ce82abe8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 1 Sep 2016 13:34:27 +0000 Subject: [PATCH 742/933] Task #9607: return empty list for empty requested id's --- SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index f64e69bf157..79126379d7e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -179,6 +179,8 @@ class RADatabase: elif len(task_ids) > 0: #assume a list/enumerable of id's conditions.append('id in %s') qargs.append(tuple(task_ids)) + elif len(task_ids) == 0: #assume a list/enumerable of id's, length 0 + return [] if mom_ids is not None: if isinstance(mom_ids, int): # just a single id @@ -187,6 +189,8 @@ class RADatabase: elif len(mom_ids) > 0: #assume a list/enumerable of id's conditions.append('mom_id in %s') qargs.append(tuple(mom_ids)) + elif len(mom_ids) == 0: #assume a list/enumerable of id's, length 0 + return [] if otdb_ids is not None: if isinstance(otdb_ids, int): # just a single id @@ -195,6 +199,8 @@ class RADatabase: elif len(otdb_ids) > 0: #assume a list/enumerable of id's conditions.append('otdb_id in %s') qargs.append(tuple(otdb_ids)) + elif len(otdb_ids) == 0: #assume a list/enumerable of id's, length 0 + return [] task_status, task_type = self._convertTaskTypeAndStatusToIds(task_status, task_type) @@ -1521,6 +1527,7 @@ if __name__ == '__main__': db = RADatabase(dbcreds=dbcreds, log_queries=True) + def resultPrint(method): print '\n-- ' + str(method.__name__) + ' --' print '\n'.join([str(x) for x in method()]) -- GitLab From b2a09611567264277f9418698403e9becb77718f Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Thu, 1 Sep 2016 13:36:16 +0000 Subject: [PATCH 743/933] Task #9655: last changes for SNMP powerunit monitorring --- .../panels/Hardware/Station_PowerUnits.pnl | 10 +-- .../objects/Hardware/Station_PowerUnit.pnl | 4 +- .../Hardware/observationFlow_stations.pnl | 79 ++++++++++++++++++- 3 files changed, 82 insertions(+), 11 deletions(-) diff --git a/MAC/Navigator2/panels/Hardware/Station_PowerUnits.pnl b/MAC/Navigator2/panels/Hardware/Station_PowerUnits.pnl index 4c25fd1195d..08fc96d098f 100644 --- a/MAC/Navigator2/panels/Hardware/Station_PowerUnits.pnl +++ b/MAC/Navigator2/panels/Hardware/Station_PowerUnits.pnl @@ -74,11 +74,11 @@ void reload() { if (nrOfPowerUnits == 1) { - setValue(\"unitsText\",\"text\", sysName + \" has 1 PowerModule\"); + setValue(\"unitsText\",\"text\", sysName + \" has 1 PowerUnit\"); } else { - setValue(\"unitsText\",\"text\", sysName + \" has \" + nrOfPowerUnits + \" PowerModules\"); + setValue(\"unitsText\",\"text\", sysName + \" has \" + nrOfPowerUnits + \" PowerUnits\"); } // set panel to ready @@ -138,7 +138,7 @@ LANG:1 6 Layer1 2 15 "unitsText" "" -1 430 40 E E E 1 E 1 E N "_WindowText" E N "_Window" E E +1 429.9770114942529 40 E E E 1 E 1 E N "_WindowText" E N "_Window" E E E E 129 0 0 0 0 0 E E E @@ -148,12 +148,12 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 1 1 2 1 E U 0 E 430 40 600 56 +E E 0 1 1 2 1 E 0.9885057471264368 0 1 4.919540229885051 0 0 E 430 40 579 56 0 2 2 "0s" 0 0 0 192 0 0 430 40 1 1 LANG:1 35 MS Shell Dlg 2,10,-1,5,75,0,0,0,0,0 0 1 -LANG:1 23 CS001 has 1 PowerModule +LANG:1 21 CS001 has 1 PowerUnit 0 LAYER, 1 1 diff --git a/MAC/Navigator2/panels/objects/Hardware/Station_PowerUnit.pnl b/MAC/Navigator2/panels/objects/Hardware/Station_PowerUnit.pnl index 3883d19d67e..f7752a65c48 100644 --- a/MAC/Navigator2/panels/objects/Hardware/Station_PowerUnit.pnl +++ b/MAC/Navigator2/panels/objects/Hardware/Station_PowerUnit.pnl @@ -94,7 +94,7 @@ void setPUvalues(string dp1, int nrOfModules, int t = 0; int s = 0; if (i <= dynlen(voltage)) v = voltage[i]/10.; - if (i <= dynlen(current)) c = current[i]/100.; + if (i <= dynlen(current)) c = current[i]/10.; if (i <= dynlen(temperature)) t = temperature[i]; if (i <= dynlen(OK)) s = OK[i]; @@ -270,7 +270,7 @@ E "main() click(); }" 0 0 1 1 2 1 E 1.138888888888889 0 1 6.7222222222223 2 1 E 110 100 830 300 -1 923 1 "" 1 +1 925 1 "" 1 0 2 917 "unitname" diff --git a/MAC/Navigator2/panels/objects/Hardware/observationFlow_stations.pnl b/MAC/Navigator2/panels/objects/Hardware/observationFlow_stations.pnl index 9c0778f8820..8aa7f77e09a 100644 --- a/MAC/Navigator2/panels/objects/Hardware/observationFlow_stations.pnl +++ b/MAC/Navigator2/panels/objects/Hardware/observationFlow_stations.pnl @@ -169,9 +169,6 @@ void updateObservations(dyn_string dps,dyn_dyn_string values) { } dynSort(stationDPList); dynSort(stationList); - string ttip = \"Stations: <\\n>\"; - for (int i=1; i<= dynlen(stationList);i++) ttip += stationList[i]+\"<\\n>\"; - setValue(\"childStateBorder\",\"toolTipText\",ttip); if (dynlen(stationDPList) < 1) { setStates(0,0); @@ -189,10 +186,84 @@ void updateStationStates(dyn_string dps,dyn_string values) { int highestState=0; int highestChildState=0; + // keep stations that are not operational, but should be according to the observationlist + dyn_string maintenanceList; + dyn_string testList; + dyn_string suspiciousList; + dyn_string errorList; + dyn_string offlineList; + for (int i= 1; i<= dynlen(dps); i++) { - if (strpos(dps[i],\"status.state\") >= 0 && values[i] > highestState) highestState=values[i]; + if (strpos(dps[i],\"status.state\") >= 0) + { + int val = values[i]; + if ( val > highestState) highestState=val; + string station = dpSubStr(dps[i],DPSUB_SYS); + if (val >= 60) + { + dynAppend(offlineList,navFunct_bareDBName(station)); + } + else if (val >= 50) + { + dynAppend(errorList,navFunct_bareDBName(station)); + } + else if (val >= 40) + { + dynAppend(suspiciousList,navFunct_bareDBName(station)); + } + else if (val >= 30) + { + dynAppend(testList,navFunct_bareDBName(station)); + } + else if (val >= 20) + { + dynAppend(maintenanceList,navFunct_bareDBName(station)); + } + } if (strpos(dps[i],\"status.childState\") >= 0 && values[i] > highestChildState) highestChildState=values[i]; + + } + dynSort(offlineList); + dynSort(errorList); + dynSort(suspiciousList); + dynSort(testList); + dynSort(maintenanceList); + dynUnique(offlineList); + dynUnique(errorList); + dynUnique(suspiciousList); + dynUnique(testList); + dynUnique(maintenanceList); + + string toolTipText = \"Unavailable (but needed)<br> or errorstate stations in current observation(s) :<br>\"; + + for (int i = 1; i <= dynlen(maintenanceList);i++) + { + if (i == 1) toolTipText += \"In Maintenance mode: <br>\"; + toolTipText += maintenanceList[i] + \"<br>\"; + } + for (int i = 1; i <= dynlen(testList);i++) + { + if (i == 1) toolTipText += \"In Test mode: <br>\"; + toolTipText += testList[i] + \"<br>\"; + } + for (int i = 1; i <= dynlen(suspiciousList);i++) + { + if (i == 1) toolTipText += \"Suspicious: <br>\"; + toolTipText += suspiciousList[i] + \"<br>\"; } + for (int i = 1; i <= dynlen(errorList);i++) + { + if (i == 1) toolTipText += \"Error: <br>\"; + toolTipText += errorList[i] + \"<br>\"; + } + for (int i = 1; i <= dynlen(offlineList);i++) + { + if (i == 1) toolTipText += \"Stations offline: <br>\"; + toolTipText += offlineList[i] + \"<br>\"; + } + + setValue(\"childStateBorder\",\"toolTipText\",toolTipText); + setStates(highestState,highestChildState); } -- GitLab From cd8176ec8c6261c3f865724e242aff887f6ac63f Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Thu, 1 Sep 2016 14:09:15 +0000 Subject: [PATCH 744/933] Task #7727: The order of directories in PATH should be $LOFARROOT/sbin:$LOFARROOT/bin; i.e. sbin should appear before bin. --- lofarinit.csh.in | 4 ++-- lofarinit.sh.in | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lofarinit.csh.in b/lofarinit.csh.in index dd7a0dadd00..d8fe0888d0a 100644 --- a/lofarinit.csh.in +++ b/lofarinit.csh.in @@ -96,9 +96,9 @@ else # Add the path to the standard paths. if (! $?PATH) then - setenv PATH $LOFARROOT/bin:$LOFARROOT/sbin + setenv PATH $LOFARROOT/sbin:$LOFARROOT/bin else - setenv PATH $LOFARROOT/bin:$LOFARROOT/sbin:$PATH + setenv PATH $LOFARROOT/sbin:$LOFARROOT/bin:$PATH endif if (! $?LD_LIBRARY_PATH) then setenv LD_LIBRARY_PATH $LOFARROOT/$lfr_libdir diff --git a/lofarinit.sh.in b/lofarinit.sh.in index c6d913ac88f..65e797a48fc 100644 --- a/lofarinit.sh.in +++ b/lofarinit.sh.in @@ -95,9 +95,9 @@ if [ "$LOFARROOT" = "" -o ! -d $LOFARROOT ]; then else # Add the path to the standard paths. if [ "$PATH" = "" ]; then - PATH=$LOFARROOT/bin:$LOFARROOT/sbin + PATH=$LOFARROOT/sbin:$LOFARROOT/bin else - PATH=$LOFARROOT/bin:$LOFARROOT/sbin:$PATH + PATH=$LOFARROOT/sbin:$LOFARROOT/bin:$PATH fi export PATH if [ "$LD_LIBRARY_PATH" = "" ]; then -- GitLab From b1fb80a260382bebb79be97b1d257e5a4b0c41a6 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Thu, 1 Sep 2016 14:33:43 +0000 Subject: [PATCH 745/933] Task #9785: TEC only solve in DPPP --- CEP/DP3/DPPP/include/DPPP/phasefitter.h | 1 + CEP/DP3/DPPP/src/GainCal.cc | 34 +++++++++++++++---------- CEP/DP3/DPPP/src/phasefitter.cc | 13 ++++++++++ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/CEP/DP3/DPPP/include/DPPP/phasefitter.h b/CEP/DP3/DPPP/include/DPPP/phasefitter.h index 82bff267fc1..f09a2db8eb6 100644 --- a/CEP/DP3/DPPP/include/DPPP/phasefitter.h +++ b/CEP/DP3/DPPP/include/DPPP/phasefitter.h @@ -285,6 +285,7 @@ class PhaseFitter void bruteForceSearchTEC2Model(double& lowerAlpha, double& upperAlpha, double& beta) const; double ternarySearchTEC2ModelAlpha(double startAlpha, double endAlpha, double& beta) const; void fillDataWithTEC2Model(double alpha, double beta); + void fillDataWithTEC1Model(double alpha); void bruteForceSearchTEC1Model(double& lowerAlpha, double& upperAlpha) const; double ternarySearchTEC1ModelAlpha(double startAlpha, double endAlpha) const; diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index e863212c633..8bfb2148014 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -124,7 +124,7 @@ namespace LOFAR { ASSERT(itsMode=="diagonal" || itsMode=="phaseonly" || itsMode=="fulljones" || itsMode=="scalarphase" || itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || - itsMode=="tec"); + itsMode=="tecandphase" || itsMode=="tec"); } GainCal::~GainCal() @@ -172,7 +172,7 @@ namespace LOFAR { itsSols.reserve(itsTimeSlotsPerParmUpdate); // Initialize phase fitters, set their frequency data - if (itsMode=="tec") { + if (itsMode=="tecandphase" || itsMode=="tec") { itsTECSols.reserve(itsTimeSlotsPerParmUpdate); itsPhaseFitters.reserve(itsNFreqCells); // TODO: could be numthreads instead @@ -260,7 +260,7 @@ namespace LOFAR { FlagCounter::showPerc1 (os, itsTimerSolve.getElapsed(), totaltime); os << " of it spent in estimating gains and computing residuals" << endl; - if (itsMode == "tec") { + if (itsMode == "tec" || itsMode == "tecandphase") { os << " "; FlagCounter::showPerc1 (os, itsTimerPhaseFit.getElapsed(), totaltime); os << " of it spent in fitting phases" << endl; @@ -451,7 +451,7 @@ namespace LOFAR { continue; } - if (itsMode=="tec") { + if (itsMode=="tec" || itsMode=="tecandphase") { iS[ch/itsNChan].incrementWeight(weight[bl*nCr*nCh+ch*nCr]); } @@ -540,7 +540,7 @@ namespace LOFAR { uint iter=0; - casa::Matrix<double> tecsol(2, info().antennaNames().size(), 0); + casa::Matrix<double> tecsol(itsMode=="tecandphase"?2:1, info().antennaNames().size(), 0); std::vector<StefCal::Status> converged(itsNFreqCells,StefCal::NOTCONVERGED); for (;iter<itsMaxIter;++iter) { @@ -556,7 +556,7 @@ namespace LOFAR { } } - if (itsMode=="tec") { + if (itsMode=="tec" || itsMode=="tecandphase") { itsTimerSolve.stop(); itsTimerPhaseFit.start(); casa::Matrix<casa::DComplex> sols_f(itsNFreqCells, info().antennaNames().size()); @@ -624,7 +624,11 @@ namespace LOFAR { if (numpoints>1) { // TODO: limit should be higher //cout<<"tecsol(0,"<<st<<")="<<tecsol(0,st)<<", tecsol(1,"<<st<<")="<<tecsol(1,st)<<endl; - cost=itsPhaseFitters[st]->FitDataToTEC2Model(tecsol(0, st), tecsol(1,st)); + if (itsMode=="tecandphase") { + cost=itsPhaseFitters[st]->FitDataToTEC2Model(tecsol(0, st), tecsol(1,st)); + } else { // itsMode=="tec" + cost=itsPhaseFitters[st]->FitDataToTEC1Model(tecsol(0, st)); + } // Update solution in stefcal for (uint freqCell=0; freqCell<itsNFreqCells; ++freqCell) { ASSERT(isFinite(phases[freqCell])); @@ -632,7 +636,9 @@ namespace LOFAR { } } else { tecsol(0, st) = 0; //std::numeric_limits<double>::quiet_NaN(); - tecsol(1, st) = 0; //std::numeric_limits<double>::quiet_NaN(); + if (itsMode=="tecandphase") { + tecsol(1, st) = 0; //std::numeric_limits<double>::quiet_NaN(); + } } if (st==34 && itsDebugLevel>0) { @@ -706,7 +712,7 @@ namespace LOFAR { } } itsSols.push_back(sol); - if (itsMode=="tec") { + if (itsMode=="tec" || itsMode=="tecandphase") { itsTECSols.push_back(tecsol); } @@ -793,7 +799,7 @@ namespace LOFAR { name=string("CommonScalarPhase:"); } else if (itsMode=="scalaramplitude") { name=string("CommonScalarAmplitude:"); - } else if (itsMode=="tec") { + } else if (itsMode=="tec" || itsMode=="tecandphase") { name=string("TEC:"); } else { @@ -881,9 +887,9 @@ namespace LOFAR { itsMode=="tec") && pol>0) { continue; } - int realimmax; // For tec, this functions as dummy between tec and commonscalarphase + int realimmax; // For tecandphase, this functions as dummy between tec and commonscalarphase if (itsMode=="phaseonly" || itsMode=="scalarphase" || - itsMode=="amplitudeonly" || itsMode=="scalaramplitude") { + itsMode=="amplitudeonly" || itsMode=="scalaramplitude" || itsMode=="tec") { realimmax=1; } else { realimmax=2; @@ -901,7 +907,7 @@ namespace LOFAR { name=name+strri[realim]; } } - if (itsMode=="tec") { + if (itsMode=="tecandphase" || itsMode=="tec") { if (realim==0) { name="TEC:"; } else { @@ -930,7 +936,7 @@ namespace LOFAR { values(freqCell, ts) = arg(itsSols[ts](pol/3,st,freqCell)); } else if (itsMode=="scalaramplitude" || itsMode=="amplitudeonly") { values(freqCell, ts) = abs(itsSols[ts](pol/3,st,freqCell)); - } else if (itsMode=="tec") { + } else if (itsMode=="tec" || itsMode=="tecandphase") { if (realim==0) { values(freqCell, ts) = itsTECSols[ts](realim,st) / 8.44797245e9; } else { diff --git a/CEP/DP3/DPPP/src/phasefitter.cc b/CEP/DP3/DPPP/src/phasefitter.cc index 07c171dd743..2391ada1996 100644 --- a/CEP/DP3/DPPP/src/phasefitter.cc +++ b/CEP/DP3/DPPP/src/phasefitter.cc @@ -114,6 +114,12 @@ void PhaseFitter::fillDataWithTEC2Model(double alpha, double beta) _phases[ch] = TEC2ModelFunc(_frequencies[ch], alpha, beta); } +void PhaseFitter::fillDataWithTEC1Model(double alpha) +{ + for(size_t ch=0; ch!=Size(); ++ch) + _phases[ch] = TEC1ModelFuncWrapped(_frequencies[ch], alpha); +} + void PhaseFitter::FitTEC2ModelParameters(double& alpha, double& beta) const { double lowerAlpha = -40000.0e6, upperAlpha = 40000.0e6; @@ -130,6 +136,13 @@ double PhaseFitter::FitDataToTEC2Model(double& alpha, double& beta) return TEC2ModelCost(alpha, beta); } +double PhaseFitter::FitDataToTEC1Model(double& alpha) +{ + FitTEC1ModelParameters(alpha); + fillDataWithTEC1Model(alpha); + return TEC1ModelCost(alpha); +} + void PhaseFitter::FitTEC1ModelParameters(double& alpha) const { double lowerAlpha = -40000.0e6, upperAlpha = 40000.0e6; -- GitLab From c78fb5f7533da2ce536c9f7f6675035b8b779b31 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 1 Sep 2016 15:01:54 +0000 Subject: [PATCH 746/933] Task #9607: reschedule aborted/finished cep4 pipelines --- .../lib/static/app/controllers/gridcontroller.js | 15 +++++++++++++++ .../angular-gantt-contextmenu-plugin.js | 15 +++++++++++++-- .../ResourceAssignmentEditor/lib/webservice.py | 11 ++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 5d3a03db921..0840d2f48fd 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -586,6 +586,21 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } + var finished_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'finished' || t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; }); + + if(finished_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Reschedule finished/aborted CEP4 pipelines</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + for(var pl of finished_selected_cep4_pipelines) { + var newTask = { id: pl.id, status: 'prescheduled' }; + dataService.putTask(newTask); + } + }); + } + var closeContextMenu = function(cme) { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index 864d971d400..a7b4b841d95 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -162,9 +162,20 @@ }); } + var finished_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'finished' || t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; }); - - + if(finished_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Reschedule finished/aborted CEP4 pipelines</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + for(var pl of finished_selected_cep4_pipelines) { + var newTask = { id: pl.id, status: 'prescheduled' }; + dataService.putTask(newTask); + } + }); + } var closeContextMenu = function() { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 4d440585730..88ada92f35a 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -316,12 +316,21 @@ def putTask(task_id): if not task: abort(404, "unknown task %s" % str(updatedTask)) + # remove output and intermediate data for restarting CEP4 pipelines + if task.get('cluster') == 'CEP4' and task['type'] == 'pipeline' and updatedTask['status'] == 'prescheduled' and task['status'] in ['finished', 'aborted', 'error']: + path_result = curpc.getPathForOTDBId(task['otdb_id']) + if path_result['found']: + result = curpc.removeTaskData(task['otdb_id']) + + if not result['deleted']: + abort(500, result['message']) + otdbrpc.taskSetStatus(task['otdb_id'], updatedTask['status']) #rarpc.updateTaskAndResourceClaims(task_id, #starttime=updatedTask.get('starttime', None), #endtime=updatedTask.get('endtime', None)) - + return "", 204 except KeyError: abort(404) -- GitLab From 820fd43f19d4fec253e82c7f7932e5b3dea5319a Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 1 Sep 2016 17:51:57 +0000 Subject: [PATCH 747/933] Task #9607: delete data from previous cep4 pipeline run upon (re)scheduling --- .../ResourceAssigner/lib/assignment.py | 20 +++++++++++++++++++ .../ResourceAssigner/lib/raservice.py | 6 ++++++ .../lib/webservice.py | 9 --------- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 206e98a41dc..ce3671fb9a6 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -53,6 +53,10 @@ from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTI from lofar.mom.momqueryservice.momqueryrpc import MoMQueryRPC from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME +from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC +from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME as DEFAULT_CLEANUP_BUSNAME +from lofar.sas.datamanagement.cleanup.config import DEFAULT_SERVICENAME as DEFAULT_CLEANUP_SERVICENAME + logger = logging.getLogger(__name__) class ResourceAssigner(): @@ -63,6 +67,8 @@ class ResourceAssigner(): re_servicename=RE_SERVICENAME, otdb_busname=DEFAULT_OTDB_SERVICE_BUSNAME, otdb_servicename=DEFAULT_OTDB_SERVICENAME, + cleanup_busname=DEFAULT_CLEANUP_BUSNAME, + cleanup_servicename=DEFAULT_CLEANUP_SERVICENAME, ra_notification_busname=DEFAULT_RA_NOTIFICATION_BUSNAME, ra_notification_prefix=DEFAULT_RA_NOTIFICATION_PREFIX, mom_busname=DEFAULT_MOMQUERY_BUSNAME, @@ -80,6 +86,7 @@ class ResourceAssigner(): self.rerpc = RPC(re_servicename, busname=re_busname, broker=broker, ForwardExceptions=True, timeout=180) self.otdbrpc = OTDBRPC(busname=otdb_busname, servicename=otdb_servicename, broker=broker, timeout=180) ## , ForwardExceptions=True hardcoded in RPCWrapper right now self.momrpc = MoMQueryRPC(servicename=mom_servicename, busname=mom_busname, broker=broker, timeout=180) + self.curpc = CleanupRPC(busname=cleanup_busname, servicename=cleanup_servicename, broker=broker) self.ra_notification_bus = ToBus(address=ra_notification_busname, broker=broker) self.ra_notification_prefix = ra_notification_prefix @@ -98,6 +105,7 @@ class ResourceAssigner(): self.rerpc.open() self.otdbrpc.open() self.momrpc.open() + self.curpc.open() self.ra_notification_bus.open() def close(self): @@ -106,6 +114,7 @@ class ResourceAssigner(): self.rerpc.close() self.otdbrpc.close() self.momrpc.close() + self.curpc.close() self.ra_notification_bus.close() def doAssignment(self, specification_tree): @@ -230,6 +239,17 @@ class ResourceAssigner(): else: logger.info('doAssignment: all claims for task %s were succesfully claimed. Setting claim statuses to allocated' % (taskId,)) self.radbrpc.updateTaskAndResourceClaims(taskId, claim_status='allocated') + + # remove any output and/or intermediate data for restarting CEP4 pipelines + if task['type'] == 'pipeline': + path_result = self.curpc.getPathForOTDBId(task['otdb_id']) + if path_result['found']: + logger.info("removing data on disk from previous run for otdb_id %s", otdb_id) + result = self.curpc.removeTaskData(task['otdb_id']) + + if not result['deleted']: + logger.warning("could not remove all data on disk from previous run for otdb_id %s: %s", otdb_id, result['message']) + # send notification that the task was scheduled, # another service sets the parset spec in otdb, and updated otdb task status to scheduled, which is then synced to radb self._sendNotification(task, 'scheduled') diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py index 0fd5d28fe2e..10fa2a01a1a 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/raservice.py @@ -90,6 +90,8 @@ def main(): from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_BUSNAME from lofar.sas.resourceassignment.resourceassigner.config import DEFAULT_RA_NOTIFICATION_PREFIX from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME + from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME as DEFAULT_CLEANUP_BUSNAME + from lofar.sas.datamanagement.cleanup.config import DEFAULT_SERVICENAME as DEFAULT_CLEANUP_SERVICENAME # Check the invocation arguments parser = OptionParser("%prog [options]", @@ -117,6 +119,8 @@ def main(): help="Name of the resource estimator service. [default: %default]") parser.add_option("--otdb_busname", dest="otdb_busname", type="string", default=DEFAULT_OTDB_SERVICE_BUSNAME, help="Name of the bus on which the OTDB service listens, default: %default") parser.add_option("--otdb_servicename", dest="otdb_servicename", type="string", default=DEFAULT_OTDB_SERVICENAME, help="Name of the OTDB service, default: %default") + parser.add_option('--cleanup_busname', dest='cleanup_busname', type='string', default=DEFAULT_CLEANUP_BUSNAME, help='Name of the bus exchange on the qpid broker on which the cleanupservice listens, default: %default') + parser.add_option('--cleanup_servicename', dest='cleanup_servicename', type='string', default=DEFAULT_CLEANUP_SERVICENAME, help='Name of the cleanupservice, default: %default') parser.add_option("--ra_notification_busname", dest="ra_notification_busname", type="string", default=DEFAULT_RA_NOTIFICATION_BUSNAME, help="Name of the notification bus on which the resourceassigner publishes its notifications. [default: %default]") @@ -142,6 +146,8 @@ def main(): re_servicename=options.re_servicename, otdb_busname=options.otdb_busname, otdb_servicename=options.otdb_servicename, + cleanup_busname=options.cleanup_busname, + cleanup_servicename=options.cleanup_servicename, ra_notification_busname=options.ra_notification_busname, ra_notification_prefix=options.ra_notification_prefix, mom_busname=options.mom_query_busname, diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 88ada92f35a..01ea4db44f7 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -316,15 +316,6 @@ def putTask(task_id): if not task: abort(404, "unknown task %s" % str(updatedTask)) - # remove output and intermediate data for restarting CEP4 pipelines - if task.get('cluster') == 'CEP4' and task['type'] == 'pipeline' and updatedTask['status'] == 'prescheduled' and task['status'] in ['finished', 'aborted', 'error']: - path_result = curpc.getPathForOTDBId(task['otdb_id']) - if path_result['found']: - result = curpc.removeTaskData(task['otdb_id']) - - if not result['deleted']: - abort(500, result['message']) - otdbrpc.taskSetStatus(task['otdb_id'], updatedTask['status']) #rarpc.updateTaskAndResourceClaims(task_id, -- GitLab From 6e9ed5132e03c2f6d01815c2a22088e1901ca323 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 1 Sep 2016 19:12:20 +0000 Subject: [PATCH 748/933] Task #9522: Added variants.head01 --- .gitattributes | 1 + CMake/variants/variants.head01 | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 CMake/variants/variants.head01 diff --git a/.gitattributes b/.gitattributes index 1c572c907c2..5284d66fa33 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2343,6 +2343,7 @@ CMake/variants/variants.dragproc -text CMake/variants/variants.fs0 -text CMake/variants/variants.gpu01 -text CMake/variants/variants.gpu1 -text +CMake/variants/variants.head01 -text CMake/variants/variants.lcs157 -text CMake/variants/variants.lexar -text CMake/variants/variants.lexar001 -text diff --git a/CMake/variants/variants.head01 b/CMake/variants/variants.head01 new file mode 100644 index 00000000000..806bc3aaf79 --- /dev/null +++ b/CMake/variants/variants.head01 @@ -0,0 +1,3 @@ +set(CASACORE_ROOT_DIR /opt/casacore-latest) +set(DAL_ROOT_DIR /opt/DAL) + -- GitLab From 050513f33e476fadb560f8550497bb7d7c3700f7 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 1 Sep 2016 19:24:08 +0000 Subject: [PATCH 749/933] Task #9522: Bail if no numpy installed --- RTCP/Cobalt/CoInterface/test/tcmpfloat.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/RTCP/Cobalt/CoInterface/test/tcmpfloat.py b/RTCP/Cobalt/CoInterface/test/tcmpfloat.py index 778e557d251..0a5c446a281 100755 --- a/RTCP/Cobalt/CoInterface/test/tcmpfloat.py +++ b/RTCP/Cobalt/CoInterface/test/tcmpfloat.py @@ -19,7 +19,12 @@ # # $Id$ -import numpy as np +try: + import numpy as np +except ImportError: + import sys + sys.exit(3) + def main(): # Generate all binary input files -- GitLab From 5f9de0b0b7bc79cf2e2f03f77ca8e03366d3945a Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 1 Sep 2016 19:31:10 +0000 Subject: [PATCH 750/933] Task #9522: Bail if no numpy installed --- RTCP/Cobalt/CoInterface/test/tcmpfloat.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh b/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh index 271e92c7bf1..4219d1909bd 100755 --- a/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh +++ b/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh @@ -5,7 +5,10 @@ # generate binary input files through tcmpfloat.py ./runctest.sh tcmpfloat -if [ $? -ne 0 ]; then echo "Failed to generate test input files"; exit 1; fi +RESULT=$? + +if [ $RESULT -eq 3 ]; then exit 3; fi +if [ $RESULT -ne 0 ]; then echo "Failed to generate test input files"; exit 1; fi status=0 -- GitLab From e075aaa40594ec22f1c309b895b1794646efff3f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 1 Sep 2016 19:37:55 +0000 Subject: [PATCH 751/933] Task #9522: Bail if no numpy installed --- RTCP/Cobalt/CoInterface/test/tcmpfloat.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh b/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh index 4219d1909bd..d629ee76ca2 100755 --- a/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh +++ b/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh @@ -7,7 +7,7 @@ ./runctest.sh tcmpfloat RESULT=$? -if [ $RESULT -eq 3 ]; then exit 3; fi +if [ $RESULT -eq 77 ]; then exit 77; fi # skip test if [ $RESULT -ne 0 ]; then echo "Failed to generate test input files"; exit 1; fi -- GitLab From 0b62eb8ea28f3d4697765f6ac4529f1b2868e1ba Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 1 Sep 2016 19:48:59 +0000 Subject: [PATCH 752/933] Task #9522: Bail if no numpy installed --- RTCP/Cobalt/CoInterface/test/tcmpfloat.py | 2 +- RTCP/Cobalt/CoInterface/test/tcmpfloat.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RTCP/Cobalt/CoInterface/test/tcmpfloat.py b/RTCP/Cobalt/CoInterface/test/tcmpfloat.py index 0a5c446a281..f27c4f4a102 100755 --- a/RTCP/Cobalt/CoInterface/test/tcmpfloat.py +++ b/RTCP/Cobalt/CoInterface/test/tcmpfloat.py @@ -23,7 +23,7 @@ try: import numpy as np except ImportError: import sys - sys.exit(3) + sys.exit(42) # signal tcmpfloat.sh to skip this test def main(): diff --git a/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh b/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh index d629ee76ca2..dfe09a4699f 100755 --- a/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh +++ b/RTCP/Cobalt/CoInterface/test/tcmpfloat.sh @@ -7,7 +7,7 @@ ./runctest.sh tcmpfloat RESULT=$? -if [ $RESULT -eq 77 ]; then exit 77; fi # skip test +if [ $RESULT -eq 42 ]; then echo "UNTESTED"; exit 0; fi # skip test if [ $RESULT -ne 0 ]; then echo "Failed to generate test input files"; exit 1; fi -- GitLab From 08ca7f05274cdeefe2eb0204b4c5e7be86ebd1f2 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 1 Sep 2016 20:08:53 +0000 Subject: [PATCH 753/933] Task #9522: Run outputproc from /opt/outputproc-$BRANCH/ on CEP4 instead of through Docker, to fix data loss that occurred if outputProc runs in docker, and the docker daemon is busy --- .../GPUProc/src/scripts/cobalt_functions.sh | 6 ++++- .../GPUProc/src/scripts/runObservation.sh | 23 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh b/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh index c9f3c01b7a0..8dd6353f0ab 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh @@ -63,7 +63,9 @@ function read_cluster_model { #SLURM=true SLURM=false # Don't use SLURM for now, let's get it working without it first GLOBALFS=true - DOCKER=true + DOCKER=false # disabled as outputproc is too slow on docker 1.9.1 (#9522) + + CEP4=true ;; *) HEADNODE=lhn001.cep2.lofar @@ -72,6 +74,8 @@ function read_cluster_model { SLURM=false GLOBALFS=false DOCKER=false + + CEP4=false ;; esac diff --git a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh index dd0c8b1fc6c..92bb651a472 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh @@ -256,8 +256,19 @@ if $SLURM; then echo "ERROR: SLURM resource allocation is not supported" exit 1 + # Allocate resources + # TODO: Start outputProc here + ssh $HEADNODE srun -N $NRCOMPUTENODES -c 1 --job-name=$OBSID bash -c 'while sleep 1; do :; done' & + + # Wait for allocation + while [ "`ssh $HEADNODE sacct --starttime=now --noheader --parsable2 --format=state --name=$OBSID | tail -n 1`" != "RUNNING" ]; do sleep 1; done + + # Obtain node list + NODE_LIST="`ssh $HEADNODE sacct --starttime=now --noheader --parsable2 --format=nodelist --name=$OBSID | tail -n 1`" + # Expand node list into something usable - NODE_LIST="`ssh $HEADNODE scontrol show hostnames $SLURM_JOB_NODELIST`" + # TODO: move ".cep4" to cluster model + NODE_LIST="`ssh $HEADNODE scontrol show hostnames $NODE_LISTi | awk '{ print $1 ".cep4"; }'`" else # Derive host list from parset NODE_LIST=$(getOutputProcHosts $PARSET) @@ -308,6 +319,11 @@ PID_LIST_FILE="$LOFARROOT/var/run/outputProc-$OBSERVATIONID.pids" function clean_up { EXIT_STATE=$1 PID_LIST=$2 + + if $SLURM; then + echo "[children] Cancelling SLURM allocation" + ssh $HEADNODE scancel --jobname=$OBSID + fi echo "[children] Sending SIGTERM" # THe kill statements might be called with an empty argument. This will @@ -351,6 +367,11 @@ if $DOCKER; then OUTPUTPROC_CMDLINE="docker-run-slurm.sh --rm --cpu-shares=24576 --cap-add=sys_nice --cap-add=sys_admin -u `id -u $SSH_USER_NAME` --net=host -v $GLOBALFS_DIR:$GLOBALFS_DIR lofar-outputproc:$TAG bash -c \"sudo echo 950000 > /sys/fs/cgroup/cpu/cpu.rt_runtime_us; $OUTPUTPROC_CMDLINE\"" fi +if $CEP4; then + TAG="`echo '${LOFAR_TAG}' | docker-template`" + OUTPUTPROC_CMDLINE="source /opt/outputproc-$TAG/lofarinit.sh; $OUTPUTPROC_CMDLINE" +fi + echo "[outputProc] command line = $OUTPUTPROC_CMDLINE" if ! $DUMMY_RUN; then -- GitLab From 376058eb886dc005d768ba212b47cee1dd108fee Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 2 Sep 2016 07:58:16 +0000 Subject: [PATCH 754/933] Task #9607: do propagate prescheduled status to radb, because the resourceassigner responds not to the otdb event, but to the rataskspecified event --- .../OTDBtoRATaskStatusPropagator/propagator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index 07af4960e49..b326523f592 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -93,7 +93,7 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): self._update_radb_task_status(treeId, 'conflict') def onObservationPrescheduled(self, treeId, modificationTime): - logger.info("not propagating prescheduled status for otdb_id %s to radb because the resource assigner takes care of this" % (treeId)) + self._update_radb_task_status(treeId, 'prescheduled') def onObservationScheduled(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'scheduled') -- GitLab From 41089695fa1cb8ad95ec1266539f50607288ed5f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 2 Sep 2016 07:58:56 +0000 Subject: [PATCH 755/933] Task #9607: always take 'now' into account when determining start/send time --- .../ResourceAssigner/lib/assignment.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index ce3671fb9a6..62c53beb542 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -141,21 +141,20 @@ class ResourceAssigner(): endTime = datetime.strptime(mainParset.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') except ValueError: logger.warning('cannot parse for start/end time from specification for otdb_id=%s. searching for sane defaults...', (otdb_id, )) + + startTime = datetime.utcnow() + timedelta(minutes=1) + maxPredecessorEndTime = self.getMaxPredecessorEndTime(specification_tree) + if maxPredecessorEndTime and maxPredecessorEndTime > startTime: + startTime = maxPredecessorEndTime + timedelta(minutes=1) taskDuration = mainParset.getInt('Observation.Scheduler.taskDuration', -1) taskDuration = timedelta(seconds=taskDuration) if taskDuration > 0 else timedelta(hours=1) - if maxPredecessorEndTime: - startTime = maxPredecessorEndTime + timedelta(minutes=1) - endTime = startTime + taskDuration - logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s based on maxPredecessorEndTime (%s)', - startTime, endTime, otdb_id, maxPredecessorEndTime) - else: - startTime = datetime.utcnow() + timedelta(hours=1) + timedelta(minutes=1) - endTime = startTime + taskDuration - logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s one hour from now', - startTime, endTime, otdb_id) + endTime = startTime + taskDuration + + logger.warning('Applying sane defaults (%s, %s) for start/end time from specification for otdb_id=%s', + startTime, endTime, otdb_id) try: logger.info('uploading auto-generated start/end time (%s, %s) to otdb for otdb_id=%s', startTime, endTime, otdb_id) -- GitLab From f50ed3dec8ee781083bbc2054bac82ffb3946e66 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 2 Sep 2016 08:09:46 +0000 Subject: [PATCH 756/933] Task #9522: Abstracted CEP4-specific config --- RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh | 4 ++-- RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh b/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh index 8dd6353f0ab..e59a910c4dd 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/cobalt_functions.sh @@ -65,7 +65,7 @@ function read_cluster_model { GLOBALFS=true DOCKER=false # disabled as outputproc is too slow on docker 1.9.1 (#9522) - CEP4=true + OUTPUTPROC_ROOT="`echo '/opt/outputproc-${LOFAR_TAG}' | docker-template`" ;; *) HEADNODE=lhn001.cep2.lofar @@ -75,7 +75,7 @@ function read_cluster_model { GLOBALFS=false DOCKER=false - CEP4=false + OUTPUTPROC_ROOT="/data/home/lofarsys/production/lofar_cobalt" ;; esac diff --git a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh index 92bb651a472..aadd9ffbac0 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/runObservation.sh @@ -355,8 +355,7 @@ echo "[outputProc] pid file = $PID_LIST_FILE" touch $PID_LIST_FILE # Construct full command line for outputProc -OUTPUTPROC_VARS="export QUEUE_PREFIX=$QUEUE_PREFIX LOFARENV=$LOFARENV;" # Variables to forward to outputProc -OUTPUTPROC_CMDLINE="$OUTPUTPROC_VARS $OUTPUT_PROC_EXECUTABLE $OBSERVATIONID" +OUTPUTPROC_CMDLINE="source $OUTPUTPROC_ROOT/lofarinit.sh; export QUEUE_PREFIX=$QUEUE_PREFIX LOFARENV=$LOFARENV; $OUTPUT_PROC_EXECUTABLE $OBSERVATIONID" # Wrap command line with Docker if required if $DOCKER; then @@ -367,11 +366,6 @@ if $DOCKER; then OUTPUTPROC_CMDLINE="docker-run-slurm.sh --rm --cpu-shares=24576 --cap-add=sys_nice --cap-add=sys_admin -u `id -u $SSH_USER_NAME` --net=host -v $GLOBALFS_DIR:$GLOBALFS_DIR lofar-outputproc:$TAG bash -c \"sudo echo 950000 > /sys/fs/cgroup/cpu/cpu.rt_runtime_us; $OUTPUTPROC_CMDLINE\"" fi -if $CEP4; then - TAG="`echo '${LOFAR_TAG}' | docker-template`" - OUTPUTPROC_CMDLINE="source /opt/outputproc-$TAG/lofarinit.sh; $OUTPUTPROC_CMDLINE" -fi - echo "[outputProc] command line = $OUTPUTPROC_CMDLINE" if ! $DUMMY_RUN; then -- GitLab From 93a3f5bfca624956b7cc9fb10f72a2611700e700 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 2 Sep 2016 09:34:07 +0000 Subject: [PATCH 757/933] Task #9607: sort on type first, then on starttime. added completing cep4 pipelines to list which can be aborted --- .../lib/static/app/controllers/gridcontroller.js | 7 ++++--- .../app/gantt-plugins/angular-gantt-contextmenu-plugin.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 0840d2f48fd..6ec2e9e35cb 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -33,7 +33,7 @@ $scope.columns = [ enableCellEdit: false, enableCellEditOnFocus: false, cellTemplate:'<div style=\'text-align:left\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>', - sort: { direction: uiGridConstants.ASC } + sort: { direction: uiGridConstants.ASC, priority: 2 } }, { field: 'endtime', displayName: 'End', @@ -74,7 +74,8 @@ $scope.columns = [ condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, selectOptions: [] - } + }, + sort: { direction: uiGridConstants.ASC, priority: 1 } }, { field: 'mom_object_group_id', displayName: 'Group', @@ -571,7 +572,7 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } - var active_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return t.status == 'active' && t.type == 'pipeline'; }); + var active_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'active' || t.status == 'completing') && t.type == 'pipeline'; }); if(active_selected_cep4_pipelines.length > 0) { var liContent = '<li><a href="#">Abort active CEP4 pipelines</a></li>' diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index a7b4b841d95..1e05df07db0 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -147,7 +147,7 @@ }); } - var active_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return t.status == 'active' && t.type == 'pipeline'; }); + var active_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'active' || t.status == 'completing') && t.type == 'pipeline'; }); if(active_selected_cep4_pipelines.length > 0) { var liContent = '<li><a href="#">Abort active CEP4 pipelines</a></li>' -- GitLab From 63270098ab71d08c16d65b91a3f45f9f10f1b1ae Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 2 Sep 2016 10:13:54 +0000 Subject: [PATCH 758/933] Task #8475: Temporarily add slashes for spaces in pulp command-line parameters on CEP4 --- CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py index 988d03222f7..87dc66fad96 100755 --- a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py @@ -139,6 +139,12 @@ class pulsar_pipeline(control): self.pulsar_parms = self.parset.makeSubset(self.parset.fullModuleName('Pulsar') + '.') pulsar_parset = os.path.join(parset_dir, "Pulsar.parset") + + if self.globalfs: + # patch for Pulp in case of DOCKER + for (k in self.pulsar_parms.keys() if k.endswith("_extra_opts")): + self.pulsar_parms.replace(k, self.pulsar_parms[k].getString().replace(" ","\\\\ ")) + self.pulsar_parms.writeFile(pulsar_parset) self.logger.debug("Processing: %s" % -- GitLab From 6ae79d403384d484dc22702c5a0bd1f3ef142780 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 2 Sep 2016 12:20:52 +0000 Subject: [PATCH 759/933] Task #8475: Temporarily add slashes for spaces in pulp command-line parameters on CEP4 --- CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py index 87dc66fad96..ddf51a5aa7c 100755 --- a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py @@ -142,7 +142,7 @@ class pulsar_pipeline(control): if self.globalfs: # patch for Pulp in case of DOCKER - for (k in self.pulsar_parms.keys() if k.endswith("_extra_opts")): + for k in [x for x in self.pulsar_parms.keys() if x.endswith("_extra_opts")]: self.pulsar_parms.replace(k, self.pulsar_parms[k].getString().replace(" ","\\\\ ")) self.pulsar_parms.writeFile(pulsar_parset) -- GitLab From dfbe32245575bad05b9324b6807d759b0d627dc9 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 2 Sep 2016 12:35:36 +0000 Subject: [PATCH 760/933] Task #9785: tec only solver wrote solutions four times, add tests --- CEP/DP3/DPPP/src/GainCal.cc | 2 +- CEP/DP3/DPPP/test/tGainCal.run | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index 8bfb2148014..8946c807991 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -884,7 +884,7 @@ namespace LOFAR { continue; } if ((itsMode=="scalarphase" || itsMode=="scalaramplitude" || - itsMode=="tec") && pol>0) { + itsMode=="tec" || itsMode=="tecandphase") && pol>0) { continue; } int realimmax; // For tecandphase, this functions as dummy between tec and commonscalarphase diff --git a/CEP/DP3/DPPP/test/tGainCal.run b/CEP/DP3/DPPP/test/tGainCal.run index 2309449fe93..c4137b09945 100755 --- a/CEP/DP3/DPPP/test/tGainCal.run +++ b/CEP/DP3/DPPP/test/tGainCal.run @@ -13,6 +13,8 @@ if test ! -f ${srcdir}/tNDPPP-generic.in_MS.tgz; then exit 3 # untested fi +set -e # Stop on any error + rm -rf tGainCal_tmp mkdir -p tGainCal_tmp # Unpack the MS and other files and do the DPPP run. @@ -41,7 +43,7 @@ diff taql.out taql.ref || exit 1 echo; echo "Test caltype=diagonal with timeslotsperparmupdate=4"; echo ../../src/NDPPP msin=tNDPPP-generic.MS msout= steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-diagonal-tpp gaincal.usebeammodel=false gaincal.caltype=diagonal gaincal.solint=4 gaincal.timeslotsperparmupdate=1 gaincal.propagatesolutions=false ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_DIAGONAL_TPP steps=[applycal] applycal.parmdb=tNDPPP-generic.MS/inst-diagonal-tpp -$taqlexe 'select from tNDPP-generic.MS where not all(near(DPPP_DIAGONAL, DPPP_DIAGONAL_TPP))' +$taqlexe 'select from tNDPPP-generic.MS where not all(near(DPPP_DIAGONAL, DPPP_DIAGONAL_TPP))' echo "Comparing the difference between applying with timeslotsperparmupdate = default and timeslotsperparmupdate=1" $taqlexe 'select from (select gsumsqr(sumsqr(abs(iif(t1.FLAG,0,t1.DPPP_DIAGONAL-t1.MODEL_DATA)))) as dpppres, gsumsqr(sumsqr(abs(iif(FLAG,0,t2.BBS_DIAGONAL-t1.MODEL_DATA)))) as bbsres from tNDPPP-generic.MS t1, tGainCal.tab t2) where dpppres>bbsres*1.02' > taql.out @@ -88,3 +90,21 @@ echo "Comparing the solutions from gaincal + applycal with gaincal directly" $taqlexe 'select from tNDPPP-generic.MS where not(all(DPPP_DIAGONAL_NCHAN_7_GAINCAL ~= DPPP_DIAGONAL_NCHAN_7))' > taql.out diff taql.out taql.ref || exit 1 +echo; echo "Test caltype=tec"; echo +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_TEC steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-tec gaincal.caltype=tec gaincal.solint=2 +# For now, only testing that the right parameter names are in the output +echo " select result of 1 rows" > taql1.ref +$taqlexe 'select from tNDPPP-generic.MS/inst-tec where (select NAME from ::NAMES)[NAMEID]=="TEC:CS001HBA0"' > taql.out +diff taql.out taql1.ref || exit 1 +$taqlexe 'select from tNDPPP-generic.MS/inst-tec where (select NAME from ::NAMES)[NAMEID]=="CommonScalarPhase:CS001HBA0"' > taql.out +diff taql.out taql.ref || exit 1 + +echo; echo "Test caltype=tecandphase"; echo +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_TEC steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-tec gaincal.caltype=tecandphase gaincal.solint=2 +# For now, only testing that the right parameter names are in the output +echo " select result of 1 rows" > taql1.ref +$taqlexe 'select from tNDPPP-generic.MS/inst-tec where (select NAME from ::NAMES)[NAMEID]=="TEC:CS001HBA0"' > taql.out +diff taql.out taql1.ref || exit 1 +$taqlexe 'select from tNDPPP-generic.MS/inst-tec where (select NAME from ::NAMES)[NAMEID]=="CommonScalarPhase:CS001HBA0"' > taql.out +diff taql.out taql1.ref || exit 1 + -- GitLab From f0d3d83ae02cd6dcc9bc0cb2e461d34c7d282e2b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 2 Sep 2016 14:28:37 +0000 Subject: [PATCH 761/933] Task #9607: apply sane start time --- .../ResourceAssigner/lib/assignment.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 62c53beb542..f78e06beb3e 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -136,12 +136,7 @@ class ResourceAssigner(): clusterName = 'CEP4' if clusterIsCEP4 else 'CEP2' if clusterIsCEP4: - try: - startTime = datetime.strptime(mainParset.getString('Observation.startTime'), '%Y-%m-%d %H:%M:%S') - endTime = datetime.strptime(mainParset.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') - except ValueError: - logger.warning('cannot parse for start/end time from specification for otdb_id=%s. searching for sane defaults...', (otdb_id, )) - + def applySaneStartEndTime(): startTime = datetime.utcnow() + timedelta(minutes=1) maxPredecessorEndTime = self.getMaxPredecessorEndTime(specification_tree) @@ -163,6 +158,17 @@ class ResourceAssigner(): except Exception as e: logger.error(e) + return startTime, endTime + + try: + startTime = datetime.strptime(mainParset.getString('Observation.startTime'), '%Y-%m-%d %H:%M:%S') + endTime = datetime.strptime(mainParset.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') + if startTime < datetime.utcnow(): + startTime, endTime = applySaneStartEndTime() + except ValueError: + logger.warning('cannot parse for start/end time from specification for otdb_id=%s. searching for sane defaults...', otdb_id) + startTime, endTime = applySaneStartEndTime() + try: # fix for MoM bug introduced before NV's holiday # MoM sets ProcessingCluster.clusterName to CEP2 even when inputxml says CEP4 -- GitLab From dd111b38e4870282eb1f6fcdcea081e5e85a7edf Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 2 Sep 2016 18:33:36 +0000 Subject: [PATCH 762/933] Task #9522: Use 2 cores/process by default, to more evenly spread resources and prevent extreme load figures. Almost all pipelines have a step that requires 2 cores. --- CEP/Pipeline/framework/lofarpipe/support/remotecommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index 9bde0ca9846..e6ccaa88d3f 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -184,7 +184,7 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config command = commandStr, job_name = jobname(command), - nr_cores = resources.get("cores", 1), + nr_cores = resources.get("cores", 2), ).split() logger.debug("Dispatching command to %s with custom_cmdline: %s" % (host, full_command_line)) -- GitLab From 47eafe76546768ede13c7fd7595226d7f1f2c749 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 2 Sep 2016 18:46:37 +0000 Subject: [PATCH 763/933] Task #9522: Run srun from Docker directly, without ssh to localhost --- Docker/lofar-pipeline/Dockerfile.tmpl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 94c6299eeff..c8b9303eed9 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -6,7 +6,7 @@ FROM lofar-base:${LOFAR_TAG} ENV AOFLAGGER_VERSION=2.8.0 # Run-time dependencies -RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ +RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib slurm-client && \ apt-get -y install python-pip python-dev && \ pip install pyfits pywcs python-monetdb && \ apt-get -y purge python-pip python-dev && \ @@ -59,4 +59,3 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ apt-get autoremove -y - -- GitLab From e361f207eb310f4c4d79a28b27e402954e7a2ebc Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 2 Sep 2016 18:46:37 +0000 Subject: [PATCH 764/933] Task #9522: Run srun from Docker directly, without ssh to localhost --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 4 ++-- MAC/Services/src/PipelineControl.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 138835c5bb1..97703d3a145 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -44,7 +44,7 @@ globalfs = yes # We take the following path to start a remote container: # -# [container] --SSH--> [host] --SRUN--> [worker node] --DOCKER--> [container] +# [container] --SRUN--> [worker node] --DOCKER--> [container] # # This path is needed because running SRUN from within the container needs a lot of cluster-specific infra # (SLURM config files, Munge keys, correct Munge user ID, munged). @@ -70,4 +70,4 @@ globalfs = yes # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = ssh -n -tt -x localhost srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker-run-slurm.sh --rm -u {uid} -v /data:/data --net=host {docker_env} lofar-pipeline:${LOFAR_TAG} {command} +cmdline = srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker-run-slurm.sh --rm -u {uid} -v /data:/data --net=host {docker_env} lofar-pipeline:${LOFAR_TAG} {command} diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 5679178f5d1..ce5f9e06857 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -485,8 +485,7 @@ wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&sta # run the pipeline runcmd docker-run-slurm.sh --rm --net=host \ -e LOFARENV={lofarenv} \ - -u $UID -e USER=$USER \ - -e HOME=$HOME -v $HOME/.ssh:$HOME/.ssh:ro \ + -v $HOME/.ssh:$HOME/.ssh:ro \ -e SLURM_JOB_ID=$SLURM_JOB_ID \ -v /data:/data \ {image} \ -- GitLab From d841b9e70b9fcd07792971adc4bea4edb8ad4eaf Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 2 Sep 2016 18:58:08 +0000 Subject: [PATCH 765/933] Task #9785: fix parmdb shape for tec only solver --- CEP/DP3/DPPP/src/GainCal.cc | 8 ++++++-- CEP/DP3/DPPP/test/tGainCal.run | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CEP/DP3/DPPP/src/GainCal.cc b/CEP/DP3/DPPP/src/GainCal.cc index 8946c807991..143beefe32b 100644 --- a/CEP/DP3/DPPP/src/GainCal.cc +++ b/CEP/DP3/DPPP/src/GainCal.cc @@ -648,7 +648,11 @@ namespace LOFAR { cout<<phases[freqCell]<<","; } cout<<phases[freqCell]<<"]"<<endl; - cout << "fitdata["<<st<<"]=[" << tecsol(0,1) << ", " << tecsol(1,1) << ", " << cost << "];" << endl; + if (itsMode=="tecandphase") { + cout << "fitdata["<<st<<"]=[" << tecsol(0,1) << ", " << tecsol(1,1) << ", " << cost << "];" << endl; + } else { + cout << "fitdata["<<st<<"]=[" << tecsol(0,1) << "];" << endl; + } } } itsTimerPhaseFit.stop(); @@ -821,7 +825,7 @@ namespace LOFAR { uint ntime=itsSols.size(); uint nchan, nfreqs; - if (itsMode=="tec") { + if (itsMode=="tec" || itsMode=="tecandphase") { nfreqs = 1; nchan = info().nchan(); } else { diff --git a/CEP/DP3/DPPP/test/tGainCal.run b/CEP/DP3/DPPP/test/tGainCal.run index c4137b09945..ea76e4fe371 100755 --- a/CEP/DP3/DPPP/test/tGainCal.run +++ b/CEP/DP3/DPPP/test/tGainCal.run @@ -100,11 +100,11 @@ $taqlexe 'select from tNDPPP-generic.MS/inst-tec where (select NAME from ::NAMES diff taql.out taql.ref || exit 1 echo; echo "Test caltype=tecandphase"; echo -../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_TEC steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-tec gaincal.caltype=tecandphase gaincal.solint=2 +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DPPP_TEC steps=[gaincal] gaincal.sourcedb=tNDPPP-generic.MS/sky gaincal.parmdb=tNDPPP-generic.MS/inst-tecandphase gaincal.caltype=tecandphase gaincal.solint=2 # For now, only testing that the right parameter names are in the output echo " select result of 1 rows" > taql1.ref -$taqlexe 'select from tNDPPP-generic.MS/inst-tec where (select NAME from ::NAMES)[NAMEID]=="TEC:CS001HBA0"' > taql.out +$taqlexe 'select from tNDPPP-generic.MS/inst-tecandphase where (select NAME from ::NAMES)[NAMEID]=="TEC:CS001HBA0"' > taql.out diff taql.out taql1.ref || exit 1 -$taqlexe 'select from tNDPPP-generic.MS/inst-tec where (select NAME from ::NAMES)[NAMEID]=="CommonScalarPhase:CS001HBA0"' > taql.out +$taqlexe 'select from tNDPPP-generic.MS/inst-tecandphase where (select NAME from ::NAMES)[NAMEID]=="CommonScalarPhase:CS001HBA0"' > taql.out diff taql.out taql1.ref || exit 1 -- GitLab From b05431a148f21b2a840fbe67c665a585c83ea0f5 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 3 Sep 2016 07:19:11 +0000 Subject: [PATCH 766/933] Task #9522: Run srun from Docker directly, without ssh to localhost --- CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl index 97703d3a145..88bf3b935c0 100644 --- a/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl +++ b/CEP/Pipeline/recipes/sip/pipeline.cfg.CEP4.tmpl @@ -70,4 +70,4 @@ globalfs = yes # /bin/bash -c # # Required because the pipeline framework needs some bash functionality in the commands it starts. -cmdline = srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} docker-run-slurm.sh --rm -u {uid} -v /data:/data --net=host {docker_env} lofar-pipeline:${LOFAR_TAG} {command} +cmdline = srun --exclusive --ntasks=1 --cpus-per-task={nr_cores} --jobid={slurm_job_id} --job-name={job_name} /data/bin/docker-run-slurm.sh --rm -u {uid} -v /data:/data --net=host {docker_env} lofar-pipeline:${LOFAR_TAG} {command} -- GitLab From 72cfdccac689aca6fdfd489c987a5f3cb00aaad7 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 3 Sep 2016 09:14:02 +0000 Subject: [PATCH 767/933] Task #9522: Do not escape {command} when running directly under srun --- CEP/Pipeline/framework/lofarpipe/support/remotecommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index e6ccaa88d3f..4513c446dcc 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -161,7 +161,7 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config dockerEnvStr = " ".join(dockerEnvPairs) # construct {command} - commandArray = [command] + [re.escape(str(arg)) for arg in arguments] + commandArray = [command] + arguments commandStr = " ".join(commandArray) # Determine job name -- GitLab From 33bf7b3ac33ea6e3a194f02dd378ad77c208d701 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 3 Sep 2016 10:02:39 +0000 Subject: [PATCH 768/933] Task #9522: Do not escape {command} when running directly under srun --- CEP/Pipeline/framework/lofarpipe/support/remotecommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py index 4513c446dcc..fa63410725c 100644 --- a/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py +++ b/CEP/Pipeline/framework/lofarpipe/support/remotecommand.py @@ -161,7 +161,7 @@ def run_via_custom_cmdline(logger, host, command, environment, arguments, config dockerEnvStr = " ".join(dockerEnvPairs) # construct {command} - commandArray = [command] + arguments + commandArray = [command] + [str(x) for x in arguments] commandStr = " ".join(commandArray) # Determine job name -- GitLab From 85ceaaf70626f1a5788f9fe75462b23e9a5fcc29 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Sat, 3 Sep 2016 13:39:13 +0000 Subject: [PATCH 769/933] Task #9522: Run srun from Docker directly, without ssh to localhost --- Docker/lofar-pipeline/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 94c6299eeff..643b4cbeab4 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -6,7 +6,7 @@ FROM lofar-base:${LOFAR_TAG} ENV AOFLAGGER_VERSION=2.8.0 # Run-time dependencies -RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ +RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib slurm-client && \ apt-get -y install python-pip python-dev && \ pip install pyfits pywcs python-monetdb && \ apt-get -y purge python-pip python-dev && \ -- GitLab From f897dfe44c998b7a1db8117c493370d53d002f4c Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Mon, 5 Sep 2016 09:41:00 +0000 Subject: [PATCH 770/933] Task #9279: remove obsolete LOFAR on DAS4 install instructions written by me years ago --- .gitattributes | 1 - .../DAS4-fs5-jenkins-install-instructions.txt | 213 ------------------ 2 files changed, 214 deletions(-) delete mode 100644 RTCP/Cobalt/GPUProc/doc/DAS4-fs5-jenkins-install-instructions.txt diff --git a/.gitattributes b/.gitattributes index bc18cd95282..f9a5a24a885 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4428,7 +4428,6 @@ RTCP/Cobalt/CobaltTest/test/tMultiPartTABOutput.sh eol=lf RTCP/Cobalt/GPUProc/doc/2ndtranspose.txt -text RTCP/Cobalt/GPUProc/doc/BGP-Cobalt-procs.dia -text RTCP/Cobalt/GPUProc/doc/BGP-Cobalt-procs.png -text svneol=unset#image/png -RTCP/Cobalt/GPUProc/doc/DAS4-fs5-jenkins-install-instructions.txt -text RTCP/Cobalt/GPUProc/doc/bf-2nd-transpose.dia -text RTCP/Cobalt/GPUProc/doc/bf-2nd-transpose.png -text svneol=unset#image/png RTCP/Cobalt/GPUProc/doc/cobalt-commissioning-report/0034-0534.resids.ft-only-BGP-points.png -text svneol=unset#image/png diff --git a/RTCP/Cobalt/GPUProc/doc/DAS4-fs5-jenkins-install-instructions.txt b/RTCP/Cobalt/GPUProc/doc/DAS4-fs5-jenkins-install-instructions.txt deleted file mode 100644 index 89a71eaeed3..00000000000 --- a/RTCP/Cobalt/GPUProc/doc/DAS4-fs5-jenkins-install-instructions.txt +++ /dev/null @@ -1,213 +0,0 @@ -Used installation commands -========================== - -Keep the latest version of this file in the LOFAR repos under LOFAR/RTCP/Cobalt/GPUProc/doc - - -Environment ------------ - -* We assume the following 'loaded modules' (e.g. through .bashrc): - module load gcc # default on DAS-4 - module load sge # idem - module load fftw3/gcc/64 - module load openmpi/gcc/64 - module load cuda55/toolkit - module load cuda55/fft - module load hwloc - (If the jenkins system is in control (JENKINS_SERVER_COOKIE has a value), - we set modules and PATH-like env vars in the jenkins build/test scripts.) -* We assume PATH contains $HOME/root/bin - and LD_LIBRARY_PATH contains $HOME/root/lib - and PYTHONPATH contains $HOME/root/lib64/python2.6/site-packages - -* Always pass at least -DCMAKE_INSTALL_PREFIX=$HOME/root to cmake, or --prefix=$HOME/root to ./configure. -* Make sure everything is linked using the same library versions (esp. for libhdf5). - - -Relevant files and directories ------------------------------- - -$HOME/root/ installation root -$HOME/root/src/ source and build directories -$HOME/root/share/aips++/data/ CASA measures table location -$HOME/jenkins/ location of the jenkins continuous build and integration system - - -log4cplus (easier to install using your OS' package manager) ---------- - -Dependency for: LOFAR - - cd $HOME/root/src - wget http://downloads.sourceforge.net/project/log4cplus/log4cplus-stable/1.1.1/log4cplus-1.1.1.tar.gz # or whatever recent version you like - tar zxf log4cplus-1.1.1.tar.gz - cd log4cplus-1.1.1 - ./configure --prefix=$HOME/root - make -j 8 install - make check # optional - - -libssh2 (easier to install using your OS' package manager) -------- - -Dependency for: LOFAR - - cd $HOME/root/src - wget http://www.libssh2.org/download/libssh2-1.4.3.tar.gz # or whatever recent version you like - tar zxf libssh2-1.4.3.tar.gz - cd libssh2-1.4.3 - ./configure --prefix=$HOME/root - make -j 8 install - make check # optional - - -cfitsio (easier to install using your OS' package manager) -------- - -Dependency for: casacore, wcslib - - cd $HOME/root/src - wget ftp://heasarc.gsfc.nasa.gov/software/fitsio/c/cfitsio_latest.tar.gz - tar zxf cfitsio_latest.tar.gz - cd cfitsio - ./configure --prefix=$HOME/root --enable-reentrant --enable-sse2 --enable-ssse3 - make -j 8 shared - make install - - -wcslib (easier to install using your OS' package manager) ------- - -Dependency for: casacore - - cd $HOME/root/src - wget ftp://ftp.atnf.csiro.au/pub/software/wcslib/wcslib.tar.bz2 - tar jxf wcslib.tar.bz2 - cd wcslib-4.18 # or whatever version you have - ./configure --prefix=$HOME/root --with-cfitsiolib=$HOME/root/lib --with-cfitsioinc=$HOME/root/include - make -j 8 install - - -CASA measures tables --------------------- - -Dependency for: casacore (keep the measure tables up to date, automatically!) - - cd $HOME/root/share # or wherever - mkdir aips++ - cd aips++ - wget ftp://ftp.atnf.csiro.au/pub/software/measures_data/measures_data.tar.bz2 ftp://ftp.atnf.csiro.au/pub/software/measures_data/measures_data.tar.bz2.md5sum - md5sum measures_data.tar.bz2 # verify that md5 hash is equal to hash in measures_data.tar.bz2.md5sum (if you automate it, note that the .md5sum file contains a CSIRO full path name) - tar jxf measures_data.tar.bz2 - - # Now, add updating it to a cron or jenkins job - - -casacore --------- - -Dependency for: LOFAR/RTCP/Cobalt - -Since we keep the measure data up to date (and since this install is the testing env standard), -we use -DDATA_DIR=..., such that others can use our install AND measures tables. - - cd $HOME/root/src - svn co http://casacore.googlecode.com/svn/trunk/ casacore - mkdir casacore/build - cd casacore/build - cmake -DCMAKE_INSTALL_PREFIX=$HOME/root -D-DUSE_HDF5=ON -DUSE_FFTW3=ON -DUSE_THREADS=ON -DUSE_OPENMP=ON -DCXX11=ON -DBUILD_PYTHON=ON -DUSE_STACKTRACE=ON -DDATA_DIR=$HOME/casacore/data .. - make -j 8 install - ctest # optional - - findmeastable # optional; verify that casacore can find the measures tables - - -casarest (not needed for LOFAR Cobalt) --------- - -Dependency for: LOFAR Calibration package, maybe others - - cd $HOME/root/src - svn co https://svn.astron.nl/casarest/trunk/casarest - mkdir casarest/build - cd casarest/build - cmake -DCMAKE_INSTALL_PREFIX=$HOME/root -DCASACORE_ROOT_DIR=$HOME/root -DLIB_EXTRA_SYNTHESIS=gfortran -DBUILD_ALL=1 .. # if you need ALL... - # On DAS-4 casarest r8758, 'make' searches for a boost lib in /usr/lib64/lib64. Patch that away. - # grep -r -l lib64/lib64 * | xargs sed -i -e 's/lib64\/lib64/lib64\//g' - make -j 8 install - - -python-casacore (formerly pyrap) (optional for LOFAR Cobalt) ------ - -Dependency for: various LOFAR/CEP packages -Depends on: python boost-python casacore numpy scons - - cd $HOME/root/src - git clone https://github.com/casacore/python-casacore.git - cd python-casacore - ./setup.py build_ext -I$HOME/root/include -L$HOME/root/lib # casacore include and lib paths - ./setup install --prefix=$HOME/root - python -c 'import casacore' # optional (test), should print nothing and exit 0 - - -blitz++ (easier to install using your OS' package manager, not needed for LOFAR Cobalt) -------- - -Dependency for: some LOFAR MAC packages - - cd $HOME/root/src - wget http://sourceforge.net/projects/blitz/files/blitz/Blitz%2B%2B%200.10/blitz-0.10.tar.gz # or whatever recent version you like - tar zxf blitz-0.10.tar.gz - cd blitz-0.10 - ./configure --prefix=$HOME/root --enable-shared --enable-optimize - make -j 8 install - - -valgrind (easier to install using your OS' package manager; optional for LOFAR Cobalt) --------- -needed for mpi to prevent tons of false positives - -Dependency for: running LOFAR tests under valgrind - - cd $HOME/root/src - wget http://valgrind.org/downloads/valgrind-3.10.1.tar.bz2 # or whatever version you like - tar jxf valgrind-3.10.1.tar.bz2 - cd valgrind-3.10.1 - ./configure --prefix=$HOME/root --with-mpicc=/cm/shared/apps/openmpi/gcc/64/1.4.4/bin/mpicc -# on cobalt, use: ./configure --prefix=/localhome/lofar/valgrind-3.10.1 --with-mpicc=/opt/openmpi/bin/mpicc - make -j 8 install - valgrind ls -l # optional (test), should exit 0 - - -Data Access Library (DAL) -------------------------- - -Dependency for: LOFAR/RTCP/Cobalt/OutputProc - - cd $HOME/root/src - git clone https://github.com/nextgen-astrodata/DAL.git DAL - mkdir DAL/build - cd DAL/build - cmake -DCMAKE_INSTALL_PREFIX=$HOME/root .. - make -j 8 install - ctest # optional - python -c 'import dal' # optional (test), should print nothing and exit 0 - - -LOFAR (Cobalt only) -------------------- - - cd $HOME/root/src - svn co --username USERNAME -N https://svn.astron.nl/LOFAR/trunk LOFAR - cd LOFAR - svn up CMake # cmake will fetch what is needed - cd .. - mkdir -p LOFAR-build/gnu_opt - cd LOFAR-build/gnu_opt - cmake -DCMAKE_INSTALL_PREFIX=$HOME/root -DBUILD_PACKAGES="Cobalt StaticMetaData" -DUSE_CUDA=ON -DUSE_OPENMP=ON -DUSE_MPI=ON ../../LOFAR # assumes you have all deps installed and a LOFAR/CMake/variants/variants.<hostname> file is in place - make -j 8 install - ctest -j 8 # optional - . $HOME/root/lofarinit.sh # required to use some of the LOFAR tools, but we will do this through jenkins - -- GitLab From 3d080ab5ff31cfda9abf13338cb5289a2b49faad Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 5 Sep 2016 10:11:45 +0000 Subject: [PATCH 771/933] Task #9607: changed postgres notification mechanism. Do not return full row as json anymore, because the payload is not allowed to be larger than 8000 chars. Now, just return on value as payload, usually the id, and handle the rest in the PostgresListener subclass --- LCS/PyCommon/postgres.py | 74 ++--- .../ResourceAssigner/lib/assignment.py | 40 +-- .../ResourceAssigner/lib/schedulechecker.py | 32 ++- .../ResourceAssignmentDatabase/radb.py | 52 ++-- .../radbbuslistener.py | 44 ++- .../radbpglistener.py | 142 ++++------ .../sql/add_notifications.sql | 252 +++++++++++------- .../sql/create_add_notifications.sql.py | 23 +- .../lib/radbchangeshandler.py | 62 +++-- .../ResourceAssignmentService/rpc.py | 12 +- .../ResourceAssignmentService/service.py | 14 +- 11 files changed, 399 insertions(+), 348 deletions(-) diff --git a/LCS/PyCommon/postgres.py b/LCS/PyCommon/postgres.py index 2db1316e191..1251ede07ad 100644 --- a/LCS/PyCommon/postgres.py +++ b/LCS/PyCommon/postgres.py @@ -33,60 +33,34 @@ import psycopg2.extensions logger = logging.getLogger(__name__) -def makePostgresNotificationQueries(schema, table, action, view_for_row=None, view_selection_id=None): +def makePostgresNotificationQueries(schema, table, action, column_name='id'): action = action.upper() if action not in ('INSERT', 'UPDATE', 'DELETE'): raise ValueError('''trigger_type '%s' not in ('INSERT', 'UPDATE', 'DELETE')''' % action) - if view_for_row and action == 'DELETE': - raise ValueError('You cannot use a view for results on action DELETE') - - if view_for_row: - change_name = '''{table}_{action}_with_{view_for_row}'''.format(schema=schema, - table=table, - action=action, - view_for_row=view_for_row) - function_name = '''NOTIFY_{change_name}'''.format(change_name=change_name) - function_sql = ''' - CREATE OR REPLACE FUNCTION {schema}.{function_name}() - RETURNS TRIGGER AS $$ - DECLARE - new_row_from_view {schema}.{view_for_row}%ROWTYPE; - BEGIN - select * into new_row_from_view from {schema}.{view_for_row} where {view_selection_id} = NEW.id LIMIT 1; - PERFORM pg_notify(CAST('{change_name}' AS text), - '{{"old":' || {old} || ',"new":' || row_to_json(new_row_from_view)::text || '}}'); - RETURN NEW; - END; - $$ LANGUAGE plpgsql; - '''.format(schema=schema, - function_name=function_name, - table=table, - action=action, - old='row_to_json(OLD)::text' if action == 'UPDATE' or action == 'DELETE' else '\'null\'', - view_for_row=view_for_row, - view_selection_id=view_selection_id if view_selection_id else 'id', - change_name=change_name.lower()) - else: - change_name = '''{table}_{action}'''.format(table=table, action=action) - function_name = '''NOTIFY_{change_name}'''.format(change_name=change_name) - function_sql = ''' - CREATE OR REPLACE FUNCTION {schema}.{function_name}() - RETURNS TRIGGER AS $$ - BEGIN - PERFORM pg_notify(CAST('{change_name}' AS text), - '{{"old":' || {old} || ',"new":' || {new} || '}}'); - RETURN {value}; - END; - $$ LANGUAGE plpgsql; - '''.format(schema=schema, - function_name=function_name, - table=table, - action=action, - old='row_to_json(OLD)::text' if action == 'UPDATE' or action == 'DELETE' else '\'null\'', - new='row_to_json(NEW)::text' if action == 'UPDATE' or action == 'INSERT' else '\'null\'', - value='OLD' if action == 'DELETE' else 'NEW', - change_name=change_name.lower()) + change_name = '''{table}_{action}'''.format(table=table, action=action) + if column_name != 'id': + change_name += '_column_' + column_name + function_name = '''NOTIFY_{change_name}'''.format(change_name=change_name) + function_sql = ''' + CREATE OR REPLACE FUNCTION {schema}.{function_name}() + RETURNS TRIGGER AS $$ + DECLARE payload text; + BEGIN + {begin_update_check}SELECT CAST({column_value} AS text) INTO payload; + PERFORM pg_notify(CAST('{change_name}' AS text), payload);{end_update_check} + RETURN {value}; + END; + $$ LANGUAGE plpgsql; + '''.format(schema=schema, + function_name=function_name, + table=table, + action=action, + column_value=('OLD' if action == 'DELETE' else 'NEW') + '.' + column_name, + value='OLD' if action == 'DELETE' else 'NEW', + change_name=change_name.lower(), + begin_update_check='IF ROW(NEW.*) IS DISTINCT FROM ROW(OLD.*) THEN\n' if action == 'UPDATE' else '', + end_update_check='\nEND IF;' if action == 'UPDATE' else '') trigger_name = 'TRIGGER_NOTIFY_%s' % function_name diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index f78e06beb3e..f2117648b52 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -135,8 +135,8 @@ class ResourceAssigner(): clusterIsCEP4 = self.checkClusterIsCEP4(mainParset) clusterName = 'CEP4' if clusterIsCEP4 else 'CEP2' - if clusterIsCEP4: - def applySaneStartEndTime(): + def applySaneStartEndTime(): + if clusterIsCEP4: startTime = datetime.utcnow() + timedelta(minutes=1) maxPredecessorEndTime = self.getMaxPredecessorEndTime(specification_tree) @@ -160,26 +160,26 @@ class ResourceAssigner(): return startTime, endTime - try: - startTime = datetime.strptime(mainParset.getString('Observation.startTime'), '%Y-%m-%d %H:%M:%S') - endTime = datetime.strptime(mainParset.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') - if startTime < datetime.utcnow(): - startTime, endTime = applySaneStartEndTime() - except ValueError: - logger.warning('cannot parse for start/end time from specification for otdb_id=%s. searching for sane defaults...', otdb_id) + try: + startTime = datetime.strptime(mainParset.getString('Observation.startTime'), '%Y-%m-%d %H:%M:%S') + endTime = datetime.strptime(mainParset.getString('Observation.stopTime'), '%Y-%m-%d %H:%M:%S') + if startTime < datetime.utcnow(): startTime, endTime = applySaneStartEndTime() + except ValueError: + logger.warning('cannot parse for start/end time from specification for otdb_id=%s. searching for sane defaults...', otdb_id) + startTime, endTime = applySaneStartEndTime() - try: - # fix for MoM bug introduced before NV's holiday - # MoM sets ProcessingCluster.clusterName to CEP2 even when inputxml says CEP4 - # so, override it here if needed, and update to otdb - processingClusterName = mainParset.getString('Observation.Cluster.ProcessingCluster.clusterName', '') - if processingClusterName != clusterName: - logger.info('overwriting and uploading processingClusterName to otdb from %s to %s for otdb_id=%s', - processingClusterName, clusterName, otdb_id) - self.otdbrpc.taskSetSpecification(otdb_id, { 'LOFAR.ObsSW.Observation.Cluster.ProcessingCluster.clusterName': clusterName }) - except Exception as e: - logger.error(e) + try: + # fix for MoM bug introduced before NV's holiday + # MoM sets ProcessingCluster.clusterName to CEP2 even when inputxml says CEP4 + # so, override it here if needed, and update to otdb + processingClusterName = mainParset.getString('Observation.Cluster.ProcessingCluster.clusterName', '') + if processingClusterName != clusterName: + logger.info('overwriting and uploading processingClusterName to otdb from %s to %s for otdb_id=%s', + processingClusterName, clusterName, otdb_id) + self.otdbrpc.taskSetSpecification(otdb_id, { 'LOFAR.ObsSW.Observation.Cluster.ProcessingCluster.clusterName': clusterName }) + except Exception as e: + logger.error(e) # insert new task and specification in the radb # any existing specification and task with same otdb_id will be deleted automatically diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index d309b2a229c..7e6d3371ede 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -53,12 +53,32 @@ def movePipelineAfterItsPredecessors(task, radbrpc, min_start_timestamp=None): if (task['starttime'] < max_pred_endtime) or (min_start_timestamp and task['starttime'] > min_start_timestamp): shift = max_pred_endtime - task['starttime'] - logger.info("Moving %s pipeline radb_id=%s otdb_id=%s by %s from \'%s\' to \'%s\'", task['status'], task['id'], task['otdb_id'], shift, task['starttime'], max_pred_endtime) - radbrpc.updateTaskAndResourceClaims(task['id'], starttime=task['starttime']+shift, endtime=task['endtime']+shift) - updated_task = radbrpc.getTask(task['id']) - if updated_task['status'] != task['status']: - logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change from %s to %s", updated_task['id'], updated_task['otdb_id'], task['status'], updated_task['status']) - #TODO: automatically resolve conflict status by moved pipeline in first free time slot. + newStartTime = task['starttime']+shift + newEndTime = task['endtime']+shift + + # move pipeline even further ahead in case there are more than 2 overlapping scheduled/queued pipelines + while True: + overlapping_pipelines = radbrpc.getTasks(lower_bound=newStartTime, upper_bound=newEndTime, task_type='pipeline', task_status='scheduled', cluster='CEP4') + overlapping_pipelines += radbrpc.getTasks(lower_bound=newStartTime, upper_bound=newEndTime, task_type='pipeline', task_status='queued', cluster='CEP4') + overlapping_pipelines += radbrpc.getTasks(lower_bound=newStartTime, upper_bound=newEndTime, task_type='pipeline', task_status='active', cluster='CEP4') + overlapping_pipelines += radbrpc.getTasks(lower_bound=newStartTime, upper_bound=newEndTime, task_type='pipeline', task_status='completing', cluster='CEP4') + overlapping_pipelines = [pl for pl in overlapping_pipelines if pl['id'] != task['id']] + + if len(overlapping_pipelines) >= 2: + max_overlapping_pipeline_endtime = max([t['endtime'] for t in overlapping_pipelines]) + shift = max_overlapping_pipeline_endtime + timedelta(minutes=1) - task['starttime'] + newStartTime = task['starttime']+shift + newEndTime = task['endtime']+shift + else: + break + + if shift != timedelta(seconds=0): + logger.info("Moving %s pipeline radb_id=%s otdb_id=%s by %s from \'%s\' to \'%s\'", task['status'], task['id'], task['otdb_id'], shift, task['starttime'], newStartTime) + radbrpc.updateTaskAndResourceClaims(task['id'], starttime=newStartTime, endtime=newEndTime) + updated_task = radbrpc.getTask(task['id']) + if updated_task['status'] != task['status']: + logger.warn("Moving of pipeline radb_id=%s otdb_id=%s caused the status to change from %s to %s", updated_task['id'], updated_task['otdb_id'], task['status'], updated_task['status']) + #TODO: automatically resolve conflict status by moved pipeline in first free time slot. except Exception as e: logger.error("Error while checking pipeline starttime: %s", e) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 79126379d7e..7f69d9723fa 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -231,21 +231,24 @@ class RADatabase: return tasks - def getTask(self, id=None, mom_id=None, otdb_id=None): - '''get a task for either the given (task)id, or for the given mom_id, or for the given otdb_id''' - ids = [id, mom_id, otdb_id] + def getTask(self, id=None, mom_id=None, otdb_id=None, specification_id=None): + '''get a task for either the given (task)id, or for the given mom_id, or for the given otdb_id, or for the given specification_id''' + ids = [id, mom_id, otdb_id, specification_id] validIds = [x for x in ids if x != None] if len(validIds) != 1: - raise KeyError("Provide one and only one id: id=%s, mom_id=%s, otdb_id=%s" % (id, mom_id, otdb_id)) + raise KeyError("Provide one and only one id: id=%s, mom_id=%s, otdb_id=%s, specification_id=%s" % (id, mom_id, otdb_id, specification_id)) query = '''SELECT * from resource_allocation.task_view tv ''' - if id: + if id is not None: query += '''where tv.id = (%s);''' - elif mom_id: + elif mom_id is not None: query += '''where tv.mom_id = (%s);''' - elif otdb_id: + elif otdb_id is not None: query += '''where tv.otdb_id = (%s);''' + elif specification_id is not None: + query += '''where tv.specification_id = (%s);''' + result = self._executeQuery(query, validIds, fetch=_FETCH_ONE) task = dict(result) if result else None @@ -272,10 +275,10 @@ class RADatabase: return task_status, task_type def insertTask(self, mom_id, otdb_id, task_status, task_type, specification_id, commit=True): - if isinstance(mom_id, int) and mom_id <= 0: + if isinstance(mom_id, int) and mom_id < 0: mom_id = None - if isinstance(otdb_id, int) and otdb_id <= 0: + if isinstance(otdb_id, int) and otdb_id < 0: otdb_id = None logger.info('insertTask mom_id=%s, otdb_id=%s, task_status=%s, task_type=%s, specification_id=%s' % @@ -358,9 +361,14 @@ class RADatabase: return self.cursor.rowcount > 0 - def getTaskPredecessorIds(self): - query = '''SELECT * from resource_allocation.task_predecessor tp;''' - items = list(self._executeQuery(query, fetch=_FETCH_ALL)) + def getTaskPredecessorIds(self, id=None): + query = '''SELECT * from resource_allocation.task_predecessor tp''' + + if id is not None : + query += ' WHERE id=%s' + + items = list(self._executeQuery(query, [id] if id is not None else None, fetch=_FETCH_ALL)) + predIdDict = {} for item in items: taskId = item['task_id'] @@ -369,9 +377,14 @@ class RADatabase: predIdDict[taskId].append(item['predecessor_id']) return predIdDict - def getTaskSuccessorIds(self): + def getTaskSuccessorIds(self, id=None): query = '''SELECT * from resource_allocation.task_predecessor tp;''' - items = list(self._executeQuery(query, fetch=_FETCH_ALL)) + + if id is not None : + query += ' WHERE id=%s' + + items = list(self._executeQuery(query, [id] if id is not None else None, fetch=_FETCH_ALL)) + succIdDict = {} for item in items: predId = item['predecessor_id'] @@ -387,11 +400,11 @@ class RADatabase: items = list(self._executeQuery(query, [task_id], fetch=_FETCH_ALL)) return [x['predecessor_id'] for x in items] - def getTaskSuccessorIdsForTask(self, task_): + def getTaskSuccessorIdsForTask(self, task_id): query = '''SELECT * from resource_allocation.task_predecessor tp WHERE tp.predecessor_id = %s;''' - items = list(self._executeQuery(query, [task_], fetch=_FETCH_ALL)) + items = list(self._executeQuery(query, [task_id], fetch=_FETCH_ALL)) return [x['task_id'] for x in items] def insertTaskPredecessor(self, task_id, predecessor_id, commit=True): @@ -1325,7 +1338,8 @@ class RADatabase: if commit: self.commit() return {'inserted': True, 'specification_id': specId, 'task_id': taskId} - except: + except Exception as e: + logger.error(e) self.rollback() return {'inserted': False, 'specification_id': None, 'task_id': None} @@ -1526,7 +1540,11 @@ if __name__ == '__main__': logger.info("Using dbcreds: %s" % dbcreds.stringWithHiddenPassword()) db = RADatabase(dbcreds=dbcreds, log_queries=True) + st1 = db.insertSpecificationAndTask(0, 0, 'approved', 'observation', datetime.utcnow(), datetime.utcnow() + timedelta(hours=1), 'foo', 'CEP4') + st2 = db.insertSpecificationAndTask(1, 1, 'approved', 'pipeline', datetime.utcnow(), datetime.utcnow() + timedelta(hours=1), 'foo', 'CEP4') + db.insertTaskPredecessor(st2['task_id'], st1['task_id']) + exit() def resultPrint(method): print '\n-- ' + str(method.__name__) + ' --' diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbbuslistener.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbbuslistener.py index fcf3ff0cf91..7f108839fba 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbbuslistener.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbbuslistener.py @@ -61,28 +61,27 @@ class RADBBusListener(AbstractBusListener): logger.info("on%s: %s" % (msg.subject.replace(self.subject_prefix, ''), str(msg.content).replace('\n', ' '))) if msg.subject == '%sTaskUpdated' % self.subject_prefix: - self.onTaskUpdated(msg.content.get('old'), msg.content.get('new')) + self.onTaskUpdated(msg.content) elif msg.subject == '%sTaskInserted' % self.subject_prefix: - self.onTaskInserted(msg.content.get('new')) + self.onTaskInserted(msg.content) elif msg.subject == '%sTaskDeleted' % self.subject_prefix: - self.onTaskDeleted(msg.content.get('old')) + self.onTaskDeleted(msg.content) elif msg.subject == '%sResourceClaimUpdated' % self.subject_prefix: - self.onResourceClaimUpdated(msg.content.get('old'), msg.content.get('new')) + self.onResourceClaimUpdated(msg.content) elif msg.subject == '%sResourceClaimInserted' % self.subject_prefix: - self.onResourceClaimInserted(msg.content.get('new')) + self.onResourceClaimInserted(msg.content) elif msg.subject == '%sResourceClaimDeleted' % self.subject_prefix: - self.onResourceClaimDeleted(msg.content.get('old')) + self.onResourceClaimDeleted(msg.content) elif msg.subject == '%sResourceAvailabilityUpdated' % self.subject_prefix: - self.onResourceAvailabilityUpdated(msg.content.get('old'), msg.content.get('new')) + self.onResourceAvailabilityUpdated(msg.content) elif msg.subject == '%sResourceCapacityUpdated' % self.subject_prefix: - self.onResourceCapacityUpdated(msg.content.get('old'), msg.content.get('new')) + self.onResourceCapacityUpdated(msg.content) else: logger.error("RADBBusListener.handleMessage: unknown subject: %s" %str(msg.subject)) - def onTaskUpdated(self, old_task, new_task): + def onTaskUpdated(self, updated_task): '''onTaskUpdated is called upon receiving a TaskUpdated message. - :param old_task: dictionary with the task before the update - :param new_task: dictionary with the updated task''' + :param updated_task: dictionary with the updated task''' pass def onTaskInserted(self, new_task): @@ -90,15 +89,14 @@ class RADBBusListener(AbstractBusListener): :param new_task: dictionary with the inserted task''' pass - def onTaskDeleted(self, old_task): + def onTaskDeleted(self, old_task_id): '''onTaskDeleted is called upon receiving a TaskDeleted message. - :param old_task: dictionary with the deleted task''' + :param old_task_id: id of the deleted task''' pass - def onResourceClaimUpdated(self, old_claim, new_claim): + def onResourceClaimUpdated(self, updated_claim): '''onResourceClaimUpdated is called upon receiving a ResourceClaimUpdated message. - :param old_claim: dictionary with the claim before the update - :param new_claim: dictionary with the updated claim''' + :param updated_claim: dictionary with the updated claim''' pass def onResourceClaimInserted(self, new_claim): @@ -106,21 +104,19 @@ class RADBBusListener(AbstractBusListener): :param new_claim: dictionary with the inserted claim''' pass - def onResourceClaimDeleted(self, old_claim): + def onResourceClaimDeleted(self, old_claim_id): '''onResourceClaimDeleted is called upon receiving a ResourceClaimDeleted message. - :param old_claim: dictionary with the deleted claim''' + :param old_claim_id: id of the deleted claim''' pass - def onResourceAvailabilityUpdated(self, old_availability, new_availability): + def onResourceAvailabilityUpdated(self, updated_availability): '''onResourceAvailabilityUpdated is called upon receiving a ResourceAvailabilityUpdated message. - :param old_availability: dictionary with the resource availability before the update - :param new_availability: dictionary with the updated availability''' + :param updated_availability: dictionary with the updated availability''' pass - def onResourceCapacityUpdated(self, old_capacity, new_capacity): + def onResourceCapacityUpdated(self, updated_capacity): '''onResourceCapacityUpdated is called upon receiving a ResourceCapacityUpdated message. - :param old_capacity: dictionary with the resource capacity before the update - :param new_capacity: dictionary with the updated capacity''' + :param updated_capacity: dictionary with the updated capacity''' pass diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py index 03065ae1c72..499821a7e8c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py @@ -56,85 +56,67 @@ class RADBPGListener(PostgresListener): self.rarpc = RARPC(busname=radb_busname, servicename=radb_servicename, broker=broker) - self.subscribe('task_update_with_task_view', self.onTaskUpdated) - self.subscribe('task_insert_with_task_view', self.onTaskInserted) + self.subscribe('task_update', self.onTaskUpdated) + self.subscribe('task_insert', self.onTaskInserted) self.subscribe('task_delete', self.onTaskDeleted) - self.subscribe('task_predecessor_insert', self.onTaskPredecessorInserted) - self.subscribe('task_predecessor_update', self.onTaskPredecessorUpdated) - self.subscribe('task_predecessor_delete', self.onTaskPredecessorDeleted) + self.subscribe('task_predecessor_insert_column_task_id', self.onTaskPredecessorChanged) + self.subscribe('task_predecessor_update_column_task_id', self.onTaskPredecessorChanged) + self.subscribe('task_predecessor_delete_column_task_id', self.onTaskPredecessorChanged) + + self.subscribe('task_predecessor_insert_column_predecessor_id', self.onTaskSuccessorChanged) + self.subscribe('task_predecessor_update_column_predecessor_id', self.onTaskSuccessorChanged) + self.subscribe('task_predecessor_delete_column_predecessor_id', self.onTaskSuccessorChanged) # when the specification starttime and endtime are updated, then that effects the task as well - # so subscribe to specification_update, and use task_view as view_for_row - self.subscribe('specification_update_with_task_view', self.onSpecificationUpdated) + self.subscribe('specification_update', self.onSpecificationUpdated) - self.subscribe('resource_claim_update_with_resource_claim_view', self.onResourceClaimUpdated) - self.subscribe('resource_claim_insert_with_resource_claim_view', self.onResourceClaimInserted) + self.subscribe('resource_claim_update', self.onResourceClaimUpdated) + self.subscribe('resource_claim_insert', self.onResourceClaimInserted) self.subscribe('resource_claim_delete', self.onResourceClaimDeleted) self.subscribe('resource_availability_update', self.onResourceAvailabilityUpdated) self.subscribe('resource_capacity_update', self.onResourceCapacityUpdated) def onTaskUpdated(self, payload = None): - self._convertPayloadAndSendNotification('TaskUpdated', payload, ['starttime', 'endtime']) + self._sendNotification('TaskUpdated', self.rarpc.getTask(payload)) def onTaskInserted(self, payload = None): - self._convertPayloadAndSendNotification('TaskInserted', payload, ['starttime', 'endtime']) + self._sendNotification('TaskInserted', self.rarpc.getTask(payload)) def onTaskDeleted(self, payload = None): - self._convertPayloadAndSendNotification('TaskDeleted', payload) - - def _onTaskPredecessorChanged(self, change_type, payload = None): - logger.info(payload) - try: - content = json.loads(payload) - except Exception as e: - logger.error('Could not parse payload: %s\n%s' % (payload, e)) - return - - try: - task_content = {} - - if change_type == 'Inserted' or change_type == 'Updated': - task_id = content['new']['task_id'] - task = self.rarpc.getTask(task_id) - task_content = {'new': task } - elif change_type == 'Deleted': - task_id = content['old']['task_id'] - task_content = {'old': {'id':task_id} } - - self._sendNotification('Task%s' % change_type, task_content) - except Exception as e: - logger.error('Could not parse payload: %s\n%s' % (payload, e)) - - def onTaskPredecessorUpdated(self, payload = None): - self._onTaskPredecessorChanged('Updated', payload) + self._sendNotification('TaskDeleted', {'id': payload }) - def onTaskPredecessorInserted(self, payload = None): - self._onTaskPredecessorChanged('Inserted', payload) + def onTaskPredecessorChanged(self, task_id): + logger.info('onTaskPredecessorChanged(task_id=%s)', task_id) + self._sendNotification('TaskUpdated', self.rarpc.getTask(task_id)) - def onTaskPredecessorDeleted(self, payload = None): - self._onTaskPredecessorChanged('Deleted', payload) + def onTaskSuccessorChanged(self, task_id): + logger.info('onTaskSuccessorChanged(task_id=%s)', task_id) + self._sendNotification('TaskUpdated', self.rarpc.getTask(task_id)) def onSpecificationUpdated(self, payload = None): # when the specification starttime and endtime are updated, then that effects the task as well - # so send a TaskUpdated notification - self._convertPayloadAndSendNotification('TaskUpdated', payload, ['starttime', 'endtime']) + self._sendNotification('TaskUpdated', self.rarpc.getTask(specification_id=payload)) def onResourceClaimUpdated(self, payload = None): - self._convertPayloadAndSendNotification('ResourceClaimUpdated', payload, ['starttime', 'endtime']) + self._sendNotification('ResourceClaimUpdated', self.rarpc.getResourceClaim(payload)) def onResourceClaimInserted(self, payload = None): - self._convertPayloadAndSendNotification('ResourceClaimInserted', payload, ['starttime', 'endtime']) + self._sendNotification('ResourceClaimInserted', self.rarpc.getResourceClaim(payload)) def onResourceClaimDeleted(self, payload = None): - self._convertPayloadAndSendNotification('ResourceClaimDeleted', payload) + self._sendNotification('ResourceClaimDeleted', {'id': payload }) def onResourceAvailabilityUpdated(self, payload = None): - self._convertPayloadAndSendNotification('ResourceAvailabilityUpdated', payload) + r = self.rarpc.getResources(resource_ids=[payload], include_availability=True)[0] + r = {k:r[k] for k in ['id', 'active']} + self._sendNotification('ResourceAvailabilityUpdated', r) def onResourceCapacityUpdated(self, payload = None): - self._convertPayloadAndSendNotification('ResourceCapacityUpdated', payload) + r = self.rarpc.getResources(resource_ids=[payload], include_availability=True)[0] + r = {k:r[k] for k in ['id', 'total_capacity', 'available_capacity', 'used_capacity']} + self._sendNotification('ResourceCapacityUpdated', r) def __enter__(self): super(RADBPGListener, self).__enter__() @@ -155,58 +137,28 @@ class RADBPGListener(PostgresListener): So, parse the requested fields, and return them as datetime. ''' try: - for state in ('old', 'new'): - if state in contentDict: - for field in fields: - try: - if contentDict[state] and field in contentDict[state]: - timestampStr = contentDict[state][field] - formatStr = '%Y-%m-%dT%H:%M:%S' if 'T' in timestampStr else '%Y-%m-%d %H:%M:%S' - if timestampStr.rfind('.') > -1: - formatStr += '.%f' - - timestamp = datetime.strptime(timestampStr, formatStr) - - contentDict[state][field] = timestamp - except Exception as e: - logger.error('Could not convert field \'%s\' to datetime: %s' % (field, e)) + for field in fields: + try: + if field in contentDict: + timestampStr = contentDict[field] + formatStr = '%Y-%m-%dT%H:%M:%S' if 'T' in timestampStr else '%Y-%m-%d %H:%M:%S' + if timestampStr.rfind('.') > -1: + formatStr += '.%f' - return contentDict - except Exception as e: - logger.error('Error while convering timestamp fields \'%s\'in %s\n%s' % (fields, contentDict, e)) - - - def _convertPayloadAndSendNotification(self, subject, payload, timestampFields = None): - try: - content = json.loads(payload) - except Exception as e: - logger.error('Could not parse payload: %s\n%s' % (payload, e)) - content=None + timestamp = datetime.strptime(timestampStr, formatStr) - return self._sendNotification(subject, content, timestampFields) + contentDict[field] = timestamp + except Exception as e: + logger.error('Could not convert field \'%s\' to datetime: %s' % (field, e)) - def _sendNotification(self, subject, content, timestampFields = None): - try: - if 'new' in content and content['new'] and 'old' in content and content['old']: - # check if new and old are equal. - # however, new and old can be based on different views, - # so, only check the values for the keys they have in common - new_keys = set(content['new'].keys()) - old_keys = set(content['old'].keys()) - common_keys = new_keys & old_keys - equal_valued_keys = [k for k in common_keys if content['new'][k] == content['old'][k]] - if len(equal_valued_keys) == len(common_keys): - logger.info('new and old values are equal, not sending notification. %s' % (content['new'])) - return - - if timestampFields: - content = self._formatTimestampsAsIso(timestampFields, content) + return contentDict except Exception as e: - logger.error('Exception while anayzing content: %s\n%s' % (content, e)) + logger.error('Error while convering timestamp fields \'%s\'in %s\n%s' % (fields, contentDict, e)) + def _sendNotification(self, subject, contentDict): try: - msg = EventMessage(context=self.notification_prefix + subject, content=content) - logger.info('Sending notification %s: %s' % (subject, str(content).replace('\n', ' '))) + msg = EventMessage(context=self.notification_prefix + subject, content=contentDict) + logger.info('Sending notification %s: %s' % (subject, str(contentDict).replace('\n', ' '))) self.event_bus.send(msg) except Exception as e: logger.error(str(e)) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_notifications.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_notifications.sql index 05fec7a48a4..05906465401 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_notifications.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/add_notifications.sql @@ -6,50 +6,48 @@ --this RADBPGListener then broadcasts the event on the lofar bus. -DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_INSERT_with_task_view ON resource_allocation.task CASCADE; -DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_INSERT_with_task_view(); +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_INSERT ON resource_allocation.task CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_INSERT(); -CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_INSERT_with_task_view() +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_INSERT() RETURNS TRIGGER AS $$ -DECLARE -new_row_from_view resource_allocation.task_view%ROWTYPE; +DECLARE payload text; BEGIN -select * into new_row_from_view from resource_allocation.task_view where id = NEW.id LIMIT 1; -PERFORM pg_notify(CAST('task_insert_with_task_view' AS text), -'{"old":' || 'null' || ',"new":' || row_to_json(new_row_from_view)::text || '}'); +SELECT CAST(NEW.id AS text) INTO payload; +PERFORM pg_notify(CAST('task_insert' AS text), payload); RETURN NEW; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_INSERT_with_task_view +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_INSERT AFTER INSERT ON resource_allocation.task FOR EACH ROW -EXECUTE PROCEDURE resource_allocation.NOTIFY_task_INSERT_with_task_view(); +EXECUTE PROCEDURE resource_allocation.NOTIFY_task_INSERT(); -DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_UPDATE_with_task_view ON resource_allocation.task CASCADE; -DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_UPDATE_with_task_view(); +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_UPDATE ON resource_allocation.task CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_UPDATE(); -CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_UPDATE_with_task_view() +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_UPDATE() RETURNS TRIGGER AS $$ -DECLARE -new_row_from_view resource_allocation.task_view%ROWTYPE; +DECLARE payload text; BEGIN -select * into new_row_from_view from resource_allocation.task_view where id = NEW.id LIMIT 1; -PERFORM pg_notify(CAST('task_update_with_task_view' AS text), -'{"old":' || row_to_json(OLD)::text || ',"new":' || row_to_json(new_row_from_view)::text || '}'); +IF ROW(NEW.*) IS DISTINCT FROM ROW(OLD.*) THEN +SELECT CAST(NEW.id AS text) INTO payload; +PERFORM pg_notify(CAST('task_update' AS text), payload); +END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_UPDATE_with_task_view +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_UPDATE AFTER UPDATE ON resource_allocation.task FOR EACH ROW -EXECUTE PROCEDURE resource_allocation.NOTIFY_task_UPDATE_with_task_view(); +EXECUTE PROCEDURE resource_allocation.NOTIFY_task_UPDATE(); DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_DELETE ON resource_allocation.task CASCADE; @@ -58,9 +56,10 @@ DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_DELETE(); CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_DELETE() RETURNS TRIGGER AS $$ +DECLARE payload text; BEGIN -PERFORM pg_notify(CAST('task_delete' AS text), -'{"old":' || row_to_json(OLD)::text || ',"new":' || 'null' || '}'); +SELECT CAST(OLD.id AS text) INTO payload; +PERFORM pg_notify(CAST('task_delete' AS text), payload); RETURN OLD; END; $$ LANGUAGE plpgsql; @@ -72,133 +71,201 @@ FOR EACH ROW EXECUTE PROCEDURE resource_allocation.NOTIFY_task_DELETE(); -DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_INSERT ON resource_allocation.task_predecessor CASCADE; -DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_INSERT(); +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_INSERT_column_task_id ON resource_allocation.task_predecessor CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_INSERT_column_task_id(); -CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_INSERT() +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_INSERT_column_task_id() RETURNS TRIGGER AS $$ +DECLARE payload text; BEGIN -PERFORM pg_notify(CAST('task_predecessor_insert' AS text), -'{"old":' || 'null' || ',"new":' || row_to_json(NEW)::text || '}'); +SELECT CAST(NEW.task_id AS text) INTO payload; +PERFORM pg_notify(CAST('task_predecessor_insert_column_task_id' AS text), payload); RETURN NEW; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_INSERT +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_INSERT_column_task_id AFTER INSERT ON resource_allocation.task_predecessor FOR EACH ROW -EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_INSERT(); +EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_INSERT_column_task_id(); -DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_UPDATE ON resource_allocation.task_predecessor CASCADE; -DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_UPDATE(); +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_UPDATE_column_task_id ON resource_allocation.task_predecessor CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_UPDATE_column_task_id(); -CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_UPDATE() +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_UPDATE_column_task_id() RETURNS TRIGGER AS $$ +DECLARE payload text; BEGIN -PERFORM pg_notify(CAST('task_predecessor_update' AS text), -'{"old":' || row_to_json(OLD)::text || ',"new":' || row_to_json(NEW)::text || '}'); +IF ROW(NEW.*) IS DISTINCT FROM ROW(OLD.*) THEN +SELECT CAST(NEW.task_id AS text) INTO payload; +PERFORM pg_notify(CAST('task_predecessor_update_column_task_id' AS text), payload); +END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_UPDATE +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_UPDATE_column_task_id AFTER UPDATE ON resource_allocation.task_predecessor FOR EACH ROW -EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_UPDATE(); +EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_UPDATE_column_task_id(); -DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_DELETE ON resource_allocation.task_predecessor CASCADE; -DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_DELETE(); +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_DELETE_column_task_id ON resource_allocation.task_predecessor CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_DELETE_column_task_id(); -CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_DELETE() +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_DELETE_column_task_id() RETURNS TRIGGER AS $$ +DECLARE payload text; BEGIN -PERFORM pg_notify(CAST('task_predecessor_delete' AS text), -'{"old":' || row_to_json(OLD)::text || ',"new":' || 'null' || '}'); +SELECT CAST(OLD.task_id AS text) INTO payload; +PERFORM pg_notify(CAST('task_predecessor_delete_column_task_id' AS text), payload); RETURN OLD; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_DELETE +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_DELETE_column_task_id AFTER DELETE ON resource_allocation.task_predecessor FOR EACH ROW -EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_DELETE(); +EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_DELETE_column_task_id(); -DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_specification_UPDATE_with_task_view ON resource_allocation.specification CASCADE; -DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_specification_UPDATE_with_task_view(); +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_INSERT_column_predecessor_id ON resource_allocation.task_predecessor CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_INSERT_column_predecessor_id(); -CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_specification_UPDATE_with_task_view() +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_INSERT_column_predecessor_id() RETURNS TRIGGER AS $$ -DECLARE -new_row_from_view resource_allocation.task_view%ROWTYPE; +DECLARE payload text; BEGIN -select * into new_row_from_view from resource_allocation.task_view where specification_id = NEW.id LIMIT 1; -PERFORM pg_notify(CAST('specification_update_with_task_view' AS text), -'{"old":' || row_to_json(OLD)::text || ',"new":' || row_to_json(new_row_from_view)::text || '}'); +SELECT CAST(NEW.predecessor_id AS text) INTO payload; +PERFORM pg_notify(CAST('task_predecessor_insert_column_predecessor_id' AS text), payload); RETURN NEW; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_specification_UPDATE_with_task_view +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_INSERT_column_predecessor_id +AFTER INSERT ON resource_allocation.task_predecessor +FOR EACH ROW +EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_INSERT_column_predecessor_id(); + + +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_UPDATE_column_predecessor_id ON resource_allocation.task_predecessor CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_UPDATE_column_predecessor_id(); + + +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_UPDATE_column_predecessor_id() +RETURNS TRIGGER AS $$ +DECLARE payload text; +BEGIN +IF ROW(NEW.*) IS DISTINCT FROM ROW(OLD.*) THEN +SELECT CAST(NEW.predecessor_id AS text) INTO payload; +PERFORM pg_notify(CAST('task_predecessor_update_column_predecessor_id' AS text), payload); +END IF; +RETURN NEW; +END; +$$ LANGUAGE plpgsql; + + +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_UPDATE_column_predecessor_id +AFTER UPDATE ON resource_allocation.task_predecessor +FOR EACH ROW +EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_UPDATE_column_predecessor_id(); + + +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_task_predecessor_DELETE_column_predecessor_id ON resource_allocation.task_predecessor CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_task_predecessor_DELETE_column_predecessor_id(); + + +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_task_predecessor_DELETE_column_predecessor_id() +RETURNS TRIGGER AS $$ +DECLARE payload text; +BEGIN +SELECT CAST(OLD.predecessor_id AS text) INTO payload; +PERFORM pg_notify(CAST('task_predecessor_delete_column_predecessor_id' AS text), payload); +RETURN OLD; +END; +$$ LANGUAGE plpgsql; + + +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_task_predecessor_DELETE_column_predecessor_id +AFTER DELETE ON resource_allocation.task_predecessor +FOR EACH ROW +EXECUTE PROCEDURE resource_allocation.NOTIFY_task_predecessor_DELETE_column_predecessor_id(); + + +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_specification_UPDATE ON resource_allocation.specification CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_specification_UPDATE(); + + +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_specification_UPDATE() +RETURNS TRIGGER AS $$ +DECLARE payload text; +BEGIN +IF ROW(NEW.*) IS DISTINCT FROM ROW(OLD.*) THEN +SELECT CAST(NEW.id AS text) INTO payload; +PERFORM pg_notify(CAST('specification_update' AS text), payload); +END IF; +RETURN NEW; +END; +$$ LANGUAGE plpgsql; + + +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_specification_UPDATE AFTER UPDATE ON resource_allocation.specification FOR EACH ROW -EXECUTE PROCEDURE resource_allocation.NOTIFY_specification_UPDATE_with_task_view(); +EXECUTE PROCEDURE resource_allocation.NOTIFY_specification_UPDATE(); -DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_resource_claim_INSERT_with_resource_claim_view ON resource_allocation.resource_claim CASCADE; -DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_resource_claim_INSERT_with_resource_claim_view(); +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_resource_claim_INSERT ON resource_allocation.resource_claim CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_resource_claim_INSERT(); -CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_resource_claim_INSERT_with_resource_claim_view() +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_resource_claim_INSERT() RETURNS TRIGGER AS $$ -DECLARE -new_row_from_view resource_allocation.resource_claim_view%ROWTYPE; +DECLARE payload text; BEGIN -select * into new_row_from_view from resource_allocation.resource_claim_view where id = NEW.id LIMIT 1; -PERFORM pg_notify(CAST('resource_claim_insert_with_resource_claim_view' AS text), -'{"old":' || 'null' || ',"new":' || row_to_json(new_row_from_view)::text || '}'); +SELECT CAST(NEW.id AS text) INTO payload; +PERFORM pg_notify(CAST('resource_claim_insert' AS text), payload); RETURN NEW; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_resource_claim_INSERT_with_resource_claim_view +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_resource_claim_INSERT AFTER INSERT ON resource_allocation.resource_claim FOR EACH ROW -EXECUTE PROCEDURE resource_allocation.NOTIFY_resource_claim_INSERT_with_resource_claim_view(); +EXECUTE PROCEDURE resource_allocation.NOTIFY_resource_claim_INSERT(); -DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_resource_claim_UPDATE_with_resource_claim_view ON resource_allocation.resource_claim CASCADE; -DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_resource_claim_UPDATE_with_resource_claim_view(); +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_resource_claim_UPDATE ON resource_allocation.resource_claim CASCADE; +DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_resource_claim_UPDATE(); -CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_resource_claim_UPDATE_with_resource_claim_view() +CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_resource_claim_UPDATE() RETURNS TRIGGER AS $$ -DECLARE -new_row_from_view resource_allocation.resource_claim_view%ROWTYPE; +DECLARE payload text; BEGIN -select * into new_row_from_view from resource_allocation.resource_claim_view where id = NEW.id LIMIT 1; -PERFORM pg_notify(CAST('resource_claim_update_with_resource_claim_view' AS text), -'{"old":' || row_to_json(OLD)::text || ',"new":' || row_to_json(new_row_from_view)::text || '}'); +IF ROW(NEW.*) IS DISTINCT FROM ROW(OLD.*) THEN +SELECT CAST(NEW.id AS text) INTO payload; +PERFORM pg_notify(CAST('resource_claim_update' AS text), payload); +END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_resource_claim_UPDATE_with_resource_claim_view +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_resource_claim_UPDATE AFTER UPDATE ON resource_allocation.resource_claim FOR EACH ROW -EXECUTE PROCEDURE resource_allocation.NOTIFY_resource_claim_UPDATE_with_resource_claim_view(); +EXECUTE PROCEDURE resource_allocation.NOTIFY_resource_claim_UPDATE(); DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_resource_claim_DELETE ON resource_allocation.resource_claim CASCADE; @@ -207,9 +274,10 @@ DROP FUNCTION IF EXISTS resource_allocation.NOTIFY_resource_claim_DELETE(); CREATE OR REPLACE FUNCTION resource_allocation.NOTIFY_resource_claim_DELETE() RETURNS TRIGGER AS $$ +DECLARE payload text; BEGIN -PERFORM pg_notify(CAST('resource_claim_delete' AS text), -'{"old":' || row_to_json(OLD)::text || ',"new":' || 'null' || '}'); +SELECT CAST(OLD.id AS text) INTO payload; +PERFORM pg_notify(CAST('resource_claim_delete' AS text), payload); RETURN OLD; END; $$ LANGUAGE plpgsql; @@ -221,42 +289,48 @@ FOR EACH ROW EXECUTE PROCEDURE resource_allocation.NOTIFY_resource_claim_DELETE(); -DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_resource_availability_UPDATE ON resource_monitoring.resource_availability CASCADE; -DROP FUNCTION IF EXISTS resource_monitoring.NOTIFY_resource_availability_UPDATE(); +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_resource_availability_UPDATE_column_resource_id ON resource_monitoring.resource_availability CASCADE; +DROP FUNCTION IF EXISTS resource_monitoring.NOTIFY_resource_availability_UPDATE_column_resource_id(); -CREATE OR REPLACE FUNCTION resource_monitoring.NOTIFY_resource_availability_UPDATE() +CREATE OR REPLACE FUNCTION resource_monitoring.NOTIFY_resource_availability_UPDATE_column_resource_id() RETURNS TRIGGER AS $$ +DECLARE payload text; BEGIN -PERFORM pg_notify(CAST('resource_availability_update' AS text), -'{"old":' || row_to_json(OLD)::text || ',"new":' || row_to_json(NEW)::text || '}'); +IF ROW(NEW.*) IS DISTINCT FROM ROW(OLD.*) THEN +SELECT CAST(NEW.resource_id AS text) INTO payload; +PERFORM pg_notify(CAST('resource_availability_update_column_resource_id' AS text), payload); +END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_resource_availability_UPDATE +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_resource_availability_UPDATE_column_resource_id AFTER UPDATE ON resource_monitoring.resource_availability FOR EACH ROW -EXECUTE PROCEDURE resource_monitoring.NOTIFY_resource_availability_UPDATE(); +EXECUTE PROCEDURE resource_monitoring.NOTIFY_resource_availability_UPDATE_column_resource_id(); -DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_resource_capacity_UPDATE ON resource_monitoring.resource_capacity CASCADE; -DROP FUNCTION IF EXISTS resource_monitoring.NOTIFY_resource_capacity_UPDATE(); +DROP TRIGGER IF EXISTS TRIGGER_NOTIFY_NOTIFY_resource_capacity_UPDATE_column_resource_id ON resource_monitoring.resource_capacity CASCADE; +DROP FUNCTION IF EXISTS resource_monitoring.NOTIFY_resource_capacity_UPDATE_column_resource_id(); -CREATE OR REPLACE FUNCTION resource_monitoring.NOTIFY_resource_capacity_UPDATE() +CREATE OR REPLACE FUNCTION resource_monitoring.NOTIFY_resource_capacity_UPDATE_column_resource_id() RETURNS TRIGGER AS $$ +DECLARE payload text; BEGIN -PERFORM pg_notify(CAST('resource_capacity_update' AS text), -'{"old":' || row_to_json(OLD)::text || ',"new":' || row_to_json(NEW)::text || '}'); +IF ROW(NEW.*) IS DISTINCT FROM ROW(OLD.*) THEN +SELECT CAST(NEW.resource_id AS text) INTO payload; +PERFORM pg_notify(CAST('resource_capacity_update_column_resource_id' AS text), payload); +END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_resource_capacity_UPDATE +CREATE TRIGGER TRIGGER_NOTIFY_NOTIFY_resource_capacity_UPDATE_column_resource_id AFTER UPDATE ON resource_monitoring.resource_capacity FOR EACH ROW -EXECUTE PROCEDURE resource_monitoring.NOTIFY_resource_capacity_UPDATE(); +EXECUTE PROCEDURE resource_monitoring.NOTIFY_resource_capacity_UPDATE_column_resource_id(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_add_notifications.sql.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_add_notifications.sql.py index 750b26c4871..07860f3b37c 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_add_notifications.sql.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_add_notifications.sql.py @@ -40,15 +40,18 @@ if __name__ == '__main__': f.write('--this RADBPGListener then broadcasts the event on the lofar bus.\n') f.write('\n') - f.writelines(makePostgresNotificationQueries('resource_allocation', 'task', 'INSERT', view_for_row='task_view')) - f.writelines(makePostgresNotificationQueries('resource_allocation', 'task', 'UPDATE', view_for_row='task_view')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'task', 'INSERT')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'task', 'UPDATE')) f.writelines(makePostgresNotificationQueries('resource_allocation', 'task', 'DELETE')) - f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'INSERT')) - f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'UPDATE')) - f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'DELETE')) - f.writelines(makePostgresNotificationQueries('resource_allocation', 'specification', 'UPDATE', view_for_row='task_view', view_selection_id='specification_id')) - f.writelines(makePostgresNotificationQueries('resource_allocation', 'resource_claim', 'INSERT', view_for_row='resource_claim_view')) - f.writelines(makePostgresNotificationQueries('resource_allocation', 'resource_claim', 'UPDATE', view_for_row='resource_claim_view')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'INSERT', column_name='task_id')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'UPDATE', column_name='task_id')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'DELETE', column_name='task_id')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'INSERT', column_name='predecessor_id')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'UPDATE', column_name='predecessor_id')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'task_predecessor', 'DELETE', column_name='predecessor_id')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'specification', 'UPDATE')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'resource_claim', 'INSERT')) + f.writelines(makePostgresNotificationQueries('resource_allocation', 'resource_claim', 'UPDATE')) f.writelines(makePostgresNotificationQueries('resource_allocation', 'resource_claim', 'DELETE')) - f.writelines(makePostgresNotificationQueries('resource_monitoring', 'resource_availability', 'UPDATE')) - f.writelines(makePostgresNotificationQueries('resource_monitoring', 'resource_capacity', 'UPDATE')) + f.writelines(makePostgresNotificationQueries('resource_monitoring', 'resource_availability', 'UPDATE', column_name='resource_id')) + f.writelines(makePostgresNotificationQueries('resource_monitoring', 'resource_capacity', 'UPDATE', column_name='resource_id')) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py index 0df9611f2f8..16109932eeb 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py @@ -77,58 +77,56 @@ class RADBChangesHandler(RADBBusListener): with self._changedCondition: self._changedCondition.notifyAll() - def onTaskUpdated(self, old_task, new_task): + def onTaskUpdated(self, updated_task): '''onTaskUpdated is called upon receiving a TaskUpdated message.''' - #ignore old_task and new_task, which miss some properties via this update mechanism - #get task with all expected properties via radbrpc - task = self._radbrpc.getTask(new_task['id']) - task_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'task', 'value':task} + updated_task['starttime'] = updated_task['starttime'].datetime() + updated_task['endtime'] = updated_task['endtime'].datetime() + task_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'task', 'value':updated_task} self._handleChange(task_change) - def onTaskInserted(self, task): + def onTaskInserted(self, new_task): '''onTaskInserted is called upon receiving a TaskInserted message. - :param task: dictionary with the inserted task''' - #ignore old_task and new_task, which miss some properties via this update mechanism - #get task with all expected properties via radbrpc - task = self._radbrpc.getTask(task['id']) - updateTaskMomDetails(task, self._momqueryrpc) - task_change = {'changeType':CHANGE_INSERT_TYPE, 'objectType':'task', 'value':task} + :param new_task: dictionary with the inserted task''' + new_task['starttime'] = new_task['starttime'].datetime() + new_task['endtime'] = new_task['endtime'].datetime() + updateTaskMomDetails(new_task, self._momqueryrpc) + task_change = {'changeType':CHANGE_INSERT_TYPE, 'objectType':'task', 'value':new_task} self._handleChange(task_change) - def onTaskDeleted(self, task): + def onTaskDeleted(self, old_task_id): '''onTaskDeleted is called upon receiving a TaskDeleted message. - :param task: dictionary with the deleted task''' - task_change = {'changeType':CHANGE_DELETE_TYPE, 'objectType':'task', 'value':task} + :param old_task_id: id of the deleted task''' + task_change = {'changeType':CHANGE_DELETE_TYPE, 'objectType':'task', 'value':{'id':old_task_id}} self._handleChange(task_change) - def onResourceClaimUpdated(self, old_claim, new_claim): + def onResourceClaimUpdated(self, updated_claim): '''onResourceClaimUpdated is called upon receiving a ResourceClaimUpdated message. - :param task: dictionary with the updated claim''' - new_claim['starttime'] = new_claim['starttime'].datetime() - new_claim['endtime'] = new_claim['endtime'].datetime() - claim_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'resourceClaim', 'value':new_claim} + :param updated_claim: dictionary with the updated claim''' + updated_claim['starttime'] = updated_claim['starttime'].datetime() + updated_claim['endtime'] = updated_claim['endtime'].datetime() + claim_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'resourceClaim', 'value':updated_claim} self._handleChange(claim_change) - def onResourceClaimInserted(self, claim): + def onResourceClaimInserted(self, new_claim): '''onResourceClaimInserted is called upon receiving a ResourceClaimInserted message. - :param claim: dictionary with the inserted claim''' - claim['starttime'] = claim['starttime'].datetime() - claim['endtime'] = claim['endtime'].datetime() - claim_change = {'changeType':CHANGE_INSERT_TYPE, 'objectType':'resourceClaim', 'value':claim} + :param new_claim: dictionary with the inserted claim''' + new_claim['starttime'] = new_claim['starttime'].datetime() + new_claim['endtime'] = new_claim['endtime'].datetime() + claim_change = {'changeType':CHANGE_INSERT_TYPE, 'objectType':'resourceClaim', 'value':new_claim} self._handleChange(claim_change) - def onResourceClaimDeleted(self, claim): + def onResourceClaimDeleted(self, old_claim_id): '''onResourceClaimDeleted is called upon receiving a ResourceClaimDeleted message. - :param claim: dictionary with the deleted claim''' - claim_change = {'changeType':CHANGE_DELETE_TYPE, 'objectType':'resourceClaim', 'value':claim} + :param old_claim_id: id of the deleted claim''' + claim_change = {'changeType':CHANGE_DELETE_TYPE, 'objectType':'resourceClaim', 'value':{'id': old_claim_id}} self._handleChange(claim_change) - def onResourceAvailabilityUpdated(self, old_availability, new_availability): - claim_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'resourceAvailability', 'value':new_availability} + def onResourceAvailabilityUpdated(self, old_availability, updated_availability): + claim_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'resourceAvailability', 'value':updated_availability} self._handleChange(claim_change) - def onResourceCapacityUpdated(self, old_capacity, new_capacity): - claim_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'resourceCapacity', 'value':new_capacity} + def onResourceCapacityUpdated(self, old_capacity, updated_capacity): + claim_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'resourceCapacity', 'value':updated_capacity} self._handleChange(claim_change) def getMostRecentChangeNumber(self): diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py index 7b4496abd62..29733fc1c6d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py @@ -147,9 +147,9 @@ class RARPC(RPCWrapper): available_capacity=available_capacity, total_capacity=total_capacity) - def getTask(self, id=None, mom_id=None, otdb_id=None): - '''get a task for either the given (task)id, or for the given mom_id, or for the given otdb_id''' - task = self.rpc('GetTask', id=id, mom_id=mom_id, otdb_id=otdb_id) + def getTask(self, id=None, mom_id=None, otdb_id=None, specification_id=None): + '''get a task for either the given (task)id, or for the given mom_id, or for the given otdb_id, or for the given specification_id''' + task = self.rpc('GetTask', id=id, mom_id=mom_id, otdb_id=otdb_id, specification_id=specification_id) if task: task['starttime'] = task['starttime'].datetime() task['endtime'] = task['endtime'].datetime() @@ -186,6 +186,12 @@ class RARPC(RPCWrapper): task['endtime'] = task['endtime'].datetime() return tasks + def getTaskPredecessorIds(self, id=None): + return self.rpc('GetTaskPredecessorIds', id=id) + + def getTaskSuccessorIds(self, **kwargs): + return self.rpc('GetTaskSuccessorIds', id=id) + def insertTaskPredecessor(self, task_id, predecessor_id): return self.rpc('InsertTaskPredecessor', task_id=task_id, predecessor_id=predecessor_id) diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/service.py b/SAS/ResourceAssignment/ResourceAssignmentService/service.py index b205c500b86..10afe86da26 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/service.py @@ -62,8 +62,10 @@ class RADBHandler(MessageHandlerInterface): 'DeleteTask': self._deleteTask, 'UpdateTask': self._updateTask, 'UpdateTaskStatusForOtdbId': self._updateTaskStatusForOtdbId, + 'GetTaskPredecessorIds': self._getTaskPredecessorIds, + 'GetTaskSuccessorIds': self._getTaskSuccessorIds, 'InsertTaskPredecessor': self._insertTaskPredecessor, - 'insertTaskPredecessors': self._insertTaskPredecessors, + 'InsertTaskPredecessors': self._insertTaskPredecessors, 'GetTaskStatuses': self._getTaskStatuses, 'GetTaskTypes': self._getTaskTypes, 'GetSpecifications': self._getSpecifications, @@ -222,7 +224,7 @@ class RADBHandler(MessageHandlerInterface): def _getTask(self, **kwargs): logger.info('GetTask: %s' % dict({k:v for k,v in kwargs.items() if v != None})) - task = self.radb.getTask(id=kwargs.get('id'), mom_id=kwargs.get('mom_id'), otdb_id=kwargs.get('otdb_id')) + task = self.radb.getTask(id=kwargs.get('id'), mom_id=kwargs.get('mom_id'), otdb_id=kwargs.get('otdb_id'), specification_id=kwargs.get('specification_id')) return task def _insertTask(self, **kwargs): @@ -258,6 +260,14 @@ class RADBHandler(MessageHandlerInterface): specification_id=kwargs.get('specification_id')) return {'id': id, 'updated': updated} + def _getTaskPredecessorIds(self, **kwargs): + logger.info('GetTaskPredecessorIds: %s' % dict({k:v for k,v in kwargs.items() if v != None})) + return convertIntKeysToString(self.radb.getTaskPredecessorIds(kwargs.get('id'))) + + def _getTaskSuccessorIds(self, **kwargs): + logger.info('GetTaskSuccessorIds: %s' % dict({k:v for k,v in kwargs.items() if v != None})) + return convertIntKeysToString(self.radb.getTaskSuccessorIds(kwargs.get('id'))) + def _insertTaskPredecessor(self, **kwargs): id = self.radb.insertTaskPredecessor(kwargs['task_id'], kwargs['predecessor_id']) -- GitLab From 842d26722a2e5406b08f049026fa3240a5bc7f4d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 5 Sep 2016 12:31:47 +0000 Subject: [PATCH 772/933] Task #9607: do not reencapsulate payload --- .../ResourceAssignmentDatabase/radbpglistener.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py index 499821a7e8c..8548c6281e7 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py @@ -85,7 +85,7 @@ class RADBPGListener(PostgresListener): self._sendNotification('TaskInserted', self.rarpc.getTask(payload)) def onTaskDeleted(self, payload = None): - self._sendNotification('TaskDeleted', {'id': payload }) + self._sendNotification('TaskDeleted', payload) def onTaskPredecessorChanged(self, task_id): logger.info('onTaskPredecessorChanged(task_id=%s)', task_id) @@ -106,7 +106,7 @@ class RADBPGListener(PostgresListener): self._sendNotification('ResourceClaimInserted', self.rarpc.getResourceClaim(payload)) def onResourceClaimDeleted(self, payload = None): - self._sendNotification('ResourceClaimDeleted', {'id': payload }) + self._sendNotification('ResourceClaimDeleted', payload) def onResourceAvailabilityUpdated(self, payload = None): r = self.rarpc.getResources(resource_ids=[payload], include_availability=True)[0] -- GitLab From 67b9a92cc7cebcf3d04cb3e8eddb8b7c6aa97d5b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 5 Sep 2016 13:04:41 +0000 Subject: [PATCH 773/933] Task #9607: store task status and type in a local cache, and use that for conversions. Added support to getTasks by list of task_status or list of task_type --- .../ResourceAssignmentDatabase/radb.py | 70 +++++++++++++------ .../ResourceAssignmentService/rpc.py | 10 +++ 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 7f69d9723fa..0516ebbb646 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -45,6 +45,8 @@ class RADatabase: self.conn = None self.cursor = None self.log_queries = log_queries + self._taskStatusCache = {} + self._taskTypeCache = {} def _connect(self): self.conn = None @@ -109,12 +111,16 @@ class RADatabase: def getTaskStatusNames(self): return [x['name'] for x in self.getTaskStatuses()] - def getTaskStatusId(self, status_name): + def getTaskStatusId(self, status_name, from_cache=False): + if from_cache and status_name in self._taskStatusCache: + return self._taskStatusCache[status_name] + query = '''SELECT id from resource_allocation.task_status WHERE name = %s;''' result = self._executeQuery(query, [status_name], fetch=_FETCH_ONE) if result: + self._taskStatusCache[status_name] = result['id'] return result['id'] raise KeyError('No such status: %s Valid values are: %s' % (status_name, ', '.join(self.getTaskStatusNames()))) @@ -127,12 +133,16 @@ class RADatabase: def getTaskTypeNames(self): return [x['name'] for x in self.getTaskTypes()] - def getTaskTypeId(self, type_name): + def getTaskTypeId(self, type_name, from_cache=False): + if from_cache and type_name in self._taskStatusCache: + return self._taskTypeCache[type_name] + query = '''SELECT id from resource_allocation.task_type WHERE name = %s;''' result = self._executeQuery(query, [type_name], fetch=_FETCH_ONE) if result: + self._taskTypeCache[type_name] = result['id'] return result['id'] raise KeyError('No such type: %s Valid values are: %s' % (type_name, ', '.join(self.getTaskTypeNames()))) @@ -205,12 +215,24 @@ class RADatabase: task_status, task_type = self._convertTaskTypeAndStatusToIds(task_status, task_type) if task_status is not None: - conditions.append('status_id = %s') - qargs.append(task_status) + if isinstance(task_status, int): # just a single id + conditions.append('status_id = %s') + qargs.append(task_status) + elif len(task_status) > 0: #assume a list/enumerable of id's + conditions.append('status_id in %s') + qargs.append(tuple(task_status)) + elif len(task_status) == 0: #assume a list/enumerable of id's, length 0 + return [] if task_type is not None: - conditions.append('type_id = %s') - qargs.append(task_type) + if isinstance(task_type, int): # just a single id + conditions.append('type_id = %s') + qargs.append(task_type) + elif len(task_type) > 0: #assume a list/enumerable of id's + conditions.append('type_id in %s') + qargs.append(tuple(task_type)) + elif len(task_type) == 0: #assume a list/enumerable of id's, length 0 + return [] if cluster is not None: conditions.append('cluster = %s') @@ -262,17 +284,29 @@ class RADatabase: return task - def _convertTaskTypeAndStatusToIds(self, task_status, task_type): - '''converts task_status and task_type to id's in case one and/or the other are strings''' - if task_status and isinstance(task_status, basestring): - #convert task_status string to task_status.id - task_status = self.getTaskStatusId(task_status) + def _convertTaskStatusToId(self, task_status): + '''converts task_status to id in case it is a string or list of strings''' + if task_status is not None: + if isinstance(task_status, basestring): + return self.getTaskStatusId(task_status, True) + else: #assume iterable + return [self._convertTaskStatusToId(x) for x in task_status] + + return task_status - if task_type and isinstance(task_type, basestring): - #convert task_type string to task_type.id - task_type = self.getTaskTypeId(task_type) + def _convertTaskTypeToId(self, task_type): + '''converts task_status to id in case it is a string or list of strings''' + if task_type is not None: + if isinstance(task_type, basestring): + return self.getTaskTypeId(task_type, True) + else: #assume iterable + return [self._convertTaskTypeToId(x) for x in task_type] - return task_status, task_type + return task_type + + def _convertTaskTypeAndStatusToIds(self, task_status, task_type): + '''converts task_status and task_type to id's in case one and/or the other are strings''' + return self._convertTaskStatusToId(task_status), self._convertTaskTypeToId(task_type) def insertTask(self, mom_id, otdb_id, task_status, task_type, specification_id, commit=True): if isinstance(mom_id, int) and mom_id < 0: @@ -309,7 +343,7 @@ class RADatabase: '''converts task_status and task_type to id's in case one and/or the other are strings''' if task_status is not None and isinstance(task_status, basestring): #convert task_status string to task_status.id - task_status = self.getTaskStatusId(task_status) + task_status = self.getTaskStatusId(task_status, True) query = '''UPDATE resource_allocation.task SET (status_id) = (%s) @@ -1540,10 +1574,6 @@ if __name__ == '__main__': logger.info("Using dbcreds: %s" % dbcreds.stringWithHiddenPassword()) db = RADatabase(dbcreds=dbcreds, log_queries=True) - st1 = db.insertSpecificationAndTask(0, 0, 'approved', 'observation', datetime.utcnow(), datetime.utcnow() + timedelta(hours=1), 'foo', 'CEP4') - st2 = db.insertSpecificationAndTask(1, 1, 'approved', 'pipeline', datetime.utcnow(), datetime.utcnow() + timedelta(hours=1), 'foo', 'CEP4') - - db.insertTaskPredecessor(st2['task_id'], st1['task_id']) exit() def resultPrint(method): diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py index 29733fc1c6d..fd258223c4a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py @@ -180,6 +180,16 @@ class RARPC(RPCWrapper): status=status) def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None, cluster=None): + '''getTasks let's you query tasks from the radb with many optional filters. + :param lower_bound: datetime specifies the lower_bound of a time window above which to select tasks + :param upper_bound: datetime specifies the upper_bound of a time window below which to select tasks + :param task_ids: int/list/tuple specifies one or more task_ids to select + :param task_status: int/string/list specifies one or more task_statuses to select in either task_status_id or task_status_name form + :param task_type: int/string/list specifies one or more task_types to select in either task_type_id or task_type_name form + :param mom_ids: int/list/tuple specifies one or more mom_ids to select + :param otdb_ids: int/list/tuple specifies one or more otdb_ids to select + :param cluster: string specifies the cluster to select + ''' tasks = self.rpc('GetTasks', lower_bound=lower_bound, upper_bound=upper_bound, task_ids=task_ids, task_status=task_status, task_type=task_type, mom_ids=mom_ids, otdb_ids=otdb_ids, cluster=cluster) for task in tasks: task['starttime'] = task['starttime'].datetime() -- GitLab From 32e2ba18be9f24566962386514f0916dae708074 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 5 Sep 2016 13:08:10 +0000 Subject: [PATCH 774/933] Task #9607: use list for task_status to get tasks --- .../ResourceAssigner/lib/schedulechecker.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index 7e6d3371ede..06670458914 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -58,10 +58,8 @@ def movePipelineAfterItsPredecessors(task, radbrpc, min_start_timestamp=None): # move pipeline even further ahead in case there are more than 2 overlapping scheduled/queued pipelines while True: - overlapping_pipelines = radbrpc.getTasks(lower_bound=newStartTime, upper_bound=newEndTime, task_type='pipeline', task_status='scheduled', cluster='CEP4') - overlapping_pipelines += radbrpc.getTasks(lower_bound=newStartTime, upper_bound=newEndTime, task_type='pipeline', task_status='queued', cluster='CEP4') - overlapping_pipelines += radbrpc.getTasks(lower_bound=newStartTime, upper_bound=newEndTime, task_type='pipeline', task_status='active', cluster='CEP4') - overlapping_pipelines += radbrpc.getTasks(lower_bound=newStartTime, upper_bound=newEndTime, task_type='pipeline', task_status='completing', cluster='CEP4') + overlapping_pipelines = radbrpc.getTasks(lower_bound=newStartTime, upper_bound=newEndTime, task_type='pipeline', task_status=['scheduled', 'queued', 'active', 'completing'], cluster='CEP4') + #exclude self overlapping_pipelines = [pl for pl in overlapping_pipelines if pl['id'] != task['id']] if len(overlapping_pipelines) >= 2: @@ -144,9 +142,7 @@ class ScheduleChecker(): now = datetime.utcnow() min_start_timestamp = now + timedelta(seconds=PIPELINE_CHECK_INTERVAL) - scheduled_pipelines = self._radbrpc.getTasks(task_status='scheduled', task_type='pipeline', cluster='CEP4') - queued_pipelines = self._radbrpc.getTasks(task_status='queued', task_type='pipeline', cluster='CEP4') - pipelines = scheduled_pipelines + queued_pipelines + pipelines = self._radbrpc.getTasks(task_status=['scheduled', 'queued'], task_type='pipeline', cluster='CEP4') if pipelines: logger.info('checking starttime of %s scheduled/queued cep4 pipelines min_start_timestamp=%s', len(pipelines), min_start_timestamp) -- GitLab From d3f5d60324388ea1cbed25021d9cbba2ad5cea96 Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Tue, 6 Sep 2016 08:29:30 +0000 Subject: [PATCH 775/933] Task #9655: last changes for SNMP powerunit monitorring --- .../objects/Hardware/powerUnit_small.pnl | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl b/MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl index 41ac46b34f6..a47224eeac6 100644 --- a/MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl +++ b/MAC/Navigator2/panels/objects/Hardware/powerUnit_small.pnl @@ -33,6 +33,7 @@ private void reload() dynAppend(connectpoints,station+\"LOFAR_PIC_POWEC0.OK:_online.._value\"); if (nrOfPowerUnits == 2) { + dynAppend(connectpoints,station+\"LOFAR_PIC_POWEC1.nrOfModules:_online.._invalid\"); dynAppend(connectpoints,station+\"LOFAR_PIC_POWEC1.nrOfModules:_online.._value\"); dynAppend(connectpoints,station+\"LOFAR_PIC_POWEC1.OK:_online.._value\"); dpConnect(\"update2Powecs\",connectpoints); @@ -65,12 +66,12 @@ void updatePowec(string dp1, bool invalid, if (invalid) { - color = \"Lofar_broken\"; - tooltip += \"POWEC0: invalid, unit might be turned off?\"; + color = \"Lofar_invalid\"; + tooltip += \"POWEC0: invalid, unit might be turned off?<br> Or wrong snmp protocol\"; } else { - + // Sometimes the boards doesn't give an answer anymore, in that case nrOfModules = 0, so thats an error, // For now only a powercycle is known to resolve this issue if (nrOfModules < 1) { @@ -80,7 +81,7 @@ void updatePowec(string dp1, bool invalid, else if (nrOfModules != dynlen(OK)) { tooltip += \"POWEC0: nrOfModules and OKArray length differ\"; - color = \"Lofar_broken\"; + color = \"Lofar_suspicious\"; } else { @@ -88,7 +89,7 @@ void updatePowec(string dp1, bool invalid, bool ok = true; for (int i = 1; i <= nrOfModules; i++) { - if (!OK[i]) + if (OK[i] != 1) { ok = false; tooltip += \" Module [\"+i+\"] not OK <br>\"; @@ -102,18 +103,30 @@ void updatePowec(string dp1, bool invalid, setValue(\"powerUnit\", \"backCol\", color); } -void update2Powecs(string dp1, int nrOfModules1, - string dp2, dyn_int OK1, - string dp3, int nrOfModules2, - string dp4, dyn_int OK2) +void update2Powecs(string dp1, bool invalid1, + string dp2, int nrOfModules1, + string dp3, dyn_int OK1, + string dp4, bool invalid2, + string dp5, int nrOfModules2, + string dp6, dyn_int OK2) { - string tooltip= station + \" PowerUnit <br>\"; string color = \"Lofar_operational\"; + if (invalid1) + { + color = \"Lofar_invalid\"; + tooltip += \"POWEC0: invalid, unit might be turned off?<br> Or wrong snmp protocol\"; + } + else if (invalid2) + { + color = \"Lofar_invalid\"; + tooltip += \"POWEC1: invalid, unit might be turned off?<br> Or wrong snmp protocol\"; + } // Sometimes the boards doesn't give an answer anymore, in that case nrOfModules = 0, so thats an error, // For now only a powercycle is known to resolve this issue - if (nrOfModules1 < 1) { + else if (nrOfModules1 < 1) + { tooltip += \"POWEC0: nrOfModules = 0, unit might be stalled\"; color = \"Lofar_suspicious\"; } @@ -125,12 +138,12 @@ void update2Powecs(string dp1, int nrOfModules1, else if (nrOfModules1 != dynlen(OK1)) { tooltip += \"POWEC0: nrOfModules and OKArray length differ\"; - color = \"Lofar_broken\"; + color = \"Lofar_suspicious\"; } else if (nrOfModules2 != dynlen(OK2)) { tooltip += \"POWEC1: nrOfModules and OKArray length differ\"; - color = \"Lofar_broken\"; + color = \"Lofar_suspicious\"; } else { @@ -138,10 +151,11 @@ void update2Powecs(string dp1, int nrOfModules1, bool ok = true; for (int i = 1; i <= nrOfModules1; i++) { - if (!OK1[i]) + if (OK1[i] != 1) { ok = false; tooltip += \" Module [\"+i+\"] not OK </br>\"; + color = \"Lofar_broken\"; } } if (ok) tooltip += \"All Modules OK <br>\"; @@ -151,10 +165,11 @@ void update2Powecs(string dp1, int nrOfModules1, for (int i = 1; i <= nrOfModules2; i++) { - if (!OK2[i]) + if (OK2[i] != 1) { ok = false; tooltip += \" Module [\"+i+\"] not OK </br>\"; + color = \"Lofar_broken\"; } } if (ok) tooltip += \"All Modules OK <br>\"; -- GitLab From 4aa39d8c9c1a0a6d3522a067365db7f043945b33 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 7 Sep 2016 08:38:16 +0000 Subject: [PATCH 776/933] Task #9607: restore old predecessor/successor relationships if needed upon insertSpecificationAndTask within transaction --- .../ResourceAssignmentDatabase/radb.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 0516ebbb646..af0854ef9b1 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -1353,22 +1353,23 @@ class RADatabase: if task: # delete old specification, task, and resource claims using cascaded delete self.deleteSpecification(task['specification_id'], False) - - task = self.getTask(mom_id=mom_id) - if task: - # delete old specification, task, and resource claims using cascaded delete - self.deleteSpecification(task['specification_id'], False) - - task = self.getTask(mom_id=mom_id) - - if task: - # delete old specification, task, and resource claims using cascaded delete - self.deleteSpecification(task['specification_id'], False) + else: + task = self.getTask(mom_id=mom_id) + if task: + # delete old specification, task, and resource claims using cascaded delete + self.deleteSpecification(task['specification_id'], False) specId = self.insertSpecification(starttime, endtime, content, cluster, False) taskId = self.insertTask(mom_id, otdb_id, task_status, task_type, specId, False) if specId >= 0 and taskId >= 0: + # restore "old" predecessor/successor relationships if needed + if task['predecessor_ids']: + self.insertTaskPredecessors(taskId, task['predecessor_ids'], False) + if task['successor_ids']: + for suc_id in task['successor_ids']: + self.insertTaskPredecessor(suc_id, taskId, False) + if commit: self.commit() return {'inserted': True, 'specification_id': specId, 'task_id': taskId} -- GitLab From f4dcd85052be76682339cff6d48b6983b0fe79c0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 7 Sep 2016 14:31:56 +0000 Subject: [PATCH 777/933] Task #9607: check for None --- .../ResourceAssignmentDatabase/radb.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index af0854ef9b1..4026758ce6e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -1349,26 +1349,27 @@ class RADatabase: Removes existing task with same mom_id if present in the same transaction. ''' try: - task = self.getTask(otdb_id=otdb_id) - if task: + existing_task = self.getTask(otdb_id=otdb_id) + if existing_task: # delete old specification, task, and resource claims using cascaded delete - self.deleteSpecification(task['specification_id'], False) + self.deleteSpecification(existing_task['specification_id'], False) else: - task = self.getTask(mom_id=mom_id) - if task: + existing_task = self.getTask(mom_id=mom_id) + if existing_task: # delete old specification, task, and resource claims using cascaded delete - self.deleteSpecification(task['specification_id'], False) + self.deleteSpecification(existing_task['specification_id'], False) specId = self.insertSpecification(starttime, endtime, content, cluster, False) taskId = self.insertTask(mom_id, otdb_id, task_status, task_type, specId, False) if specId >= 0 and taskId >= 0: # restore "old" predecessor/successor relationships if needed - if task['predecessor_ids']: - self.insertTaskPredecessors(taskId, task['predecessor_ids'], False) - if task['successor_ids']: - for suc_id in task['successor_ids']: - self.insertTaskPredecessor(suc_id, taskId, False) + if existing_task: + if existing_task['predecessor_ids']: + self.insertTaskPredecessors(taskId, existing_task['predecessor_ids'], False) + if existing_task['successor_ids']: + for suc_id in existing_task['successor_ids']: + self.insertTaskPredecessor(suc_id, taskId, False) if commit: self.commit() -- GitLab From 88bc74bbbb8f0dab627af5aa1a8a0734ac97be86 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Wed, 7 Sep 2016 14:36:05 +0000 Subject: [PATCH 778/933] Task #9785: more fixes for tecandphase renaming --- CEP/DP3/DPPP/src/StefCal.cc | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/CEP/DP3/DPPP/src/StefCal.cc b/CEP/DP3/DPPP/src/StefCal.cc index 75aa83ae695..2b278a26ce1 100644 --- a/CEP/DP3/DPPP/src/StefCal.cc +++ b/CEP/DP3/DPPP/src/StefCal.cc @@ -57,11 +57,13 @@ namespace LOFAR { _nCr=4; _nSp=1; _savedNCr=4; - } else if (_mode=="scalarphase" || _mode=="tec" || _mode=="scalaramplitude") { + } else if (_mode=="scalarphase" || _mode=="tec" || _mode=="scalaramplitude" + || _mode=="tecandphase") { _nCr=1; _nSp=2; _savedNCr=1; } else { // mode=="phaseonly", mode=="diagonal", mode=="amplitudeonly" + ASSERT (_mode=="phaseonly" || _mode=="diagonal" || _mode=="amplitudeonly"); _nCr=1; _nSp=1; _savedNCr=2; @@ -70,9 +72,11 @@ namespace LOFAR { _vis.resize(IPosition(6,_nSt,2,_solInt,_nChan,2,_nSt)); _mvis.resize(IPosition(6,_nSt,2,_solInt,_nChan,2,_nSt)); - if (_mode=="fulljones" || _mode=="scalarphase" || _mode=="tec" || _mode=="scalaramplitude") { + if (_mode=="fulljones" || _mode=="scalarphase" || _mode=="tec" || _mode=="scalaramplitude" + || _mode=="tecandphase") { _nUn = _nSt; - } else { // mode=="phaseonly", mode=="diagonal", mode=="amplitudeonly" + } else { + ASSERT (_mode=="phaseonly" || _mode=="diagonal" || _mode=="amplitudeonly"); _nUn = 2*_nSt; } @@ -108,7 +112,8 @@ namespace LOFAR { if (initSolutions) { double ginit=1.0; - if (_mode != "phaseonly" && _mode != "scalarphase" && _mode != "tec") { + if (_mode != "phaseonly" && _mode != "scalarphase" && + _mode != "tec" && _mode != "tecandphase") { // Initialize solution with sensible amplitudes double fronormvis=0; double fronormmod=0; @@ -329,7 +334,7 @@ namespace LOFAR { ASSERT(ww!=0); _g(st1,0)=tt/ww; //cout<<", g="<<iS.g(st1,0)<<endl; - if (_mode=="phaseonly" || _mode=="scalarphase" || _mode=="tec") { + if (_mode=="phaseonly" || _mode=="scalarphase" || _mode=="tec" || _mode=="tecandphase") { ASSERT(abs(_g(st1,0))!=0); _g(st1,0)/=abs(_g(st1,0)); ASSERT(isFinite(_g(st1,0))); @@ -401,11 +406,11 @@ namespace LOFAR { double c2 = 1.2; double dgxx; bool threestep = false; - uint maxBadIters=(_mode=="tec"?2:3); + uint maxBadIters=(_mode=="tec"||_mode=="tecandphase"?2:3); int sstep=0; - if ((_detectStalling && iter > 3) || (_mode=="tec" && iter>2)) { + if ((_detectStalling && iter > 3) || ((_mode=="tec"||_mode=="tecandphase") && iter>2)) { double improvement = _dgx-_dg; if (abs(improvement) < 5.0e-2*_dg) { -- GitLab From b47f731bd511b668ad2fd3d5c9207c41a27e5b59 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Wed, 7 Sep 2016 14:37:29 +0000 Subject: [PATCH 779/933] Task #9834: add missing loop in stefcal --- CEP/DP3/DPPP/src/StefCal.cc | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/CEP/DP3/DPPP/src/StefCal.cc b/CEP/DP3/DPPP/src/StefCal.cc index 2b278a26ce1..a9e2f624103 100644 --- a/CEP/DP3/DPPP/src/StefCal.cc +++ b/CEP/DP3/DPPP/src/StefCal.cc @@ -309,21 +309,21 @@ namespace LOFAR { double ww=0; // Same as w, but specifically for pol==false DComplex tt=0; // Same as t, but specifically for pol==false - DComplex* z_p=_z.data(); mvis_p=&_mvis(IPosition(6,0,0,0,0,st1/_nSt,st1%_nSt)); vis_p = &_vis(IPosition(6,0,0,0,0,st1/_nSt,st1%_nSt)); for (uint st1pol=0;st1pol<_nSp;++st1pol) { for (uint ch=0;ch<_nChan;++ch) { for (uint time=0;time<_solInt;++time) { - DComplex* h_p=_h.data(); - for (uint st2=0;st2<_nUn;++st2) { - *z_p = h_p[st2] * *mvis_p; //itsMVis(IPosition(6,st2%nSt,st2/nSt,time,ch,st1/nSt,st1%nSt)); - ASSERT(isFinite(*z_p)); - ww+=norm(*z_p); - tt+=conj(*z_p) * *vis_p; //itsVis(IPosition(6,st2%nSt,st2/nSt,time,ch,st1/nSt,st1%nSt)); - mvis_p++; - vis_p++; - z_p++; + for (uint st2pol=0;st2pol<_nSp;++st2pol) { + DComplex* h_p=_h.data(); + for (uint st2=0;st2<_nUn;++st2) { + DComplex z(h_p[st2] * *mvis_p); //itsMVis(IPosition(6,st2%nSt,st2/nSt,time,ch,st1/nSt,st1%nSt)); + ASSERT(isFinite(z)); + ww+=norm(z); + tt+=conj(z) * *vis_p; //itsVis(IPosition(6,st2%nSt,st2/nSt,time,ch,st1/nSt,st1%nSt)); + mvis_p++; + vis_p++; + } } //cout<<"iS.z bij ch="<<ch<<"="<<iS.z<<endl<<"----"<<endl; } -- GitLab From b3a92bf5373d88e6b992453a22b65980cb2f2503 Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Fri, 9 Sep 2016 08:08:00 +0000 Subject: [PATCH 780/933] Task #9612: Fix broken build... --- CMake/LofarPackageList.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index 8a3f4855120..5fee65dd22d 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -1,7 +1,7 @@ # - Create for each LOFAR package a variable containing the absolute path to # its source directory. # -# Generated by gen_LofarPackageList_cmake.sh at ma 29 aug 2016 12:57:19 CEST +# Generated by gen_LofarPackageList_cmake.sh at Fri Sep 9 08:05:41 UTC 2016 # # ---- DO NOT EDIT ---- # @@ -38,6 +38,7 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(DPPP_AOFlag_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/DPPP_AOFlag) set(SPW_Combine_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/SPWCombine) set(LofarFT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Imager/LofarFT) + set(AOFlagger_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/AOFlagger) set(AWImager2_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Imager/AWImager2) set(Laps-GRIDInterface_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/LAPS/GRIDInterface) set(Laps-ParsetCombiner_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/LAPS/ParsetCombiner) -- GitLab From 081bb610341257396153a1c55206b3c9bf466ca2 Mon Sep 17 00:00:00 2001 From: Pieter Donker <donker@astron.nl> Date: Fri, 9 Sep 2016 08:31:42 +0000 Subject: [PATCH 781/933] Task #9843: stationtest new software setup and some new functions power tools now universal --- .gitattributes | 71 +- LCU/checkhardware/checkHardware.conf | 82 - LCU/checkhardware/checkHardware.py | 707 ------- LCU/checkhardware/check_hardware.conf | 244 +++ LCU/checkhardware/check_hardware.py | 625 +++++++ .../checkhardware_lib/__init__.py | 13 + LCU/checkhardware/checkhardware_lib/data.py | 405 ++++ LCU/checkhardware/checkhardware_lib/db.py | 531 ++++++ LCU/checkhardware/checkhardware_lib/db_new.py | 0 .../checkhardware_lib/general.py | 146 ++ .../checkhardware_lib/hardware_tests.py | 32 + LCU/checkhardware/checkhardware_lib/hba.py | 827 +++++++++ LCU/checkhardware/checkhardware_lib/lba.py | 474 +++++ LCU/checkhardware/checkhardware_lib/lofar.py | 608 ++++++ .../checkhardware_lib/reporting.py | 448 +++++ LCU/checkhardware/checkhardware_lib/rsp.py | 150 ++ .../checkhardware_lib/settings.py | 158 ++ .../spectrum_checks/__init__.py | 10 + .../spectrum_checks/cable_reflection.py | 62 + .../checkhardware_lib/spectrum_checks/down.py | 245 +++ .../spectrum_checks/down_old.py | 162 ++ .../checkhardware_lib/spectrum_checks/flat.py | 24 + .../spectrum_checks/noise.py | 109 ++ .../spectrum_checks/oscillation.py | 68 + .../spectrum_checks/peakslib.py | 149 ++ .../spectrum_checks/rf_power.py | 82 + .../spectrum_checks/short.py | 25 + .../spectrum_checks/spurious.py | 64 + .../spectrum_checks/summator_noise.py | 69 + .../spectrum_checks/tools.py | 19 + LCU/checkhardware/checkhardware_lib/spu.py | 135 ++ LCU/checkhardware/checkhardware_lib/tbb.py | 166 ++ .../checkhardware_lib/test_db.py | 0 .../{doStationTest.sh => do_station_test.sh} | 41 +- LCU/checkhardware/lib/data_lib.py | 228 --- LCU/checkhardware/lib/general_lib.py | 126 -- LCU/checkhardware/lib/lofar_lib.py | 510 ------ LCU/checkhardware/lib/search_lib.py | 498 ----- LCU/checkhardware/lib/test_db.py | 883 --------- LCU/checkhardware/lib/test_lib.py | 1623 ----------------- LCU/checkhardware/rtsm.py | 1095 ++++++----- ...{showBadSpectra.py => show_bad_spectra.py} | 11 +- ...{showTestResult.py => show_test_result.py} | 92 +- .../{updatePVSS.py => update_pvss.py} | 202 +- MAC/APL/APLCommon/src/swlevel | 796 ++++---- MAC/Tools/Power/ecSetObserving.py | 69 - .../{isResetTrip.py => ec_reset_trip.py} | 19 +- MAC/Tools/Power/ec_set_observing.py | 57 + MAC/Tools/Power/isEcLib.py | 259 --- MAC/Tools/Power/nlStatus.py | 48 - MAC/Tools/Power/nlStatusData.py | 61 - .../Power/{isReset48V.py => reset_48v.py} | 19 +- .../Power/{isResetLCU.py => reset_lcu.py} | 15 +- MAC/Tools/Power/{nlEcLib.py => st_ec_lib.py} | 163 +- MAC/Tools/Power/{isStatus.py => status.py} | 19 +- .../Power/{isStatusData.py => status_data.py} | 4 +- .../{isTurnOff48V.py => turn_off_48v.py} | 19 +- .../{isTurnOffLCU.py => turn_off_lcu.py} | 15 +- .../Power/{isTurnOn48V.py => turn_on_48v.py} | 19 +- .../Power/{isTurnOnLCU.py => turn_on_lcu.py} | 19 +- 60 files changed, 7459 insertions(+), 6361 deletions(-) delete mode 100644 LCU/checkhardware/checkHardware.conf delete mode 100755 LCU/checkhardware/checkHardware.py create mode 100755 LCU/checkhardware/check_hardware.conf create mode 100755 LCU/checkhardware/check_hardware.py create mode 100644 LCU/checkhardware/checkhardware_lib/__init__.py create mode 100644 LCU/checkhardware/checkhardware_lib/data.py create mode 100644 LCU/checkhardware/checkhardware_lib/db.py create mode 100644 LCU/checkhardware/checkhardware_lib/db_new.py create mode 100644 LCU/checkhardware/checkhardware_lib/general.py create mode 100644 LCU/checkhardware/checkhardware_lib/hardware_tests.py create mode 100644 LCU/checkhardware/checkhardware_lib/hba.py create mode 100644 LCU/checkhardware/checkhardware_lib/lba.py create mode 100644 LCU/checkhardware/checkhardware_lib/lofar.py create mode 100644 LCU/checkhardware/checkhardware_lib/reporting.py create mode 100644 LCU/checkhardware/checkhardware_lib/rsp.py create mode 100644 LCU/checkhardware/checkhardware_lib/settings.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/__init__.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/cable_reflection.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/down.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/down_old.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/flat.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/noise.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/oscillation.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/peakslib.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/rf_power.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/short.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/spurious.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/summator_noise.py create mode 100644 LCU/checkhardware/checkhardware_lib/spectrum_checks/tools.py create mode 100644 LCU/checkhardware/checkhardware_lib/spu.py create mode 100644 LCU/checkhardware/checkhardware_lib/tbb.py create mode 100644 LCU/checkhardware/checkhardware_lib/test_db.py rename LCU/checkhardware/{doStationTest.sh => do_station_test.sh} (61%) delete mode 100644 LCU/checkhardware/lib/data_lib.py delete mode 100644 LCU/checkhardware/lib/general_lib.py delete mode 100644 LCU/checkhardware/lib/lofar_lib.py delete mode 100644 LCU/checkhardware/lib/search_lib.py delete mode 100644 LCU/checkhardware/lib/test_db.py delete mode 100644 LCU/checkhardware/lib/test_lib.py rename LCU/checkhardware/{showBadSpectra.py => show_bad_spectra.py} (92%) rename LCU/checkhardware/{showTestResult.py => show_test_result.py} (91%) rename LCU/checkhardware/{updatePVSS.py => update_pvss.py} (87%) delete mode 100755 MAC/Tools/Power/ecSetObserving.py rename MAC/Tools/Power/{isResetTrip.py => ec_reset_trip.py} (61%) create mode 100755 MAC/Tools/Power/ec_set_observing.py delete mode 100755 MAC/Tools/Power/isEcLib.py delete mode 100755 MAC/Tools/Power/nlStatus.py delete mode 100755 MAC/Tools/Power/nlStatusData.py rename MAC/Tools/Power/{isReset48V.py => reset_48v.py} (64%) mode change 100755 => 100644 rename MAC/Tools/Power/{isResetLCU.py => reset_lcu.py} (83%) rename MAC/Tools/Power/{nlEcLib.py => st_ec_lib.py} (64%) rename MAC/Tools/Power/{isStatus.py => status.py} (63%) rename MAC/Tools/Power/{isStatusData.py => status_data.py} (95%) rename MAC/Tools/Power/{isTurnOff48V.py => turn_off_48v.py} (62%) rename MAC/Tools/Power/{isTurnOffLCU.py => turn_off_lcu.py} (71%) rename MAC/Tools/Power/{isTurnOn48V.py => turn_on_48v.py} (62%) rename MAC/Tools/Power/{isTurnOnLCU.py => turn_on_lcu.py} (62%) diff --git a/.gitattributes b/.gitattributes index 5284d66fa33..d4dd52253b0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2929,19 +2929,41 @@ LCU/StationTest/xc_160_setup.sh eol=lf LCU/StationTest/xc_160_verify.sh eol=lf LCU/StationTest/xc_200_setup.sh eol=lf LCU/StationTest/xc_200_verify.sh eol=lf -LCU/checkhardware/checkHardware.conf -text -LCU/checkhardware/checkHardware.py -text -LCU/checkhardware/doStationTest.sh -text svneol=unset#application/x-shellscript -LCU/checkhardware/lib/data_lib.py -text -LCU/checkhardware/lib/general_lib.py -text -LCU/checkhardware/lib/lofar_lib.py -text -LCU/checkhardware/lib/search_lib.py -text -LCU/checkhardware/lib/test_db.py -text -LCU/checkhardware/lib/test_lib.py -text +LCU/checkhardware/check_hardware.conf -text +LCU/checkhardware/check_hardware.py -text +LCU/checkhardware/checkhardware_lib/__init__.py -text +LCU/checkhardware/checkhardware_lib/data.py -text +LCU/checkhardware/checkhardware_lib/db.py -text +LCU/checkhardware/checkhardware_lib/db_new.py -text +LCU/checkhardware/checkhardware_lib/general.py -text +LCU/checkhardware/checkhardware_lib/hardware_tests.py -text +LCU/checkhardware/checkhardware_lib/hba.py -text +LCU/checkhardware/checkhardware_lib/lba.py -text +LCU/checkhardware/checkhardware_lib/lofar.py -text +LCU/checkhardware/checkhardware_lib/reporting.py -text +LCU/checkhardware/checkhardware_lib/rsp.py -text +LCU/checkhardware/checkhardware_lib/settings.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/__init__.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/cable_reflection.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/down.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/down_old.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/flat.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/noise.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/oscillation.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/peakslib.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/rf_power.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/short.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/spurious.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/summator_noise.py -text +LCU/checkhardware/checkhardware_lib/spectrum_checks/tools.py -text +LCU/checkhardware/checkhardware_lib/spu.py -text +LCU/checkhardware/checkhardware_lib/tbb.py -text +LCU/checkhardware/checkhardware_lib/test_db.py -text +LCU/checkhardware/do_station_test.sh -text svneol=unset#application/x-shellscript LCU/checkhardware/rtsm.py -text -LCU/checkhardware/showBadSpectra.py -text -LCU/checkhardware/showTestResult.py -text -LCU/checkhardware/updatePVSS.py -text +LCU/checkhardware/show_bad_spectra.py -text +LCU/checkhardware/show_test_result.py -text +LCU/checkhardware/update_pvss.py -text LTA/LTAIngest/ClientForm-0.1.17/ClientForm-0.1.17/PKG-INFO -text LTA/LTAIngest/ClientForm-0.1.17/PKG-INFO -text LTA/LTAIngest/SOAPpy-0.12.0/LICENSE -text @@ -4335,20 +4357,17 @@ MAC/Tools/NTP/timepps.h -text MAC/Tools/NTP/timer.c -text MAC/Tools/NTP/timer.c.patch -text MAC/Tools/NTP/timex.h -text -MAC/Tools/Power/ecSetObserving.py -text -MAC/Tools/Power/isEcLib.py -text -MAC/Tools/Power/isReset48V.py -text -MAC/Tools/Power/isResetLCU.py -text -MAC/Tools/Power/isResetTrip.py -text -MAC/Tools/Power/isStatus.py -text -MAC/Tools/Power/isStatusData.py -text -MAC/Tools/Power/isTurnOff48V.py -text -MAC/Tools/Power/isTurnOffLCU.py -text -MAC/Tools/Power/isTurnOn48V.py -text -MAC/Tools/Power/isTurnOnLCU.py -text -MAC/Tools/Power/nlEcLib.py -text -MAC/Tools/Power/nlStatus.py -text -MAC/Tools/Power/nlStatusData.py -text +MAC/Tools/Power/ec_reset_trip.py -text +MAC/Tools/Power/ec_set_observing.py -text +MAC/Tools/Power/reset_48v.py -text +MAC/Tools/Power/reset_lcu.py -text +MAC/Tools/Power/st_ec_lib.py -text +MAC/Tools/Power/status.py -text +MAC/Tools/Power/status_data.py -text +MAC/Tools/Power/turn_off_48v.py -text +MAC/Tools/Power/turn_off_lcu.py -text +MAC/Tools/Power/turn_on_48v.py -text +MAC/Tools/Power/turn_on_lcu.py -text MAC/Tools/Rubidium/filter.py -text MAC/Tools/Rubidium/rlp.py -text MAC/Tools/Rubidium/rr.py -text diff --git a/LCU/checkhardware/checkHardware.conf b/LCU/checkhardware/checkHardware.conf deleted file mode 100644 index d2ddd83069c..00000000000 --- a/LCU/checkhardware/checkHardware.conf +++ /dev/null @@ -1,82 +0,0 @@ - -# configuration file for checkHardware.py -# -# tests to do in given levels, level-0 = empty, test can be given as arguments -# -# Select checks to do, can be combined with all levels -# -s(rcumode) : signal check for rcumode (also down and flat-check in rcumode 1..4). -# -o(rcumode) : oscillation check for rcumode. -# -sp(rcumode) : spurious check for rcumode. -# -n(rcumode)[=300] : noise check for rcumode, optional data time in seconds -# default data time = 120 sec. -# -e(rcumode)[=60] : do all RCU5 element tests, optional data time in seconds. -# default data time = 10 sec. -# -m(rcumode) : do modem check. -# -sn(rcumode) : do summator noise check. -# -# -rcu(mode) : do all rcu checks for given mode, no element tests done. -# -# -rbc : RSP voltage/temperature check -# -spu : SPU voltage check. -# -tm : TBB memmory check. -# - -level-0-tests= -level-1-tests=RBC,SPU,TM,RCU1,RCU3,RCU5 -level-2-tests=RBC,SPU,TM,RCU1,RCU3,RCU5,E5 -level-3-tests=S1,S3 - -# TBB versions -tbbdriver-version=2.51 -tbbctl-version=2.51 -tp-version=2.4 -mp-version=3.0 - -# RSP versions -ap-version=8.2 -bp-version=8.2 -#ap-version=9.3 -#bp-version=9.3 - -# LBA test settings, limits in dB -# limits (min/max) in dB -# test-sb = subband used in RF test - -lbl-test-sb=301 -lbh-test-sb=301 - -lba-rf-min-signal=75.0 -lba-rf-min-deviation=-2.0 -lba-rf-max-deviation=2.0 - -lba-noise-min-deviation=-2.5 -lba-noise-max-deviation=2.5 -lba-noise-max-difference=1.5 - - -# RCU5 test settings -# limits (min/max) in dB -# test_sb = subband used in RF test - -hba-test-sb=357 - -hba-rf-min-signal=80.0 -hba-rf-min-deviation=-24.0 -hba-rf-max-deviation=12.0 - -ehba-rf-min-signal=70.0 -ehba-rf-min-deviation=-24.0 -ehba-rf-max-deviation=12.0 - -hba-noise-min-deviation=-3.0 -hba-noise-max-deviation=1.5 -hba-noise-max-difference=1.5 - -ehba-noise-min-deviation=-3.0 -ehba-noise-max-deviation=1.5 -ehba-noise-max-difference=1.5 - -# General settings -log-dir-global=/globalhome/log/stationtest -log-dir-local=/opt/stationtest/data - diff --git a/LCU/checkhardware/checkHardware.py b/LCU/checkhardware/checkHardware.py deleted file mode 100755 index cda79248ee1..00000000000 --- a/LCU/checkhardware/checkHardware.py +++ /dev/null @@ -1,707 +0,0 @@ -#!/usr/bin/python - - -info = ''' ---------------------------------------------------------------------------- - Usage of arguments - -cf=fullfilename : full path and filename for the configurationfile to use. - -l=2 : set level to 2 (default level is 0) - level 0 : manual checks, use keys listed below - level 1..n : see checkhardware.conf file for checks done - - To start a long check set number of runs or start and stop time, if start time - is not given the first run is started immediately - -r=1 : repeats, number of runs to do - -start=[date_time]: start time of first run, format [YYYYMMDD_HH:MM:SS] - -stop=[date_time] : stop time of last run, format [YYYYMMDD_HH:MM:SS] - - Set logging level, can be: debug|info|warning|error - -ls=debug : print all information on screen, default=info - -lf=info : print debug|warning|error information to log file, default=debug - - Select checks to do, can be combined with all levels - -s(rcumode) : signal check for rcumode - -sh(rcumode) : short test for rcumode 1..4 - -f(rcumode) : flat test for rcumode 1..4 - -d(rcumode) : down test for rcumode 1..4 - -o(rcumode) : oscillation check for rcumode - -sp(rcumode) : spurious check for rcumode - -n(rcumode)[=120] : noise check for rcumode, optional data time in seconds default = 120 sec. - -e(rcumode)[=120] : do all HBA element tests, optional data time in seconds default = 10 sec. - -sn(rcumode) : HBA summator noise check. - -m(rcumode) : HBA modem check. - - -rcu(mode) : do all rcu(mode) tests - - -rv : RSP version check, always done - -tv : TBB version check, always done - - -rbc : RSP board check, voltage and temperature - -spu : SPU voltage check - -tm : TBB memmory check - - example : ./checkHardware.py -s5 -n5=180 - ----------------------------------------------------------------------------''' - - -import os -import sys -import traceback - -check_version = '0815' - -mainPath = r'/opt/stationtest' -libPath = os.path.join(mainPath, 'lib') -sys.path.insert(0, libPath) - -logPath = r'/localhome/stationtest/log' - -import time -import datetime -import logging - -from general_lib import * -from lofar_lib import * -from test_lib import * -from test_db import * -from data_lib import * -from search_lib import search_version - -os.umask(001) - -logger = None - -rcu_keys = ('RCU1', 'RCU2', 'RCU3', 'RCU4', 'RCU5', 'RCU6', 'RCU7') -rcu_m1_keys = ('O1', 'SP1', 'N1', 'S1', 'SH1', 'D1', 'F1') -rcu_m2_keys = ('O2', 'SP2', 'N2', 'S2', 'SH2', 'D2', 'F2') -rcu_m3_keys = ('O3', 'SP3', 'N3', 'S3', 'SH3', 'D3', 'F3') -rcu_m4_keys = ('O4', 'SP4', 'N4', 'S4', 'SH4', 'D4', 'F4') -rcu_m5_keys = ('M5', 'O5', 'SN5', 'SP5', 'N5', 'S5', 'E5') -rcu_m6_keys = ('M6', 'O6', 'SN6', 'SP6', 'N6', 'S6', 'E6') -rcu_m7_keys = ('M7', 'O7', 'SN7', 'SP7', 'N7', 'S7', 'E7') - -rcu_m12_keys = rcu_m1_keys + rcu_m2_keys -rcu_m34_keys = rcu_m3_keys + rcu_m4_keys -rcu_m567_keys = rcu_m5_keys + rcu_m6_keys + rcu_m7_keys - -rsp_keys = ('RV', 'SPU', 'RBC') + rcu_keys + rcu_m12_keys + rcu_m34_keys + rcu_m567_keys -tbb_keys = ('TV', 'TM') -control_keys = ('R', 'START', 'STOP') -all_keys = control_keys + rsp_keys + tbb_keys -rsp_check = False -tbb_check = False - -args = dict() - -# version checks are always done -args['RV'] = '-' -args['TV'] = '-' - - -def printHelp(): - print info - - -# return readable info for test -def getTestInfo(key=''): - if key[-1] in '1234567': - test_name = '' - test = key[:-1] - mode = key[-1] - - if mode in '1234': - ant_type = 'LBA' - else: - ant_type = 'HBA' - - if test in ('O',): - test_name += 'Oscillation' - if test in ('SP',): - test_name += 'Spurious' - if test in ('N',): - test_name += 'Noise' - if test in ('S',): - test_name += 'RF' - if test in ('SH',): - test_name += 'Short' - if test in ('D',): - test_name += 'Down' - if test in ('F',): - test_name += 'Flat' - if test in ('S',): - test_name += 'RF' - if test in ('SN',): - test_name += 'Summator noise' - if test in ('E',): - test_name += 'Element' - if test in ('M',): - test_name += 'Modem' - if test in ('RCU',): - test_name += 'All tests' - - return '%s mode-%c %s check' % (ant_type, mode, test_name) - - if key in 'RV': - return 'RSP Version check' - if key in 'SPU': - return 'SPU check' - if key in 'RBC': - return 'RSP board check' - if key in 'TV': - return 'TBB Version check' - if key in 'TM': - return 'TBB Memory check' - if key in 'START': - return 'START checks' - if key in 'STOP': - return 'STOP checks' - if key in 'R': - return 'Number of test repeats set to' - return('') - - -def addToArgs(key, value): - if key == '': - return - global args, rsp_check, tbb_check - if key in rsp_keys or key in tbb_keys or key in ('H', 'L', 'LS', 'LF', 'R', 'START', 'STOP'): - if value != '-': - args[key] = value - else: - args[key] = '-' - - if key in rsp_keys: - rsp_check = True - if key in tbb_keys: - tbb_check = True - else: - sys.exit('Unknown key %s' % (key)) - return - - -def getArguments(): - key = '' - value = '-' - for arg in sys.argv[1:]: - if arg[0] == '-': - opt = arg[1:].upper() - valpos = opt.find('=') - if valpos != -1: - key, value = opt.strip().split('=') - else: - key, value = opt, '-' - addToArgs(key=key, value=value) - return - - -# get checklevel and set tests to do -def setLevelTests(conf): - level = args.get('L', '0') - if level == '0': - return - tests = conf.getStr('level-%s-tests' % (level)).split(',') - for tst in tests: - opt = tst.upper() - valpos = opt.find('=') - if valpos != -1: - key, value = opt.strip().split('=') - else: - key, value = opt, '-' - key = tst.upper() - addToArgs(key=key, value=value) - return - - -# get and unpack configuration file -class cConfiguration: - def __init__(self): - self.conf = dict() - if 'CF' in args: - filename = args.get('CF') - else: - filename = '/localhome/stationtest/config/checkHardware.conf' - - f = open('/localhome/stationtest/config/checkHardware.conf', 'r') - data = f.readlines() - f.close() - for line in data: - if line[0] in ('#', '\n', ' '): - continue - if line.find('#') > 0: - line = line[:line.find('#')] - try: - key, value = line.strip().split('=') - key = key.replace('_', '-') - self.conf[key] = value - except: - print 'Not a valid configuration setting: %s' % (line) - - def getInt(self, key, default=0): - return (int(self.conf.get(key, str(default)))) - - def getFloat(self, key, default=0.0): - return (float(self.conf.get(key, str(default)))) - - def getStr(self, key): - return (self.conf.get(key, '')) - - -# setup default python logging system -# logstream for screen output -# filestream for program log file -def init_logging(): - global logger - - log_levels = {'DEBUG' : logging.DEBUG, - 'INFO' : logging.INFO, - 'WARNING': logging.WARNING, - 'ERROR' : logging.ERROR} - - try: - screen_log_level = args.get('LS', 'WARNING') - file_log_level = args.get('LF', 'DEBUG') - except: - print 'Not a legal log level, try again' - sys.exit(-1) - - station = getHostName() - - # create logger - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) - - # check if log dir exist - if not os.access(logPath, os.F_OK): - os.mkdir(logPath) - - # create file handler - full_filename = os.path.join(logPath, 'checkHardware.log') - # backup_filename = os.path.join(mainPath, 'checkHardware_bk.log') - # sendCmd('cp', '%s %s' % (full_filename, backup_filename)) - file_handler = logging.FileHandler(full_filename, mode='w') - formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') - file_handler.setFormatter(formatter) - file_handler.setLevel(log_levels[file_log_level]) - logger.addHandler(file_handler) - - if len(logger.handlers) == 1: - # create console handler - stream_handler = logging.StreamHandler() - fmt = '%s %%(levelname)-8s %%(message)s' % (station) - formatter = logging.Formatter(fmt) - stream_handler.setFormatter(formatter) - stream_handler.setLevel(log_levels[screen_log_level]) - logger.addHandler(stream_handler) - return - - -def backupLogFiles(): - for nr in range(8, -1, -1): - if nr == 0: - full_filename = os.path.join(logPath, 'checkHardware.log') - else: - full_filename = os.path.join(logPath, 'checkHardware.log.%d' % (nr)) - full_filename_new = os.path.join(logPath, 'checkHardware.log.%d' % (nr+1)) - if os.path.exists(full_filename): - os.rename(full_filename, full_filename_new) - return - - -def waitForStart(start_datetime): - start_time = time.mktime(start_datetime.timetuple()) - if start_time > time.time(): - logger.info('delayed start, sleep till %s' % (time.asctime(start_datetime.timetuple()))) - - while start_time > time.time(): - wait_time = start_time - time.time() - sleep_time = min(wait_time, 3600.0) - time.sleep(sleep_time) - return - - -def main(): - getArguments() - # print args - if len(args) == 0 or 'H' in args: - printHelp() - sys.exit() - - backupLogFiles() # backup logfiles, max 10 logfiles .9 is the oldest - - init_logging() - init_lofar_lib() - init_test_db() - init_test_lib() - init_data_lib() - - conf = cConfiguration() - - setLevelTests(conf) - - StID = getHostName() - - logger.info('== START HARDWARE CHECK ==') - logger.info('== requested checks and settings ==') - logger.info('-'*40) - for i in all_keys: - if i in args: - if args.get(i) == '-': - logger.info(' %s' % (getTestInfo(i))) - else: - logger.info(' %s, time = %s' % (getTestInfo(i), args.get(i))) - logger.info('-'*40) - - removeAllDataFiles() - - # use format YYYYMMDD_HH:MM:SS - stop_time = -1 - if 'STOP' in args: - stop = args.get('STOP') - if len(stop) != 17: - return 'wrong stoptime format must be YYYYMMDD_HH:MM:SS' - stop_datetime = datetime.datetime(int(stop[:4]), int(stop[4:6]), int(stop[6:8]), - int(stop[9:11]), int(stop[12:14]), int(stop[15:])) - stop_time = time.mktime(stop_datetime.timetuple()) - - if 'START' in args: - start = args.get('START') - if len(start) != 17: - return 'wrong starttime format must be YYYYMMDD_HH:MM:SS' - start_datetime = datetime.datetime(int(start[:4]), int(start[4:6]), int(start[6:8]), - int(start[9:11]), int(start[12:14]), int(start[15:])) - if (time.mktime(start_datetime.timetuple()) < time.time()): - # print time.mktime(start_datetime.timetuple()), time.time() - logger.error('Stop program, StartTime in past') - return 2 - if(time.mktime(start_datetime.timetuple()) > stop_time): - logger.error('Stop program, stop before start') - return 2 - waitForStart(start_datetime) - - logger.info('run checks till %s' % (time.asctime(stop_datetime.timetuple()))) - - start_time = time.gmtime() - # Read in RemoteStation.conf - ID, nRSP, nTBB, nLBL, nLBH, nHBA, HBA_SPLIT = readStationConfig() - - # setup intern database with station layout - db = cDB(StID, nRSP, nTBB, nLBL, nLBH, nHBA, HBA_SPLIT) - - if (stop_time > 0.0): - db.setTestEndTime((stop_time-120.0)) - - # set manualy marked bad antennas - global_log_dir = conf.getStr('log-dir-global') - host = getHostName() - if os.path.exists(global_log_dir): - full_filename = os.path.join(global_log_dir, 'bad_antenna_list.txt') - logger.info('add bad_antenna_list data from file "%s" to db' % (full_filename)) - f = open(full_filename, 'r') - data = f.readlines() - for line in data: - if line[0] == '#': - continue - ant_list = line.strip().split(' ') - if ant_list[0].strip().upper() == host.upper(): - if len(ant_list) > 1: - for ant in ant_list[1:]: - ant_type = ant[:3].strip().upper() - if ant_type == 'LBA': - ant_nr = int(ant[3:].strip()) - # print 'ant type=%s nr=%d' % (ant_type, ant_nr) - if ant_nr < nLBH: - db.lbh.ant[ant_nr].on_bad_list = 1 - else: - db.lbl.ant[ant_nr-nLBH].on_bad_list = 1 - elif ant_type == 'HBA': - ant_nr = int(ant[3:].strip()) - # print 'ant type=%s nr=%d' % (ant_type, ant_nr) - db.hba.tile[ant_nr].on_bad_list = 1 - break - else: - logger.warn('bad_antenna_list data from file "%s" not found' % (full_filename)) - - db.script_versions = 'CHECK=%s,DB=%s,TEST=%s,SEARCH=%s,LOFAR=%s,GENERAL=%s' %\ - (check_version, db_version, test_version, search_version, lofar_version, general_version) - db.check_start_time = time.gmtime() - - writeMessage('!!! This station will be in use for a test! Please do not use the station! (script version %s) !!!' % (check_version)) - start_level, board_errors = swlevel() - sw_level, board_errors = swlevel(2) - if start_level == 1: - logger.info('Wait 30 seconds while startup RSPDriver') - time.sleep(30.0) - if sw_level == 2: - tbb = cTBB(db) - rsp = cRSP(db) - spu = cSPU(db) - - # do RSP tests if requested - if rsp_check is True: - # check if RSPDriver is running - if checkActiveRSPDriver() == False: - logger.warn('RSPDriver not running') - else: - # wait for RSP boards ready, and reset 48V if needed, max 2x if no board errors after 48V reset - rsp_ready = False - restarts = 2 - while (not rsp_ready) and (restarts > 0): - if waitRSPready() == False: - logger.warn('Not all RSP boards ready, reset 48V to recover') - swlevel(1) - reset48V() - restarts -= 1 - time.sleep(30.0) - level, board_errors = swlevel(2) - if len(board_errors) > 0: - db.board_errors = board_errors - restarts = 0 - else: - time.sleep(30.0) - else: - rsp_ready = True - restarts = 2 - - # if all rsp boards ready do all tests - if rsp_ready: - if 'RV' in args: - rsp.checkVersions(conf.getStr('bp-version'), conf.getStr('ap-version')) - - resetRSPsettings() - - lbl = cLBA(db, db.lbl) - lbh = cLBA(db, db.lbh) - hba = cHBA(db, db.hba) - - repeats = int(args.get('R', '1')) - repeat_cnt = 1 - - runtime = 0 - db.tests = '' - while (repeat_cnt <= repeats) or ((stop_time > -1) and ((time.time() + runtime) < stop_time)): - - try: - runstart = time.time() - if stop_time > -1: - logger.info('\n=== Start testrun %d ===\n' % (repeat_cnt)) - else: - logger.info('\n=== Start testrun %d of %d ===\n' % (repeat_cnt, repeats)) - - if 'SPU' in args: - spu.checkStatus() - - if 'RBC' in args: - rsp.checkBoard() - - # check if mode 1,2 is available on this station - if StID in CoreStations or StID in RemoteStations: - for mode in (1, 2): - lbl.reset() - # do all rcumode 1,2 tests - if 'RCU%d' % mode in args or 'SH%d' % mode in args: - lbl.checkShort(mode=mode) - - if 'RCU%d' % mode in args or 'F%d' % mode in args: - lbl.checkFlat(mode=mode) - - if 'RCU%d' % mode in args or 'D%d' % mode in args: - lbl.checkDown(mode=mode, subband=conf.getInt('lbl-test-sb', 301)) - - if 'RCU%d' % mode in args or 'O%d' % mode in args: - lbl.checkOscillation(mode=mode) - - if 'RCU%d' % mode in args or 'SP%d' % mode in args: - lbl.checkSpurious(mode=mode) - - if 'RCU%d' % mode in args or 'N%d' % mode in args: - if 'RCU%d' % mode in args or args.get('N%d' % (mode)) == '-': - recordtime = 120 - else: - recordtime = int(args.get('N%d' % (mode))) - lbl.checkNoise( - mode=mode, - record_time=recordtime, - low_deviation=conf.getFloat('lba-noise-min-deviation', -3.0), - high_deviation=conf.getFloat('lba-noise-max-deviation', 2.5), - max_diff=conf.getFloat('lba-noise-max-difference', 2.0)) - - if 'RCU%d' % mode in args or 'S%d' % mode in args: - lbl.checkSignal( - mode=mode, - subband=conf.getInt('lbl-test-sb', 301), - min_signal=conf.getFloat('lba-rf-min-signal', 75.0), - low_deviation=conf.getFloat('lba-rf-min-deviation', -2.0), - high_deviation=conf.getFloat('lba-rf-max-deviation', 2.0)) - - for mode in (3, 4): - lbh.reset() - # do all rcumode 3,4 tests - if 'RCU%d' % mode in args or 'SH%d' % mode in args: - lbh.checkShort(mode=mode) - - if 'RCU%d' % mode in args or 'F%d' % mode in args: - lbh.checkFlat(mode=mode) - - if 'RCU%d' % mode in args or 'D%d' % mode in args: - lbh.checkDown(mode=mode, subband=conf.getInt('lbh-test-sb', 301)) - - if 'RCU%d' % mode in args or 'O%d' % mode in args: - lbh.checkOscillation(mode=mode) - - if 'RCU%d' % mode in args or 'SP%d' % mode in args: - lbh.checkSpurious(mode=mode) - - if 'RCU%d' % mode in args or 'N%d' % mode in args: - if 'RCU%d' % mode in args or args.get('N%d' % (mode)) == '-': - recordtime = 120 - else: - recordtime = int(args.get('N%d' % (mode))) - lbh.checkNoise( - mode=mode, - record_time=recordtime, - low_deviation=conf.getFloat('lba-noise-min-deviation', -3.0), - high_deviation=conf.getFloat('lba-noise-max-deviation', 2.5), - max_diff=conf.getFloat('lba-noise-max-difference', 1.5)) - - if 'RCU%d' % mode in args or 'S%d' % mode in args: - lbh.checkSignal( - mode=mode, - subband=conf.getInt('lbh-test-sb', 301), - min_signal=conf.getFloat('lba-rf-min-signal', 75.0), - low_deviation=conf.getFloat('lba-rf-min-deviation', -2.0), - high_deviation=conf.getFloat('lba-rf-max-deviation', 2.0)) - - for mode in (5, 6, 7): - # do all rcumode 5, 6, 7 tests - hba.reset() - - if 'RCU%d' % mode in args or 'M%d' % mode in args: - hba.checkModem(mode=mode) - hba.turnOffBadTiles() - - if 'RCU%d' % mode in args or 'O%d' % mode in args: - hba.checkOscillation(mode=mode) - - if 'RCU%d' % mode in args or 'SN%d' % mode in args: - hba.checkSummatorNoise(mode=mode) - - if 'RCU%d' % mode in args or 'SP%d' % mode in args: - hba.checkSpurious(mode=mode) - - if 'RCU%d' % mode in args or 'N%d' % mode in args: - if 'RCU%d' % mode in args or args.get('N%d' % (mode)) == '-': - recordtime = 120 - else: - recordtime = int(args.get('N%d' % (mode))) - hba.checkNoise( - mode=mode, - record_time=recordtime, - low_deviation=conf.getFloat('hba-noise-min-deviation', -3.0), - high_deviation=conf.getFloat('hba-noise-max-deviation', 2.5), - max_diff=conf.getFloat('hba-noise-max-difference', 2.0)) - - # if 'RCU%d' % mode in args or 'S%d' % mode in args: - if 'S%d' % mode in args: - hba.checkSignal( - mode=mode, - subband=conf.getInt('hba-test-sb', 155), - min_signal=conf.getFloat('hba-rf-min-signal', 80.0), - low_deviation=conf.getFloat('hba-rf-min-deviation', -24.0), - high_deviation=conf.getFloat('hba-rf-max-deviation', 12.0)) - - runtime = (time.time() - runstart) - - # All element test - if 'E%d' % mode in args: - if args.get('E%d' % (mode)) == '-': - recordtime = 10 - else: - recordtime = int(args.get('E%d' % (mode))) - - hba.checkElements( - mode=mode, - record_time=recordtime, - subband=conf.getInt('hba-test-sb', 155), - noise_low_deviation=conf.getFloat('ehba-noise-min-deviation', -3.0), - noise_high_deviation=conf.getFloat('ehba-noise-max-deviation', 2.5), - noise_max_diff=conf.getFloat('ehba-noise-max-difference', 1.5), - rf_min_signal=conf.getFloat('ehba-rf-min-signal', 70.0), - rf_low_deviation=conf.getFloat('ehba-rf-min-deviation', -24.0), - rf_high_deviation=conf.getFloat('ehba-rf-max-deviation', 12.0)) - - # stop test if driver stopped - db.rsp_driver_down = not checkActiveRSPDriver() - if db.rsp_driver_down and (restarts > 0): - restarts -= 1 - reset48V() - time.sleep(30.0) - level, board_errors = swlevel(2) - if len(board_errors) > 0: - db.board_errors = board_errors - break - else: - time.sleep(30.0) - - # one run done - repeat_cnt += 1 - - except: - logging.error('Caught %s', str(sys.exc_info()[0])) - logging.error(str(sys.exc_info()[1])) - logging.error('TRACEBACK:\n%s', traceback.format_exc()) - logging.error('Aborting NOW') - break - - db.rsp_driver_down = not checkActiveRSPDriver() - if not db.rsp_driver_down: - resetRSPsettings() - - # do TBB tests if requested - db.tbb_driver_down = not checkActiveTBBDriver() - if (not db.tbb_driver_down) and (tbb_check is True): - # wait for TBB boards ready - if waitTBBready(nTBB) == 1: - try: - if 'TV' in args: - db.addTestDone('TV') - tbb.checkVersions(conf.getStr('tbbdriver-version'), conf.getStr('tbbctl-version'), - conf.getStr('tp-version'), conf.getStr('mp-version')) - if 'TM' in args: - db.addTestDone('TM') - tbb.checkMemory() - except: - logging.error('Program fault, TBB test') - logging.error('Caught %s', str(sys.exc_info()[0])) - logging.error(str(sys.exc_info()[1])) - logging.error('TRACEBACK:\n%s', traceback.format_exc()) - logging.error('Aborting NOW') - - db.check_stop_time = time.gmtime() - - try: - # do db test and write result files to log directory - log_dir = conf.getStr('log-dir-local') - if os.path.exists(log_dir): - logger.info('write result data') - db.test(log_dir) - else: - logger.warn('not a valid log directory') - if not db.rsp_driver_down: - logger.info('Going back to swlevel %d' % (start_level)) - swlevel(start_level) - # delete files from data directory - removeAllDataFiles() - except: - logging.error('Program fault, reporting and cleanup') - logging.error('Caught %s', str(sys.exc_info()[0])) - logging.error(str(sys.exc_info()[1])) - logging.error('TRACEBACK:\n%s', traceback.format_exc()) - logging.error('Aborting NOW') - - logger.info('Test ready.') - writeMessage('!!! The test is ready and the station can be used again! !!!') - - return 0 - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/LCU/checkhardware/check_hardware.conf b/LCU/checkhardware/check_hardware.conf new file mode 100755 index 00000000000..1ae6e3b070e --- /dev/null +++ b/LCU/checkhardware/check_hardware.conf @@ -0,0 +1,244 @@ +# +# configuration file for check_hardware.py +# + +[configuration] +version= 00.01 +station= CS001C + +# checks to do if '-l=x' argument is given, all checks behind list.x are executed +# always checks will be done always +# +# S(rcumode) : signal check for rcumode (also down and flat-check in rcumode 1..4). +# O(rcumode) : oscillation check for rcumode. +# SP(rcumode) : spurious check for rcumode. +# N(rcumode)[= 300]: noise check for rcumode, optional data time in seconds +# default data time= 120 sec. +# E(rcumode)[= 60] : do all RCU5 element tests, optional data time in seconds. +# default data time= 10 sec. +# M(rcumode) : do modem +# SN(rcumode) : do summator noise +# +# RCU(mode) : do all rcu checks for given mode, no element tests done. +# +# RBC : RSP voltage/temperature check +# TBC : TBB voltage/temperature check +# SPU : SPU voltage +# TM : TBB memmory +[check] +always= RV, TV, RBC, TBC +list.0= +list.1= SPU,TM,RCU1,RCU3,RCU5 +list.2= SPU,TM,RCU1,RCU3,RCU5,E7 +list.3= S1,S3 + +[spu] +temperature.min= 10.0 +temperature.max= 35.0 +voltage.3_3.min= 3.1 +voltage.3_3.max= 3.4 +voltage.3_3.max-drop= 0.3 +voltage.5_0.min= 4.5 +voltage.5_0.max= 5.0 +voltage.5_0.max-drop= 0.3 +voltage.8_0.min= 7.4 +voltage.8_0.max= 8.0 +voltage.8_0.max-drop= 0.3 +voltage.48_0.min= 43.0 +voltage.48_0.max= 48.0 +voltage.48_0.max-drop= 2.0 + +[tbb] +version.tp= 2.4 +version.mp= 3.0 +temperature.min= 10.0 +temperature.max= 45.0 +temperature.tp.min= 10.0 +temperature.tp.max= 75.0 +temperature.mp.min= 10.0 +temperature.mp.max= 75.0 +temperature.mp.max_delta= 10.0 +voltage.1_2.min= 1.1 +voltage.1_2.max= 1.3 +voltage.2_5.min= 2.4 +voltage.2_5.max= 2.6 +voltage.3_3.min= 3.1 +voltage.3_3.max= 3.4 + +[rsp] +version.ap= 9.3 +version.bp= 9.3 +temperature.min= 10.0 +temperature.max= 50.0 +temperature.ap.min= 10.0 +temperature.ap.max= 80.0 +temperature.ap.max_delta= 10.0 +temperature.bp.min= 10.0 +temperature.bp.max= 80.0 +temperature.bp.max_delta= 10.0 +voltage.1_2.min= 1.1 +voltage.1_2.max= 1.3 +voltage.2_5.min= 2.4 +voltage.2_5.max= 2.6 +voltage.3_3.min= 3.1 +voltage.3_3.max= 3.4 + +[rcumode.1-3] +short.mean-pwr.min= 55.0 +short.mean-pwr.max= 61.0 +flat.mean-pwr.min= 61.0 +flat.mean-pwr.max= 64.5 +rf.subbands= 231:371 +rf.min-sb-pwr= 75.0 +rf.negative-deviation= -3.0 +rf.positive-deviation= 3.0 +noise.negative-deviation= -2.5 +noise.positive-deviation= 2.5 +noise.max-difference= 1.5 +noise.passband= 1:511 +oscillation.min-peak-pwr= 6.0 +oscillation.passband= 1:511 +cable-reflection.min-peak-pwr= 0.8 +cable-reflection.passband= 1:511 +spurious.min-peak-pwr= 3.0 +spurious.passband= 1:511 +down.passband= 231:371 + +[rcumode.2-4] +short.mean-pwr.min= 55.0 +short.mean-pwr.max= 61.0 +flat.mean-pwr.min= 61.0 +flat.mean-pwr.max= 64.5 +rf.subbands= 231:371 +rf.min-sb-pwr= 75.0 +rf.negative-deviation= -3.0 +rf.positive-deviation= 3.0 +noise.negative-deviation= -2.5 +noise.positive-deviation= 2.5 +noise.max-difference= 1.5 +noise.passband= 1:511 +oscillation.min-peak-pwr= 6.0 +oscillation.passband= 1:511 +cable-reflection.min-peak-pwr= 0.8 +cable-reflection.passband= 1:511 +spurious.min-peak-pwr= 3.0 +spurious.passband= 1:511 +down.passband= 231:371 + +[rcumode.5.tile] +short.mean-pwr.min= 55.0 +short.mean-pwr.max= 61.0 +flat.mean-pwr.min= 61.0 +flat.mean-pwr.max= 64.5 +rf.subbands= 105 +rf.min-sb-pwr= 80.0 +rf.negative-deviation= -24.0 +rf.positive-deviation= 12.0 +noise.negative-deviation= -3.0 +noise.positive-deviation= 1.5 +noise.max-difference= 1.5 +noise.passband= 1:511 +summator-noise.min-peak-pwr= 0.8 +summator-noise.passband= 45:135 +cable-reflection.min-peak-pwr= 0.8 +cable-reflection.passband= 1:511 +oscillation.min-peak-pwr= 6.0 +oscillation.passband= 1:511 +spurious.min-peak-pwr= 3.0 +spurious.passband= 1:511 + +[rcumode.5.element] +rf.subbands= 105 +rf.min-sb-pwr= 70.0 +rf.negative-deviation= -24.0 +rf.positive-deviation= 12.0 +noise.negative-deviation= -3.0 +noise.positive-deviation= 1.5 +noise.max-difference= 1.5 +noise.passband= 1:511 +oscillation.min-peak-pwr= 6.0 +oscillation.passband= 1:511 +spurious.min-peak-pwr= 3.0 +spurious.passband= 1:511 + +[rcumode.6.tile] +short.mean-pwr.min= 55.0 +short.mean-pwr.max= 61.0 +flat.mean-pwr.min= 61.0 +flat.mean-pwr.max= 64.5 +rf.subbands= 105 +rf.min-sb-pwr= 80.0 +rf.negative-deviation= -24.0 +rf.positive-deviation= 12.0 +noise.negative-deviation= -3.0 +noise.positive-deviation= 1.5 +noise.max-difference= 1.5 +noise.passband= 1:511 +summator-noise.min-peak-pwr= 0.8 +summator-noise.passband= 45:135 +cable-reflection.min-peak-pwr= 0.8 +cable-reflection.passband= 1:511 +oscillation.min-peak-pwr= 6.0 +oscillation.passband= 1:511 +spurious.min-peak-pwr= 3.0 +spurious.passband= 1:511 + +[rcumode.6.element] +rf.subbands= 105 +rf.min-sb-pwr= 70.0 +rf.negative-deviation= -24.0 +rf.positive-deviation= 12.0 +noise.negative-deviation= -3.0 +noise.positive-deviation= 1.5 +noise.max-difference= 1.5 +noise.passband= 1:511 +oscillation.min-peak-pwr= 6.0 +oscillation.passband= 1:511 +spurious.min-peak-pwr= 3.0 +spurious.passband= 1:511 + +[rcumode.7.tile] +short.mean-pwr.min= 55.0 +short.mean-pwr.max= 61.0 +flat.mean-pwr.min= 61.0 +flat.mean-pwr.max= 64.5 +rf.subbands= 105 +rf.min-sb-pwr= 80.0 +rf.negative-deviation= -24.0 +rf.positive-deviation= 12.0 +noise.negative-deviation= -3.0 +noise.positive-deviation= 1.5 +noise.max-difference= 1.5 +noise.passband= 1:511 +summator-noise.min-peak-pwr= 0.8 +summator-noise.passband= 45:135 +cable-reflection.min-peak-pwr= 0.8 +cable-reflection.passband= 1:511 +oscillation.min-peak-pwr= 6.0 +oscillation.passband= 1:511 +spurious.min-peak-pwr= 3.0 +spurious.passband= 1:511 + +[rcumode.7.element] +rf.subbands= 105 +rf.min-sb-pwr= 70.0 +rf.negative-deviation= -24.0 +rf.positive-deviation= 12.0 +noise.negative-deviation= -3.0 +noise.positive-deviation= 1.5 +noise.max-difference= 1.5 +noise.passband= 1:511 +oscillation.min-peak-pwr= 6.0 +oscillation.passband= 1:511 +spurious.min-peak-pwr= 3.0 +spurious.passband= 1:511 + +# General settings +[paths] +global-data= /globalhome/log/stationtest +local-data= /opt/stationtest/data +local-report-dir= /localhome/stationtest/data +global-report-dir= /globalhome/log/stationtest + +[files] +bad-antenna-list= /globalhome/log/stationtest/bad_antenna_list.txt diff --git a/LCU/checkhardware/check_hardware.py b/LCU/checkhardware/check_hardware.py new file mode 100755 index 00000000000..95b91f5392e --- /dev/null +++ b/LCU/checkhardware/check_hardware.py @@ -0,0 +1,625 @@ +#!/usr/bin/python + + +info = ''' ---------------------------------------------------------------------------- + Usage of arguments + -cf=fullfilename : full path and filename for the configurationfile to use. + -l=2 : set level to 2 (default level is 0) + level 0 : manual checks, use keys listed below + level 1..n : see checkhardware.conf file for checks done + + To start a long check set number of runs or start and stop time, if start time + is not given the first run is started immediately + -r=1 : repeats, number of runs to do + -start=[date_time]: start time of first run, format [YYYYMMDD_HH:MM:SS] + -stop=[date_time] : stop time of last run, format [YYYYMMDD_HH:MM:SS] + + Set logging level, can be: debug|info|warning|error + -ls=debug : print all information on screen, default=info + -lf=info : print debug|warning|error information to log file, default=debug + + Select checks to do, can be combined with all levels + -s(rcumode) : signal check for rcumode + -sh(rcumode) : short test for rcumode 1..4 + -f(rcumode) : flat test for rcumode 1..4 + -d(rcumode) : down test for rcumode 1..4 + -o(rcumode) : oscillation check for rcumode + -sp(rcumode) : spurious check for rcumode + -n(rcumode)[=120] : noise check for rcumode, optional data time in seconds default = 120 sec. + -e(rcumode)[=12] : do all HBA element tests, optional data time in seconds default = 4 sec. + -sn(rcumode) : HBA summator noise check. + -m(rcumode) : HBA modem check. + + -rcu(mode) : do all rcu(mode) tests + + -rv : RSP version check, always done + -tv : TBB version check, always done + + -rbc : RSP board check, voltage and temperature + -tbc : TBB board check, voltage and temperature + -spu : SPU voltage check + -tm : TBB memmory check + + example : ./checkHardware.py -s5 -n5=180 + ----------------------------------------------------------------------------''' + + +import os +import sys +import traceback +from time import sleep +import datetime +from socket import gethostname +import logging + +os.umask(001) + +conf_file = r'check_hardware.conf' + +mainpath = r'/opt/stationtest' +maindatapath = r'/localhome/stationtest' + +confpath = os.path.join(maindatapath, 'config') +logpath = os.path.join(maindatapath, 'log') + +# if not exists make path +if not os.access(logpath, os.F_OK): + os.mkdir(logpath) + +hostname = gethostname().split('.')[0].upper() +station_name = hostname + +# first start main logging before including checkhardware_lib +# backup log files +for nr in xrange(8, -1, -1): + if nr == 0: + full_filename = os.path.join(logpath, 'check_hardware.log') + else: + full_filename = os.path.join(logpath, 'check_hardware.log.%d' % nr) + full_filename_new = os.path.join(logpath, 'check_hardware.log.%d' % (nr + 1)) + if os.path.exists(full_filename): + os.rename(full_filename, full_filename_new) + +# make and setup logger +logger = logging.getLogger('main') +logger.setLevel(logging.DEBUG) +# create file handler +filename = 'check_hardware.log' +full_filename = os.path.join(logpath, filename) +file_handler = logging.FileHandler(full_filename, mode='w') +formatter = logging.Formatter('%(asctime)s %(name)-14s %(levelname)-8s %(message)s') +file_handler.setFormatter(formatter) +file_handler.setLevel(logging.DEBUG) +logger.addHandler(file_handler) + +# create console handler +stream_handler = logging.StreamHandler() +formatter = logging.Formatter('%(asctime)s %(name)-14s %(levelname)-7s %(message)s') +stream_handler.setFormatter(formatter) +stream_handler.setLevel(logging.WARNING) +logger.addHandler(stream_handler) + +file_logger_handler = logger.handlers[0] +screen_logger_handler = logger.handlers[1] + +sleep(2.0) +logger.debug("logger is working") + +# now include checkhardware library +from checkhardware_lib import * + +check_version = '0516' + +rcu_keys = ('RCU1', 'RCU2', 'RCU3', 'RCU4', 'RCU5', 'RCU6', 'RCU7') +rcu_m1_keys = ('O1', 'SP1', 'N1', 'S1', 'SH1', 'D1', 'F1') +rcu_m2_keys = ('O2', 'SP2', 'N2', 'S2', 'SH2', 'D2', 'F2') +rcu_m3_keys = ('O3', 'SP3', 'N3', 'S3', 'SH3', 'D3', 'F3') +rcu_m4_keys = ('O4', 'SP4', 'N4', 'S4', 'SH4', 'D4', 'F4') +rcu_m5_keys = ('M5', 'O5', 'SN5', 'SP5', 'N5', 'S5', 'E5') +rcu_m6_keys = ('M6', 'O6', 'SN6', 'SP6', 'N6', 'S6', 'E6') +rcu_m7_keys = ('M7', 'O7', 'SN7', 'SP7', 'N7', 'S7', 'E7') + +rcu_m12_keys = rcu_m1_keys + rcu_m2_keys +rcu_m34_keys = rcu_m3_keys + rcu_m4_keys +rcu_m567_keys = rcu_m5_keys + rcu_m6_keys + rcu_m7_keys + +rsp_keys = ('RV', 'SPU', 'RBC') + rcu_keys + rcu_m12_keys + rcu_m34_keys + rcu_m567_keys +tbb_keys = ('TV', 'TM', 'TBC') +control_keys = ('R', 'START', 'STOP', 'TST') +all_keys = control_keys + rsp_keys + tbb_keys +rsp_check = False +tbb_check = False + +args = dict() + +# version checks are always done +args['RV'] = '-' +args['TV'] = '-' + +def print_help(): + print info + + +# return readable info for test +def get_test_info(key=''): + if key[-1] in '1234567': + test_name = '' + test = key[:-1] + mode = key[-1] + + if mode in '1234': + ant_type = 'LBA' + else: + ant_type = 'HBA' + + if test in ('O',): + test_name += 'Oscillation' + if test in ('SP',): + test_name += 'Spurious' + if test in ('N',): + test_name += 'Noise' + if test in ('S',): + test_name += 'RF' + if test in ('SH',): + test_name += 'Short' + if test in ('D',): + test_name += 'Down' + if test in ('F',): + test_name += 'Flat' + if test in ('SN',): + test_name += 'Summator noise' + if test in ('E',): + test_name += 'Element' + if test in ('M',): + test_name += 'Modem' + if test in ('RCU',): + test_name += 'All tests' + + return '%s mode-%c %s check' % (ant_type, mode, test_name) + + if key == 'RV': + return 'RSP Version check' + if key == 'SPU': + return 'SPU check' + if key == 'RBC': + return 'RSP board checks' + if key == 'TV': + return 'TBB Version check' + if key == 'TM': + return 'TBB Memory check' + if key == 'TBC': + return 'TBB board checks' + if key == 'START': + return 'START checks' + if key == 'STOP': + return 'STOP checks' + if key == 'R': + return 'Number of test repeats set to' + return '' + + +def add_to_args(key, value): + if key == '': + return + global args, rsp_check, tbb_check + if key in rsp_keys or key in tbb_keys or key in ('H', 'L', 'LS', 'LF', 'R', 'START', 'STOP', 'TST'): + if value != '-': + args[key] = value + else: + args[key] = '-' + + if key in rsp_keys: + rsp_check = True + if key in tbb_keys: + tbb_check = True + else: + sys.exit('Unknown key %s' % key) + return + + +def get_arguments(): + for arg in sys.argv[1:]: + if arg[0] == '-': + opt = arg[1:].strip().upper() + valpos = opt.find('=') + if valpos != -1: + key, value = opt.split('=') + else: + key, value = opt, '-' + add_to_args(key=key.strip(), value=value.strip()) + return + + +# get checklevel and set tests to do +def set_tests(conf): + level = args.get('L', '0') + if level == '0': + return + tests = conf.as_string('always').split(',') + tests += conf.as_string('list.%s' % level).split(',') + logger.debug("test= %s" % tests) + for tst in tests: + opt = tst.strip().upper() + valpos = opt.find('=') + if valpos != -1: + key, value = opt.split('=') + else: + key, value = opt, '-' + add_to_args(key=key.strip(), value=value.strip()) + return + +# setup default python logging system +# logstream for screen output +def init_logging(): + log_levels = {'DEBUG': logging.DEBUG, + 'INFO': logging.INFO, + 'WARNING': logging.WARNING, + 'ERROR': logging.ERROR} + + file_log_level = args.get('LF', 'DEBUG') + if file_log_level not in log_levels: + sys.exit('LF=%s, Not a legal log level, try again' % file_log_level) + + screen_log_level = args.get('LS', 'WARNING') + if screen_log_level not in log_levels: + sys.exit('LS=%s, not a legal log level, try again' % screen_log_level) + + if 'LF' in args: + file_logger_handler.setLevel(log_levels[file_log_level]) + if 'LS' in args: + screen_logger_handler.setLevel(log_levels[screen_log_level]) + return + + +def wait_for_start(start_datetime): + start_time = time.mktime(start_datetime.timetuple()) + if start_time > time.time(): + logger.info('delayed start, sleep till %s' % (time.asctime(start_datetime.timetuple()))) + + while start_time > time.time(): + wait_time = start_time - time.time() + sleep_time = min(wait_time, 3600.0) + time.sleep(sleep_time) + return + + +def main(): + global station_name + get_arguments() + # print args + if len(args) == 0 or 'H' in args: + print_help() + sys.exit() + + init_logging() + init_lofar_lib() + + full_filename = os.path.join(confpath, conf_file) + conf = TestSettings(filename=full_filename) + + if 'TST' in args: + lofar.testmode = True + logger.info("**** NOW IN TESTMODE ****") + + set_tests(conf.group('check')) + + logger.info('== START HARDWARE CHECK ==') + logger.info('== requested checks and settings ==') + logger.info('-'*40) + for i in all_keys: + if i in args: + if args.get(i) == '-': + logger.info(' %s' % (get_test_info(i))) + else: + logger.info(' %s, time = %s' % (get_test_info(i), args.get(i))) + logger.info('-'*40) + + # use format YYYYMMDD_HH:MM:SS + stop_time = -1 + if 'STOP' in args: + stop = args.get('STOP') + if len(stop) != 17: + return 'wrong stoptime format must be YYYYMMDD_HH:MM:SS' + stop_datetime = datetime.datetime(int(stop[:4]), int(stop[4:6]), int(stop[6:8]), + int(stop[9:11]), int(stop[12:14]), int(stop[15:])) + stop_time = time.mktime(stop_datetime.timetuple()) + + if 'START' in args: + start = args.get('START') + if len(start) != 17: + return 'wrong starttime format must be YYYYMMDD_HH:MM:SS' + start_datetime = datetime.datetime(int(start[:4]), int(start[4:6]), int(start[6:8]), + int(start[9:11]), int(start[12:14]), int(start[15:])) + if time.mktime(start_datetime.timetuple()) < time.time(): + # print time.mktime(start_datetime.timetuple()), time.time() + logger.error('Stop program, StartTime in past') + return 2 + if time.mktime(start_datetime.timetuple()) > stop_time: + logger.error('Stop program, stop before start') + return 2 + wait_for_start(start_datetime) + + logger.info('run checks till %s' % (time.asctime(stop_datetime.timetuple()))) + + + # Read in RemoteStation.conf + st_id, n_rsp, n_tbb, n_lbl, n_lbh, n_hba, hba_split = read_station_config() + logger.info("Station configuration %s:" % station_name) + logger.info(" ID = %d" % st_id) + logger.info(" nr RSP boards = %d" % n_rsp) + logger.info(" nr TB boards = %d" % n_tbb) + logger.info(" nr LBA low antennas = %d" % n_lbl) + logger.info(" nr LBA high antennas = %d" % n_lbh) + logger.info(" nr HBA high antennas = %d" % n_hba) + logger.info(" has HBA splitter = %d" % hba_split) + + # setup intern database with station layout + db = DB(station_name, n_rsp, n_tbb, n_lbl, n_lbh, n_hba, hba_split) + # if in local testmode + logger.debug("testmode= %s" % str(is_test_mode_active())) + if st_id == 9999: + station_name = 'CS001C' + activate_test_mode() + logger.debug("testmode= %s" % str(is_test_mode_active())) + if stop_time > 0.0: + db.set_test_end_time((stop_time - 120.0)) + + # set manualy marked bad antennas + full_filename = conf().get('files.bad-antenna-list') + try: + f = open(full_filename, 'r') + data = f.readlines() + f.close() + logger.info('get bad_antenna_list data "%s"' % full_filename) + for line in data: + if line[0] == '#': + continue + ant_list = line.strip().split(' ') + if ant_list[0].strip().upper() == station_name.upper(): + if len(ant_list) > 1: + for ant in ant_list[1:]: + ant_type = ant[:3].strip().upper() + if ant_type == 'LBA': + ant_nr = int(ant[3:].strip()) + # print 'ant type=%s nr=%d' % (ant_type, ant_nr) + if ant_nr < n_lbh: + db.lbh.ant[ant_nr].on_bad_list = 1 + else: + db.lbl.ant[ant_nr-n_lbh].on_bad_list = 1 + elif ant_type == 'HBA': + ant_nr = int(ant[3:].strip()) + # print 'ant type=%s nr=%d' % (ant_type, ant_nr) + db.hba.tile[ant_nr].on_bad_list = 1 + break + except IOError: + logger.warning('bad_antenna_list data from file "%s" not found' % full_filename) + + db.check_start_time = time.gmtime() + + write_message( + '!!! This station will be in use for a test! Please do not use the station! (script version %s) !!!' + % check_version + ) + start_level, board_errors = swlevel() + if start_level < 0: + sw_level, board_errors = swlevel(1) + start_level = abs(start_level) + sw_level, board_errors = swlevel(2) + if start_level < 2: + logger.info('Wait 30 seconds while startup RSPDriver') + time.sleep(30.0) + rsp_ready, tbb_ready = check_active_boards(db, n_rsp, n_tbb, 2) + if rsp_ready: + # do RSP tests if requested + if rsp_check is True: + if 'RV' in args: + rsp = RSP(db) + rsp.check_versions(conf.group('rsp')) + + reset_rsp_settings() + + repeats = int(args.get('R', '1')) + repeat_cnt = 1 + + runtime = 0 + db.tests = '' + while repeat_cnt <= repeats or (stop_time > -1 and (time.time() + runtime) < stop_time): + + try: + runstart = time.time() + if stop_time > -1: + logger.info('\n=== Start testrun %d ===\n' % repeat_cnt) + else: + logger.info('\n=== Start testrun %d of %d ===\n' % (repeat_cnt, repeats)) + + if 'SPU' in args: + spu = SPU(db) + spu.check_status(conf.group('spu')) + + if 'RBC' in args: + rsp = RSP(db) + rsp.check_board(conf.group('rsp')) + + # check if mode 1,2 is available on this station + if station_name in CoreStations or station_name in RemoteStations: + for mode in (1, 2): + lbl = LBA(db, db.lbl) + settings = conf.rcumode(mode) + # do all rcumode 1,2 tests + if 'RCU%d' % mode in args or 'SH%d' % mode in args: + lbl.check_short(mode=mode, parset=settings) + + if 'RCU%d' % mode in args or 'F%d' % mode in args: + lbl.check_flat(mode=mode, parset=settings) + + if 'RCU%d' % mode in args or 'D%d' % mode in args: + lbl.check_down(mode=mode, parset=settings) + + if 'RCU%d' % mode in args or 'O%d' % mode in args: + lbl.check_oscillation(mode=mode, parset=settings) + + if 'RCU%d' % mode in args or 'SP%d' % mode in args: + lbl.check_spurious(mode=mode, parset=settings) + + if 'RCU%d' % mode in args or 'N%d' % mode in args: + if 'RCU%d' % mode in args or args.get('N%d' % mode) == '-': + recordtime = 60 + else: + recordtime = int(args.get('N%d' % mode)) + lbl.check_noise(mode=mode, record_time=recordtime, parset=settings) + + if 'RCU%d' % mode in args or 'S%d' % mode in args: + lbl.check_rf_power(mode=mode, parset=settings) + + for mode in (3, 4): + lbh = LBA(db, db.lbh) + settings = conf.rcumode(mode) + # do all rcumode 3,4 tests + if 'RCU%d' % mode in args or 'SH%d' % mode in args: + lbh.check_short(mode=mode, parset=settings) + + if 'RCU%d' % mode in args or 'F%d' % mode in args: + lbh.check_flat(mode=mode, parset=settings) + + if 'RCU%d' % mode in args or 'D%d' % mode in args: + lbh.check_down(mode=mode, parset=settings) + + if 'RCU%d' % mode in args or 'O%d' % mode in args: + lbh.check_oscillation(mode=mode, parset=settings) + + if 'RCU%d' % mode in args or 'SP%d' % mode in args: + lbh.check_spurious(mode=mode, parset=settings) + + if 'RCU%d' % mode in args or 'N%d' % mode in args: + if 'RCU%d' % mode in args or args.get('N%d' % mode) == '-': + recordtime = 60 + else: + recordtime = int(args.get('N%d' % mode)) + lbh.check_noise(mode=mode, record_time=recordtime, parset=settings) + + if 'RCU%d' % mode in args or 'S%d' % mode in args: + lbh.check_rf_power(mode=mode, parset=settings) + + for mode in (5, 6, 7): + # do all rcumode 5, 6, 7 tests + hba = HBA(db, db.hba) + tile_settings = conf.group('rcumode.%d.tile' % mode) + elem_settings = conf.group('rcumode.%d.element' % mode) + + if 'RCU%d' % mode in args or 'M%d' % mode in args: + hba.check_modem(mode=mode) + hba.turn_off_bad_tiles() + + if 'RCU%d' % mode in args or 'O%d' % mode in args: + hba.check_oscillation(mode=mode, parset=tile_settings) + + if 'RCU%d' % mode in args or 'SN%d' % mode in args: + hba.check_summator_noise(mode=mode, parset=tile_settings) + + if 'RCU%d' % mode in args or 'SP%d' % mode in args: + hba.check_spurious(mode=mode, parset=tile_settings) + + if 'RCU%d' % mode in args or 'N%d' % mode in args: + if 'RCU%d' % mode in args or args.get('N%d' % mode) == '-': + recordtime = 60 + else: + recordtime = int(args.get('N%d' % mode)) + hba.check_noise(mode=mode, record_time=recordtime, parset=tile_settings) + + # if 'RCU%d' % mode in args or 'S%d' % mode in args: + if 'S%d' % mode in args: + hba.check_rf_power(mode=mode, parset=tile_settings) + + runtime = (time.time() - runstart) + + # All element test + if 'E%d' % mode in args: + if args.get('E%d' % mode) == '-': + recordtime = 4 + else: + recordtime = int(args.get('E%d' % mode)) + + hba.check_elements(mode=mode, record_time=recordtime, parset=elem_settings) + + # stop test if driver stopped + db.rsp_driver_down = not check_active_rspdriver() + if db.rsp_driver_down and (restarts > 0): + restarts -= 1 + reset_48_volt() + time.sleep(30.0) + level, board_errors = swlevel(2) + if len(board_errors) > 0: + db.board_errors = board_errors + break + else: + time.sleep(30.0) + + # one run done + repeat_cnt += 1 + + except: + logger.error('Caught %s', str(sys.exc_info()[0])) + logger.error(str(sys.exc_info()[1])) + logger.error('TRACEBACK:\n%s', traceback.format_exc()) + logger.error('Aborting NOW') + break + + db.rsp_driver_down = not check_active_rspdriver() + if not db.rsp_driver_down: + reset_rsp_settings() + + + # do TBB tests if requested + if tbb_check is True: + tbb = TBB(db) + try: + if 'TV' in args: + tbb.check_versions(conf.group('tbb')) + + if 'TBC' in args: + tbb.check_board(conf.group('tbb')) + + if 'TM' in args: + tbb.check_memory() + except: + logger.error('Program fault, TBB test') + logger.error('Caught %s', str(sys.exc_info()[0])) + logger.error(str(sys.exc_info()[1])) + logger.error('TRACEBACK:\n%s', traceback.format_exc()) + logger.error('Aborting NOW') + + db.tbb_driver_down = not check_active_tbbdriver() + + db.check_stop_time = time.gmtime() + + try: + # do db test and write result files to log directory + report_dir = conf().as_string('paths.local-report-dir') + if os.path.exists(report_dir): + logger.info('write result data') + db.test() + make_report(db, report_dir) + else: + logger.warning('not a valid report directory') + # delete files from data directory + remove_all_data_files() + except: + logger.error('Program fault, reporting and cleanup') + logger.error('Caught %s', str(sys.exc_info()[0])) + logger.error(str(sys.exc_info()[1])) + logger.error('TRACEBACK:\n%s', traceback.format_exc()) + logger.error('Aborting NOW') + + logger.info('Check if boards are still ok') + check_active_boards(db, n_rsp, n_tbb, 1) + if not db.rsp_driver_down: + logger.info('Going back to swlevel %d' % start_level) + swlevel(start_level) + logger.info('Test ready.') + write_message('!!! The test is ready and the station can be used again! !!!') + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/LCU/checkhardware/checkhardware_lib/__init__.py b/LCU/checkhardware/checkhardware_lib/__init__.py new file mode 100644 index 00000000000..837db9972b7 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/__init__.py @@ -0,0 +1,13 @@ +#import logging +#logger = logging.getLogger('main') + +from general import * +from lofar import * +from settings import TestSettings +from db import DB, db_version +from reporting import make_report +from spu import SPU +from tbb import TBB +from rsp import RSP +from lba import LBA +from hba import HBA diff --git a/LCU/checkhardware/checkhardware_lib/data.py b/LCU/checkhardware/checkhardware_lib/data.py new file mode 100644 index 00000000000..83d48a9b816 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/data.py @@ -0,0 +1,405 @@ +#!/usr/bin/python + +""" +data library for reading in sample data +""" + +# from general_lib import * +from lofar import mode_to_band, is_test_mode_active, rspctl, select_str +import os +import numpy as np +import logging +from time import sleep + +test_version = '0815' + +logger = logging.getLogger('main.data') +logger.debug("starting data logger") + + +class AntennaData: + bands = {'10_90' : (1, 3), + '30_90' : (2, 4), + '110_190': (5,), + '170_210': (6,), + '210_250': (7,)} + + XPOL = 'x' + YPOL = 'y' + XYPOL = 'xy' + + def __init__(self, args): + self._args = args + if 'data-dir' not in args or 'n_rcus' not in args: + logger.error("missing arguments") + return + self._data_dir = self._args['data-dir'] + self._n_rcus = int(self._args.get('n_rcus', 96)) + self._sbdata = np.zeros((self._n_rcus, 1, 512), dtype=np.float64) + self._rcu_state = [0] * self._n_rcus # 0=Off, 1=On + self._rcu_mode = [0] * self._n_rcus # mode=0..7, 0=off + self._rcu_mask = [] + self._sb_mask = {} + self._rcus = {} + self._requested_seconds = 0 + for band in self.bands.keys(): + self._sb_mask[band] = [] + self._rcus[band] = {self.XPOL: [], self.YPOL: [], self.XYPOL: []} + + def _reset(self): + self._n_rcus = int(self._args.get('n_rcus', 96)) + self._rcu_state = [0] * self._n_rcus # 0=Off, 1=On + self._rcu_mode = [0] * self._n_rcus # mode=0..7, 0=off + self._rcu_mask = [] + self._sb_mask = {} + self._rcus = {} + for band in self.bands.keys(): + self._sb_mask[band] = [] + self._rcus[band] = {self.XPOL: [], self.YPOL: [], self.XYPOL: []} + + def seconds(self): + return self._sbdata.shape[1] + + def max_rcus(self): + return self._n_rcus + + def antenna(self, rcu): + ant = rcu / 2 + if self._rcu_mode[rcu] in (1, 2): + ant += 48 + return ant + + def mode(self, rcu): + return self._rcu_mode[rcu] + + def polarity(self, rcu): + """ + check polarity of signal + :param rcu: rcu number + :return: pol, 0=x, 1=y + """ + pol = None + if self._rcu_state[rcu]: + if self._rcu_mode[rcu] in (1, 2): + if rcu % 2 == 0: + pol = self.YPOL + else: + pol = self.XPOL + else: + if rcu % 2 == 0: + pol = self.XPOL + else: + pol = self.YPOL + return pol + + def rcus(self, band, polarity): + # logger.debug("band='%s' polarity='%s'" % (band, polarity)) + pol = None + if polarity in (0, 'X', 'x'): + pol = self.XPOL + if polarity in (1, 'Y', 'y'): + pol = self.YPOL + if polarity in (2, 'XY', 'xy'): + pol = self.XYPOL + if not pol: + return [] + # if not filled, fill it now + if len(self._rcus[band][pol]) == 0: + for rcu_nr, state in enumerate(self._rcu_state): + if state == 1: + if self._rcu_mode[rcu_nr] in self.bands[band]: + if pol is self.XYPOL: + self._rcus[band][pol].append(rcu_nr) + elif self.polarity(rcu_nr) == pol: + self._rcus[band][pol].append(rcu_nr) + #logger.debug("pol=%s selected rcus=%s" % (pol, ','.join([str(i) for i in sorted(self._rcus[band][pol])]))) + #logger.debug("pol=%s selected rcus=%s" % (pol, str(sorted(self._rcus[band][pol])).replace(' ','') )) + return sorted(self._rcus[band][pol]) + + def mask_rcu(self, rcus): + """ + mask rcu, this rcus will be ignored + :param rcus: list with rcus to mask + """ + if type(rcus) in (list, tuple): + for rcu in rcus: + if rcu not in self._rcu_mask: + self._rcu_mask.append(rcu) + else: + if rcus not in self._rcu_mask: + self._rcu_mask.append(rcus) + + def reset_masked_rcus(self): + self._rcu_mask = [] + + def reset_masked_sb(self, band): + self._sb_mask[band] = [] + + def set_passband(self, band, subbands): + """ + mask subbands, these subbands will be ignored + :param band: band to add + :param subbands: list with subbands to mask + """ + for sb in xrange(1,512,1): + if sb not in subbands: + if sb not in self._sb_mask: + self._sb_mask[band].append(sb) + + def mask_sb(self, band, subbands): + """ + mask subbands, these subbands will be ignored + :param band: band to add + :param subbands: list with subbands to mask + """ + for sb in subbands: + if sb not in self._sb_mask: + self._sb_mask[band].append(sb) + + def band_active(self, band): + """ + Checks if data available + :param band: band to check + :return: True if data available else False + """ + if self.rcus(band, 'xy'): + return True + return False + + def collect(self, n_seconds=2, slow=False): + """ + Collect new data + :param n_seconds: seconds to record + :param slow: get data i 2 steps + :return: None + """ + self._requested_seconds = n_seconds + self._reset() + self._get_rcu_info() + self._record_antenna_data(n_seconds, slow) + self._sbdata = self._read_files() + + def _get_rcu_info(self): + """ + get rcu information, state, mode and swapped + :return: + """ + n_rcus = int(self._args.get('n_rcus', 96)) + self._rcu_state = [0] * n_rcus # 0=Off, 1=On + self._rcu_mode = [0] * n_rcus # mode=0..7, 0=off + + # RCU[ 0].control=0x10337a9c => ON, mode:3, delay=28, att=06 + answer = rspctl("--rcu") + if answer.count('mode:') == n_rcus: + for line in answer.splitlines(): + if line.find('mode:') == -1: + continue + rcu = line[line.find('[') + 1: line.find(']')].strip() + state = line[line.find('=>') + 2: line.find(',')].strip() + mode = line[line.find('mode:') + 5] + if rcu.isdigit() and state in ("OFF", "ON") and mode.isdigit(): + if state == "OFF": + self._rcu_state[int(rcu)] = 0 + else: + self._rcu_state[int(rcu)] = 1 + self._rcu_mode[int(rcu)] = int(mode) + #logger.debug("rcu-info mode= %s" % str(self._rcu_mode)) + #logger.debug("rcu-info state= %s" % str(self._rcu_state)) + + def _record_antenna_data(self, n_seconds, slow): + """ + record antenna data using rspctl --statistics cmd, for all active rcus a file will be made + :param n_seconds: number of seconds to sample data + :param slow: slow down lcu disk usage + :return: + """ + self._remove_all_datafiles() # cleanup data directory + x_list = [] + y_list = [] + xy_list = [] + for rcu_nr, state in enumerate(self._rcu_state): + if state == 1: + xy_list.append(rcu_nr) + if rcu_nr % 2 == 0: + x_list.append(rcu_nr) + else: + y_list.append(rcu_nr) + + if slow is True: + rcus = select_str(x_list) + logger.debug("Wait %d seconds while recording X data" % n_seconds) + rspctl('--statistics --duration=%d --integration=1 --directory=%s --select=%s' % ( + n_seconds, self._data_dir, rcus), wait=0.0) + + rcus = select_str(y_list) + logger.debug("Wait %d seconds while recording Y data" % n_seconds) + rspctl('--statistics --duration=%d --integration=1 --directory=%s --select=%s' % ( + n_seconds, self._data_dir, rcus), wait=0.0) + else: + rcus = select_str(xy_list) + logger.debug("Wait %d seconds while recording XY data" % n_seconds) + rspctl('--statistics --duration=%d --integration=1 --directory=%s --select=%s' % ( + n_seconds, self._data_dir, rcus), wait=0.0) + + def _remove_all_datafiles(self): + """ + remove all *.dat files from data_dir + """ + logger.debug("testmode= %s" % str(is_test_mode_active())) + if not is_test_mode_active(): + if os.access(self._data_dir, os.F_OK): + files = os.listdir(self._data_dir) + # print files + for filename in files: + if filename[-3:] == 'dat' or filename[-3:] == 'nfo': + os.remove(os.path.join(self._data_dir, filename)) + + def _read_file(self, full_filename): + if not is_test_mode_active(): + sleep(0.02) + data = np.fromfile(full_filename, dtype=np.float64) + n_samples = len(data) + if (n_samples % 512) > 0: + logger.warning("data error: number of samples (%d) not multiple of 512 in '%f'" % ( + n_samples, full_filename)) + n_frames = n_samples / 512 + data = data.reshape(n_frames, 512) + #logger.info("recorded data shape %s" %(str(data.shape))) + return data[:self._requested_seconds,:] + + def _read_files(self): + files_in_dir = os.listdir(self._data_dir) + if len(files_in_dir) == 0: + logger.warning('No data recorded !!') + self._reset() + return + + data_shape = self._read_file(os.path.join(self._data_dir, files_in_dir[0])).shape + ssdata = np.zeros((self._n_rcus, data_shape[0], data_shape[1]), dtype=np.float64) + for file_name in sorted(files_in_dir): + # path, filename = os.split(file_name) + # filename format: 20160228_174114_sst_rcu000.dat + rcu = int(file_name.split('.')[0][-3:]) + ssdata[rcu, :, :] = self._read_file(os.path.join(self._data_dir, file_name)) + + # logger.debug("%s rcu=%d" %(file_name, rcu)) + + # mask zero values and convert to dBm + # logger.debug("rcu0=%s" % ssdata[:,0,301]) + ssdata_db = np.log10(np.ma.masked_less(ssdata, self._args.get('minvalue', 1.0))) * 10.0 + # do not use subband 0 + # logger.debug("rcu0=%s" % ssdata_db[:,0,301]) + ssdata_db[:, :, 0] = np.ma.masked + # logger.debug("rcu0=%s" % ssdata_db[:,0,301]) + logger.debug("recorded data shape %s" %(str(ssdata_db.shape))) + return ssdata_db + + # subbands is list to mask + def get_masked_data(self, band='', mask_subbands=True, mask_rcus=True): + data = self._sbdata.copy() + if mask_subbands: + data[:, :, self._sb_mask[band]] = np.ma.masked + if mask_rcus: + data[self._rcu_mask, :, :] = np.ma.masked + return data + + # spectra(s) for one rcu + def rcu_median_spectra(self, rcu, masked): + spec = self.rcu_spectras(rcu, masked) + if spec.shape[0] > 1: + return np.median(spec, axis=0) + return spec[0,:] + + + def rcu_mean_spectra(self, rcu, masked): + spec = self.rcu_spectras(rcu, masked) + if spec.shape[0] > 1: + return np.mean(spec, axis=0) + return spec[0,:] + + + def rcu_spectras(self, rcu, masked): + if rcu in range(self._n_rcus): + if masked: + return self.get_masked_data(band=mode_to_band(self.mode(rcu)), mask_rcus=False)[rcu, :, :] + else: + return self._sbdata[rcu, :, :] + + logger.error("Not valid arguments %s" % ( + str(rcu))) + return None + + # spectras for one band and polarity + def mean_spectras(self, freq_band, polarity, masked): + spec = self.spectras(freq_band, polarity, masked) + if spec.shape[1] > 1: + return np.mean(spec, axis=1) + return spec[:,0,:] + + def mean_all_spectras(self, freq_band, polarity, masked): + spec = self.mean_spectras(freq_band, polarity, masked) + if spec.shape[0] > 1: + return np.mean(spec, axis=0) + return spec[0,:] + + def median_spectras(self, freq_band, polarity, masked): + spec = self.spectras(freq_band, polarity, masked) + if spec.shape[1] > 1: + return np.median(spec, axis=1) + return spec[:,0,:] + + def median_all_spectras(self, freq_band, polarity, masked): + spec = self.median_spectras(freq_band, polarity, masked) + if spec.shape[0] > 1: + return np.median(spec, axis=0) + return spec[0,:] + + def spectras(self, freq_band, polarity, masked): + return self.subbands(freq_band, polarity, range(512), masked) + + def subbands(self, freq_band, polarity, sb_set, masked): + sb_range = range(512) + pol = None + band = None + if polarity in (0, 'X', 'x'): + pol = self.XPOL + if polarity in (1, 'Y', 'y'): + pol = self.YPOL + if polarity in (2, 'XY', 'xy'): + pol = self.XYPOL + if freq_band in self.bands.keys(): + band = freq_band + + if isinstance(sb_set, int): + if sb_set in sb_range: + sb = sb_set + else: + sb = None + else: + sb = list(sb_set) + for i in sb: + if i not in sb_range: + sb = None + + if pol and band and sb: + rcu_list = self.rcus(band, pol) + #logger.debug("rcu-list=%s" % str(rcu_list)) + # logger.debug("sb-list=%s" % str(sb)) + if masked: + masked_data = self.get_masked_data(band=band) + rcu_data = masked_data[rcu_list, :, :] + sb_data = rcu_data[:, :, sb] + # logger.debug("subbands():: sb_data.shape=%s" % str(sb_data.shape)) + # logger.debug("subbands():: sb_data= %s" % str(sb_data)) + return sb_data + else: + rcu_data = self._sbdata[rcu_list, :, :] + sb_data = rcu_data[:, :, sb] + # logger.debug("subbands():: sb_data.shape=%s" % str(sb_data.shape)) + # logger.debug("subbands():: sb_data= %s" % str(sb_data)) + return sb_data + + logger.error("Not valid arguments %s, %s, %s" % (str(band), + str(polarity), + str(sb_set))) + return None diff --git a/LCU/checkhardware/checkhardware_lib/db.py b/LCU/checkhardware/checkhardware_lib/db.py new file mode 100644 index 00000000000..0fbf81bcdf3 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/db.py @@ -0,0 +1,531 @@ +#!/usr/bin/python + +from copy import deepcopy +from general import * +from lofar import * +import time +import logging +import string + +db_version = '0415' + +logger = logging.getLogger('main.db') +logger.debug("starting db logger") + + +class DB: + def __init__(self, StID, nRSP, nTBB, nLBL, nLBH, nHBA, HBA_SPLIT): + self.StID = StID + self.nr_rsp = nRSP + self.nr_spu = nRSP / 4 + self.nr_rcu = nRSP * 8 + self.nr_lbl = nLBL + self.nr_lbh = nLBH + self.nr_hba = nHBA + self.hba_split = HBA_SPLIT + self.nr_tbb = nTBB + + self.script_versions = '' + + self.board_errors = list() + self.rcumode = -1 + self.tests_done = list() + self.check_start_time = 0 + self.check_stop_time = 0 + self.rsp_driver_down = False + self.tbb_driver_down = False + + self.station_error = 0 + + self.test_end_time = -1 + self.rcus_changes = False + + self.spu = list() + for i in range(self.nr_spu): + self.spu.append(self.SPU(i)) + + self.rsp = list() + for i in range(nRSP): + self.rsp.append(self.RSP(i)) + + self.tbb = list() + for i in range(nTBB): + self.tbb.append(self.TBB(i)) + + self.rcu_state = list() + for i in range(self.nr_rcu): + self.rcu_state.append(0) + + self.lbl = deepcopy(self.LBA(label='LBL', nr_antennas=nLBL, nr_offset=48)) + self.lbh = deepcopy(self.LBA(label='LBH', nr_antennas=nLBH, nr_offset=0)) + self.hba = deepcopy(self.HBA(nr_tiles=nHBA, split=self.hba_split)) + + def set_test_end_time(self, end_time): + if end_time > time.time(): + self.test_end_time = end_time + else: + logger.warning("end time in past") + return + + # returns True if before end time + def check_end_time(self, duration=0.0): + if self.test_end_time == -1: + return True + if (time.time() + duration) < self.test_end_time: + return True + else: + return False + + # add only ones + def add_test_done(self, name): + if name not in self.tests_done: + self.tests_done.append(name) + + # check if already done + def is_test_done(self, name): + if name in self.tests_done: + return False + return True + + # test + def test(self): + for _spu in self.spu: + ok = _spu.test() + if not ok: + self.station_error = 1 + + for _rsp in self.rsp: + ok = _rsp.test() + if not ok: + self.station_error = 1 + + for _tbb in self.tbb: + ok = _tbb.test() + if not ok: + self.station_error = 1 + + # test rcu's first + for _rcu in range(self.nr_rcu): + error_count = 0 + + ant_nr = _rcu / 2 + pol_nr = _rcu % 2 # 0=X, 1=Y + + if pol_nr == 0: + if self.nr_lbl > 0 and ant_nr < self.nr_lbl and self.lbl.ant[ant_nr].x.error: + error_count += 1 + if ant_nr < self.nr_lbh and self.lbh.ant[ant_nr].x.error: + error_count += 1 + if ant_nr < self.nr_hba and self.hba.tile[ant_nr].x.rcu_error: + error_count += 1 + else: + if self.nr_lbl > 0 and ant_nr < self.nr_lbl and self.lbl.ant[ant_nr].y.error: + error_count += 1 + if ant_nr < self.nr_lbh and self.lbh.ant[ant_nr].y.error: + error_count += 1 + if ant_nr < self.nr_hba and self.hba.tile[ant_nr].y.rcu_error: + error_count += 1 + + if error_count >= 2: + self.rcu_state[_rcu] = 1 + + self.station_error = max(self.station_error, self.lbl.test(), self.lbh.test(), self.hba.test()) + + return self.station_error + + + # ======================================================================================================================= + # database from here + class SPU: + def __init__(self, nr): + self.nr = nr + self.rcu_5_0_volt = 0.0 + self.lba_8_0_volt = 0.0 + self.hba_48_volt = 0.0 + self.spu_3_3V = 0.0 + self.rcu_ok = 1 + self.lba_ok = 1 + self.hba_ok = 1 + self.spu_ok = 1 + self.voltage_ok = 1 + self.temp = 0.0 + self.temp_ok = 1 + + def test(self): + self.voltage_ok = 0 + if self.rcu_ok and self.lba_ok and self.hba_ok and self.spu_ok: + self.voltage_ok = 1 + return self.voltage_ok + + class RSP: + def __init__(self, nr): + self.nr = nr + + self.test_done = 0 + self.board_ok = 1 + self.ap_version = 'ok' + self.bp_version = 'ok' + self.version_ok = 1 + self.voltage1_2 = 0.0 + self.voltage2_5 = 0.0 + self.voltage3_3 = 0.0 + self.voltage_ok = 1 + self.pcb_temp = 0.0 + self.bp_temp = 0.0 + self.ap0_temp = 0.0 + self.ap1_temp = 0.0 + self.ap2_temp = 0.0 + self.ap3_temp = 0.0 + self.temp_ok = 1 + + def test(self): + if self.ap_version != 'ok' or self.bp_version != 'ok': + self.version_ok = 0 + return self.version_ok and self.voltage_ok and self.temp_ok + + # used by LBA and HBA antenna class + class Polarity: + def __init__(self, rcu=None): + self.rcu = rcu + self.rcu_off = 0 # 0 = RCU on, 1 = RCU off + self.rcu_error = 0 + + # status variables 0|1 + self.error = 0 # + self.too_low = 0 # + self.too_high = 0 # + self.low_noise = 0 # + self.high_noise = 0 # + self.jitter = 0 # + self.osc = 0 # + self.no_signal = 0 # signal below 2dB + self.summator_noise = 0 # + self.spurious = 0 # + self.flat = 0 # + self.short = 0 # + + # test result of signal test, + # only for HBA element test, first value ctrl=129 second value ctrl=253 + self.rf_test_subband = [0, 0] + self.rf_ref_signal = [-1, -1] + self.test_signal = [0.0, 0.0] + + # for down test + self.down_pwr = 0.0 + self.down_offset = 0 + + # measured values filled on error + # proc : bad time in meausured time 0..100% + # val : max or min meausured value + self.low_seconds = 0 + self.low_bad_seconds = 0 + self.low_val = 100.0 # + self.low_diff = 0.0 + self.low_ref = 0.0 # + + self.high_seconds = 0 + self.high_bad_seconds = 0 + self.high_val = 0.0 # + self.high_diff = 0.0 + self.high_ref = 0.0 # + + self.jitter_seconds = 0 + self.jitter_bad_seconds = 0 + self.jitter_val = 0.0 + self.jitter_ref = 0.0 + + self.flat_val = 0.0 + self.short_val = 0.0 + + class LBA: + def __init__(self, label, nr_antennas, nr_offset=0): + self.rsp_driver_down = False + self.noise_check_done = 0 + self.signal_check_done = 0 + self.short_check_done = 0 + self.flat_check_done = 0 + self.down_check_done = 0 + self.spurious_check_done = 0 + self.oscillation_check_done = 0 + + self.noise_low_deviation = 0.0 + self.noise_high_deviation = 0.0 + self.noise_max_fluctuation = 0.0 + + self.rf_low_deviation = 0.0 + self.rf_high_deviation = 0.0 + self.rf_subband = 0 + + self.check_time_noise = 0 + self.nr_antennas = nr_antennas + self.nr_offset = nr_offset + self.label = label + self.error = 0 + self.rf_signal_to_low = 0 + self.avg_x = 0 + self.avg_y = 0 + self.rf_test_subband_x = 0 + self.rf_test_subband_y = 0 + self.rf_ref_signal_x = 0 + self.rf_ref_signal_y = 0 + self.nr_bad_antennas = -1 + self.ant = list() + for i in range(self.nr_antennas): + self.ant.append(self.Antenna(i, self.nr_offset)) + return + + def test(self): + if self.rsp_driver_down: + return self.error + if self.noise_check_done or self.signal_check_done or self.short_check_done or \ + self.flat_check_done or self.down_check_done or self.signal_check_done or \ + self.spurious_check_done or self.oscillation_check_done: + self.nr_bad_antennas = 0 + + for ant in self.ant: + ant.test() + ant_error = max(ant.x.error, ant.y.error) + self.error = max(self.error, ant_error) + if ant_error: + self.nr_bad_antennas += 1 + return self.error + + # return select string for rspctl command + def select_list(self): + select = list() + for ant in self.ant: + if ant.on_bad_list == 0: + select.append(ant.x.rcu) + select.append(ant.y.rcu) + return select + + def reset_rcu_state(self): + for ant in self.ant: + ant.x.rcu_off = 0 + ant.y.rcu_off = 0 + + class Antenna: + def __init__(self, nr, nr_offset): + self.nr = nr + self.nr_pvss = self.nr + nr_offset + self.on_bad_list = 0 + if nr_offset == 0: + self.x = DB.Polarity(rcu=(self.nr * 2)) + self.y = DB.Polarity(rcu=((self.nr * 2) + 1)) + else: + self.x = DB.Polarity(rcu=(self.nr * 2) + 1) + self.y = DB.Polarity(rcu=((self.nr * 2))) + self.down = 0 + return + + def test(self): + self.x.error = max(self.x.too_low, self.x.too_high, self.x.osc, self.x.high_noise, self.x.low_noise, + self.x.jitter, self.x.spurious, self.down, self.x.flat, self.x.short) + self.y.error = max(self.y.too_low, self.y.too_high, self.y.osc, self.y.high_noise, self.y.low_noise, + self.y.jitter, self.y.spurious, self.down, self.y.flat, self.y.short) + return + + class HBA: + def __init__(self, nr_tiles, split): + self.rsp_driver_down = False + self.modem_check_done = 0 + self.noise_check_done = 0 + self.signal_check_done = 0 + self.spurious_check_done = 0 + self.oscillation_check_done = 0 + self.summatornoise_check_done = 0 + self.element_check_done = 0 + + self.hba_split = split + self.check_time_noise = 0 + self.check_time_noise_elements = 0 + self.nr_tiles = nr_tiles + self.error = 0 + self.rf_signal_to_low = 0 + # only used for tile RF test + # first value ctrl=129 second value ctrl=253 + self.rf_test_subband_x = [0, 0] + self.rf_test_subband_y = [0, 0] + self.rf_ref_signal_x = [0.0, 0.0] + self.rf_ref_signal_y = [0.0, 0.0] + self.tile = list() + self.nr_bad_tiles = -1 + self.nr_bad_tiles_0 = -1 + self.nr_bad_tiles_1 = -1 + for i in range(self.nr_tiles): + self.tile.append(self.Tile(i)) + return + + def test(self): + if self.rsp_driver_down: + return self.error + if self.modem_check_done or self.noise_check_done or self.signal_check_done or self.spurious_check_done or\ + self.oscillation_check_done or self.summatornoise_check_done or self.element_check_done: + if self.hba_split == 1: + self.nr_bad_tiles_0 = 0 + self.nr_bad_tiles_1 = 0 + else: + self.nr_bad_tiles = 0 + + for tile in self.tile: + tile.test(self.element_check_done or self.modem_check_done) + tile_error = max(tile.x.error, tile.y.error) + self.error = max(self.error, tile_error) + + if tile_error: + if self.hba_split == 1: + if tile.nr < 24: + self.nr_bad_tiles_0 += 1 + else: + self.nr_bad_tiles_1 += 1 + else: + self.nr_bad_tiles += 1 + return self.error + + # return select string for rspctl command + def select_list(self): + select = list() + for tile in self.tile: + if tile.on_bad_list == 0: + select.append(tile.x.rcu) + select.append(tile.y.rcu) + return select + + def reset_rcu_state(self): + for tile in self.tile: + tile.x.rcu_off = 0 + tile.y.rcu_off = 0 + + class Tile: + def __init__(self, nr): + self.nr = nr + self.on_bad_list = 0 + self.x = DB.Polarity(rcu=(nr * 2)) + self.y = DB.Polarity(rcu=(nr * 2 + 1)) + + self.noise_low_deviation = 0.0 + self.noise_high_deviation = 0.0 + self.noise_max_fluctuation = 0.0 + + self.rf_low_deviation = 0.0 + self.rf_high_deviation = 0.0 + self.rf_subband = 0 + + self.no_power = 0 # signal around 60dB + self.p_summator_error = 0 + self.c_summator_error = 0 + self.nr_elements = 16 + self.element = list() + for i in range(1, self.nr_elements + 1, 1): + self.element.append(self.Element(i)) + return + + def test(self, check_done): + no_modem_cnt = 0 + modem_err_cnt = 0 + no_power_cnt = 0 + x_no_signal_cnt = 0 + y_no_signal_cnt = 0 + if check_done: + for elem in self.element: + elem.test() + if elem.x.no_signal: + x_no_signal_cnt += 1 + if elem.y.no_signal: + y_no_signal_cnt += 1 + if elem.no_power: + no_power_cnt += 1 + if elem.no_modem: + no_modem_cnt += 1 + if elem.modem_error: + modem_err_cnt += 1 + + self.x.error = max(self.x.error, elem.x.error) + self.y.error = max(self.y.error, elem.y.error) + + if (no_modem_cnt >= 8) or (modem_err_cnt >= 8): + self.c_summator_error = 1 + if no_power_cnt >= 15: + self.p_summator_error = 1 + if x_no_signal_cnt == 16: + self.x.rcu_error = 1 + if y_no_signal_cnt == 16: + self.y.rcu_error = 1 + + self.x.error = max(self.x.error, self.x.too_low, self.x.too_high, self.x.low_noise, self.x.no_signal, + self.x.high_noise, self.x.jitter, self.x.osc, + self.x.summator_noise, self.x.spurious, self.p_summator_error, self.c_summator_error) + + self.y.error = max(self.y.error, self.y.too_low, self.y.too_high, self.y.low_noise, self.y.no_signal, + self.y.high_noise, self.y.jitter, self.y.osc, + self.y.summator_noise, self.y.spurious, self.p_summator_error, self.c_summator_error) + return + + class Element: + def __init__(self, nr): + self.nr = nr + self.x = DB.Polarity() + self.y = DB.Polarity() + + self.noise_low_deviation = 0.0 + self.noise_high_deviation = 0.0 + self.noise_max_fluctuation = 0.0 + + self.rf_low_deviation = 0.0 + self.rf_high_deviation = 0.0 + self.rf_subband = 0 + + self.no_power = 0 # signal around 60dB + self.no_modem = 0 # modem reponse = ?? + self.modem_error = 0 # wrong response from modem + + return + + def test(self): + modem_err = 0 + if self.no_modem or self.modem_error: + modem_err = 1 + + self.x.error = max(self.x.too_low, self.x.too_high, self.x.low_noise, self.x.high_noise, + self.x.no_signal, + self.x.jitter, self.no_power, self.x.spurious, self.x.osc, modem_err) + + self.y.error = max(self.y.too_low, self.y.too_high, self.y.low_noise, self.y.high_noise, + self.y.no_signal, + self.y.jitter, self.no_power, self.y.spurious, self.y.osc, modem_err) + return + + class TDS: + def __init__(self): + self.test_done = 0 + self.ok = 1 + + class TBB: + def __init__(self, nr): + self.nr = nr + self.board_active = 1 + self.board_ok = 1 + self.test_done = 0 + self.tp_version = 'ok' + self.mp_version = 'ok' + self.memory_size = 0 + self.version_ok = 1 + self.memory_ok = 1 + self.voltage1_2 = 0.0 + self.voltage2_5 = 0.0 + self.voltage3_3 = 0.0 + self.voltage_ok = 1 + self.pcb_temp = 0.0 + self.tp_temp = 0.0 + self.mp0_temp = 0.0 + self.mp1_temp = 0.0 + self.mp2_temp = 0.0 + self.mp3_temp = 0.0 + self.temp_ok = 1 + + def test(self): + if self.tp_version != 'ok' or self.mp_version != 'ok': + self.version_ok = 0 + if self.memory_size != 0: + self.memory_ok = 0 + return self.version_ok and self.voltage_ok and self.temp_ok diff --git a/LCU/checkhardware/checkhardware_lib/db_new.py b/LCU/checkhardware/checkhardware_lib/db_new.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/LCU/checkhardware/checkhardware_lib/general.py b/LCU/checkhardware/checkhardware_lib/general.py new file mode 100644 index 00000000000..b018fd01db2 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/general.py @@ -0,0 +1,146 @@ +r""" +general script +""" + +from subprocess import (Popen, PIPE) +import traceback +import time +import os +import sys +import logging + +general_version = '0913' +logger = logging.getLogger('main.general') +logger.debug("starting general logger") + + +def write_message(msg): + run_cmd('wall %s' % str(msg)) + return + + +# Return date string in the following format YYYYMMDD +def get_short_date_str(tm=time.gmtime()): + return time.strftime("%Y%m%d", tm) + + +# Return time string in the following format HH:MM:SS +def get_date_str(tm=time.gmtime()): + return time.strftime("%d-%m-%Y", tm) + + +# Return time string in the following format HH:MM:SS +def get_time_str(tm=time.gmtime()): + return time.strftime("%H:%M:%S", tm) + + +# Return time string in the following format HH:MM:SS +def get_date_time_str(tm=time.gmtime()): + return time.strftime("%d-%m-%YT%H:%M:%S", tm) + + +# Run cmd with args and return response +def run_cmd(cmd=''): + if cmd != '': + try: + _cmd = cmd.replace(' =', '=').replace('= ', '=') + cmd_list = _cmd.split() + #print cmd_list + cmdline = Popen(cmd_list, stdout=PIPE, stderr=PIPE) + (so, se) = cmdline.communicate() + if len(so) != 0: + return so + else: + return 'Error, %s' % se + except: + logger.error('Caught %s', str(sys.exc_info()[0])) + logger.error(str(sys.exc_info()[1])) + logger.error('TRACEBACK:\n%s', traceback.format_exc()) + return 'Exception Error' + + return '' + + +# Get Host name +def get_hostname(): + retries = 0 + while retries < 3: + try: + host = run_cmd('hostname -s') + if host == 'Exception Error': + host = 'Unknown' + retries += 1 + if host != 'Unknown': + break + except: + host = 'Unknown' + retries += 1 + + return host.strip() + + +# file logger +class MyLogger: + def __init__(self, logdir, filename, screen_prefix=''): + self.fullFilename = os.path.join(logdir, filename) + self.logfile = open(self.fullFilename, 'w') + self.prefix = screen_prefix + self.start_time = time.time() + + def __del__(self): + self.logfile.close() + + def get_full_filename(self): + return self.fullFilename + + def reset_start_time(self, screen=False): + self.start_time = time.time() + self.info("Start time %s" % (time.strftime("%H:%M:%S", time.gmtime(self.start_time))), screen=screen) + + def print_busy_time(self, screen=False): + self.info("Time from start %s" % (time.strftime("%H:%M:%S", (time.gmtime(time.time() - self.start_time)))), + screen=screen) + + def print_time_now(self, screen=False): + self.info("Time %s" % (time.strftime("%H:%M:%S", time.gmtime(time.time()))), screen=screen) + + def info(self, msg, no_end=False, screen=False): + if len(msg) != 0: + if screen: + print self.prefix + ' ' + msg + if not no_end: + msg += '\n' + self.logfile.write(msg) + self.logfile.flush() + + +class MyTestLogger(MyLogger): + def __init__(self, logdir, hostname): + filename = '%s_station_test.csv' % hostname.upper() + MyLogger.__init__(self, logdir, filename) + + def add_line(self, info): + MyLogger.info(self, info) + + +class MyStationLogger(MyLogger): + def __init__(self, logdir, hostname, filetime=time.gmtime()): + filename = "stationtest_%s.log" % hostname + MyLogger.__init__(self, logdir, filename) + MyLogger.info(self, "StID >: %s" % hostname) + MyLogger.info(self, "Lgfl >: %s" % (os.path.join(logdir, filename))) + testdate = time.strftime("%a, %d %b %Y %H:%M:%S", filetime) + MyLogger.info(self, "Time >: %s" % testdate) + + def add_line(self, info): + MyLogger.info(self, info) + + +class MyPVSSLogger(MyLogger): + def __init__(self, logdir, hostname): + filename = '%s_station_test_pvss.log' % hostname + MyLogger.__init__(self, logdir, filename) + # cLogger.info(self, "# PVSS input file") + + def add_line(self, info): + MyLogger.info(self, info) diff --git a/LCU/checkhardware/checkhardware_lib/hardware_tests.py b/LCU/checkhardware/checkhardware_lib/hardware_tests.py new file mode 100644 index 00000000000..6d58fa4b31e --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/hardware_tests.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +# test lib + +from checkhardware_lib.spectrum_checks import * +from data import * +from lofar import * + +test_version = '0815' + +logger = None + + +def init_test_lib(): + global logger + logger = logging.getLogger() + logger.debug("init logger test_lib") + + +# HBASubband = dict( DE601C=155, DE602C=155, DE603C=284, DE604C=474, DE605C=479, FR606C=155, SE607C=287, UK608C=155 ) +# DefaultLBASubband = 301 +# DefaultHBASubband = 155 + + + + + + + + + + + diff --git a/LCU/checkhardware/checkhardware_lib/hba.py b/LCU/checkhardware/checkhardware_lib/hba.py new file mode 100644 index 00000000000..ea1bc08af6f --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/hba.py @@ -0,0 +1,827 @@ +import logging +from data import AntennaData +from spectrum_checks import * +from lofar import * + +logger = logging.getLogger('main.hba') +logger.debug("starting hba logger") + +# class for testing HBA antennas +class HBA(object): + def __init__(self, db, hba): + self.db = db + self.hba = hba + self.antenna_data = AntennaData({'n_rcus': hba.nr_tiles * 2, 'data-dir': data_dir()}) + self.rcumode = 0 + + def reset(self): + self.db.rcus_changed = False + + def turn_on_tiles(self): + pass + + def turn_off_tile(self, tile_nr): + tile = self.hba.tile[tile_nr] + tile.x.rcu_off = 1 + tile.y.rcu_off = 1 + logger.info("turned off tile %d RCU(%d,%d)" % (tile.nr, tile.x.rcu, tile.y.rcu)) + rspctl("--rcumode=0 --select=%d,%d" % (tile.x.rcu, tile.y.rcu), wait=2.0) + self.db.rcus_changed = True + return + + def turn_off_bad_tiles(self): + for tile in self.hba.tile: + if tile.x.rcu_off and tile.y.rcu_off: + continue + no_modem = 0 + modem_error = 0 + for elem in tile.element: + if elem.no_modem: + no_modem += 1 + if elem.modem_error: + modem_error += 1 + if tile.x.osc or tile.y.osc or (no_modem >= 8) or (modem_error >= 8): + self.turn_off_tile(tile.nr) + return + + def set_mode(self, mode): + if self.db.rcumode != mode: + self.db.rcumode = mode + turn_off_rcus() + turn_on_rcus(mode=mode, rcus=self.hba.select_list()) + self.hba.reset_rcu_state() + + def record_data(self, rec_time, new_data=False): + if new_data or self.db.rcus_changed or self.antenna_data.seconds() < rec_time: + logger.debug('record info changed') + self.db.rcus_changed = False + self.antenna_data.collect(n_seconds=rec_time) + for tile in self.hba.tile: + if tile.x.rcu_off or tile.y.rcu_off: + self.antenna_data.mask_rcu([tile.x.rcu, tile.y.rcu]) + + + def check_modem(self, mode): + # setup internal test db + n_elements = 16 + n_tests = 7 + modem_tst = list() + for tile_nr in range(self.db.nr_hba): + tile = list() + for elem_nr in range(n_elements): + test = list() + for tst_nr in range(n_tests): + test.append([0, 0]) + tile.append(test) + modem_tst.append(tile) + # done + + logger.info("=== Start HBA modem test ===") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=50.0): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + + time.sleep(4.0) + ctrlstr = list() + ctrlstr.append(('129,' * 16)[:-1]) # 0ns + ctrlstr.append(('133,' * 16)[:-1]) # 0.5ns + ctrlstr.append(('137,' * 16)[:-1]) # 1ns + ctrlstr.append(('145,' * 16)[:-1]) # 2ns + ctrlstr.append(('161,' * 16)[:-1]) # 4ns + ctrlstr.append(('193,' * 16)[:-1]) # 8ns + ctrlstr.append(('253,' * 16)[:-1]) # 15.5ns + # rsp_hba_delay(delay=ctrlstr[6], rcus=self.hba.selectList(), discharge=False) + tst_nr = 0 + for ctrl in ctrlstr: + + rsp_hba_delay(delay=ctrl, rcus=self.hba.select_list(), discharge=False) + #data = rspctl('--realdelays', wait=1.0).splitlines() + data = rspctl('--realdelays', wait=0.0).splitlines() + + ctrllist = ctrl.split(',') + for line in data: + if line[:3] == 'HBA': + rcu = int(line[line.find('[') + 1:line.find(']')]) + hba_nr = rcu / 2 + if hba_nr >= self.hba.nr_tiles: + continue + if self.hba.tile[hba_nr].on_bad_list: + continue + realctrllist = line[line.find('=') + 1:].strip().split() + for elem in self.hba.tile[hba_nr].element: + if ctrllist[elem.nr - 1] != realctrllist[elem.nr - 1]: + logger.info("Modemtest Tile=%d RCU=%d Element=%d ctrlword=%s response=%s" % ( + hba_nr, rcu, elem.nr, ctrllist[elem.nr - 1], realctrllist[elem.nr - 1])) + + if realctrllist[elem.nr - 1].count('?') == 3: + # elem.no_modem += 1 + modem_tst[hba_nr][elem.nr - 1][tst_nr][0] = 1 + else: + # elem.modem_error += 1 + modem_tst[hba_nr][elem.nr - 1][tst_nr][1] = 1 + tst_nr += 1 + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + # analyse test results and add to DB + no_modem = dict() + modem_error = dict() + for tile_nr in range(self.db.nr_hba): + n_no_modem = dict() + n_modem_error = dict() + for elem_nr in range(n_elements): + n_no_modem[elem_nr] = 0 + n_modem_error[elem_nr] = 0 + for tst_nr in range(n_tests): + if modem_tst[tile_nr][elem_nr][tst_nr][0]: + n_no_modem[elem_nr] += 1 + if modem_tst[tile_nr][elem_nr][tst_nr][1]: + n_modem_error[elem_nr] += 1 + no_modem[tile_nr] = n_no_modem + modem_error[tile_nr] = n_modem_error + + n_tile_err = 0 + for tile in no_modem: + n_elem_err = 0 + for elem in no_modem[tile]: + if no_modem[tile][elem] == n_tests: + n_elem_err += 1 + if n_elem_err == n_elements: + n_tile_err += 1 + + if n_tile_err < (self.db.nr_hba / 2): + for tile_nr in range(self.db.nr_hba): + for elem_nr in range(n_elements): + #if no_modem[tile_nr][elem_nr] >= 2: # 2 or more ctrl values went wrong + if no_modem[tile_nr][elem_nr]: # 1 or more ctrl values went wrong + self.db.hba.tile[tile_nr].element[elem_nr].no_modem = 1 + + n_tile_err = 0 + for tile in modem_error: + n_elem_err = 0 + for elem in modem_error[tile]: + if modem_error[tile][elem] == n_tests: + n_elem_err += 1 + if n_elem_err == n_elements: + n_tile_err += 1 + + if n_tile_err < (self.db.nr_hba / 2): + for tile_nr in range(self.db.nr_hba): + for elem_nr in range(n_elements): + #if no_modem[tile_nr][elem_nr] >= 2: # 2 or more ctrl values went wrong + if no_modem[tile_nr][elem_nr]: # 1 or more ctrl values went wrong + self.db.hba.tile[tile_nr].element[elem_nr].modem_error = 1 + + self.hba.modem_check_done = 1 + self.db.add_test_done('M%d' % mode) + logger.info("=== Done HBA modem test ===") + # self.db.rcumode = 0 + return + + # check for summator noise and turn off RCU + def check_summator_noise(self, mode, parset): + logger.info("=== Start HBA tile based summator-noise test ===") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=25.0): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + + delay_str = ('253,' * 16)[:-1] + rsp_hba_delay(delay=delay_str, rcus=self.hba.select_list()) + + self.record_data(rec_time=12) + + for pol_nr, pol in enumerate(('X', 'Y')): + sum_noise = check_for_summator_noise(data=self.antenna_data, band=mode_to_band(mode), + pol=pol, parset=parset) + for n in sum_noise: + rcu, cnt, n_peaks = n + tile = rcu / 2 + logger.info("RCU %d Tile %d Summator-Noise cnt=%3.1f peaks=%3.1f" % ( + rcu, tile, cnt, n_peaks)) + if pol == 'X': + self.hba.tile[tile].x.summator_noise = 1 + else: + self.hba.tile[tile].y.summator_noise = 1 + self.turn_off_tile(tile) + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.hba.summatornoise_check_done = 1 + self.db.add_test_done('SN%d' % mode) + logger.info("=== Done HBA tile based summator-noise test ===") + return + + # check for oscillating tiles and turn off RCU + # stop one RCU each run + def check_oscillation(self, mode, parset): + logger.info("=== Start HBA tile based oscillation test ===") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=35.0): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + + delay_str = ('253,' * 16)[:-1] + get_new_data = rsp_hba_delay(delay=delay_str, rcus=self.hba.select_list()) + + clean = False + while not clean: + if not self.db.check_end_time(duration=25.0): + logger.warning("check stopped, end time reached") + return + + clean = True + + self.record_data(rec_time=12, new_data=get_new_data) + + for pol_nr, pol in enumerate(('X', 'Y')): + # result is a sorted list on maxvalue + result = check_for_oscillation(data=self.antenna_data, band=mode_to_band(mode), pol=pol, parset=parset) + max_sum = n_peaks = 0 + if len(result) > 1: + if len(result) == 2: + rcu, max_sum, n_peaks, rcu_low = result[1] + else: + ref_low = result[0][3] + max_low_tile = (-1, -1) + max_sum_tile = (-1, -1) + for i in result[1:]: + rcu, max_sum, n_peaks, tile_low = i + # rcu = (tile * 2) + pol_nr + if max_sum > max_sum_tile[0]: + max_sum_tile = (max_sum, rcu) + if (tile_low - ref_low) > max_low_tile[0]: + max_low_tile = (tile_low, rcu) + + rcu_low, rcu = max_low_tile + + clean = False + get_new_data = True + tile = rcu / 2 + # tile_polarity = rcu % 2 + # rcu = (tile * 2) + pol_nr + logger.info("RCU %d Tile %d Oscillation sum=%3.1f peaks=%d low=%3.1f" % ( + rcu, tile, max_sum, n_peaks, rcu_low)) + self.turn_off_tile(tile) + if pol_nr == 0: + self.hba.tile[tile].x.osc = 1 + else: + self.hba.tile[tile].y.osc = 1 + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.hba.oscillation_check_done = 1 + self.db.add_test_done('O%d' % mode) + logger.info("=== Done HBA tile based oscillation test ===") + return + + def check_noise(self, mode, record_time, parset): + logger.info("=== Start HBA tile based noise test ===") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=(record_time + 60.0)): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + + for tile in self.hba.tile: + if tile.x.rcu_off or tile.y.rcu_off: + logger.info("skip low-noise test for tile %d, RCUs turned off" % tile.nr) + + delay_str = ('253,' * 16)[:-1] + get_new_data = rsp_hba_delay(delay=delay_str, rcus=self.hba.select_list()) + + self.record_data(rec_time=record_time, new_data=get_new_data) + + for pol_nr, pol in enumerate(('X', 'Y')): + # result is a sorted list on maxvalue + low_noise, high_noise, jitter = check_for_noise(data=self.antenna_data, band=mode_to_band(mode), pol=pol, + parset=parset) + + for n in low_noise: + rcu, val, bad_secs, ref, diff = n + tile = rcu / 2 + if self.hba.tile[tile].x.rcu_off or self.hba.tile[tile].y.rcu_off: + continue + logger.info("RCU %d Tile %d Low-Noise value=%3.1f bad=%d(%d) limit=%3.1f diff=%3.3f" % ( + rcu, tile, val, bad_secs, self.antenna_data.seconds(), ref, diff)) + + if pol == 'X': + tile_polarity = self.hba.tile[tile].x + else: + tile_polarity = self.hba.tile[tile].y + + tile_polarity.low_seconds += self.antenna_data.seconds() + tile_polarity.low_bad_seconds += bad_secs + if val < tile_polarity.low_val: + tile_polarity.low_noise = 1 + tile_polarity.low_val = val + tile_polarity.low_ref = ref + tile_polarity.low_diff = diff + + for n in high_noise: + rcu, val, bad_secs, ref, diff = n + tile = rcu / 2 + logger.info("RCU %d Tile %d High-Noise value=%3.1f bad=%d(%d) limit=%3.1f diff=%3.1f" % ( + rcu, tile, val, bad_secs, self.antenna_data.seconds(), ref, diff)) + + if pol == 'X': + tile_polarity = self.hba.tile[tile].x + else: + tile_polarity = self.hba.tile[tile].y + + tile_polarity.high_seconds += self.antenna_data.seconds() + tile_polarity.high_bad_seconds += bad_secs + if val > tile_polarity.high_val: + tile_polarity.high_noise = 1 + tile_polarity.high_val = val + tile_polarity.high_ref = ref + tile_polarity.high_diff = diff + + for n in jitter: + rcu, val, ref, bad_secs = n + tile = rcu / 2 + logger.info("RCU %d Tile %d Jitter, fluctuation=%3.1fdB normal=%3.1fdB" % (rcu, tile, val, ref)) + + if pol == 'X': + tile_polarity = self.hba.tile[tile].x + else: + tile_polarity = self.hba.tile[tile].y + + tile_polarity.jitter_seconds += self.antenna_data.seconds() + tile_polarity.jitter_bad_seconds += bad_secs + if val > tile_polarity.jitter_val: + tile_polarity.jitter = 1 + tile_polarity.jitter_val = val + tile_polarity.jitter_ref = ref + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.hba.noise_check_done = 1 + self.db.add_test_done('NS%d=%d' % (mode, record_time)) + logger.info("=== Done HBA tile based noise test ===") + return + + def check_spurious(self, mode, parset): + logger.info("=== Start HBA tile based spurious test ===") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=12.0): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + + delay_str = ('253,' * 16)[:-1] + get_new_data = rsp_hba_delay(delay=delay_str, rcus=self.hba.select_list()) + + self.record_data(rec_time=12, new_data=get_new_data) + + for pol_nr, pol in enumerate(('X', 'Y')): + # result is a sorted list on maxvalue + result = check_for_spurious(data=self.antenna_data, band=mode_to_band(mode), pol=pol, parset=parset) + for rcu in result: + tile = rcu / 2 + logger.info("RCU %d Tile %d pol %c Spurious" % (rcu, tile, pol)) + if pol == 'X': + self.hba.tile[tile].x.spurious = 1 + else: + self.hba.tile[tile].y.spurious = 1 + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.hba.spurious_check_done = 1 + self.db.add_test_done('SP%d' % mode) + logger.info("=== Done HBA spurious test ===") + return + + def check_rf_power(self, mode, parset): + logger.info("=== Start HBA tile based RF test ===") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=37.0): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + + # check twice + # 2 ... check if all elements are turned off, normal value between 60.0 and 62.0 + # 128 ... + # 253 ... + for tile in self.hba.tile: + if tile.x.rcu_off or tile.y.rcu_off: + logger.debug("skip signal test for tile %d, RCUs turned off" % tile.nr) + + ctrl_2_subband = None + + for ctrl in ('128,', '253,', '2,'): + if not self.db.check_end_time(duration=80.0): + logger.warning("check stopped, end time reached") + return + + ctrl_nr = -1 + if ctrl == '128,': + ctrl_nr = 0 + elif ctrl == '253,': + ctrl_nr = 1 + + logger.debug("HBA signal test, ctrl word %s" % (ctrl[:-1])) + + delay_str = (ctrl * 16)[:-1] + rsp_hba_delay(delay=delay_str, rcus=self.hba.select_list()) + + + check_for_valid_retries = 3 + record_time = 2 + while check_for_valid_retries > 0: + self.record_data(rec_time=record_time, new_data=True) + if ctrl_nr == -1: + parset.parset['rf']['subbands'] = str(ctrl_2_subband) + + logger.debug("subband=%s" % parset.parset['rf']['subbands']) + test_info_x, signal_info_x = check_rf_power(data=self.antenna_data, band=mode_to_band(mode), + pol='X', parset=parset) + test_info_y, signal_info_y = check_rf_power(data=self.antenna_data, band=mode_to_band(mode), + pol='Y', parset=parset) + if ctrl_nr > -1: + if test_info_x['valid'] and test_info_y['valid']: + check_for_valid_retries = 0 + else: + logger.warning("HBA, No valid test signal, try again") + check_for_valid_retries -= 1 + record_time = 30 + else: + check_for_valid_retries = 0 + + logger.debug("X data: control-word=%s, test subband=%d, median val=%5.3f" % ( + ctrl[:-1], test_info_x['subband'], test_info_x['test_val'])) + + logger.debug("Y data: control-word=%s, test subband=%d, median val=%5.3f" % ( + ctrl[:-1], test_info_y['subband'], test_info_y['test_val'])) + + if ctrl_nr > -1: + ctrl_2_subband = test_info_x['subband'] + self.hba.rf_ref_signal_x[ctrl_nr] = test_info_x['test_val'] + self.hba.rf_ref_signal_y[ctrl_nr] = test_info_y['test_val'] + self.hba.rf_test_subband_x[ctrl_nr] = test_info_x['subband'] + self.hba.rf_test_subband_y[ctrl_nr] = test_info_y['subband'] + + if test_info_x['valid'] and test_info_y['valid']: + for tile in self.hba.tile: + if tile.x.rcu_off or tile.y.rcu_off: + continue + + if str(tile.x.rcu) in signal_info_x: + tile.x.test_signal[ctrl_nr] = signal_info_x[str(tile.x.rcu)]['value'] + + if signal_info_x[str(tile.x.rcu)]['status'] == 'no_signal': + tile.x.no_signal = 1 + elif signal_info_x[str(tile.x.rcu)]['status'] == 'no_power': + tile.no_power = 1 + elif signal_info_x[str(tile.x.rcu)]['status'] == 'low': + tile.x.too_low = 1 + elif signal_info_x[str(tile.x.rcu)]['status'] == 'high': + tile.x.too_high = 1 + + if str(tile.y.rcu) in signal_info_y: + tile.y.test_signal[ctrl_nr] = signal_info_y[str(tile.y.rcu)]['value'] + + if signal_info_y[str(tile.y.rcu)]['status'] == 'no_signal': + tile.y.no_signal = 1 + elif signal_info_y[str(tile.y.rcu)]['status'] == 'no_power': + tile.no_power = 1 + elif signal_info_y[str(tile.y.rcu)]['status'] == 'low': + tile.y.too_low = 1 + elif signal_info_y[str(tile.y.rcu)]['status'] == 'high': + tile.y.too_high = 1 + + if str(tile.x.rcu) in signal_info_x and str(tile.y.rcu) in signal_info_y: + if signal_info_x[str(tile.x.rcu)]['status'] != 'normal' or \ + signal_info_y[str(tile.y.rcu)]['status'] != 'normal': + logger.info("HBA Tile=%d: control-word=%s X=%3.1fdB(%s) Y=%3.1fdB(%s)" % ( + tile.nr, + ctrl[:-1], + signal_info_x[str(tile.x.rcu)]['value'], + signal_info_x[str(tile.x.rcu)]['status'], + signal_info_y[str(tile.y.rcu)]['value'], + signal_info_y[str(tile.y.rcu)]['status'])) + else: + logger.warning("HBA, No valid test signal") + self.hba.rf_signal_to_low = 1 + else: + # TODO: not valid, so no values, change spectrum_checks.py + for tile in self.hba.tile: + if tile.x.rcu_off or tile.y.rcu_off: + continue + if signal_info_x[str(tile.x.rcu)]['status'] in ('high',): + logger.warning("Tile %d rcu %d not switched off" % (tile.nr, tile.x.rcu)) + if signal_info_y[str(tile.y.rcu)]['status'] in ('high',): + logger.warning("Tile %d rcu %d not switched off" % (tile.nr, tile.y.rcu)) + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.hba.signal_check_done = 1 + self.db.add_test_done('S%d' % mode) + logger.info("=== Done HBA signal test ===") + return + + # Next tests are element based + # + # 8bit control word + # + # bit-7 RF on/off 1 = on + # bit-6 delay 1 = 8 ns + # bit-5 delay 1 = 4 ns + # bit-4 delay 1 = 2 ns + # bit-3 delay 1 = 1 ns + # bit-2 delay 1 = 0.5 ns + # bit-1 LNA on/off 1 = off + # bit-0 LED on/off 1 = on + # + # control word = 0 (signal - 30 db) + # control word = 2 (signal - 40 db) + # + def check_elements(self, mode, record_time, parset): + + logger.info("=== Start HBA element based tests ===") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + self.set_mode(mode) + + n_rcus_off = 0 + for ctrl in ('128', '253'): + ctrl_nr = -1 + if ctrl == '128': + ctrl_nr = 0 + elif ctrl == '253': + ctrl_nr = 1 + + parset.parset['ctrl-word'] = ctrl + + #logger.info(" check elements with ctrlword %s" % (ctrl)) + for elem in range(self.hba.tile[0].nr_elements): + logger.info(" check elements %d with control-word %s" % ((elem + 1), ctrl)) + + if not self.db.check_end_time(duration=45.0): + logger.warning("check stopped, end time reached") + return + + if n_rcus_off > 0: + rsp_rcu_mode(mode=mode, rcus=self.hba.select_list()) + n_rcus_off = 0 + for tile in self.hba.tile: + if tile.element[elem].no_modem or tile.element[elem].modem_error: + self.turn_off_tile(tile.nr) + n_rcus_off += 1 + logger.debug("skip tile %d, modem error" % tile.nr) + + delay_str = ('2,' * elem + ctrl + ',' + '2,' * 15)[:33] + rsp_hba_delay(delay=delay_str, rcus=self.hba.select_list()) + + clean = False + while not clean: + if not self.db.check_end_time(duration=(record_time + 45.0)): + logger.warning("check stopped, end time reached") + return + + self.record_data(rec_time=record_time, new_data=True) + + clean, n_off = self.check_oscillation_elements(elem, parset) + n_rcus_off += n_off + if n_off > 0: + continue + n_off = self.check_spurious_elements(elem, parset) + n_rcus_off += n_off + if n_off > 0: + continue + self.check_noise_elements(elem, parset) + self.check_rf_power_elements(elem, ctrl_nr, parset) + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.hba.element_check_done = 1 + self.db.add_test_done('E%d' % mode) + logger.info("=== Done HBA element tests ===") + return + + # check for oscillating tiles and turn off RCU + # stop one RCU each run + # elem counts from 0..15 (for user output use 1..16) + def check_oscillation_elements(self, elem, parset): + logger.info("--- oscillation test --") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + clean = True + n_rcus_off = 0 + # result is a sorted list on maxvalue + result = check_for_oscillation(data=self.antenna_data, band=mode_to_band(self.db.rcumode), pol='XY', + parset=parset) + if len(result) > 1: + clean = False + rcu, peaks_sum, n_peaks, rcu_low = sorted(result[1:], reverse=True)[0] # result[1] + tile = rcu / 2 + if self.hba.tile[tile].element[elem].no_modem or self.hba.tile[tile].element[elem].modem_error: + return True, 0 + tile_polarity = rcu % 2 + logger.info("%s RCU %d Tile %d Element %d Oscillation sum=%3.1f peaks=%d, low=%3.1f" % ( + parset.as_string('ctrl-word'), rcu, tile, elem + 1, peaks_sum, n_peaks, rcu_low)) + self.turn_off_tile(tile) + n_rcus_off += 1 + if tile_polarity == 0: + self.hba.tile[tile].element[elem].x.osc = 1 + else: + self.hba.tile[tile].element[elem].y.osc = 1 + return clean, n_rcus_off + + def check_spurious_elements(self, elem, parset): + logger.info("--- spurious test ---") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + n_rcus_off = 0 + # result is a sorted list on maxvalue + result = check_for_spurious(data=self.antenna_data, band=mode_to_band(self.db.rcumode), pol='XY', + parset=parset) + for rcu in result: + tile = rcu / 2 + tile_polarity = rcu % 2 + logger.info("%s RCU %d Tile %d Element %d pol %d Spurious" % (parset.as_string('ctrl-word'), rcu, tile, elem + 1, tile_polarity)) + self.turn_off_tile(tile) + n_rcus_off += 1 + if tile_polarity == 0: + self.hba.tile[tile].element[elem].x.spurious = 1 + else: + self.hba.tile[tile].element[elem].y.spurious = 1 + return n_rcus_off + + def check_noise_elements(self, elem, parset): + logger.info("--- noise test ---") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + # result is a sorted list on maxvalue + low_noise, high_noise, jitter = check_for_noise(data=self.antenna_data, band=mode_to_band(self.db.rcumode), + pol='XY', parset=parset) + + for n in low_noise: + rcu, val, bad_secs, ref, diff = n + tile = rcu / 2 + logger.info("%s RCU %d Tile %d Element %d Low-Noise value=%3.1f bad=%d(%d) limit=%3.1f diff=%3.3f" % ( + parset.as_string('ctrl-word'), rcu, tile, elem + 1, val, bad_secs, self.antenna_data.seconds(), ref, diff)) + + if rcu % 2 == 0: + elem_polarity = self.hba.tile[tile].element[elem].x + else: + elem_polarity = self.hba.tile[tile].element[elem].y + + elem_polarity.low_seconds += self.antenna_data.seconds() + elem_polarity.low_bad_seconds += bad_secs + if val < elem_polarity.low_val: + elem_polarity.low_noise = 1 + elem_polarity.low_val = val + elem_polarity.low_ref = ref + elem_polarity.low_diff = diff + + for n in high_noise: + rcu, val, bad_secs, ref, diff = n + tile = rcu / 2 + logger.info("%s RCU %d Tile %d Element %d High-Noise value=%3.1f bad=%d(%d) ref=%3.1f diff=%3.1f" % ( + parset.as_string('ctrl-word'), rcu, tile, elem + 1, val, bad_secs, self.antenna_data.seconds(), ref, diff)) + + if rcu % 2 == 0: + elem_polarity = self.hba.tile[tile].element[elem].x + else: + elem_polarity = self.hba.tile[tile].element[elem].y + + elem_polarity.high_seconds += self.antenna_data.seconds() + elem_polarity.high_bad_seconds += bad_secs + if val > elem_polarity.high_val: + elem_polarity.high_noise = 1 + elem_polarity.high_val = val + elem_polarity.high_ref = ref + elem_polarity.high_diff = diff + + for n in jitter: + rcu, val, ref, bad_secs = n + tile = rcu / 2 + logger.info("%s RCU %d Tile %d Element %d Jitter, fluctuation=%3.1fdB normal=%3.1fdB" % ( + parset.as_string('ctrl-word'), rcu, tile, elem + 1, val, ref)) + + if rcu % 2 == 0: + elem_polarity = self.hba.tile[tile].element[elem].x + else: + elem_polarity = self.hba.tile[tile].element[elem].y + + elem_polarity.jitter_seconds += self.antenna_data.seconds() + elem_polarity.jitter_bad_seconds += bad_secs + if val > elem_polarity.jitter_val: + elem_polarity.jitter = 1 + elem_polarity.jitter_val = val + elem_polarity.jitter_ref = ref + return + + def check_rf_power_elements(self, elem, ctrl_nr, parset): + + logger.info("--- RF test ---") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + test_info_x, signal_info_x = check_rf_power(data=self.antenna_data, band=mode_to_band(self.db.rcumode), + pol='X', parset=parset) + logger.debug("X data: test subband=%d, median val=%5.3f" % (test_info_x['subband'], + test_info_x['test_val'])) + + test_info_y, signal_info_y = check_rf_power(data=self.antenna_data, band=mode_to_band(self.db.rcumode), + pol='Y', parset=parset) + logger.debug("Y data: test subband=%d, median val=%5.3f" % (test_info_y['subband'], + test_info_y['test_val'])) + + if test_info_x['valid'] and test_info_y['valid']: + for tile in self.hba.tile: + if tile.x.rcu_off or tile.y.rcu_off: + continue + + tile.element[elem].x.rf_ref_signal[ctrl_nr] = test_info_x['test_val'] + tile.element[elem].y.rf_ref_signal[ctrl_nr] = test_info_y['test_val'] + tile.element[elem].x.rf_test_subband[ctrl_nr] = test_info_x['subband'] + tile.element[elem].y.rf_test_subband[ctrl_nr] = test_info_y['subband'] + + if str(tile.x.rcu) in signal_info_x: + tile.element[elem].x.test_signal[ctrl_nr] = signal_info_x[str(tile.x.rcu)]['value'] + if signal_info_x[str(tile.x.rcu)]['status'] == 'no_signal': + tile.element[elem].x.no_signal = 1 + elif signal_info_x[str(tile.x.rcu)]['status'] == 'no_power': + tile.element[elem].no_power = 1 + elif signal_info_x[str(tile.x.rcu)]['status'] == 'low': + tile.element[elem].x.too_low = 1 + elif signal_info_x[str(tile.x.rcu)]['status'] == 'high': + tile.element[elem].x.too_high = 1 + + if str(tile.y.rcu) in signal_info_y: + tile.element[elem].y.test_signal[ctrl_nr] = signal_info_y[str(tile.y.rcu)]['value'] + if signal_info_y[str(tile.y.rcu)]['status'] == 'no_signal': + tile.element[elem].y.no_signal = 1 + elif signal_info_y[str(tile.y.rcu)]['status'] == 'no_power': + tile.element[elem].no_power = 1 + elif signal_info_y[str(tile.y.rcu)]['status'] == 'low': + tile.element[elem].y.too_low = 1 + elif signal_info_y[str(tile.y.rcu)]['status'] == 'high': + tile.element[elem].y.too_high = 1 + + if str(tile.x.rcu) in signal_info_x and str(tile.y.rcu) in signal_info_y: + if signal_info_x[str(tile.x.rcu)]['status'] != 'normal' or \ + signal_info_y[str(tile.y.rcu)]['status'] != 'normal': + logger.info("%s HBA Tile=%d Elem=%d: X=%3.1fdB(%s) Y=%3.1fdB(%s)" % ( + parset.as_string('ctrl-word'), + tile.nr, + elem + 1, + signal_info_x[str(tile.x.rcu)]['value'], + signal_info_x[str(tile.x.rcu)]['status'], + signal_info_y[str(tile.y.rcu)]['value'], + signal_info_y[str(tile.y.rcu)]['status'])) + else: + logger.warning("HBA Elem=%d, No valid test signal" % (elem + 1)) + # self.hba.rf_signal_to_low = 1 + + return diff --git a/LCU/checkhardware/checkhardware_lib/lba.py b/LCU/checkhardware/checkhardware_lib/lba.py new file mode 100644 index 00000000000..feab5aa504d --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/lba.py @@ -0,0 +1,474 @@ +import logging +from data import AntennaData +from spectrum_checks import * +from lofar import * + +logger = logging.getLogger('main.lba') +logger.debug("starting lba logger") + +# class for testing LBA antennas +class LBA(object): + def __init__(self, db, lba): + self.db = db + self.lba = lba + self.antenna_data = AntennaData({'n_rcus': self.lba.nr_antennas * 2, 'data-dir': data_dir()}) + self.db.rcus_changed = False + + # Average normal value = 150.000.000 (81.76 dBm) -3dB +3dB + # LOW/HIGH LIMIT is used for calculating mean value + self.lowLimit = -3.0 # dB + self.highLimit = 3.0 # dB + + # MEAN LIMIT is used to check if mean of all antennas is ok + self.meanLimit = 66.0 # dB + + def reset(self): + self.db.rcus_changed = False + + def turn_off_ant(self, ant_nr): + ant = self.lba.ant[ant_nr] + ant.x.rcu_off = 1 + ant.y.rcu_off = 1 + logger.info("turned off antenna %d RCU(%d,%d)" % (ant.nr_pvss, ant.x.rcu, ant.y.rcu)) + rspctl("--rcumode=0 --select=%d,%d" % (ant.x.rcu, ant.y.rcu), wait=2.0) + rspctl("--rcuenable=0 --select=%d,%d" % (ant.x.rcu, ant.y.rcu), wait=2.0) + self.db.rcus_changed = True + return + + def set_mode(self, mode): + if self.db.rcumode != mode: + self.db.rcumode = mode + turn_off_rcus() + turn_on_rcus(mode=mode, rcus=self.lba.select_list()) + self.lba.reset_rcu_state() + + def record_data(self, rec_time, new_data=False): + if new_data or self.db.rcus_changed or self.antenna_data.seconds() < rec_time: + self.db.rcus_changed = False + logger.debug('record info changed') + self.antenna_data.collect(n_seconds=rec_time) + for ant in self.lba.ant: + if ant.x.rcu_off or ant.y.rcu_off: + self.antenna_data.mask_rcu([ant.x.rcu, ant.y.rcu]) + + # check for oscillating tiles and turn off RCU + # stop one RCU each run + def check_oscillation(self, mode, parset): + logger.info("=== Start %s oscillation test ===" % self.lba.label) + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=28.0): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + band = mode_to_band(mode) + + clean = False + while not clean: + if not self.db.check_end_time(duration=18.0): + logger.warning("check stopped, end time reached") + return + + clean = True + self.record_data(rec_time=3, new_data=True) + + + for pol in ('X', 'Y'): + # result is a sorted list on maxvalue + result = check_for_oscillation(data=self.antenna_data, band=band, pol=pol, parset=parset) + if len(result) > 1: + clean = False + rcu, peaks_sum, n_peaks, ant_low = sorted(result[1:], reverse=True)[0] # result[1] + ant = rcu / 2 + logger.info("RCU %d LBA %d Oscillation sum=%3.1f peaks=%d low=%3.1fdB" % ( + rcu, self.lba.ant[ant].nr_pvss, peaks_sum, n_peaks, ant_low)) + self.turn_off_ant(ant) + if pol == 'X': + self.lba.ant[ant].x.osc = 1 + else: + self.lba.ant[ant].y.osc = 1 + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.lba.oscillation_check_done = 1 + self.db.add_test_done('O%d' % mode) + logger.info("=== Done %s oscillation test ===" % self.lba.label) + return + + def check_noise(self, mode, record_time, parset): + logger.info("=== Start %s noise test ===" % self.lba.label) + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=(record_time + 100.0)): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + band = mode_to_band(mode) + + for ant in self.lba.ant: + if ant.x.rcu_off or ant.y.rcu_off: + logger.info("skip low-noise test for antenna %d, RCUs turned off" % ant.nr) + + self.record_data(rec_time=record_time) + + # result is a sorted list on maxvalue + low_noise, high_noise, jitter = check_for_noise(data=self.antenna_data, band=band, pol='XY', parset=parset) + + for n in low_noise: + rcu, val, bad_secs, ref, diff = n + ant = rcu / 2 + if self.lba.ant[ant].x.rcu_off or self.lba.ant[ant].y.rcu_off: + continue + # self.turnOffAnt(ant) + logger.info("RCU %d Ant %d Low-Noise value=%3.1f bad=%d(%d) limit=%3.1f diff=%3.3f" % ( + rcu, self.lba.ant[ant].nr_pvss, val, bad_secs, self.antenna_data.seconds(), ref, diff)) + + self.antenna_data.mask_rcu(rcu) + if self.antenna_data.polarity(rcu) == self.antenna_data.XYPOL: + antenna = self.lba.ant[ant].x + else: + antenna = self.lba.ant[ant].y + + antenna.low_seconds += self.antenna_data.seconds() + antenna.low_bad_seconds += bad_secs + if val < self.lba.ant[ant].x.low_val: + antenna.low_noise = 1 + antenna.low_val = val + antenna.low_ref = ref + antenna.low_diff = diff + + for n in high_noise: + rcu, val, bad_secs, ref, diff = n + ant = rcu / 2 + # self.turnOffAnt(ant) + logger.info("RCU %d Ant %d High-Noise value=%3.1f bad=%d(%d) ref=%3.1f diff=%3.1f" % ( + rcu, self.lba.ant[ant].nr_pvss, val, bad_secs, self.antenna_data.seconds(), ref, diff)) + + self.antenna_data.mask_rcu(rcu) + if self.antenna_data.polarity(rcu) == self.antenna_data.XYPOL: + antenna = self.lba.ant[ant].x + else: + antenna = self.lba.ant[ant].y + + antenna.high_seconds += self.antenna_data.seconds() + antenna.high_bad_seconds += bad_secs + if val > self.lba.ant[ant].x.high_val: + antenna.high_noise = 1 + antenna.high_val = val + antenna.high_ref = ref + antenna.high_diff = diff + + for n in jitter: + rcu, val, ref, bad_secs = n + ant = rcu / 2 + logger.info("RCU %d Ant %d Jitter, fluctuation=%3.1fdB normal=%3.1fdB" % ( + rcu, self.lba.ant[ant].nr_pvss, val, ref)) + + self.antenna_data.mask_rcu(rcu) + if self.antenna_data.polarity(rcu) == self.antenna_data.XYPOL: + antenna = self.lba.ant[ant].x + else: + antenna = self.lba.ant[ant].y + + antenna.jitter_seconds += self.antenna_data.seconds() + antenna.jitter_bad_seconds += bad_secs + if val > antenna.jitter_val: + antenna.jitter = 1 + antenna.jitter_val = val + antenna.jitter_ref = ref + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.lba.noise_check_done = 1 + self.db.add_test_done('NS%d=%d' % (mode, record_time)) + logger.info("=== Done %s noise test ===" % self.lba.label) + return + + def check_spurious(self, mode, parset): + logger.info("=== Start %s spurious test ===" % self.lba.label) + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=12.0): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + + self.record_data(rec_time=3) + + # result is a sorted list on maxvalue + result = check_for_spurious(data=self.antenna_data, band=mode_to_band(mode), pol='XY', parset=parset) + for rcu in result: + ant = rcu / 2 + # self. turnOffAnt(ant) + logger.info("RCU %d Ant %d pol %s Spurious" % ( + rcu, self.lba.ant[ant].nr_pvss, self.antenna_data.polarity(rcu))) + + self.antenna_data.mask_rcu(rcu) + if self.antenna_data.polarity(rcu) == self.antenna_data.XYPOL: + self.lba.ant[ant].x.spurious = 1 + else: + self.lba.ant[ant].y.spurious = 1 + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.lba.spurious_check_done = 1 + self.db.add_test_done('SP%d' % mode) + logger.info("=== Done %s spurious test ===" % self.lba.label) + return + + def check_short(self, mode, parset): + logger.info("=== Start %s Short test ===" % self.lba.label) + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=15.0): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + band = mode_to_band(mode) + self.record_data(rec_time=3) + + # search for shorted cable (input), mean signal all subbands between 55 and 61 dB + logger.debug("Check Short") + short = check_for_short(data=self.antenna_data, band=band, parset=parset) + for i in short: + rcu, mean_val = i + ant = rcu / 2 + + logger.info("%s %2d RCU %3d Short, mean value band=%5.1fdB" % ( + self.lba.label, self.lba.ant[ant].nr_pvss, rcu, mean_val)) + + self.antenna_data.mask_rcu(rcu) + if self.antenna_data.polarity(rcu) == self.antenna_data.XYPOL: + self.lba.ant[ant].x.short = 1 + self.lba.ant[ant].x.short_val = mean_val + else: + self.lba.ant[ant].y.short = 1 + self.lba.ant[ant].y.short_val = mean_val + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.lba.short_check_done = 1 + self.db.add_test_done('SH%d' % mode) + logger.info("=== Done %s Short test ===" % self.lba.label) + return + + def check_flat(self, mode, parset): + logger.info("=== Start %s Flat test ===" % self.lba.label) + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=15.0): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + band = mode_to_band(mode) + + self.record_data(rec_time=3) + + # search for flatliners, mean signal all subbands between 63 and 65 dB + logger.debug("Check Flat") + flat = check_for_flat(data=self.antenna_data, band=band, parset=parset) + for i in flat: + rcu, mean_val = i + ant = rcu / 2 + + logger.info("%s %2d RCU %3d Flat, mean value band=%5.1fdB" % ( + self.lba.label, + self.lba.ant[ant].nr_pvss, + rcu, + mean_val)) + + self.antenna_data.mask_rcu(rcu) + if self.antenna_data.polarity(rcu) == self.antenna_data.XYPOL: + self.lba.ant[ant].x.flat = 1 + self.lba.ant[ant].x.flat_val = mean_val + else: + self.lba.ant[ant].y.flat = 1 + self.lba.ant[ant].y.flat_val = mean_val + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.lba.flat_check_done = 1 + self.db.add_test_done('F%d' % mode) + logger.info("=== Done %s Flat test ===" % self.lba.label) + return + + def check_down(self, mode, parset): + logger.info("=== Start %s Down test ===" % self.lba.label) + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=15.0): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + self.record_data(rec_time=3) + + # mark lba as down if top of band is lower than normal and top is shifted more than 10 subbands to left or right + logger.debug("Check Down") + down, shifted = check_for_down(data=self.antenna_data, band=mode_to_band(mode), parset=parset) + #logger.debug("down_info=%s" % str(down)) + + for rcu, max_sb, max_val, max_bw, band_pwr in down: + if rcu == 'Xref': + x_ref_max_sb = max_sb + x_ref_max_val = max_val + x_ref_max_bw = max_bw + x_ref_band_pwr = band_pwr + logger.info("down test X rcus's median values: sb=%d, pwr=%3.1fdB, bw=%d, band-pwr=%3.1fdB" % ( + x_ref_max_sb, x_ref_max_val, x_ref_max_bw, x_ref_band_pwr)) + continue + if rcu == 'Yref': + y_ref_max_sb = max_sb + y_ref_max_val = max_val + y_ref_max_bw = max_bw + y_ref_band_pwr = band_pwr + logger.info("down test Y rcu's median values: sb=%d, pwr=%3.1fdB, bw=%d, band-pwr=%3.1fdB" % ( + y_ref_max_sb, y_ref_max_val, y_ref_max_bw, y_ref_band_pwr)) + continue + + max_offset = 292 - max_sb + ant = rcu / 2 + + if self.lba.ant[ant].x.flat or self.lba.ant[ant].x.short or \ + self.lba.ant[ant].y.flat or self.lba.ant[ant].y.short: + continue + if self.antenna_data.polarity(rcu) == self.antenna_data.XPOL: + self.lba.ant[ant].x.down_pwr = max_val + self.lba.ant[ant].x.down_offset = max_offset + self.lba.ant[ant].down = 1 + else: + self.lba.ant[ant].y.down_pwr = max_val + self.lba.ant[ant].y.down_offset = max_offset + self.lba.ant[ant].down = 1 + + self.antenna_data.mask_rcu([self.lba.ant[ant].x.rcu, self.lba.ant[ant].y.rcu]) + + for a in xrange(self.lba.nr_antennas): + if self.lba.ant[a].down: + logger.info("%s %2d RCU %3d/%3d Down, Xoffset=%d Yoffset=%d" % ( + self.lba.label, self.lba.ant[a].nr_pvss, + self.lba.ant[a].x.rcu, self.lba.ant[a].y.rcu, + self.lba.ant[a].x.down_offset, self.lba.ant[a].y.down_offset)) + + for i in shifted: + rcu, max_sb, mean_max_sb = i + ant = rcu / 2 + logger.info("%s %2d RCU %3d shifted top on sb=%d, normal=sb%d" % ( + self.lba.label, self.lba.ant[ant].nr_pvss, rcu, max_sb, mean_max_sb)) + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.lba.down_check_done = 1 + self.db.add_test_done('D%d' % mode) + logger.info("=== Done %s Down test ===" % self.lba.label) + return + + def check_rf_power(self, mode, parset): + logger.info("=== Start %s RF test ===" % self.lba.label) + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + if not self.db.check_end_time(duration=15.0): + logger.warning("check stopped, end time reached") + return + + self.set_mode(mode) + self.record_data(rec_time=2) + + test_info_x, signal_info_x = check_rf_power(data=self.antenna_data, band=mode_to_band(mode), + pol='X', parset=parset) + logger.debug("X data: test subband=%d, median val=%5.3f" % (test_info_x['subband'], test_info_x['test_val'])) + + test_info_y, signal_info_y = check_rf_power(data=self.antenna_data, band=mode_to_band(mode), + pol='Y', parset=parset) + logger.debug("Y data: test subband=%d, median val=%5.3f" % (test_info_y['subband'], test_info_y['test_val'])) + + # logger.debug("signal_info_x=%s" % str(signal_info_x)) + # logger.debug("signal_info_y=%s" % str(signal_info_y)) + + self.lba.rf_ref_signal_y = test_info_y['test_val'] + self.lba.rf_ref_signal_x = test_info_x['test_val'] + self.lba.rf_test_subband_x = test_info_x['subband'] + self.lba.rf_test_subband_y = test_info_y['subband'] + + rcu_list = self.antenna_data.rcus(mode_to_band(mode), 'XY') + if test_info_x['valid'] and test_info_y['valid']: + for ant in self.lba.ant: + if ant.x.rcu_off or ant.y.rcu_off: + continue + + if str(ant.x.rcu) in signal_info_x: + ant.x.test_signal = signal_info_x[str(ant.x.rcu)]['value'] + if signal_info_x[str(ant.x.rcu)]['status'] == 'no_signal': + ant.x.no_signal = 1 + elif signal_info_x[str(ant.x.rcu)]['status'] == 'no_power': + ant.no_power = 1 + elif signal_info_x[str(ant.x.rcu)]['status'] == 'low': + ant.x.too_low = 1 + elif signal_info_x[str(ant.x.rcu)]['status'] == 'high': + ant.x.too_high = 1 + + if str(ant.y.rcu) in signal_info_y: + ant.y.test_signal = signal_info_y[str(ant.y.rcu)]['value'] + if signal_info_y[str(ant.y.rcu)]['status'] == 'no_signal': + ant.y.no_signal = 1 + elif signal_info_y[str(ant.y.rcu)]['status'] == 'no_power': + ant.no_power = 1 + elif signal_info_y[str(ant.y.rcu)]['status'] == 'low': + ant.y.too_low = 1 + elif signal_info_y[str(ant.y.rcu)]['status'] == 'high': + ant.y.too_high = 1 + + if str(ant.x.rcu) in signal_info_x and str(ant.y.rcu) in signal_info_y: + if signal_info_x[str(ant.x.rcu)]['status'] != 'normal' or \ + signal_info_y[str(ant.y.rcu)]['status'] != 'normal': + logger.info("LBA Ant=%d: X=%3.1fdB(%s) Y=%3.1fdB(%s)" % ( + ant.nr, + signal_info_x[str(ant.x.rcu)]['value'], + signal_info_x[str(ant.x.rcu)]['status'], + signal_info_y[str(ant.y.rcu)]['value'], + signal_info_y[str(ant.y.rcu)]['status'])) + else: + logger.warning("LBA, No valid test signal") + self.lba.rf_signal_to_low = 1 + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return + + self.lba.signal_check_done = 1 + self.db.add_test_done('S%d' % mode) + logger.info("=== Done %s RF test ===" % self.lba.label) + return + + # end of cLBA class diff --git a/LCU/checkhardware/checkhardware_lib/lofar.py b/LCU/checkhardware/checkhardware_lib/lofar.py new file mode 100644 index 00000000000..6a05a5294c9 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/lofar.py @@ -0,0 +1,608 @@ +# lofar_lib + +import os +import sys +import time +import logging +import socket +import struct +import string +from general import * + +os.umask(001) +lofar_version = '0514' +testmode = False +#testmode = True + + +CoreStations = ('CS001C', 'CS002C', 'CS003C', 'CS004C', 'CS005C', 'CS006C', 'CS007C', 'CS011C', 'CS013C', 'CS017C', + 'CS021C', 'CS024C', 'CS026C', 'CS028C', 'CS030C', 'CS031C', 'CS032C', 'CS101C', 'CS103C', 'CS201C', + 'CS301C', 'CS302C', 'CS401C', 'CS501C') + +RemoteStations = ('RS106C', 'RS205C', 'RS208C', 'RS210C', 'RS305C', 'RS306C', 'RS307C', 'RS310C', 'RS406C', 'RS407C', + 'RS409C', 'RS503C', 'RS508C', 'RS509C') + +InternationalStations = ('DE601C', 'DE602C', 'DE603C', 'DE604C', 'DE605C', 'DE609C', 'FR606C', 'SE607C', + 'UK608C', 'PL610C', 'PL611C', 'PL612C') + +StationType = {'CS': 1, 'RS': 2, 'IS': 3} + +logger = logging.getLogger('main.lofar') +logger.debug("starting lofar logger") + +active_delay_str = ('555,' * 16)[:-1] + + +def activate_test_mode(): + global testmode + testmode = True + + +def is_test_mode_active(): + return testmode + + +def init_lofar_lib(): + if not os.access(data_dir(), os.F_OK): + os.mkdir(data_dir()) + + +def data_dir(): + return r'/localhome/stationtest/sb_data' + + +# remove all *.dat +def remove_all_data_files(): + if not testmode: + if os.access(data_dir(), os.F_OK): + files = os.listdir(data_dir()) + # print files + for f in files: + if f[-3:] == 'dat' or f[-3:] == 'nfo': + os.remove(os.path.join(data_dir(), f)) + + +# return station type +def get_station_type(StID): + if StID in CoreStations: + return StationType['CS'] + if StID in RemoteStations: + return StationType['RS'] + if StID in InternationalStations: + return StationType['IS'] + + +# read from RemoteStation.conf file number of RSP and TB Boards +""" +# +# THIS FILE IS GENERATED, DO NOT MODIFY IT. +# +# RemoteStation.conf for CS002 +# +# Describes the amount of available hardware on the station. +# + +RS.STATION_ID = 2 +RS.N_RSPBOARDS = 12 +RS.N_TBBOARDS = 6 +RS.N_LBAS = 96 +RS.N_HBAS = 48 +RS.HBA_SPLIT = Yes +RS.WIDE_LBAS = Yes +""" + + +def read_station_config(): + f = open('/opt/lofar/etc/RemoteStation.conf', 'r') + lines = f.readlines() + f.close() + + st_id = nrsp = ntbb = nlba = nlbl = nlbh = nhba = hba_split = 0 + + for line in lines: + if (line[0] == '#') or (len(line) < 2): + continue + key, val = line.split('=') + key = key.strip() + val = val.strip() + if key == "RS.STATION_ID": + st_id = int(val) + continue + if key == "RS.N_RSPBOARDS": + nrsp = int(val) + continue + if key == "RS.N_TBBOARDS": + ntbb = int(val) + continue + if key == "RS.N_LBAS": + nlba = int(val) + if nlba == nrsp * 8: + nlbl = nlba / 2 + nlbh = nlba / 2 + else: + nlbl = 0 + nlbh = nlba + continue + if key == "RS.N_HBAS": + nhba = int(val) + continue + if key == "RS.HBA_SPLIT": + if string.upper(val) == "YES": + hba_split = 1 + continue + return st_id, nrsp, ntbb, nlbl, nlbh, nhba, hba_split + + +# [lofarsys@RS306C stationtest]$ swlevel 2 +# Going to level 2 +# Starting RSPDriver +# Loading image 4 on RSPboard 0 ... +# Loading image 4 on RSPboard 1 ... +# Loading image 4 on RSPboard 2 ... +# Loading image 4 on RSPboard 3 ... +# Loading image 4 on RSPboard 4 ... +# Loading image 4 on RSPboard 5 ... +# Loading image 4 on RSPboard 6 ... +# Loading image 4 on RSPboard 7 ... +# RSPboard 8: Error requesting active firmware version (communication error) +# Loading image 4 on RSPboard 9 ... +# Loading image 4 on RSPboard 10 ... +# Loading image 4 on RSPboard 11 ... +# One or more boards have a communication problem; try reset the 48V +# root 21470 1 1 10:41 pts/2 00:00:00 /opt/lofar/bin/RSPDriver +# Starting TBBDriver +# root 21492 1 0 10:41 pts/2 00:00:00 /opt/lofar/bin/TBBDriver +# +# Status of all software level: +# 1 : PVSS00pmon 16177 +# 1 : SoftwareMonitor 16227 +# 1 : LogProcessor 16248 +# 1 : ServiceBroker 16278 +# 1 : SASGateway 16299 +# --- +# 2 : RSPDriver 21470 +# 2 : TBBDriver 21492 +# --- +# 3 : CalServer DOWN +# 3 : BeamServer DOWN +# --- +# 4 : HardwareMonitor DOWN +# --- +# 5 : SHMInfoServer DOWN +# --- +# 6 : CTStartDaemon DOWN +# 6 : StationControl DOWN +# 6 : ClockControl DOWN +# 6 : CalibrationControl DOWN +# 6 : BeamControl DOWN +# 6 : TBBControl DOWN +# --- + +def swlevel(level=None): + # level = None + _level = level + board_errors = list() + if level is not None: + if _level < 0: + _level *= -1 + answer = run_cmd('swlevel %d' % _level) + else: + answer = run_cmd('swlevel') + + #print answer + current_level = 0 + for line in answer.splitlines(): + if line.find("Going to level") > -1: + current_level = int(line.split()[-1]) + + elif line.find("Currently set level") > -1: + current_level = int(line.strip().split()[-1]) + if current_level < 0: + logger.warning("Current swlevel is %d" % current_level) + if line.find("Error requesting active firmware version") > -1: + endpos = line.find(":") + board_errors.append(int(line[:endpos].split()[1])) + logger.warning(line) + logger.info("current swlevel = %s" % current_level) + return current_level, board_errors + + +def reset_48_volt(): + logger.info("Try to reset 48V power") + ec_name = socket.gethostname()[:-1] + "ec" + ec_ip = socket.gethostbyname(ec_name) + logger.debug("EC to connect = %s" % ec_ip) + + try: + sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error: + # sck.close() + return + except: + raise + + try: + sck.settimeout(4.0) + sck.connect((ec_ip, 10000)) + time.sleep(0.5) + cmd = struct.pack('hhh', 22, 0, 0) + logger.debug("send cmd") + sck.send(cmd) + sck.settimeout(4.0) + logger.debug("recv cmd") + data = sck.recv(6) + sck.close() + logger.debug("reset done") + except socket.error: + logger.error("ec socket connect error") + sck.close() + except: + raise + + + + +# Run rspctl command with given args and return response +def rspctl(args='', wait=0.0): + if str(args) != '': + logger.debug("rspctl %s" % str(args)) + response = run_cmd('rspctl %s' % str(args)) + if not testmode and wait > 0.0: + time.sleep(wait) + return response + return 'No args given' + + +# Run tbbctl command with given args and return response +def tbbctl(args=''): + if str(args) != '': + logger.debug("tbbctl %s" % str(args)) + return run_cmd('tbbctl %s' % str(args)) + return 'No args given' + + +def check_active_tbbdriver(): + answer = run_cmd('swlevel').strip().splitlines() + for line in answer: + if line.find('TBBDriver') > -1: + if line.find('DOWN') != -1: + return False + return True + + +# wait until all boards have a working image loaded +# returns 1 if ready or 0 if timed_out +def wait_tbb_ready(n_boards=6): + board_active = [True] * n_boards + timeout = 30 + logger.info("wait for working TBB boards ") + while timeout > 0: + answer = tbbctl('--version') + # print answer + if answer.find('TBBDriver is NOT responding') > 0: + if timeout < 10: + logger.warning("TBBDriver is NOT responding, try again in every 5 seconds") + time.sleep(5.0) + timeout -= 5 + continue + + # check if image_nr > 0 for all boards + if answer.count('V') == (n_boards * 4): + logger.debug("All boards in working image") + return True, board_active + + reset_list = [] + board_not_ready = False + for line in answer.splitlines(): + if line.count('V') == 4: + board_nr = int(line.split()[0]) + board_active[board_nr] = True + if 'boards not active' in line: + board_nr = int(line.split()[0]) + board_active[board_nr] = False + if 'mpi time-out' in line: + board_nr = line.split()[0] + reset_list.append(board_nr) + if 'board not ready' in line: + board_not_ready = True + + if board_not_ready: + timeout += 5.0 + + if len(reset_list) > 0: + tbbctl('--reset --select=%s' % ','.join(reset_list)) + reset_list = [] + timeout += 35.0 + time.sleep(35.0) + + time.sleep(1.0) + timeout -= 1 + + logger.warning("Not all TB boards in working image") + return False, board_active + + +def check_active_rspdriver(): + answer = run_cmd('swlevel').strip().splitlines() + for line in answer: + if line.find('RSPDriver') > -1: + if line.find('DOWN') != -1: + return False + return True + + +# wait until all boards have a working image loaded +# returns 1 if ready or 0 if timed_out +# +# [lofarsys@RS306C ~]$ rspctl --version +# RSP[ 0] RSP version = 0, BP version = 0.0, AP version = 0.0 +# RSP[ 1] RSP version = 0, BP version = 0.0, AP version = 0.0 +# RSP[ 2] RSP version = 0, BP version = 0.0, AP version = 0.0 +# RSP[ 3] RSP version = 0, BP version = 0.0, AP version = 0.0 +# RSP[ 4] RSP version = 0, BP version = 0.0, AP version = 0.0 +# RSP[ 5] RSP version = 0, BP version = 0.0, AP version = 0.0 +# RSP[ 6] RSP version = 0, BP version = 0.0, AP version = 0.0 +# RSP[ 7] RSP version = 0, BP version = 0.0, AP version = 0.0 +# RSP[ 8] RSP version = 0, BP version = 0.0, AP version = 0.0 +# RSP[ 9] RSP version = 0, BP version = 0.0, AP version = 0.0 +# RSP[10] RSP version = 0, BP version = 0.0, AP version = 0.0 +# RSP[11] RSP version = 0, BP version = 0.0, AP version = 0.0 + +def wait_rsp_ready(): + timeout = 60 + logger.info("wait for working RSP boards ") + sys.stdout.flush() + while timeout > 0: + answer = rspctl('--version') + # print answer + if answer.count('No Response') > 0: + time.sleep(5.0) + timeout -= 5 + if timeout < 60: + return 0 + continue + # check if image_nr > 0 for all boards + if answer.count('0.0') == 0: + logger.debug("All boards in working image") + return 1 + else: + logger.warning("Not all RSP boards in working image") + logger.debug(answer) + if not testmode: + time.sleep(5.0) + timeout -= 1 + return 0 + + +def check_active_boards(db, n_rsp_boards, n_tbb_boards, n_retries): + # check if RSPDriver is running + rsp_ready = False + tbb_ready = False + restarts = n_retries + + # wait for RSP boards ready, and reset 48V if needed, max 2x if no board errors after 48V reset + while (not rsp_ready or not tbb_ready) and restarts > 0: + if not check_active_rspdriver() or not check_active_tbbdriver(): + logger.warning('RSPDriver and/or TBBDriver not running') + swlevel(1) + swlevel(2) + #time.sleep(30.0) + + rsp_ready = wait_rsp_ready() + tbb_ready, tbbs_active = wait_tbb_ready(n_tbb_boards) + if not tbb_ready: + active_tbbs_changed = False + for tbb_nr, active in enumerate(tbbs_active): + if active != db.tbb[tbb_nr].board_active: + active_tbbs_changed = True + if not active_tbbs_changed: + tbb_ready = True + + if rsp_ready and tbb_ready : + break + + if not rsp_ready: + logger.warning('Not all RSP boards ready, reset 48V to recover') + if not rsp_ready: + logger.warning('Not all TBB boards ready, reset 48V to recover') + swlevel(1) + reset_48_volt() + restarts -= 1 + time.sleep(10.0) + level, board_errors = swlevel(2) + if len(board_errors) > 0: + db.board_errors = board_errors + + if not rsp_ready: + logger.warning('RSP not all boards ready') + if not tbb_ready: + logger.warning('TBB not all boards ready') + # put tbbs active information in the db + for tbb_nr, active in enumerate(tbbs_active): + if active in (False, None): + db.tbb[tbb_nr].board_active = 0 + else: + db.tbb[tbb_nr].board_active = 1 + return rsp_ready, tbb_ready + + +def mode_to_band(mode): + bands = {'10_90' : (1, 3), + '30_90' : (2, 4), + '110_190': (5,), + '170_210': (6,), + '210_250': (7,)} + for band, modes in bands.iteritems(): + if mode in modes: + return band + return '0' + + +# convert select-list to select-string +def select_str(sel_list): + last_sel = -2 + is_set = False + select = "" + for sel in sorted(sel_list): + if sel == last_sel + 1: + is_set = True + else: + if is_set: + is_set = False + select += ':%d' % last_sel + select += ",%d" % sel + last_sel = sel + if is_set: + select += ':%d' % last_sel + return select[1:] + + +# convert select-string to sel_list +def extract_select_str(select_string): + select_string = select_string.strip() + if not select_string: + return [] + select_list = list() + str_number = '' + int_number = None + first_set_number = None + is_set = False + #for ch in sel_str: + select_string_size = len(select_string) + last_i = select_string_size - 1 + for i in xrange(select_string_size): + ch = select_string[i] + if is_set and ch in '.': + continue + + if ch.isalnum(): + str_number += ch + if i < last_i: + continue + + if str_number: + int_number = int(str_number.strip()) + str_number = '' + + if int_number and (ch in ',' or i == last_i): + if is_set: + for nr in xrange(first_set_number, int_number+1, 1): + select_list.append(nr) + is_set = False + else: + select_list.append(int_number) + int_number = None + + if ch in ':.': + first_set_number = int_number + is_set = True + + return sorted(select_list) + + +def get_clock(): + answer = rspctl("--clock") + # print answer[-6:-3] + clock = float(answer[-7:-4]) + return clock + + +# function used for antenna testing +def swap_xy(state): + if state in (0, 1): + if state == 1: + logger.debug("XY-output swapped") + else: + logger.debug("XY-output normal") + rspctl('--swapxy=%d' % state) + + +def reset_rsp_settings(): + if rspctl('--clock').find('200MHz') < 0: + rspctl('--clock=200') + logger.debug("Changed Clock to 200MHz") + if not testmode: + time.sleep(2.0) + rspctl('--wg=0', wait=0.0) + rspctl('--rcuprsg=0', wait=0.0) + rspctl('--datastream=0', wait=0.0) + rspctl('--splitter=0', wait=0.0) + rspctl('--specinv=0', wait=0.0) + rspctl('--bitmode=16', wait=0.0) + rspctl('--rcumode=0', wait=0.0) + rspctl('--rcuenable=0', wait=0.0) + rspctl('--swapxy=0', wait=0.0) # 0=normal, 1=swapped + # rspctl ('--hbadelays=%s' %(('128,'*16)[:-1]), wait=8.0) + + +def turn_on_rcus(mode, rcus): + select = select_str(rcus) + logger.debug("turn RCU's on, mode %d" % mode) + logger.debug("enable rcus") + rspctl('--rcuenable=1 --select=%s' % select, wait=0.0) + logger.debug("setweights") + rspctl('--aweights=8000,0', wait=0.0) + + if mode == 5: + rspctl('--specinv=1', wait=0.0) + else: + rspctl('--specinv=0', wait=0.0) + + logger.debug("set rcu mode") + rsp_rcu_mode(mode, rcus) + return + + +def turn_off_rcus(): + logger.debug("RCU's off, mode 0") + rspctl('--rcumode=0', wait=0.0) + rspctl('--rcuenable=0', wait=0.0) + rspctl('--aweights=0,0', wait=1.0) + + +# set rcu mode, if mode > 4(hba) turn on hba's in steps to avoid power dips +def rsp_rcu_mode(mode, rcus): + if mode in range(1, 8, 1): # all modes + select = select_str(rcus) + rspctl('--rcumode=%d --select=%s' % (mode, select), wait=6.0) + return 0 + return -1 + + +# set hba_delays in steps to avoid power dips, and discharge if needed +def rsp_hba_delay(delay, rcus, discharge=True): + global active_delay_str + + if delay == active_delay_str: + logger.debug("requested delay already active, skip hbadelay command") + return 1 + + select = select_str(rcus) + if discharge: + # count number of elements off in last command + n_hba_off = 0 + for i in active_delay_str.split(','): + if int(i, 10) & 0x02: + n_hba_off += 1 + + # count number of elements on in new command, and make discharge string + n_hba_on = 0 + discharge_str = '' + if n_hba_off > 0: + for i in delay.split(','): + if int(i, 10) & 0x02: + discharge_str += "2," + else: + discharge_str += "0," + n_hba_on += 1 + + # discharge if needed + if n_hba_off > 0 and n_hba_on > 0: + logger.debug("set hbadelays to 0 for 1 second") + rspctl('--hbadelay=%s --select=%s' % (discharge_str[:-1], select), wait=6.0) + + logger.debug("send hbadelay command") + rspctl('--hbadelay=%s --select=%s' % (delay, select), wait=6.0) + + active_delay_str = delay + return 0 diff --git a/LCU/checkhardware/checkhardware_lib/reporting.py b/LCU/checkhardware/checkhardware_lib/reporting.py new file mode 100644 index 00000000000..0d3b51b3f75 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/reporting.py @@ -0,0 +1,448 @@ +#!/usr/bin/env python +""" +Make report from measurement, using information in test_db +""" +import logging +from general import get_date_time_str, get_short_date_str, get_hostname, MyTestLogger + +logger = logging.getLogger('main.reporting') +logger.debug("starting reporting logger") + + +def make_report(db, logdir): + # print logdir + date = get_short_date_str(db.check_start_time) + log = MyTestLogger(logdir, db.StID) + + log.add_line("%s,NFO,---,VERSIONS,%s" % (date, db.script_versions)) + + log.add_line("%s,NFO,---,STATION,NAME=%s" % (date, db.StID)) + + log.add_line("%s,NFO,---,RUNTIME,START=%s,STOP=%s" % ( + date, get_date_time_str(db.check_start_time), get_date_time_str(db.check_stop_time))) + + info = "" + bad = "" + for ant in db.lbl.ant: + if ant.on_bad_list == 1: + bad += "%d " % ant.nr_pvss + if len(bad) > 0: + info += "LBL=%s," % (bad[:-1]) + + bad = "" + for ant in db.lbh.ant: + if ant.on_bad_list == 1: + bad += "%d " % ant.nr_pvss + if len(bad) > 0: + info += "LBH=%s," % (bad[:-1]) + + bad = "" + for tile in db.hba.tile: + if tile.on_bad_list == 1: + bad += "%d " % tile.nr + if len(bad) > 0: + info += "HBA=%s," % (bad[:-1]) + if len(info) > 0: + log.add_line("%s,NFO,---,BADLIST,%s" % (date, info[:-1])) + + if db.rsp_driver_down: + log.add_line("%s,NFO,---,DRIVER,RSPDRIVER=DOWN" % date) + + if db.tbb_driver_down: + log.add_line("%s,NFO,---,DRIVER,TBBDRIVER=DOWN" % date) + + if len(db.board_errors): + boardstr = '' + for board in db.board_errors: + boardstr += "RSP-%d=DOWN," % board + log.add_line("%s,NFO,---,BOARD,%s" % (date, boardstr[:-1])) + + log.add_line("%s,NFO,---,CHECKS,%s" % (date, ",".join(db.tests_done))) + + log.add_line("%s,NFO,---,STATISTICS,BAD_LBL=%d,BAD_LBH=%d,BAD_HBA=%d,BAD_HBA0=%d,BAD_HBA1=%d" % ( + date, db.lbl.nr_bad_antennas, db.lbh.nr_bad_antennas, db.hba.nr_bad_tiles, + db.hba.nr_bad_tiles_0, db.hba.nr_bad_tiles_1)) + + spu_report(db, date, log) + rsp_report(db, date, log) + tbb_report(db, date, log) + + for lba in (db.lbl, db.lbh): + lba_report(lba, date, log) + + hba_tile_report(db, date, log) + hba_element_report(db, date, log) + + return + + +def spu_report(db, date, log): + logger.debug("generate spu report") + for spu in db.spu: + spu.test() + if not spu.voltage_ok: + valstr = '' + if not spu.rcu_ok: + valstr += ",RCU-5.0V=%3.1f" % spu.rcu_5_0_volt + if not spu.lba_ok: + valstr += ",LBA-8.0V=%3.1f" % spu.lba_8_0_volt + if not spu.hba_ok: + valstr += ",HBA-48V=%3.1f" % spu.hba_48_volt + if not spu.spu_ok: + valstr += ",SPU-3.3V=%3.1f" % spu.spu_3_3_volt + if len(valstr): + log.add_line("%s,SPU,%03d,VOLTAGE%s" % (date, spu.nr, valstr)) + + if not spu.temp_ok: + log.add_line("%s,SPU,%03d,TEMPERATURE,PCB=%2.0f" % ( + date, spu.nr, spu.temp)) + + +def rsp_report(db, date, log): + logger.debug("generate rsp report") + for rsp in db.rsp: + rsp.test() + if not rsp.version_ok: + log.add_line("%s,RSP,%03d,VERSION,BP=%s,AP=%s" % ( + date, rsp.nr, rsp.bp_version, rsp.ap_version)) + if not rsp.voltage_ok: + log.add_line("%s,RSP,%03d,VOLTAGE,1.2V=%3.2f,2.5V=%3.2f,3.3V=%3.2f" % ( + date, rsp.nr, rsp.voltage1_2, rsp.voltage2_5, rsp.voltage3_3)) + if not rsp.temp_ok: + log.add_line("%s,RSP,%03d,TEMPERATURE,PCB=%2.0f,BP=%2.0f,AP0=%2.0f,AP1=%2.0f,AP2=%2.0f,AP3=%2.0f" % ( + date, rsp.nr, rsp.pcb_temp, rsp.bp_temp, rsp.ap0_temp, rsp.ap1_temp, rsp.ap2_temp, + rsp.ap3_temp)) + + +def tbb_report(db, date, log): + logger.debug("generate tbb report") + for tbb in db.tbb: + tbb.test() + if not tbb.version_ok: + log.add_line("%s,TBB,%03d,VERSION,TP=%s,MP=%s" % ( + date, tbb.nr, tbb.tp_version, tbb.mp_version)) + if not tbb.memory_ok: + log.add_line("%s,TBB,%03d,MEMORY" % (date, tbb.nr)) + + if not tbb.voltage_ok: + log.add_line("%s,TBB,%03d,VOLTAGE,1.2V=%3.2f,2.5V=%3.2f,3.3V=%3.2f" % ( + date, tbb.nr, tbb.voltage1_2, tbb.voltage2_5, tbb.voltage3_3)) + if not tbb.temp_ok: + log.add_line("%s,TBB,%03d,TEMPERATURE,PCB=%2.0f,TP=%2.0f,MP0=%2.0f,MP1=%2.0f,MP2=%2.0f,MP3=%2.0f" % ( + date, tbb.nr, tbb.pcb_temp, tbb.tp_temp, tbb.mp0_temp, tbb.mp1_temp, tbb.mp2_temp, + tbb.mp3_temp)) + + +def rcu_report(db, date, log): + logger.debug("generate rcu rapport") + for rcu in range(db.nr_rcu): + if db.rcu_state[rcu]: + log.add_line("%s,RCU,%03d,BROKEN" % (date, rcu)) + + +def lba_report(db, date, log): + logger.debug("generate %s report" % db.label.lower()) + if db.signal_check_done: + if db.rf_signal_to_low: + log.add_line("%s,%s,---,TOOLOW,MEDIANX=%3.1f,MEDIANY=%3.1f" % ( + date, db.label, db.rf_ref_signal_x, db.rf_ref_signal_y)) + else: + if db.error: + log.add_line("%s,%s,---,TESTSIGNAL,SUBBANDX=%d,SIGNALX=%3.1f,SUBBANDY=%d,SIGNALY=%3.1f" % ( + date, db.label, db.rf_test_subband_x, db.rf_ref_signal_x, + db.rf_test_subband_y, db.rf_ref_signal_y)) + + if db.noise_check_done or db.oscillation_check_done or db.spurious_check_done or \ + db.signal_check_done or db.short_check_done or db.flat_check_done or db.down_check_done: + for ant in db.ant: + if ant.down: + log.add_line("%s,%s,%03d,DOWN,X=%3.1f,Y=%3.1f,Xoff=%d,Yoff=%d" % ( + date, db.label, ant.nr_pvss, ant.x.down_pwr, ant.y.down_pwr, ant.x.down_offset, ant.y.down_offset)) + else: + if db.signal_check_done: + valstr = '' + if ant.x.too_low or ant.x.too_high: + valstr += ",X=%3.1f" % ant.x.test_signal + if ant.y.too_low or ant.y.too_high: + valstr += ",Y=%3.1f" % ant.y.test_signal + if len(valstr): + log.add_line("%s,%s,%03d,RF_FAIL%s" % (date, db.label, ant.nr_pvss, valstr)) + + if db.flat_check_done: + valstr = '' + if ant.x.flat: + valstr += ",Xmean=%3.1f" % ant.x.flat_val + if ant.y.flat: + valstr += ",Ymean=%3.1f" % ant.y.flat_val + if len(valstr): + log.add_line("%s,%s,%03d,FLAT%s" % (date, db.label, ant.nr_pvss, valstr)) + + if db.short_check_done: + valstr = '' + if ant.x.short: + valstr += ",Xmean=%3.1f" % ant.x.short_val + if ant.y.short: + valstr += ",Ymean=%3.1f" % ant.y.short_val + if len(valstr): + log.add_line("%s,%s,%03d,SHORT%s" % (date, db.label, ant.nr_pvss, valstr)) + + if db.oscillation_check_done: + valstr = '' + if ant.x.osc: + valstr += ',X=1' + if ant.y.osc: + valstr += ',Y=1' + if len(valstr): + log.add_line("%s,%s,%03d,OSCILLATION%s" % (date, db.label, ant.nr_pvss, valstr)) + + if db.spurious_check_done: + valstr = '' + if ant.x.spurious: + valstr += ',X=1' + if ant.y.spurious: + valstr += ',Y=1' + if len(valstr): + log.add_line("%s,%s,%03d,SPURIOUS%s" % (date, db.label, ant.nr_pvss, valstr)) + + if db.noise_check_done: + noise = False + valstr = '' + if not ant.x.flat and ant.x.low_noise: + proc = (100.0 / ant.x.low_seconds) * ant.x.low_bad_seconds + valstr += ',Xproc=%5.3f,Xval=%3.1f,Xdiff=%5.3f,Xref=%3.1f' % ( + proc, ant.x.low_val, ant.x.low_diff, ant.x.low_ref) + if not ant.y.flat and ant.y.low_noise: + proc = (100.0 / ant.y.low_seconds) * ant.y.low_bad_seconds + valstr += ',Yproc=%5.3f,Yval=%3.1f,Ydiff=%5.3f,Yref=%3.1f' % ( + proc, ant.y.low_val, ant.y.low_diff, ant.y.low_ref) + if len(valstr): + log.add_line("%s,%s,%03d,LOW_NOISE%s" % (date, db.label, ant.nr_pvss, valstr)) + noise = True + + valstr = '' + if ant.x.high_noise: + proc = (100.0 / ant.x.high_seconds) * ant.x.high_bad_seconds + valstr += ',Xproc=%5.3f,Xval=%3.1f,Xdiff=%5.3f,Xref=%3.1f' % ( + proc, ant.x.high_val, ant.x.high_diff, ant.x.high_ref) + if ant.y.high_noise: + proc = (100.0 / ant.y.high_seconds) * ant.y.high_bad_seconds + valstr += ',Yproc=%5.3f,Yval=%3.1f,Ydiff=%5.3f,Yref=%3.1f' % ( + proc, ant.y.high_val, ant.y.high_diff, ant.y.high_ref) + if len(valstr): + log.add_line("%s,%s,%03d,HIGH_NOISE%s" % (date, db.label, ant.nr_pvss, valstr)) + noise = True + + valstr = '' + if not noise and ant.x.jitter: + proc = (100.0 / ant.x.jitter_seconds) * ant.x.jitter_bad_seconds + valstr += ',Xproc=%5.3f,Xdiff=%5.3f,Xref=%3.1f' % ( + proc, ant.x.jitter_val, ant.x.jitter_ref) + if not noise and ant.y.jitter: + proc = (100.0 / ant.y.jitter_seconds) * ant.y.jitter_bad_seconds + valstr += ',Xproc=%5.3f,Ydiff=%5.3f,Yref=%3.1f' % ( + proc, ant.y.jitter_val, ant.y.jitter_ref) + if len(valstr): + log.add_line("%s,%s,%03d,JITTER%s" % (date, db.label, ant.nr_pvss, valstr)) + # lba = None + + +def hba_tile_report(db, date, log): + logger.debug("generate hba-tile report") + if db.hba.signal_check_done: + if db.hba.rf_signal_to_low: + log.add_line("%s,HBA,---,TOOLOW,MEDIANX=%3.1f,MEDIANY=%3.1f" % ( + date, db.hba.rf_ref_signal_x[0], db.hba.rf_ref_signal_y[0])) + else: + if db.hba.error: + log.add_line("%s,HBA,---,TESTSIGNAL,SUBBANDX=%d,SIGNALX=%3.1f,SUBBANDY=%d,SIGNALY=%3.1f" % ( + date, db.hba.rf_test_subband_x[0], db.hba.rf_ref_signal_x[0], + db.hba.rf_test_subband_y[0], db.hba.rf_ref_signal_y[0])) + + for tile in db.hba.tile: + if tile.x.error or tile.y.error: + # check for broken summators + if db.hba.modem_check_done: + valstr = '' + if tile.c_summator_error: + log.add_line("%s,HBA,%03d,C_SUMMATOR" % (date, tile.nr)) + else: + for elem in tile.element: + if elem.no_modem: + valstr += ",E%02d=??" % elem.nr + + elif elem.modem_error: + valstr += ",E%02d=error" % elem.nr + if len(valstr): + log.add_line("%s,HBA,%03d,MODEM%s" % (date, tile.nr, valstr)) + + if db.hba.noise_check_done: + valstr = '' + noise = False + + if tile.x.low_noise: + proc = (100.0 / tile.x.low_seconds) * tile.x.low_bad_seconds + valstr += ',Xproc=%5.3f,Xval=%3.1f,Xdiff=%5.3f,Xref=%3.1f' % ( + proc, tile.x.low_val, tile.x.low_diff, tile.x.low_ref) + if tile.y.low_noise: + proc = (100.0 / tile.y.low_seconds) * tile.y.low_bad_seconds + valstr += ',Yproc=%5.3f,Yval=%3.1f,Ydiff=%5.3f,Yref=%3.1f' % ( + proc, tile.y.low_val, tile.y.low_diff, tile.y.low_ref) + if len(valstr): + log.add_line("%s,HBA,%03d,LOW_NOISE%s" % (date, tile.nr, valstr)) + noise = True + + valstr = '' + if tile.x.high_noise: + proc = (100.0 / tile.x.high_seconds) * tile.x.high_bad_seconds + valstr += ',Xproc=%5.3f,Xval=%3.1f,Xdiff=%5.3f,Xref=%3.1f' % ( + proc, tile.x.high_val, tile.x.high_diff, tile.x.high_ref) + if tile.y.high_noise: + proc = (100.0 / tile.y.high_seconds) * tile.y.high_bad_seconds + valstr += ',Yproc=%5.3f,Yval=%3.1f,Ydiff=%5.3f,Yref=%3.1f' % ( + proc, tile.y.high_val, tile.y.high_diff, tile.y.high_ref) + if len(valstr): + log.add_line("%s,HBA,%03d,HIGH_NOISE%s" % (date, tile.nr, valstr)) + noise = True + + valstr = '' + if (not noise) and tile.x.jitter: + proc = (100.0 / tile.x.jitter_seconds) * tile.x.jitter_bad_seconds + valstr += ',Xproc=%5.3f,Xdiff=%5.3f,Xref=%3.1f' % (proc, tile.x.jitter_val, tile.x.jitter_ref) + if (not noise) and tile.y.jitter: + proc = (100.0 / tile.y.jitter_seconds) * tile.y.jitter_bad_seconds + valstr += ',Yproc=%5.3f,Ydiff=%5.3f,Yref=%3.1f' % (proc, tile.y.jitter_val, tile.y.jitter_ref) + if len(valstr): + log.add_line("%s,HBA,%03d,JITTER%s" % (date, tile.nr, valstr)) + + if db.hba.oscillation_check_done: + valstr = '' + if tile.x.osc: + valstr += ',X=1' + if tile.y.osc: + valstr += ',Y=1' + if len(valstr): + log.add_line("%s,HBA,%03d,OSCILLATION%s" % (date, tile.nr, valstr)) + + if tile.p_summator_error: + log.add_line("%s,HBA,%03d,P_SUMMATOR" % (date, tile.nr)) + + if db.hba.summatornoise_check_done: + valstr = '' + if tile.x.summator_noise: + valstr += ',X=1' + if tile.y.summator_noise: + valstr += ',Y=1' + if len(valstr): + log.add_line("%s,HBA,%03d,SUMMATOR_NOISE%s" % (date, tile.nr, valstr)) + + if db.hba.spurious_check_done: + valstr = '' + if tile.x.spurious: + valstr += ',X=1' + if tile.y.spurious: + valstr += ',Y=1' + if len(valstr): + log.add_line("%s,HBA,%03d,SPURIOUS%s" % (date, tile.nr, valstr)) + + if db.hba.signal_check_done: + valstr = '' + if tile.x.too_low or tile.x.too_high: + valstr += ",X=%3.1f %d %3.1f %3.1f %d %3.1f" % ( + tile.x.test_signal[0], db.hba.rf_test_subband_x[0], db.hba.rf_ref_signal_x[0], + tile.x.test_signal[1], db.hba.rf_test_subband_x[1], db.hba.rf_ref_signal_x[1]) + if tile.y.too_low or tile.y.too_high: + valstr += ",Y=%3.1f %d %3.1f %3.1f %d %3.1f" % ( + tile.y.test_signal[0], db.hba.rf_test_subband_y[0], db.hba.rf_ref_signal_y[0], + tile.y.test_signal[1], db.hba.rf_test_subband_y[1], db.hba.rf_ref_signal_y[1]) + if len(valstr): + log.add_line("%s,HBA,%03d,RF_FAIL%s" % (date, tile.nr, valstr)) + + +def hba_element_report(db, date, log): + logger.debug("generate hba-element report") + if not db.hba.element_check_done: + return + + for tile in db.hba.tile: + valstr = '' + for elem in tile.element: + # if tile.x.rcu_off or tile.y.rcu_off: + # continue + if db.hba.modem_check_done and (elem.no_modem or elem.modem_error): + if not tile.c_summator_error: + if elem.no_modem: + valstr += ",M%d=??" % elem.nr + + elif elem.modem_error: + valstr += ",M%d=error" % elem.nr + else: + if elem.x.osc or elem.y.osc: + if elem.x.osc: + valstr += ",OX%d=1" % elem.nr + if elem.y.osc: + valstr += ",OY%d=1" % elem.nr + + elif elem.x.spurious or elem.y.spurious: + if elem.x.spurious: + valstr += ",SPX%d=1" % elem.nr + if elem.y.spurious: + valstr += ",SPY%d=1" % elem.nr + + elif elem.x.low_noise or elem.x.high_noise or elem.y.low_noise or \ + elem.y.high_noise or elem.x.jitter or elem.y.jitter: + if elem.x.low_noise: + valstr += ",LNX%d=%3.1f %5.3f" % (elem.nr, elem.x.low_val, elem.x.low_diff) + + if elem.x.high_noise: + valstr += ",HNX%d=%3.1f %5.3f" % (elem.nr, elem.x.high_val, elem.x.high_diff) + + if (not elem.x.low_noise) and (not elem.x.high_noise) and (elem.x.jitter > 0.0): + valstr += ",JX%d=%5.3f" % (elem.nr, elem.x.jitter) + + if elem.y.low_noise: + valstr += ",LNY%d=%3.1f %5.3f" % (elem.nr, elem.y.low_val, elem.y.low_diff) + + if elem.y.high_noise: + valstr += ",HNY%d=%3.1f %5.3f" % (elem.nr, elem.y.high_val, elem.y.high_diff) + + if (not elem.y.low_noise) and (not elem.y.high_noise) and (elem.y.jitter > 0.0): + valstr += ",JY%d=%5.3f" % (elem.nr, elem.y.jitter) + else: + #logger.debug("check signal elem %d" % elem.nr) + if elem.x.rf_ref_signal[0] == 0 and elem.x.rf_ref_signal[1] == 0: + #logger.debug("x ref signal == 0") + log.add_line("%s,HBA,%03d,NOSIGNAL,E%02dX" % (date, + tile.nr, + elem.nr)) + else: + if elem.x.error == 1: + #logger.debug("x-error") + valstr += ",X%d=%3.1f %d %3.1f %3.1f %d %3.1f" % (elem.nr, + elem.x.test_signal[0], + elem.x.rf_test_subband[0], + elem.x.rf_ref_signal[0], + elem.x.test_signal[1], + elem.x.rf_test_subband[1], + elem.x.rf_ref_signal[1]) + + if elem.y.rf_ref_signal[0] == 0 and elem.y.rf_ref_signal[1] == 0: + #logger.debug("y ref signal == 0") + log.add_line("%s,HBA,%03d,NOSIGNAL,E%02dY" % (date, + tile.nr, + elem.nr)) + else: + if elem.y.error == 1: + #logger.debug("y-error") + valstr += ",Y%d=%3.1f %d %3.1f %3.1f %d %3.1f" % (elem.nr, + elem.y.test_signal[0], + elem.y.rf_test_subband[0], + elem.y.rf_ref_signal[0], + elem.y.test_signal[1], + elem.y.rf_test_subband[1], + elem.y.rf_ref_signal[1]) + + if len(valstr): + logger.debug("tile %d valstr=%s" % (tile.nr, valstr)) + log.add_line("%s,HBA,%03d,E_FAIL%s" % (date, + tile.nr, + valstr)) diff --git a/LCU/checkhardware/checkhardware_lib/rsp.py b/LCU/checkhardware/checkhardware_lib/rsp.py new file mode 100644 index 00000000000..297830d9082 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/rsp.py @@ -0,0 +1,150 @@ + +import numpy as np +from lofar import * + +logger = logging.getLogger('main.rsp') +logger.debug("starting rsp logger") + +# class for checking RSP boards using rspctl +class RSP(object): + def __init__(self, db): + self.db = db + self.nr = self.db.nr_rsp + + # check software versions of driver, tbbctl and TP/MP firmware + def check_versions(self, parset): + logger.info("=== RSP Version check ===") + #print parset.get_set() + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + answer = rspctl('--version') + + # check if Driver is available + if answer.find('No Response') > 0: + logger.warning("No RSPDriver") + images_ok = False + else: + infolines = answer.splitlines() + info = infolines + + images_ok = True + # check if image_nr > 0 for all boards + if str(info).count('0.0') != 0: + logger.warning("Not all boards in working image") + images_ok = False + + for rsp in self.db.rsp: + board_info = info[rsp.nr].split(',') + + if board_info[1].split()[3] != parset.as_string('version.bp'): + logger.warning("Board %d Not right BP version" % rsp.nr) + rsp.bp_version = board_info[1].split()[3] + images_ok = False + + if board_info[2].split()[3] != parset.as_string('version.ap'): + logger.warning("Board %d Not right AP version" % rsp.nr) + rsp.ap_version = board_info[2].split()[3] + images_ok = False + + if not check_active_rspdriver(): + logger.warning("RSPDriver down while testing, skip result") + return False + + logger.info("=== Done RSP Version check ===") + self.db.add_test_done('RV') + return images_ok + + def check_board(self, parset): + ok = True + logger.info("=== RSP Board check ===") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return False + answer = rspctl('--status') + + bp_temp = np.zeros((self.db.nr_rsp,), float) + bp_temp[:] = -1 + + ap_temp = np.zeros((self.db.nr_rsp, 4), float) + ap_temp[:,:] = -1 + + p2 = 0 + for rsp in self.db.rsp: + p1 = answer.find("RSP[%2d]" % rsp.nr, p2) + p2 = answer.find("\n", p1) + d = [float(i.split(":")[1].strip()) for i in answer[p1 + 7:p2].split(',')] + if len(d) == 3: + logger.debug("RSP board %2d, voltages: 1.2V=%4.2f, 2.5V=%4.2f, 3.3V=%4.2f" % ( + rsp.nr, d[0], d[1], d[2])) + rsp.voltage1_2 = d[0] + rsp.voltage2_5 = d[1] + rsp.voltage3_3 = d[2] + + for rsp in self.db.rsp: + p1 = answer.find("RSP[%2d]" % rsp.nr, p2) + p2 = answer.find("\n", p1) + d = [float(i.split(":")[1].strip()) for i in answer[p1 + 7:p2].split(',')] + if len(d) == 6: + logger.debug("RSP board %2d, temperatures: pcb=%3.0f, bp=%3.0f, ap0=%3.0f, ap1=%3.0f, ap2=%3.0f, ap3=%3.0f" % ( + rsp.nr, d[0], d[1], d[2], d[3], d[4], d[5])) + rsp.pcb_temp = d[0] + rsp.bp_temp = d[1] + rsp.ap0_temp = d[2] + rsp.ap1_temp = d[3] + rsp.ap2_temp = d[4] + rsp.ap3_temp = d[5] + bp_temp[rsp.nr] = d[1] + ap_temp[rsp.nr, 0] = d[2] + ap_temp[rsp.nr, 1] = d[3] + ap_temp[rsp.nr, 2] = d[4] + ap_temp[rsp.nr, 3] = d[5] + + bp_temp = np.ma.masked_less(bp_temp, 0) + bp_check_temp = np.ma.median(bp_temp[:]) + parset.as_float('temperature.bp.max_delta') + logger.debug("bp avarage temp= %3.1f, check temperature= %3.1f" % ( + np.ma.mean(bp_temp[:]), bp_check_temp)) + + ap_temp = np.ma.masked_less(ap_temp, 0) + ap_check_temp = np.ma.median(ap_temp[:,:]) + parset.as_float('temperature.ap.max_delta') + logger.debug("ap avarage temp= %3.1f, check temperature= %3.1f" % ( + np.ma.mean(ap_temp[:,:]), ap_check_temp)) + + for rsp in self.db.rsp: + if not (parset.as_float('voltage.1_2.min') <= rsp.voltage1_2 <= parset.as_float('voltage.1_2.max')): + rsp.voltage_ok = 0 + logger.info("RSP board %2d bad voltage 1.2V=%4.2fV" % (rsp.nr, rsp.voltage1_2)) + if not (parset.as_float('voltage.2_5.min') <= rsp.voltage2_5 <= parset.as_float('voltage.2_5.max')): + rsp.voltage_ok = 0 + logger.info("RSP board %2d bad voltage 2.5V=%4.2fV" % (rsp.nr, rsp.voltage2_5)) + if not (parset.as_float('voltage.3_3.min') <= rsp.voltage3_3 <= parset.as_float('voltage.3_3.max')): + rsp.voltage_ok = 0 + logger.info("RSP board %2d bad voltage 3.3V=%4.2fV" % (rsp.nr, rsp.voltage3_3)) + + if rsp.pcb_temp > parset.as_float('temperature.max'): + rsp.temp_ok = 0 + logger.info("RSP board %2d, high temperature pcb_temp=%3.0f" % (rsp.nr, rsp.pcb_temp)) + + if rsp.bp_temp > parset.as_float('temperature.bp.max') or rsp.bp_temp > bp_check_temp: + rsp.temp_ok = 0 + logger.info("RSP board %2d, high temperature bp_temp=%3.0f" % (rsp.nr, rsp.bp_temp)) + + if rsp.ap0_temp > parset.as_float('temperature.ap.max') or rsp.ap0_temp > ap_check_temp: + rsp.temp_ok = 0 + logger.info("RSP board %2d, high temperature ap0_temp=%3.0f" % (rsp.nr, rsp.ap0_temp)) + + if rsp.ap1_temp > parset.as_float('temperature.ap.max') or rsp.ap1_temp > ap_check_temp: + rsp.temp_ok = 0 + logger.info("RSP board %2d, high temperature ap1_temp=%3.0f" % (rsp.nr, rsp.ap1_temp)) + + if rsp.ap2_temp > parset.as_float('temperature.ap.max') or rsp.ap2_temp > ap_check_temp: + rsp.temp_ok = 0 + logger.info("RSP board %2d, high temperature ap2_temp=%3.0f" % (rsp.nr, rsp.ap2_temp)) + + if rsp.ap3_temp > parset.as_float('temperature.ap.max') or rsp.ap3_temp > ap_check_temp: + rsp.temp_ok = 0 + logger.info("RSP board %2d, high temperature ap3_temp=%3.0f" % (rsp.nr, rsp.ap3_temp)) + logger.info("=== Done RSP Board check ===") + self.db.add_test_done('RBC') + return ok + # end of RSP class diff --git a/LCU/checkhardware/checkhardware_lib/settings.py b/LCU/checkhardware/checkhardware_lib/settings.py new file mode 100644 index 00000000000..7d0b3b5548b --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/settings.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python + +""" +Test settings for all test, settings are read from checkhardware.conf +""" +import logging +from lofar import extract_select_str + +logger = logging.getLogger('main.settings') +logger.debug("starting settings logger") + + +class TestSettings(object): + def __init__(self, filename): + self.parset = ParameterSet() + self.parset.import_file(filename) + + def __call__(self): + return self.parset + + def group(self, dev): + pre_key = dev + ps = ParameterSet(self.parset.make_subset(pre_key)) + return ps + + def rcumode(self, mode): + pre_key = 'rcumode' + if mode in (1, 3): + pre_key += '.1-3' + elif mode in (2, 4): + pre_key += '.2-4' + elif mode in (5, 6, 7): + pre_key += '.%d' % mode + ps = ParameterSet(self.parset.make_subset(pre_key)) + return ps + + +class ParameterSet(object): + def __init__(self, data=None): + self.parset = {} + if data: + if type(data) == dict: + self.parset = data + elif type(data) == str: + self.import_string(data) + + def clear(self): + self.parset = {} + + def get_set(self): + return self.parset + + def import_file(self, filename): + fd = open(filename, 'r') + data = fd.readlines() + fd.close() + self.import_string(data) + #for k, v in self.parset.iteritems(): + # logger.debug("parset: %s=%s" % (str(k), str(v))) + + def import_string(self, data): + pre_key = '' + for line in data: + if line.strip() == '' or line.startswith('#'): + continue + if line.startswith('['): + pre_key = line[line.find('[') + 1: line.find(']')] + continue + + ps = self.parset + kv_pair = line.strip().split('=') + key = kv_pair[0] + try: + value = kv_pair[1] + except IndexError: + logger.debug("%s has no value" % key) + value = '' + except: + raise + full_key = pre_key + '.' + key + key_list = full_key.strip().split('.') + # print key_list, key_list[-1] + + last_key = key_list[-1] # .replace('_', '.') + for k in key_list[:-1]: + # k = k.replace('_', '.') + if k in ps: + ps = ps[k] + else: + ps[k] = {} + ps = ps[k] + ps[last_key] = value.strip() + + def make_subset(self, subset): + keys = subset.strip().split('.') + ps = self.parset + for key in keys: + ps = ps[key] + return ps + + def replace(self, key, value): + self.parset[key] = value + + def as_int(self, key, default=None): + value = self.get(key, default) + if value is None: + return 0 + try: + return int(value) + except ValueError: + logger.error("wrong conversion to integer") + return 0 + except: + raise + + def as_int_list(self, key, default=None): + value_str = self.get(key, default) + if value_str is None: + return [] + return extract_select_str(value_str) + + def as_float(self, key, default=None): + value = self.get(key, default) + if value is None: + return 0.0 + try: + return float(value) + except ValueError: + logger.error("wrong conversion to float") + return 0.0 + except: + raise + + def as_string(self, key, default=None): + value = self.get(key, default) + if value is None: + return '' + try: + return str(value).strip() + except ValueError: + logger.error("wrong conversion to string") + return '' + except: + raise + + def get(self, key, default_val=None): + ps = self.parset + keys = key.split('.') + try: + for k in keys: + ps = ps[k] + except KeyError: + ps = default_val + logger.debug("key %s not found return default value" % key) + except: + raise + value = ps + return value diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/__init__.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/__init__.py new file mode 100644 index 00000000000..5fa857b8386 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/__init__.py @@ -0,0 +1,10 @@ + +from flat import check_for_flat +from short import check_for_short +from down import check_for_down +from noise import check_for_noise +from spurious import check_for_spurious +from oscillation import check_for_oscillation +from rf_power import check_rf_power +from summator_noise import check_for_summator_noise +from cable_reflection import check_for_cable_reflection diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/cable_reflection.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/cable_reflection.py new file mode 100644 index 00000000000..45a3c6dc534 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/cable_reflection.py @@ -0,0 +1,62 @@ +import logging +from peakslib import * + +logger = logging.getLogger('main.chk.cab..') +logger.debug("init logger") + +def check_for_cable_reflection(data, band, pol, parset): + """ + :param data: recorded antenna data data + :param band: band to check + :param pol: polarity to check + :param parset: parameterset with check settings + :return: list with found failures + """ + min_peak_pwr = parset.as_float('cable-reflection.min-peak-pwr') + passband = parset.as_int_list('cable-reflection.passband') + data.set_passband(band, passband) + + cr_info = list() # cable reflection + _data = data.spectras(freq_band=band, polarity=pol, masked=True) + + secs = _data.shape[1] + for data_nr, rcu in enumerate(data.rcus(band, pol)): + # logger.debug("rcu=%d data_nr=%d" %(rcu, data_nr)) + sum_cr_peaks = 0 + max_peaks = 0 + for sec in range(secs): + peaks_ref = SearchPeak(_data[:, sec, :].mean(axis=0)) + if peaks_ref.valid_data: + peaks_ref.search(delta=min_peak_pwr) + + peaks = SearchPeak(_data[data_nr, sec, :]) + if peaks.valid_data: + peaks.search(delta=min_peak_pwr, skip_list=peaks_ref.max_peaks) + n_peaks = peaks.n_max_peaks() + if n_peaks < 3: + continue + cr_peaks = 0 + last_sb, min_sb, max_sb = peaks.max_peaks[0] + last_sb_val = peaks.get_peak_value(last_sb) + for sb, min_sb, max_sb in peaks.max_peaks[1:]: + sb_val = peaks.get_peak_value(sb) + + sb_diff = sb - last_sb + sb_val_diff = sb_val - last_sb_val + if sb_diff in (6, 7): + if abs(sb_val_diff) < 2.0: + cr_peaks += 1 + elif cr_peaks < 6 and abs(sb_val_diff) > 3.0: + cr_peaks = 0 + last_sb = sb + last_sb_val = sb_val + + sum_cr_peaks += cr_peaks + max_peaks = max(max_peaks, n_peaks) + + if sum_cr_peaks > (secs * 3.0): + cr_peaks = sum_cr_peaks / secs + cr_info.append((rcu, cr_peaks, max_peaks)) + + data.reset_masked_sb(band) + return cr_info diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/down.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/down.py new file mode 100644 index 00000000000..d92f54f833b --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/down.py @@ -0,0 +1,245 @@ +from peakslib import * + +logger = logging.getLogger('main.chk.dow..') +logger.debug("init logger") + +def check_for_down(data, band, parset): + """ + :param data: recorded antenna data data + :param band: band to check + :param parset: parameterset with check settings + :return: list with found failures + """ + subbands =parset.as_int_list('down.passband') + if subbands is None: + logger.warning("no passband found, use default 250:350") + subbands = range(250,351,1) + + down_info = list() + shifted_info = list() + start_sb = min(subbands) + stop_sb = max(subbands) + + normal_center_sb = 294 + normal_bw_3db = 45 + + x_data = data.median_spectras(freq_band=band, polarity='x', masked=True) + y_data = data.median_spectras(freq_band=band, polarity='y', masked=True) + median_spectra_x = ma.median(x_data, axis=0) + median_spectra_y = ma.median(y_data, axis=0) + + + x_data_delta = x_data - median_spectra_x + x_data_delta = x_data_delta + x_data_delta.min() + y_data_delta = y_data - median_spectra_y + y_data_delta = y_data_delta + y_data_delta.min() + + min_peak_width = 10 + + # get some median information + peaks = SearchPeak(median_spectra_x) + if not peaks.valid_data: + return down_info, shifted_info + + peaks.search(delta=3, min_width=min_peak_width) + #logger.debug("found X peaks with bandwidth > %d = %s" % (min_peak_width, str([i[0] for i in peaks.max_peaks]))) + + # search for nearest peak to center of subbands + x_median_max_peak_val = 0.0 + x_median_max_peak_sb = 0 + x_median_min_sb = 0 + x_median_max_sb = 0 + min_gap = 500 + for max_pos, min_sb, max_sb in peaks.max_peaks: + if abs(max_pos - normal_center_sb) < min_gap: + min_gap = abs(max_pos - normal_center_sb) + x_median_max_peak_val = peaks.get_peak_value(max_pos) + x_median_max_peak_sb = max_pos + x_median_min_sb = min_sb + x_median_max_sb = max_sb + + x_median_bandwidth = x_median_max_sb - x_median_min_sb + x_median_subbands_pwr = float(ma.median(x_data[:, start_sb: stop_sb])) + logger.debug("median X peak all rcu's in band %d .. %d : subband=%d, bw(3dB)=%d, median-value-band=%3.1fdB" % ( + start_sb, stop_sb, x_median_max_peak_sb, x_median_bandwidth, x_median_subbands_pwr)) + + down_info.append(('Xref', x_median_max_peak_sb, x_median_max_peak_val, x_median_bandwidth, x_median_subbands_pwr)) + + + peaks = SearchPeak(median_spectra_y) + if not peaks.valid_data: + return down_info, shifted_info + + peaks.search(delta=3, min_width=min_peak_width) + #logger.debug("found Y peaks with bandwidth > %d = %s" % (min_peak_width, str([i[0] for i in peaks.max_peaks]))) + + # search for nearest peak to center of subbands + y_median_max_peak_val = 0.0 + y_median_max_peak_sb = 0 + y_median_min_sb = 0 + y_median_max_sb = 0 + min_gap = 500 + for max_pos, min_sb, max_sb in peaks.max_peaks: + if abs(max_pos - normal_center_sb) < min_gap: + min_gap = abs(max_pos - normal_center_sb) + y_median_max_peak_val = peaks.get_peak_value(max_pos) + y_median_max_peak_sb = max_pos + y_median_min_sb = min_sb + y_median_max_sb = max_sb + + y_median_bandwidth = y_median_max_sb - y_median_min_sb + y_median_subbands_pwr = float(ma.median(x_data[:, start_sb: stop_sb])) + logger.debug("median Y peak all rcu's in band %d .. %d : subband=%d, bw(3dB)=%d, median-value-band=%3.1fdB" % ( + start_sb, stop_sb, y_median_max_peak_sb, y_median_bandwidth, y_median_subbands_pwr)) + + down_info.append(('Yref', y_median_max_peak_sb, y_median_max_peak_val, y_median_bandwidth, y_median_subbands_pwr)) + + + mean_pwr_array = zeros((data.max_rcus()), 'f') + peaks_pwr_array = zeros((data.max_rcus()), 'f') + peaks_sb_array = zeros((data.max_rcus()), 'i') + peaks_bw_array = zeros((data.max_rcus()), 'i') + + # for all rcus fill above arrays with data + for data_nr, rcu in enumerate(data.rcus(band, 'x')): + mean_pwr_array[rcu] = float(ma.median(x_data[data_nr, start_sb: stop_sb])) + peaks = SearchPeak(x_data_delta[data_nr, :]) + if peaks.valid_data: + peaks.search(delta=3, min_width=min_peak_width) + #logger.debug("RCU %d found X peaks with bandwidth > %d = %s" % (rcu, min_peak_width, str([i[0] for i in peaks.max_peaks]))) + + # search for nearest peak to center of subbands + max_peak_val = 0.0 + max_peak_sb = 0 + min_sb = 0 + max_sb = 0 + min_gap = 500 + for _max_pos, _min_sb, _max_sb in peaks.max_peaks: + if _max_pos not in subbands: + continue + if abs(_max_pos - normal_center_sb) < min_gap: + min_gap = abs(_max_pos - normal_center_sb) + max_peak_val = x_data[data_nr, _max_pos] + max_peak_sb = _max_pos + min_sb = _min_sb + max_sb = _max_sb + + if max_peak_sb > 0: + logger.debug("RCU %d X: max. peak(s) found on subband(s) %s" % (rcu, str([i[0] for i in peaks.max_peaks]))) + peaks = SearchPeak(x_data[data_nr, :]) + peak_bandwidth, _min_sb, _max_sb = peaks.get_peak_width(max_peak_sb, 3) + # peakwidth, min_sb, max_sb = peaks.getPeakWidth(maxpeak_sb, delta) + peaks_bw_array[rcu] = peak_bandwidth + peaks_pwr_array[rcu] = max_peak_val + peaks_sb_array[rcu] = max_peak_sb + else: + peaks = SearchPeak(x_data[data_nr, :]) + peak_bandwidth, _min_sb, _max_sb = peaks.get_peak_width(normal_center_sb, 3) + peaks_bw_array[rcu] = peak_bandwidth + peaks_sb_array[rcu] = normal_center_sb + peaks_pwr_array[rcu] = x_data[data_nr, _max_pos] + + for data_nr, rcu in enumerate(data.rcus(band, 'y')): + mean_pwr_array[rcu] = float(ma.median(y_data[data_nr, start_sb: stop_sb])) + peaks = SearchPeak(y_data_delta[data_nr, :]) + if peaks.valid_data: + peaks.search(delta=3, min_width=min_peak_width) + #logger.debug("RCU %d found Y peaks with bandwidth > %d = %s" % (rcu, min_peak_width, str([i[0] for i in peaks.max_peaks]))) + + # search for nearest peak to center of subbands + max_peak_val = 0.0 + max_peak_sb = 0 + min_sb = 0 + max_sb = 0 + min_gap = 200 + for _max_pos, _min_sb, _max_sb in peaks.max_peaks: + if _max_pos not in subbands: + continue + if abs(_max_pos - normal_center_sb) < min_gap: + min_gap = abs(_max_pos - normal_center_sb) + max_peak_val = y_data[data_nr, _max_pos] + max_peak_sb = _max_pos + min_sb = _min_sb + max_sb = _max_sb + + + + if max_peak_sb > 0: + logger.debug("RCU %d Y: max. peak(s) found on subband(s) %s" % (rcu, str([i[0] for i in peaks.max_peaks]))) + peaks = SearchPeak(y_data[data_nr, :]) + peak_bandwidth, _min_sb, _max_sb = peaks.get_peak_width(max_peak_sb, 3) + # peakwidth, min_sb, max_sb = peaks.getPeakWidth(maxpeak_sb, delta) + peaks_bw_array[rcu] = peak_bandwidth + peaks_pwr_array[rcu] = max_peak_val + peaks_sb_array[rcu] = max_peak_sb + else: + peaks = SearchPeak(y_data[data_nr, :]) + peak_bandwidth, _min_sb, _max_sb = peaks.get_peak_width(normal_center_sb, 3) + peaks_bw_array[rcu] = peak_bandwidth + peaks_sb_array[rcu] = normal_center_sb + peaks_pwr_array[rcu] = y_data[data_nr, _max_pos] + + # check for down or shifted tops of the noise-floor + x_rcus = data.rcus(band, 'x') + for x_rcu in x_rcus: + if data.mode(x_rcu) in (1, 2): + y_rcu = x_rcu - 1 + else: + y_rcu = x_rcu + 1 + x_value_trigger = False + x_down_trigger = False + y_value_trigger = False + y_down_trigger = False + thunderstorm = False + logger.debug("rcu=%d: X-top, sb=%d, pwr=%3.1fdB, bw=%d, mean-value-band=%3.1f" % ( + x_rcu, peaks_sb_array[x_rcu], peaks_pwr_array[x_rcu], peaks_bw_array[x_rcu], mean_pwr_array[x_rcu])) + logger.debug("rcu=%d: Y-top, sb=%d, pwr=%3.1fdB, bw=%d, mean-value-band=%3.1f" % ( + y_rcu, peaks_sb_array[y_rcu], peaks_pwr_array[y_rcu], peaks_bw_array[y_rcu], mean_pwr_array[y_rcu])) + + # if both polarity's not shifted and 1 pol is higher then average and the other is lower then average + if abs(peaks_sb_array[x_rcu] - normal_center_sb) < 3 and abs(peaks_sb_array[y_rcu] - normal_center_sb) < 3: + if (mean_pwr_array[x_rcu] < (x_median_subbands_pwr - 3.0) and mean_pwr_array[y_rcu] > (y_median_subbands_pwr + 3.0)): + thunderstorm = True + if (mean_pwr_array[y_rcu] < (y_median_subbands_pwr - 3.0) and mean_pwr_array[x_rcu] > (x_median_subbands_pwr + 3.0)): + thunderstorm = True + + if thunderstorm: + logger.debug("skip down test, probably thunderstorm active") + else: + if 66.0 < mean_pwr_array[x_rcu] < (x_median_subbands_pwr - 2.0): + logger.debug("rcu=%d: mean signal in test band for X lower than normal" % x_rcu) + x_value_trigger = True + if 66.0 < mean_pwr_array[y_rcu] < (y_median_subbands_pwr - 2.0): + logger.debug("rcu=%d: mean signal in test band for Y lower than normal" % y_rcu) + y_value_trigger = True + + if abs(peaks_sb_array[x_rcu] - normal_center_sb) > 10 or abs(normal_bw_3db - peaks_bw_array[x_rcu]) > 10: + if peaks_bw_array[x_rcu] > 3: + logger.debug("rcu=%d: X broken or antenna down" % x_rcu) + x_down_trigger = True + if abs(peaks_sb_array[y_rcu] - normal_center_sb) > 10 or abs(normal_bw_3db - peaks_bw_array[y_rcu]) > 10: + if peaks_bw_array[y_rcu] > 3: + logger.debug("rcu=%d: Y broken or antenna down" % y_rcu) + y_down_trigger = True + + if (x_value_trigger and x_down_trigger) or (y_value_trigger and y_down_trigger): + down_info.append((x_rcu, peaks_sb_array[x_rcu], peaks_pwr_array[x_rcu], peaks_bw_array[x_rcu], mean_pwr_array[x_rcu])) + down_info.append((y_rcu, peaks_sb_array[y_rcu], peaks_pwr_array[y_rcu], peaks_bw_array[y_rcu], mean_pwr_array[y_rcu])) + #logger.debug("down_info=%s" % str(down_info)) + else: + if (peaks_bw_array[x_rcu] > 20) and (abs(peaks_sb_array[x_rcu] - normal_center_sb) > 10): + logger.debug("rcu=%d: X-top shifted normal=%d, now=%d" % ( + x_rcu, normal_center_sb, peaks_sb_array[x_rcu])) + shifted_info.append((x_rcu, peaks_sb_array[x_rcu], normal_center_sb)) + if (peaks_bw_array[y_rcu] > 20) and (abs(peaks_sb_array[y_rcu] - normal_center_sb) > 10): + logger.debug("rcu=%d: Y-top shifted normal=%d, now=%d" % ( + y_rcu, normal_center_sb, peaks_sb_array[y_rcu])) + shifted_info.append((y_rcu, peaks_sb_array[y_rcu], normal_center_sb)) + + + # if more than half the antennes are down or shifted, skip test + if len(down_info) > len(data.rcus(band, 'xy')) / 2: + down_info = list() + if len(shifted_info) > len(data.rcus(band, 'xy')) / 2: + shifted_info = list() + return down_info, shifted_info diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/down_old.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/down_old.py new file mode 100644 index 00000000000..d316b365e7d --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/down_old.py @@ -0,0 +1,162 @@ +from peakslib import * + +logger = logging.getLogger('main.chk.dow..') +logger.debug("init logger") + +def check_for_down(data, band, parset): + """ + :param data: recorded antenna data data + :param band: band to check + :param parset: parameterset with check settings + :return: list with found failures + """ + subbands =parset.as_int_list('down.passband') + if subbands is None: + logger.warning("no passband found, use default 250:350") + subbands = range(250,351,1) + + # _data = (rcus x subbands) + _data = data.median_spectras(freq_band=band, polarity='xy', masked=True) + down_info = list() + shifted_info = list() + start_sb = min(subbands) + stop_sb = max(subbands) + center_sb = (stop_sb + start_sb) / 2 + + peaks = SearchPeak(ma.median(_data, axis=0)) + if not peaks.valid_data: + return down_info, shifted_info + + min_peak_width = 3 + peaks.search(delta=3, min_width=min_peak_width) + logger.debug("found peaks with bandwidth > %d = %s" % (min_peak_width, str([i[0] for i in peaks.max_peaks]))) + + # search for nearest peak to center of subbands + median_max_peak_val = 0.0 + median_max_peak_sb = 0 + median_min_sb = 0 + median_max_sb = 0 + min_gap = 100 + for max_pos, min_sb, max_sb in peaks.max_peaks: + if abs(max_pos - center_sb) < min_gap: + min_gap = abs(max_pos - center_sb) + median_max_peak_val = peaks.get_peak_value(max_pos) + median_max_peak_sb = max_pos + median_min_sb = min_sb + median_max_sb = max_sb + + median_bandwidth = median_max_sb - median_min_sb + + #(median_max_val, median_max_sb, min_sb, max_sb) = peaks.get_max_peak(sb_list=subbands) + # peakwidth, min_sb, max_sb = peaks.getPeakWidth(median_max_sb, delta) + # median_max_sb += start_sb + + median_subbands_pwr = float(ma.median(_data[:, start_sb: stop_sb])) + logger.debug("reference peak in band %d .. %d : subband=%d, bw(3dB)=%d, median-value-band=%3.1fdB" % ( + start_sb, stop_sb, median_max_peak_sb, median_bandwidth, median_subbands_pwr)) + + down_info.append((-1, median_max_peak_sb, median_max_peak_val, median_bandwidth, median_subbands_pwr)) + + + mean_pwr_array = zeros((data.max_rcus()), 'f') + peaks_pwr_array = zeros((data.max_rcus()), 'f') + peaks_sb_array = zeros((data.max_rcus()), 'i') + peaks_bw_array = zeros((data.max_rcus()), 'i') + + for data_nr, rcu in enumerate(data.rcus(band, 'xy')): + mean_pwr_array[rcu] = float(ma.mean(_data[data_nr, start_sb: stop_sb])) + peaks = SearchPeak(_data[data_nr, :]) + if peaks.valid_data: + peaks.search(delta=3, min_width=3) + + # search for nearest peak to center of subbands + max_peak_val = 0.0 + max_peak_sb = 0 + min_sb = 0 + max_sb = 0 + min_gap = 100 + for _max_pos, _min_sb, _max_sb in peaks.max_peaks: + if abs(_max_pos - center_sb) < min_gap: + min_gap = abs(_max_pos - center_sb) + max_peak_val = peaks.get_peak_value(max_pos) + max_peak_sb = _max_pos + min_sb = _min_sb + max_sb = _max_sb + + peak_bandwidth = max_sb - min_sb + + #(maxpeak_val, maxpeak_sb, min_sb, max_sb) = peaks.get_max_peak(sb_list=subbads) + + if max_peak_sb > 0: + # peakwidth, min_sb, max_sb = peaks.getPeakWidth(maxpeak_sb, delta) + peaks_bw_array[rcu] = peak_bandwidth + peaks_pwr_array[rcu] = max_peak_val + peaks_sb_array[rcu] = max_peak_sb + else: + peaks_bw_array[rcu] = stop_sb - start_sb + peaks_sb_array[rcu] = (stop_sb + start_sb) / 2 + peaks_pwr_array[rcu] = peaks.get_peak_value(peaks_sb_array[rcu]) + + + x_rcus = data.rcus(band, 'x') + for x_rcu in x_rcus: + if data.mode(x_rcu) in (1, 2): + y_rcu = x_rcu - 1 + else: + y_rcu = x_rcu + 1 + x_value_trigger = False + x_down_trigger = False + y_value_trigger = False + y_down_trigger = False + thunderstorm = False + logger.debug("rcu=%d: X-top, sb=%d, pwr=%3.1fdB, bw=%d, mean-value-band=%3.1f" % ( + x_rcu, peaks_sb_array[x_rcu], peaks_pwr_array[x_rcu], peaks_bw_array[x_rcu], mean_pwr_array[x_rcu])) + logger.debug("rcu=%d: Y-top, sb=%d, pwr=%3.1fdB, bw=%d, mean-value-band=%3.1f" % ( + y_rcu, peaks_sb_array[y_rcu], peaks_pwr_array[y_rcu], peaks_bw_array[y_rcu], mean_pwr_array[y_rcu])) + + # if both polaritys not shifted and 1 pol is higher then average and the other is lower then average + if abs(peaks_sb_array[x_rcu] - median_max_sb) < 3 and abs(peaks_sb_array[y_rcu] - median_max_sb) < 3: + if (mean_pwr_array[x_rcu] < (median_subbands_pwr - 3.0) and mean_pwr_array[y_rcu] > (median_subbands_pwr + 3.0)): + thunderstorm = True + if (mean_pwr_array[y_rcu] < (median_subbands_pwr - 3.0) and mean_pwr_array[x_rcu] > (median_subbands_pwr + 3.0)): + thunderstorm = True + + if thunderstorm: + logger.debug("skip down test, probably thunderstorm active") + else: + if 66.0 < mean_pwr_array[x_rcu] < (median_subbands_pwr - 2.0): + logger.debug("rcu=%d: mean signal in test band for X lower than normal" % x_rcu) + x_value_trigger = True + if 66.0 < mean_pwr_array[y_rcu] < (median_subbands_pwr - 2.0): + logger.debug("rcu=%d: mean signal in test band for Y lower than normal" % y_rcu) + y_value_trigger = True + + if abs(peaks_sb_array[x_rcu] - median_max_peak_sb) > 10 or abs(median_bandwidth - peaks_bw_array[x_rcu]) > 10: + if peaks_bw_array[x_rcu] > 3: + logger.debug("rcu=%d: X broken or antenna down" % x_rcu) + x_down_trigger = True + if abs(peaks_sb_array[y_rcu] - median_max_peak_sb) > 10 or abs(median_bandwidth - peaks_bw_array[y_rcu]) > 10: + if peaks_bw_array[y_rcu] > 3: + logger.debug("rcu=%d: Y broken or antenna down" % y_rcu) + y_down_trigger = True + + if (x_value_trigger and x_down_trigger) or (y_value_trigger and y_down_trigger): + down_info.append((x_rcu, peaks_sb_array[x_rcu], peaks_pwr_array[x_rcu], peaks_bw_array[x_rcu], mean_pwr_array[x_rcu])) + down_info.append((y_rcu, peaks_sb_array[y_rcu], peaks_pwr_array[y_rcu], peaks_bw_array[x_rcu], mean_pwr_array[y_rcu])) + else: + if (peaks_bw_array[x_rcu] > 20) and (abs(peaks_sb_array[x_rcu] - median_max_peak_sb) > 10): + logger.debug("rcu=%d: X-top shifted normal=%d, now=%d" % ( + x_rcu, median_max_peak_sb, peaks_sb_array[x_rcu])) + shifted_info.append((x_rcu, peaks_sb_array[x_rcu], median_max_peak_sb)) + if (peaks_bw_array[y_rcu] > 20) and (abs(peaks_sb_array[y_rcu] - median_max_peak_sb) > 10): + logger.debug("rcu=%d: Y-top shifted normal=%d, now=%d" % ( + y_rcu, median_max_peak_sb, peaks_sb_array[y_rcu])) + shifted_info.append((y_rcu, peaks_sb_array[y_rcu], median_max_peak_sb)) + + + # if more than half the antennes are down or shifted, skip test + if len(down_info) > (_data.shape[0] / 2): + down_info = list() + if len(shifted_info) > (_data.shape[0] / 2): + shifted_info = list() + return down_info, shifted_info diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/flat.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/flat.py new file mode 100644 index 00000000000..58d63bbc2af --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/flat.py @@ -0,0 +1,24 @@ +import logging +from peakslib import * + +logger = logging.getLogger('main.chk.fla..') +logger.debug("init logger") + +def check_for_flat(data, band, parset): + """ + :param data: recorded antenna data data + :param band: band to check + :param parset: parameterset with check settings + :return: list with found failures + """ + min_pwr = parset.as_float('flat.mean-pwr.min') + max_pwr = parset.as_float('flat.mean-pwr.max') + + _data = data.mean_spectras(freq_band=band, polarity='xy', masked=True) + flat_info = list() + for data_nr, rcu in enumerate(data.rcus(band, 'xy')): + mean_signal = ma.mean(_data[data_nr, :]) + if min_pwr < mean_signal < max_pwr: + logger.info("rcu=%d: cable probable off" % rcu) + flat_info.append((rcu, mean_signal)) + return flat_info diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/noise.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/noise.py new file mode 100644 index 00000000000..d6a7d1f51bf --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/noise.py @@ -0,0 +1,109 @@ +import logging +from peakslib import * + +logger = logging.getLogger('main.chk.noi..') +logger.debug("init logger") + +def check_for_noise(data, band, pol, parset): + """ + :param data: recorded antenna data data + :param band: band to check + :param pol: polarity to check + :param parset: parameterset with check settings + :return: list with found failures + """ + low_deviation = parset.as_float('noise.negative-deviation') + high_deviation = parset.as_float('noise.positive-deviation') + max_diff = parset.as_float('noise.max-difference') + passband = parset.as_int_list('noise.passband') + if passband is None: + logger.warning("no passband found, use default 1:511") + passband = range(1,512,1) + data.set_passband(band, passband) + + _data = data.spectras(freq_band=band, polarity=pol, masked=True) + #logger.info("data shape %s" %(str(_data.shape))) + high_info = list() + low_info = list() + jitter_info = list() + + ref_value = float(ma.median(_data)) + # loop over rcus + for data_nr, rcu in enumerate(data.rcus(band, pol)): + # logger.debug("rcu=%d data_nr=%d" %(rcu, data_nr)) + data_nr_value = float(ma.median(_data[data_nr, :, :])) + if data_nr_value < (ref_value + low_deviation): + logger.debug("data_nr=%d: masked, low signal, ref=%5.3f val=%5.3f" % (data_nr, ref_value, data_nr_value)) + low_info.append((rcu, _data[data_nr, :, :].min(), -1, (ref_value+low_deviation), + (_data[data_nr, :, :].max() - _data[data_nr, :, :].min()))) + data.mask_rcu(rcu) + spec_median = ma.median(_data, axis=2) + spec_max = spec_median.max(axis=1) + spec_min = spec_median.min(axis=1) + ref_value = float(ma.median(_data)) + ref_diff = float(ma.median(spec_max) - ma.median(spec_min)) + ref_std = float(ma.std(spec_median)) + # high_limit = ref_value + min(max((ref_std * 3.0),0.75), high_deviation) + high_limit = ref_value + max((ref_std * 3.0), high_deviation) + low_limit = ref_value + min((ref_std * -3.0), low_deviation) + n_secs = _data.shape[1] + logger.debug("median-signal=%5.3fdB, median-fluctuation=%5.3fdB, std=%5.3f, high_limit=%5.3fdB low_limit=%5.3fdB" % ( + ref_value, ref_diff, ref_std, high_limit, low_limit)) + # loop over rcus + for data_nr, rcu in enumerate(data.rcus(band, pol)): + # logger.debug("rcu=%d data_nr=%d" %(rcu, data_nr)) + peaks = SearchPeak(_data[data_nr, 0, :]) + if not peaks.valid_data: + return low_info, high_info, jitter_info + peaks.search(delta=10.0) + if peaks.n_max_peaks() >= 30: + logger.debug("rcu=%d: found %d peaks, skip noise test" % (rcu, peaks.n_max_peaks())) + else: + n_bad_high_secs = 0 + n_bad_low_secs = 0 + if _data.shape[1] == 1: + n_bad_high_secs = 1 + n_bad_low_secs = 1 + + data_nr_max_diff = spec_max[data_nr] - spec_min[data_nr] + # logger.debug("data_nr_max_diff %f" %(data_nr_max_diff)) + # loop over secs + for val in spec_median[data_nr, :]: + # logger.debug("data_nr=%d: high-noise value=%5.3fdB max-ref-value=%5.3fdB" %(data_nr, val, ref_value)) + if val > high_limit: + n_bad_high_secs += 1 + + if val < low_limit: + n_bad_low_secs += 1 + + if n_bad_high_secs > 1: + high_info.append((rcu, spec_max[data_nr], n_bad_high_secs, high_limit, data_nr_max_diff)) + logger.debug("rcu=%d: max-noise=%5.3f %d of %d seconds bad" % ( + rcu, spec_max[data_nr], n_bad_high_secs, n_secs)) + + if n_bad_low_secs > 1: + low_info.append((rcu, spec_min[data_nr], n_bad_low_secs, low_limit, data_nr_max_diff)) + logger.debug("rcu=%d: min-noise=%5.3f %d of %d seconds bad" % ( + rcu, spec_min[data_nr], n_bad_low_secs, n_secs)) + + if (n_bad_high_secs == 0) and (n_bad_low_secs == 0): + max_cnt = 0 + min_cnt = 0 + if data_nr_max_diff > (ref_diff + max_diff): + check_high_value = ref_value + (ref_diff / 2.0) + check_low_value = ref_value - (ref_diff / 2.0) + for val in spec_median[data_nr, :]: + if val > check_high_value: + max_cnt += 1 + if val < check_low_value: + min_cnt += 1 + + # minimal 20% of the values must be out of the check band + secs = _data.shape[1] + if max_cnt > (secs * 0.10) and min_cnt > (secs * 0.10): + n_bad_jitter_secs = max_cnt + min_cnt + jitter_info.append((rcu, data_nr_max_diff, ref_diff, n_bad_jitter_secs)) + logger.debug("rcu=%d: max spectrum fluctuation %5.3f dB" % (rcu, data_nr_max_diff)) + + data.reset_masked_sb(band) + return low_info, high_info, jitter_info diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/oscillation.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/oscillation.py new file mode 100644 index 00000000000..424d703297d --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/oscillation.py @@ -0,0 +1,68 @@ +import logging +from peakslib import * + + +logger = logging.getLogger('main.chk.osc..') +logger.debug("init logger") + + +def check_for_oscillation(data, band, pol, parset): + """ + :param data: recorded antenna data data + :param band: band to check + :param pol: polarity to check + :param parset: parameterset with check settings + :return: list with found failures + """ + min_peak_pwr = parset.as_float('oscillation.min-peak-pwr') + passband = parset.as_int_list('oscillation.passband') + if passband is None: + logger.warning("no passband found, use default 1:511") + passband = range(1,512,1) + data.set_passband(band, passband) + + info = list() + _data = data.spectras(freq_band=band, polarity=pol, masked=True) + mean_spectras = ma.mean(_data, axis=1) + mean_spectra = ma.mean(mean_spectras, axis=0) + mean_low = ma.mean(_data.min(axis=1)) + info.append((-1, 0, 0, 0)) + + for data_nr, rcu in enumerate(data.rcus(band, pol)): + # logger.debug("rcu=%d rcu_bin=%d" %(rcu, rcu_bin)) + peaks = SearchPeak(mean_spectras[data_nr, :] - mean_spectra) + if peaks.valid_data: + peaks.search(delta=min_peak_pwr, min_width=2, max_width=8) + max_val = mean_spectras[data_nr, :].max() + max_n_peaks = peaks.n_max_peaks() + max_sum_peaks = peaks.get_sum_peaks() + + bin_low = _data[data_nr, :, :].min(axis=0).mean() + + logger.debug("rcu=%d: number-of-peaks=%d max_value=%3.1f peaks_sum=%5.3f low_value=%3.1f" % ( + rcu, max_n_peaks, max_val, max_sum_peaks, bin_low)) + + out_of_band_peaks = 0 + for peak_sb, min_sb, max_sb in peaks.max_peaks: + if peak_sb in range(0, 25, 1): + out_of_band_peaks += 1 + if peak_sb in range(488, 512, 1): + out_of_band_peaks += 1 + if out_of_band_peaks >= 2: + logger.debug("detected out of band peaks") + # info.append((rcu, max_sum_peaks, max_n_peaks, bin_low)) + continue + + if max_n_peaks > 5: + if bin_low > (mean_low + 2.0): # peaks.getSumPeaks() > (median_sum_peaks * 2.0): + logger.debug("detected peaks in complete band") + info.append((rcu, max_sum_peaks, max_n_peaks, bin_low)) + continue + + if max_val > 150.0: # only one high peek + logger.debug("detected peak > 150 dB") + info.append((rcu, max_sum_peaks, max_n_peaks, bin_low)) + continue + + data.reset_masked_sb(band) + return info # (sorted(info,reverse=True)) diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/peakslib.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/peakslib.py new file mode 100644 index 00000000000..21221b21544 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/peakslib.py @@ -0,0 +1,149 @@ + +from numpy import ma, fft, power, arange, isscalar, NaN, Inf, zeros +from sys import exit +import logging +from checkhardware_lib.data import * + +logger = logging.getLogger('main.chk.pea..') +logger.debug("init logger") + +class SearchPeak(object): + """ + search for all peaks (min & max) in spectra + """ + def __init__(self, data): + self.valid_data = False + if len(data.shape) == 1: + self.valid_data = True + self.data = data.copy() + self.n_data = len(data) + self.max_peaks = [] + self.min_peaks = [] + return + + def search(self, delta, min_width=1, max_width=100, skip_list=()): + self.max_peaks = [] + self.min_peaks = [] + + x = arange(0, len(self.data), 1) + + if not isscalar(delta): + exit('argument delta must be a scalar') + + if delta <= 0: + exit('argument delta must be positive') + + maxval, minval = -200, 200 + maxpos, minpos = 1, 1 + + lookformax = True + + # add subband to skiplist + skiplist = [] + if len(skip_list) > 0: + for max_pos, min_sb, max_sb in skip_list: + for sb in range(min_sb, max_sb+1, 1): + skiplist.append(sb) + + # skip subband 0 (always high signal) + for i in range(1, self.n_data, 1): + # sleep(0.001) + if ma.count_masked(self.data) > 1 and self.data.mask[i] is True: + continue + + now = self.data[i] + if now > maxval: + maxval = now + maxpos = x[i] + + if now < minval: + minval = now + minpos = x[i] + + if lookformax: + if now < (maxval - delta): + if maxpos not in skiplist: + peakwidth, min_sb, max_sb = self.get_peak_width(maxpos, delta) + # logger.debug("maxpos=%d, width=%d" %(maxpos, peakwidth)) + if min_width < peakwidth < max_width: + self.max_peaks.append([maxpos, min_sb, max_sb]) + minval = now + minpos = x[i] + lookformax = False + else: + if now > (minval + delta): + if minpos not in skiplist: + self.min_peaks.append([minpos, minpos, minpos]) + maxval = now + maxpos = x[i] + lookformax = True + + # if no peak found with the given delta, return maximum found + if len(self.max_peaks) == 0: + self.max_peaks.append([-1, -1, -1]) + return + + # return data[nr] + def get_peak_value(self, nr): + try: + return self.data[nr] + except IndexError: + return NaN + except: + raise + + def get_peak_width(self, nr, delta): + peakval = self.data[nr] + minnr = nr + maxnr = nr + for sb in range(nr, 0, -1): + if self.data[sb] < peakval: + minnr = sb + if self.data[sb] <= (peakval - delta): + break + for sb in range(nr, self.data.shape[0], 1): + if self.data[sb] < peakval: + maxnr = sb + if self.data[sb] <= (peakval - delta): + break + return maxnr-minnr, minnr, maxnr + + # return value and subband nr + def get_max_peak(self, sb_list=None): + maxval = 0.0 + minsb = 0.0 + maxsb = 0.0 + binnr = -1 + if sb_list is None: + check_range = range(512) + else: + check_range = sb_list + for peak, min_sb, max_sb in self.max_peaks: + if peak not in check_range: + continue + if self.data[peak] > maxval: + maxval = self.data[peak] + binnr = peak + minsb = min_sb + maxsb = max_sb + return maxval, binnr, minsb, maxsb + + def get_sum_peaks(self): + peaksum = 0.0 + for peak, min_sb, max_sb in self.max_peaks: + peaksum += self.data[peak] + return peaksum + + # return value and sbband nr + def get_min_peak(self): + minval = Inf + nr_bin = -1 + + for peak in self.min_peaks: + if self.data[peak] < minval: + minval = self.data[peak] + nr_bin = peak + return minval, nr_bin + + def n_max_peaks(self): + return len(self.max_peaks) diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/rf_power.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/rf_power.py new file mode 100644 index 00000000000..5593667d556 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/rf_power.py @@ -0,0 +1,82 @@ +import logging +from peakslib import * + +logger = logging.getLogger('main.chk.rf_..') +logger.debug("init logger") + + +def check_rf_power(data, band, pol, parset): + """ + :param data: recorded antenna data data + :param band: band to check + :param pol: polarity to check + :param parset: parameterset with check settings + :return: list with found failures + """ + #logger.debug('parset= %s' % str(parset)) + subbands = parset.as_int_list('rf.subbands') + min_signal = parset.as_float('rf.min-sb-pwr') + low_deviation = parset.as_float('rf.negative-deviation') + high_deviation = parset.as_float('rf.positive-deviation') + + logger.debug("band=%s pol=%s subband=%s" % ( + band, pol, str(subbands))) + logger.debug("min_signal=%5.1fdB low_deviation=%5.1fdB high_deviation=%5.1fdB" % ( + min_signal, low_deviation, high_deviation)) + + signal_info = dict() + test_info = dict() + test_sb = None + + # _data = (rcus x secs x subbands), all data for given band and pol + _data = data.spectras(freq_band=band, polarity=pol, masked=True) + # 1 spectra with median from all rcu spectra (max from all seconds) + median_spectra = ma.median(ma.max(_data, axis=1), axis=0) + + if len(subbands) > 1: + peaks = SearchPeak(median_spectra) + peaks.search(delta=6.0, min_width=2) + maxval, binnr, minsb, maxsb = peaks.get_max_peak(sb_list=subbands) + if maxval >= min_signal: + test_sb = binnr + else: + test_sb = subbands[0] + + if not test_sb: + test_info['valid'] = False + test_info['subband'] = -1 + test_info['test_val'] = 0.0 + return test_info, signal_info + + test_sb_value = median_spectra[test_sb] + + test_info['subband'] = test_sb + test_info['test_val'] = test_sb_value + + # logger.debug("test_sb_value=%s min_signal=%s" % (str(test_sb_value), str(min_signal))) + if test_sb_value > min_signal: + test_info['valid'] = True + else: + test_info['valid'] = False + + # sb_data = rcus values, median value of total seconds + sb_data = ma.max(data.subbands(freq_band=band, polarity=pol, sb_set=test_sb, masked=True), axis=1) + rcu_list = data.rcus(band=band, polarity=pol) + logger.debug("used test_sb=%d" % test_sb) + #logger.debug("sb_data=%s" % str(sb_data)) + for data_nr, rcu in enumerate(rcu_list): + # logger.debug("data_nr=%d rcu=%d val=%5.1fdB" % (data_nr, rcu, sb_data[data_nr])) + if np.ma.is_masked(sb_data[data_nr]): + signal_info[str(rcu)] = {'value': 0.0, 'status': 'masked'} + elif sb_data[data_nr] < 2.0: + signal_info[str(rcu)] = {'value': sb_data[data_nr], 'status': 'no_signal'} + elif 55.0 < sb_data[data_nr] < 65.0: + signal_info[str(rcu)] = {'value': sb_data[data_nr], 'status': 'no_power'} + elif sb_data[data_nr] < (test_sb_value + low_deviation): + signal_info[str(rcu)] = {'value': sb_data[data_nr], 'status': 'low'} + elif sb_data[data_nr] > (test_sb_value + high_deviation): + signal_info[str(rcu)] = {'value': sb_data[data_nr], 'status': 'high'} + else: + signal_info[str(rcu)] = {'value': sb_data[data_nr], 'status': 'normal'} + + return test_info, signal_info diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/short.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/short.py new file mode 100644 index 00000000000..d2d7b6c59b7 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/short.py @@ -0,0 +1,25 @@ +import logging +from peakslib import * + +logger = logging.getLogger('main.chk.sho..') +logger.debug("init logger") + + +def check_for_short(data, band, parset): + """ + :param data: recorded antenna data data + :param band: band to check + :param parset: parameterset with check settings + :return: list with found failures + """ + min_pwr = parset.as_float('short.mean-pwr.min') + max_pwr = parset.as_float('short.mean-pwr.max') + + _data = data.mean_spectras(freq_band=band, polarity='xy', masked=True) + short_info = list() + for data_nr, rcu in enumerate(data.rcus(band, 'xy')): + mean_signal = ma.mean(_data[data_nr, :]) + if min_pwr < mean_signal < max_pwr: + logger.info("rcu=%d: cable shorted" % rcu) + short_info.append((rcu, mean_signal)) + return short_info diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/spurious.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/spurious.py new file mode 100644 index 00000000000..de4e67849ee --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/spurious.py @@ -0,0 +1,64 @@ +import logging +from peakslib import * + +logger = logging.getLogger('main.chk.spu..') +logger.debug("init logger") + + +def check_for_spurious(data, band, pol, parset): + """ + :param data: recorded antenna data data + :param band: band to check + :param pol: polarity to check + :param parset: parameterset with check settings + :return: list with found failures + """ + min_peak_pwr = parset.as_float('spurious.min-peak-pwr') + passband = parset.as_int_list('spurious.passband') + if passband is None: + logger.warning("no passband found, use default 1:511") + passband = range(1,512,1) + data.set_passband(band, passband) + + info = list() + + _data = data.spectras(freq_band=band, polarity=pol, masked=True) + max_data = ma.max(_data, axis=1) + mean_data = ma.mean(_data, axis=1) + median_spec = ma.mean(max_data, axis=0) + peaks = SearchPeak(median_spec) + if not peaks.valid_data: + return info + + # first mask peaks available in all data + peaks.search(delta=(min_peak_pwr / 2.0)) # deta=20 for HBA + for peak, min_sb, max_sb in peaks.max_peaks: + peakwidth = max_sb - min_sb + if peakwidth > 8: + continue + min_sb = max(min_sb - 1, 0) + max_sb = min(max_sb + 1, peaks.n_data - 1) + logger.debug("mask sb %d..%d" % (min_sb, max_sb)) + for i in range(min_sb, max_sb, 1): + mean_data[:, i] = ma.masked + + # search in all data for spurious + for data_nr, rcu in enumerate(data.rcus(band, pol)): + # logger.debug("rcu=%d data_nr=%d" % (rcu, data_nr)) + peaks = SearchPeak(mean_data[data_nr, :]) + if peaks.valid_data: + peaks.search(delta=min_peak_pwr) + for peak, min_sb, max_sb in peaks.max_peaks: + peakwidth = max_sb - min_sb + if peakwidth > 10: + continue + peak_val = peaks.get_peak_value(peak) + if peakwidth < 100 and peak_val != NaN: + logger.debug("rcu=%d: spurious, subband=%d..%d, peak=%3.1fdB" % ( + rcu, min_sb, max_sb, peak_val)) + if peaks.n_max_peaks() > 10: + # print data_nr, peaks.nMaxPeaks() + info.append(rcu) + + data.reset_masked_sb(band) + return info diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/summator_noise.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/summator_noise.py new file mode 100644 index 00000000000..5c57d121b65 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/summator_noise.py @@ -0,0 +1,69 @@ +import logging +from peakslib import * + + +logger = logging.getLogger('main.chk.sum..') +logger.debug("init logger") + + +def check_for_summator_noise(data, band, pol, parset): + """ + :param data: recorded antenna data data + :param band: band to check + :param pol: polarity to check + :param parset: parameterset with check settings + :return: list with found failures + """ + min_peak_pwr = parset.as_float('summator-noise.min-peak-pwr') + passband = parset.as_int_list('summator-noise.passband') + if passband is None: + logger.warning("no passband found, use default 1:511") + passband = range(1,512,1) + data.set_passband(band, passband) + + sn_info = list() # summator noise + _data = data.spectras(freq_band=band, polarity=pol, masked=True) + + n_secs = _data.shape[1] + secs = (n_secs/2,n_secs-1) + + for data_nr, rcu in enumerate(data.rcus(band, pol)): + # logger.debug("rcu=%d data_nr=%d" %(rcu, data_nr)) + sum_sn_peaks = 0 + max_peaks = 0 + for sec in secs: + peaks_ref = SearchPeak(_data[:, sec, :].mean(axis=0)) + if peaks_ref.valid_data: + peaks_ref.search(delta=min_peak_pwr, min_width=3) + + peaks = SearchPeak(_data[data_nr, sec, :]) + if peaks.valid_data: + peaks.search(delta=min_peak_pwr, skip_list=peaks_ref.max_peaks) + n_peaks = peaks.n_max_peaks() + if n_peaks < 3: + continue + sn_peaks = 0 + last_sb, min_sb, max_sb = peaks.max_peaks[0] + last_sb_val = peaks.get_peak_value(last_sb) + for sb, min_sb, max_sb in peaks.max_peaks[1:]: + sb_val = peaks.get_peak_value(sb) + + sb_diff = sb - last_sb + sb_val_diff = sb_val - last_sb_val + if sb_diff in (3, 4): + if abs(sb_val_diff) < 2.0: + sn_peaks += 1 + elif sn_peaks < 6 and abs(sb_val_diff) > 3.0: + sn_peaks = 0 + last_sb = sb + last_sb_val = sb_val + + sum_sn_peaks += sn_peaks + max_peaks = max(max_peaks, n_peaks) + + if sum_sn_peaks > (len(secs) * 3.0): + sn_peaks = sum_sn_peaks / len(secs) + sn_info.append((rcu, sn_peaks, max_peaks)) + + data.reset_masked_sb(band) + return sn_info diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/tools.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/tools.py new file mode 100644 index 00000000000..8ff28e2c701 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/tools.py @@ -0,0 +1,19 @@ +import logging +from peakslib import * + +logger = logging.getLogger('main.chk.too..') +logger.debug("init logger") + +def psd(data, sampletime): + """ + :param data: data for fft + :param sampletime: used sampletime + :return: fft + """ + if data.ndim != 1: + return [], [] + fft_data = fft.fft(data) + n = fft_data.size + psd_freq = fft.fftfreq(n, sampletime) + _psd = power(abs(fft_data), 2) / n + return _psd[:n / 2], psd_freq[:n / 2] diff --git a/LCU/checkhardware/checkhardware_lib/spu.py b/LCU/checkhardware/checkhardware_lib/spu.py new file mode 100644 index 00000000000..43300ff085b --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/spu.py @@ -0,0 +1,135 @@ +import logging +from lofar import * + +logger = logging.getLogger('main.spu') +logger.debug("starting spu logger") + +class SPU(object): + def __init__(self, db): + self.db = db + self.board_info_str = [] + self.board_info_val = [-1, 0.0, 0.0, 0.0, 0.0, 0] + + def extract_board_info(self, line): + li = line.split("|") + if not li[0].strip().isdigit(): + return False + + self.board_info_str = [i.strip() for i in li] + + if li[0].strip().isdigit(): + self.board_info_val[0] = int(li[0].strip()) + else: + self.board_info_val[0] = -1 + + for i in xrange(1, 5, 1): + if li[i].strip().replace('.', '').isdigit(): + self.board_info_val[i] = float(li[i].strip()) + else: + self.board_info_val[i] = 0.0 + + if li[5].strip().isdigit(): + self.board_info_val[5] = int(li[5].strip()) + else: + self.board_info_val[5] = 0 + return True + + def check_status(self, parset): + """ + check PSU if boards idle and fully loaded + """ + logger.info("=== SPU status check ===") + if not check_active_rspdriver(): + logger.warning("RSPDriver down, skip test") + return + + noload = [] + fullload_3 = [] + fullload_5 = [] + logger.debug("check spu no load") + answer = rspctl('--spustatus') + + # check if Driver is available + if answer.find('No Response') > 0: + logger.warning("No RSPDriver") + + else: + infolines = answer.splitlines() + for line in infolines: + if self.extract_board_info(line): + bi = self.board_info_val + noload.append([bi[0], bi[1], bi[2], bi[3], bi[4]]) + self.db.spu[bi[0]].temp = bi[5] + bi = self.board_info_str + logger.debug("Subrack %s voltages: rcu=%s lba=%s hba=%s spu=%s temp: %s" % ( + bi[0], bi[1], bi[2], bi[3], bi[4], bi[5])) + + # turn on all hbas + logger.debug("check spu full load mode 3") + rsp_rcu_mode(3, self.db.lbh.select_list()) + answer = rspctl('--spustatus') + infolines = answer.splitlines() + for line in infolines: + if self.extract_board_info(line): + bi = self.board_info_val + fullload_3.append([bi[0], bi[1], bi[2], bi[3], bi[4]]) + bi = self.board_info_str + logger.debug("Subrack %s voltages: rcu=%s lba=%s hba=%s spu=%s temp: %s" % ( + bi[0], bi[1], bi[2], bi[3], bi[4], bi[5])) + + # turn on all hbas + logger.debug("check spu full load mode 5") + rsp_rcu_mode(5, self.db.hba.select_list()) + answer = rspctl('--spustatus') + infolines = answer.splitlines() + for line in infolines: + if self.extract_board_info(line): + bi = self.board_info_val + fullload_5.append([bi[0], bi[1], bi[2], bi[3], bi[4]]) + bi = self.board_info_str + logger.debug("Subrack %s voltages: rcu=%s lba=%s hba=%s spu=%s temp: %s" % ( + bi[0], bi[1], bi[2], bi[3], bi[4], bi[5])) + + #logger.debug("noload=%s" % str(noload)) + #logger.debug("fullload_3=%s" % str(fullload_3)) + #logger.debug("fullload_5=%s" % str(fullload_5)) + for sr in range(self.db.nr_spu): + #logger.debug("sr=%d" % sr) + # calculate mean of noload, fullload_3, fullload_5 + self.db.spu[sr].rcu_5_0_volt = (noload[sr][1] + fullload_3[sr][1] + fullload_5[sr][1]) / 3.0 + self.db.spu[sr].lba_8_0_volt = fullload_3[sr][2] + self.db.spu[sr].hba_48_volt = fullload_5[sr][3] + self.db.spu[sr].spu_3_3_volt = (noload[sr][4] + fullload_3[sr][4] + fullload_5[sr][4]) / 3.0 + + if self.db.spu[sr].temp > parset.as_float('temperature.max'): + self.db.spu[sr].temp_ok = 0 + + if (not (parset.as_float('voltage.5_0.min') <= noload[sr][1] <= parset.as_float('voltage.5_0.max')) or + not (parset.as_float('voltage.5_0.min') < fullload_3[sr][1] <= parset.as_float('voltage.5_0.max')) or + not (parset.as_float('voltage.5_0.min') <= fullload_5[sr][1] <= parset.as_float('voltage.5_0.max')) or + (noload[sr][1] - fullload_3[sr][1]) > parset.as_float('voltage.5_0.max-drop')): + self.db.spu[sr].rcu_ok = 0 + logger.info("SPU voltage 5.0V out of range") + + if (not (parset.as_float('voltage.8_0.min') <= noload[sr][2] <= parset.as_float('voltage.8_0.max')) or + not (parset.as_float('voltage.8_0.min') <= fullload_3[sr][2] <= parset.as_float('voltage.8_0.max')) or + (noload[sr][2] - fullload_3[sr][2]) > parset.as_float('voltage.8_0.max-drop')): + self.db.spu[sr].lba_ok = 0 + logger.info("SPU voltage 8.0V out of range") + + if (not (parset.as_float('voltage.48_0.min') <= noload[sr][3] <= parset.as_float('voltage.48_0.max')) or + not (parset.as_float('voltage.48_0.min') <= fullload_5[sr][3] <= parset.as_float('voltage.48_0.max')) or + (noload[sr][3] - fullload_5[sr][3]) > parset.as_float('voltage.48_0.max-drop')): + self.db.spu[sr].hba_ok = 0 + logger.info("SPU voltage 48V out of range") + + if (not (parset.as_float('voltage.3_3.min') <= noload[sr][4] <= parset.as_float('voltage.3_3.max')) or + not (parset.as_float('voltage.3_3.min') <= fullload_3[sr][4] <= parset.as_float('voltage.3_3.max')) or + not (parset.as_float('voltage.3_3.min') <= fullload_5[sr][4] <= parset.as_float('voltage.3_3.max')) or + (noload[sr][4] - fullload_5[sr][4]) > parset.as_float('voltage.3_3.max-drop')): + self.db.spu[sr].spu_ok = 0 + logger.info("SPU voltage 3.3V out of range") + + logger.info("=== Done SPU check ===") + self.db.add_test_done('SPU') + return diff --git a/LCU/checkhardware/checkhardware_lib/tbb.py b/LCU/checkhardware/checkhardware_lib/tbb.py new file mode 100644 index 00000000000..28fe500c1f5 --- /dev/null +++ b/LCU/checkhardware/checkhardware_lib/tbb.py @@ -0,0 +1,166 @@ +import logging +import numpy as np +from lofar import * + +logger = logging.getLogger('main.tbb') +logger.debug("starting tbb logger") + +# class for checking TBB boards using tbbctl +class TBB(object): + def __init__(self, db): + self.db = db + self.nr = self.db.nr_tbb + self.driverstate = True + # tbbctl('--free') + + # check software versions of driver, tbbctl and TP/MP firmware + def check_versions(self, parset): + logger.info("=== TBB Version check ===") + answer = tbbctl('--version') + + # check if Driver is available + if answer.find('TBBDriver is NOT responding') > 0: + logger.warning("No TBBDriver") + self.driverstate = False + else: + infolines = answer.splitlines() + info = infolines[4:6] + infolines[9:-1] + + # check if image_nr > 0 for all boards + if str(info).count('V') != (self.nr * 4): + logger.warning("WARNING, Not all boards in working image") + + for tbb in self.db.tbb: + board_info = info[2 + tbb.nr].strip().split(' ') + # print board_info + if board_info[3].split()[1] != parset.as_string('version.tp'): + logger.warning("Board %d Not right TP version" % tbb.nr) + tbb.tp_version = board_info[3].split()[1] + + if board_info[4].split()[1] != parset.as_string('version.mp'): + logger.warning("Board %d Not right MP version" % tbb.nr) + tbb.mp_version = board_info[4].split()[1] + logger.info("=== Done TBB Version check ===") + self.db.add_test_done('TV') + return + + # Check memory address and data lines + def check_memory(self): + logger.info("=== TBB Memory check ===") + tbbctl('--free') + for tbb in self.db.tbb: + if not tbb.board_active: + logger.info("Board %d ot active" % tbb.nr) + tbb.memory_ok = 0 + continue + answer = tbbctl('--testddr=%d' % tbb.nr) + info = answer.splitlines()[-3:] + memory_ok = True + if info[0].strip() != 'All Addresslines OK': + logger.info("Board %d Addresline error" % tbb.nr) + memory_ok = False + + if info[1].strip() != 'All Datalines OK': + logger.info("Board %d Datalines error" % tbb.nr) + memory_ok = False + + if not memory_ok: + tbb.memory_ok = 0 + logger.info(answer) + # turn on recording again + tbbctl('--alloc') + tbbctl('--record') + rspctl('--tbbmode=transient') + logger.info("=== Done TBB Memory check ===") + self.db.add_test_done('TM') + return + + def check_board(self, parset): + board_ok = True + logger.info("=== TBB Board check ===") + if not check_active_tbbdriver(): + logger.warning("TBBDriver down, skip test") + return False + answer = tbbctl('--status') + + mp_temp = np.zeros((self.db.nr_tbb, 4), float) + mp_temp[:,:] = -1 + + for line in answer.splitlines(): + if 'ETH' in line or 'clock' in line: + info = line.split() + else: + continue + #print info + try: + tbb_nr = int(info[0].strip()) + if not self.db.tbb[tbb_nr].board_active: + logger.info("Board %d ot active" % tbb_nr) + continue + if tbb_nr in range(12): + tbb = self.db.tbb[tbb_nr] + tbb.voltage1_2 = float(info[3][:-1]) + tbb.voltage2_5 = float(info[4][:-1]) + tbb.voltage3_3 = float(info[5][:-1]) + tbb.pcb_temp = float(info[6][:-2]) + tbb.tp_temp = float(info[7][:-2]) + tbb.mp0_temp = float(info[8][:-2]) + tbb.mp1_temp = float(info[10][:-2]) + tbb.mp2_temp = float(info[12][:-2]) + tbb.mp3_temp = float(info[14][:-2]) + mp_temp[tbb_nr, 0] = tbb.mp0_temp + mp_temp[tbb_nr, 1] = tbb.mp1_temp + mp_temp[tbb_nr, 2] = tbb.mp2_temp + mp_temp[tbb_nr, 3] = tbb.mp3_temp + + except ValueError: + logger.warning("value error in parse stage: %s" % line) + except IndexError: + logger.warning("index error in parse stage: %s" % line) + except: + raise + + mp_temp = np.ma.masked_less(mp_temp, 1.0) + mp_check_temp = np.ma.median(mp_temp[:,:]) + parset.as_float('temperature.mp.max_delta') + + for tbb in self.db.tbb: + logger.debug("TBB board %2d, voltages: 1.2V=%4.2f, 2.5V=%4.2f, 3.3V=%4.2f" % ( + tbb.nr, tbb.voltage1_2, tbb.voltage2_5, tbb.voltage3_3)) + + if not (parset.as_float('voltage.1_2.min') <= tbb.voltage1_2 <= parset.as_float('voltage.1_2.max')): + tbb.voltage_ok = 0 + logger.info("TBB board %2d, bad voltage 1.2V=%4.2fV" % (tbb.nr, tbb.voltage1_2)) + if not (parset.as_float('voltage.2_5.min') <= tbb.voltage2_5 <= parset.as_float('voltage.2_5.max')): + tbb.voltage_ok = 0 + logger.info("TBB board %2d, bad voltage 2.5V=%4.2fV" % (tbb.nr, tbb.voltage2_5)) + if not (parset.as_float('voltage.3_3.min') <= tbb.voltage3_3 <= parset.as_float('voltage.3_3.max')): + tbb.voltage_ok = 0 + logger.info("TBB board %2d bad voltage 3.3V=%4.2fV" % (tbb.nr, tbb.voltage3_3)) + + for tbb in self.db.tbb: + logger.debug( + "TBB board %2d, temperatures: pcb=%3.0f, tp=%3.0f, mp0=%3.0f, mp1=%3.0f, mp2=%3.0f, mp3=%3.0f" % ( + tbb.nr, tbb.pcb_temp, tbb.tp_temp, tbb.mp0_temp, tbb.mp1_temp, tbb.mp2_temp, tbb.mp3_temp)) + if tbb.pcb_temp > parset.as_float('temperature.max'): + tbb.temp_ok = 0 + logger.info("TBB board %2d, high temperature pcb_temp=%3.0f" % (tbb.nr, tbb.pcb_temp)) + if tbb.tp_temp > parset.as_float('temperature.tp.max'): + tbb.temp_ok = 0 + logger.info("TBB board %2d, high temperature tp_temp=%3.0f" % (tbb.nr, tbb.tp_temp)) + if tbb.mp0_temp > parset.as_float('temperature.mp.max') or tbb.mp0_temp > mp_check_temp: + tbb.temp_ok = 0 + logger.info("TBB board %2d, high temperature mp0_temp=%3.0f" % (tbb.nr, tbb.mp0_temp)) + if tbb.mp1_temp > parset.as_float('temperature.mp.max') or tbb.mp1_temp > mp_check_temp: + tbb.temp_ok = 0 + logger.info("TBB board %2d, high temperature mp1_temp=%3.0f" % (tbb.nr, tbb.mp1_temp)) + if tbb.mp2_temp > parset.as_float('temperature.mp.max') or tbb.mp2_temp > mp_check_temp: + tbb.temp_ok = 0 + logger.info("TBB board %2d, high temperature mp2_temp=%3.0f" % (tbb.nr, tbb.mp2_temp)) + if tbb.mp3_temp > parset.as_float('temperature.mp.max') or tbb.mp3_temp > mp_check_temp: + tbb.temp_ok = 0 + logger.info("TBB board %2d, high temperature mp3_temp=%3.0f" % (tbb.nr, tbb.mp3_temp)) + logger.debug("mp check temperature= %3.1f" % mp_check_temp) + logger.info("=== Done TBB Board check ===") + self.db.add_test_done('TBC') + return board_ok + # end of cTBB class diff --git a/LCU/checkhardware/checkhardware_lib/test_db.py b/LCU/checkhardware/checkhardware_lib/test_db.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/LCU/checkhardware/doStationTest.sh b/LCU/checkhardware/do_station_test.sh similarity index 61% rename from LCU/checkhardware/doStationTest.sh rename to LCU/checkhardware/do_station_test.sh index 5e58022ea93..e71eac95099 100755 --- a/LCU/checkhardware/doStationTest.sh +++ b/LCU/checkhardware/do_station_test.sh @@ -1,5 +1,5 @@ #!/bin/bash - + # default values START="" @@ -27,7 +27,7 @@ done if [ $HELP == "yes" ] then echo "Usage:" - echo " doStationTest.sh -s 20130624_04:00:00 -e 20130624_06:00:00 -u" + echo " do_station_test.sh -s 20130624_04:00:00 -e 20130624_06:00:00 -u" echo " -s : start time" echo " -e : end time" echo " -u : update pvss" @@ -38,18 +38,21 @@ then exit fi -host=`hostname -s` +hostname=`hostname -s` +host=`echo "$hostname" | awk '{ print toupper($1) }'` + +cd /opt/stationtest/ # set filenames and dirs -locallogdir="/opt/stationtest/data/" -globallogdir="/globalhome/log/stationtest/" +local_data_dir="/opt/stationtest/data/" +global_data_dir="/globalhome/log/stationtest/" if [ $LEVEL -ne 0 ] then SERVICE="no" fi -filenameNow=$host"_StationTest.csv" +filenameNow=$host"_station_test.csv" if [ $SERVICE == "yes" ] then LEVEL=2 @@ -57,7 +60,7 @@ then filenameLocalHistory=$host"_S_StationTestHistory.csv" else filenameLocal=$host"_L"$LEVEL"_StationTest.csv" - filenameLocalHistory=$host"_L"$LEVEL"_StationTestHistory.csv" + filenameLocalHistory=$host"_L"$LEVEL"_stationTestHistory.csv" fi filenameBadRCUs=$host"_bad_rcus.txt" @@ -81,9 +84,9 @@ fi # Check hardware if [ $SERVICE == "yes" ] then - checkHardware.py $level $start $stop -ls=info + ./check_hardware.py $level $start $stop -ls=info else - checkHardware.py $level $start $stop + ./check_hardware.py $level $start $stop fi err=$? @@ -95,25 +98,25 @@ then #new settings by Wilfred, 9-7-2013 if [ $UPDATE == "yes" ] then - updatePVSS.py -N=5,50,3 -J=5,50,3 -E -S=10 -LBLN=5,50,3 -LBLJ=5,50,3 -LBLS=10 -LBHN=5,50,3 -LBHJ=5,50,3 -LBHS=10 + ./update_pvss.py -N=5,50,3 -J=5,50,3 -E -S=10 -LBLN=5,50,3 -LBLJ=5,50,3 -LBLS=10 -LBHN=5,50,3 -LBHJ=5,50,3 -LBHS=10 else - updatePVSS.py -no_update -N=5,50,3 -J=5,50,3 -E -S=10 -LBLN=5,50,3 -LBLJ=5,50,3 -LBLS=10 -LBHN=5,50,3 -LBHJ=5,50,3 -LBHS=10 + ./update_pvss.py -no_update -N=5,50,3 -J=5,50,3 -E -S=10 -LBLN=5,50,3 -LBLJ=5,50,3 -LBLS=10 -LBHN=5,50,3 -LBHJ=5,50,3 -LBHS=10 fi - + # Copy to local filename file in local dir - cp $locallogdir$filenameNow $locallogdir$filenameLocal - + cp $local_data_dir$filenameNow $local_data_dir$filenameLocal + # Add to history - cat $locallogdir$filenameNow >> $locallogdir$filenameLocalHistory + cat $local_data_dir$filenameNow >> $local_data_dir$filenameLocalHistory # Copy from local to global dir - cp $locallogdir$filenameLocal $globallogdir - cp $locallogdir$filenameLocalHistory $globallogdir - cp $locallogdir$filenameBadRCUs $globallogdir + cp $local_data_dir$filenameLocal $global_data_dir + cp $local_data_dir$filenameLocalHistory $global_data_dir + cp $local_data_dir$filenameBadRCUs $global_data_dir fi if [ $SERVICE == "yes" ] then # Show last results on screen - showTestResult.py -f=$locallogdir$filenameNow + show_test_result.py -f=$local_data_dir$filenameNow fi diff --git a/LCU/checkhardware/lib/data_lib.py b/LCU/checkhardware/lib/data_lib.py deleted file mode 100644 index 9a802045a63..00000000000 --- a/LCU/checkhardware/lib/data_lib.py +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/python -# data lib - -from general_lib import * -from lofar_lib import * -from search_lib import * -import os -import numpy as np -import logging -from time import sleep - -test_version = '0815' - -logger = None -def init_data_lib(): - global logger - logger = logging.getLogger() - logger.debug("init logger data_lib") - -# get and return recorded data in various ways -class cRCUdata: - global logger - def __init__(self, n_rcus, minvalue=1): - self.n_rcus = n_rcus - self.minvalue = minvalue - self.active_rcus = range(self.n_rcus) - self.subband_mask = [] # subband_mask selects subbands to block - self.active_rcus_changed = False - self.reset() - self.reset_masks() - - def reset(self): - self.ssData = np.ones((self.n_rcus, 1, 512), np.float64) - self.testSignal_X = -1.0 - self.testSubband_X = 0 - self.testSignal_Y = -1.0 - self.testSubband_Y = 0 - self.frames = 0 - self.clock = 200.0 - self.rec_time = 0 - - def reset_masks(self): - logger.debug('reset rcu mask') - self.rcu_mask = [] # rcu_mask selects rcus to block - - def getRecTime(self): - return self.rec_time - - def setActiveRcus(self, rcus): - self.active_rcus = rcus - - def setInActiveRcu(self, rcu): - logger.debug('delete rcu %d from active-rcu list' % rcu) - if rcu in self.active_rcus: - self.active_rcus.remove(rcu) - self.add_to_rcu_mask(rcu) - self.active_rcus_changed = True - - def getActiveRcus(self, pol='XY'): - rcus = [] - for i in self.active_rcus: - if pol in ('X', 'x') and i % 2 == 0: - rcus.append(i) - if pol in ('Y', 'y') and i % 2 == 1: - rcus.append(i) - if pol in ('XY', 'xy'): - rcus.append(i) - return (rcus) - - def isActiveRcusChanged(self): - return self.active_rcus_changed - - def resetActiveRcusChanged(self): - self.active_rcus_changed = False - - def add_to_rcu_mask(self, rcu): - if rcu not in self.rcu_mask: - self.rcu_mask.append(rcu) - logger.debug('bad-rcu-mask=%s' % str(sorted(self.rcu_mask))) - - def setMask(self, blocked_subbands): - self.subband_mask = blocked_subbands - - def getMask(self): - return (self.subband_mask) - - def record(self, rec_time=2, read=True, slow=False): - removeAllDataFiles() - self.reset() - self.rec_time = rec_time - - if slow == True: - x_list = [] - y_list = [] - for i in sorted(self.active_rcus): - if i % 2 == 0: - x_list.append(i) - else: - y_list.append(i) - rcus = selectStr(x_list) - logger.debug("Wait %d seconds while recording X data" %(rec_time)) - rspctl('--statistics --duration=%d --integration=1 --directory=%s --select=%s' %(rec_time, dataDir(), rcus), wait=0.0) - - rcus = selectStr(y_list) - logger.debug("Wait %d seconds while recording Y data" %(rec_time)) - rspctl('--statistics --duration=%d --integration=1 --directory=%s --select=%s' %(rec_time, dataDir(), rcus), wait=0.0) - else: - rcus = selectStr(self.active_rcus) - logger.debug("Wait %d seconds while recording XY data" %(rec_time)) - rspctl('--statistics --duration=%d --integration=1 --directory=%s --select=%s' %(rec_time, dataDir(), rcus), wait=0.0) - - if read == True: - self.readFiles() - - def readFile(self, full_filename): - sleep(0.02) - data = np.fromfile(full_filename, dtype=np.float64) - n_samples = len(data) - if (n_samples % 512) > 0: - logger.warn("data error: number of samples (%d) not multiple of 512 in '%f'" %(n_samples, full_filename)) - self.frames = n_samples / 512 - data = data.reshape(self.frames,512) - #logger.info("recorded data shape %s" %(str(data.shape))) - return (data) - - def readFiles(self): - files_in_dir = sorted(os.listdir(dataDir())) - if len(files_in_dir) == 0: - logger.warn('No data recorded !!') - self.reset() - return - data_shape = self.readFile(os.path.join(dataDir(),files_in_dir[0])).shape - ssdata = np.zeros((self.n_rcus, data_shape[0],data_shape[1]), dtype=np.float64) - for file_name in files_in_dir: - #path, filename = os.split(file_name) - rcu = int(file_name.split('.')[0][-3:]) - ssdata[rcu,:,:] = self.readFile(os.path.join(dataDir(),file_name)) - #logger.debug("%s rcu=%d" %(file_name, rcu)) - # mask zero values and convert to dBm - self.ssData = np.log10(np.ma.masked_less(ssdata, self.minvalue)) * 10.0 - # do not use subband 0 - self.ssData[:,:,0] = np.ma.masked - - # subbands is list to mask - def getMaskedData(self): - data = self.ssData.copy() - for sb in self.subband_mask: - data[:,:,sb] = np.ma.masked - for rcu in range(self.n_rcus): - if rcu in self.rcu_mask: - data[rcu,:,:] = np.ma.masked - return (data) - - def getMeanSpectra(self, pol='XY'): - if pol in ('XY', 'xy'): - return (self.ssData[:,:,:].mean(axis=1).mean(axis=0)) - if pol in (0, 'X', 'x'): - return (self.ssData[0::2,:,:].mean(axis=1).mean(axis=0)) - if pol in (1, 'Y', 'y'): - return (self.ssData[1::2,:,:].mean(axis=1).mean(axis=0)) - - def getMedianSpectra(self, pol='XY'): - if pol in ('XY', 'xy'): - return (np.median(np.mean(self.ssData, axis=1), axis=0)) - if pol in (0, 'X', 'x'): - return (np.median(np.mean(self.ssData[0::2,:,:], axis=1), axis=0)) - if pol in (1, 'Y', 'y'): - return (np.median(np.mean(self.ssData[1::2,:,:], axis=1), axis=0)) - - def getSpectra(self, rcu): - return(np.mean(self.ssData[rcu,:,:], axis=0)) - - def getSubbands(self, rcu): - return (self.getMaskedData()[int(rcu),:,:].mean(axis=0)) - - def getSubbandX(self, subband=None): - if subband is None: - return (self.getMaskedData()[0::2,:,self.testSubband_Y].mean(axis=1)) - return (self.getMaskedData()[0::2,:,subband].mean(axis=1)) - - def getSubbandY(self, subband=None): - if subband is None: - return (self.getMaskedData()[1::2,:,self.testSubband_Y].mean(axis=1)) - return (self.getMaskedData()[1::2,:,subband].mean(axis=1)) - - def getAll(self, pol='XY'): - if pol in ('XY', 'xy'): - return (self.getMaskedData()) - if pol in ('x','X',0): - return (self.getMaskedData()[0::2,:,:]) - if pol in ('y','Y',1): - return (self.getMaskedData()[1::2,:,:]) - - def getAllX(self): - return (self.getMaskedData()[0::2,:,:]) - - def getAllY(self): - return (self.getMaskedData()[1::2,:,:]) - - def getMedianRcu(self, rcu): - return(np.ma.median(self.getMaskedData()[int(rcu),:,:].mean(axis=0))) - - def searchTestSignal(self, subband=-1, minsignal=75.0, maxsignal=100.0): - # ss = median for all band over all rcu's - # forget subband 0 - ssX = np.ma.median(self.getMaskedData()[::2,:,:].mean(axis=1),axis=0) - ssY = np.ma.median(self.getMaskedData()[1::2,:,:].mean(axis=1),axis=0) - - if subband != -1: - if ssX[subband] > minsignal and ssY[subband] > minsignal: - self.testSignal_X = ssX[subband] - self.testSubband_X = subband - self.testSignal_Y = ssY[subband] - self.testSubband_Y = subband - return - else: - logger.debug("Test signal on subband %d not strong enough X=%3.1fdB Y=%3.1fdB" %(subband, ssX[subband], ssY[subband])) - - # no subband given or not in requested range, look for better - for i in range(0,ssX.shape[0],1): - if ssX[i] > minsignal and ssX[i] < maxsignal and ssX[i] > self.testSignal_X: - self.testSignal_X = ssX[i] - self.testSubband_X = i - if ssY[i] > minsignal and ssY[i] < maxsignal and ssY[i] > self.testSignal_Y: - self.testSignal_Y = ssY[i] - self.testSubband_Y = i - return -#### end of cRCUdata class #### diff --git a/LCU/checkhardware/lib/general_lib.py b/LCU/checkhardware/lib/general_lib.py deleted file mode 100644 index 36d5de786fa..00000000000 --- a/LCU/checkhardware/lib/general_lib.py +++ /dev/null @@ -1,126 +0,0 @@ -r""" -general script -""" - -from subprocess import (Popen, PIPE) -import traceback -import time -import os -import sys -import logging - -general_version = '0913' -logger = logging.getLogger() - -def writeMessage(msg): - sendCmd('wall', msg) - return - -# Return date string in the following format YYYYMMDD -def getShortDateStr(tm=time.gmtime()): - return (time.strftime("%Y%m%d", tm)) - -# Return time string in the following format HH:MM:SS -def getDateStr(tm=time.gmtime()): - return (time.strftime("%d-%m-%Y", tm)) - -# Return time string in the following format HH:MM:SS -def getTimeStr(tm=time.gmtime()): - return (time.strftime("%H:%M:%S", tm)) -# Return time string in the following format HH:MM:SS -def getDateTimeStr(tm=time.gmtime()): - return (time.strftime("%d-%m-%YT%H:%M:%S", tm)) - -# Run cmd with args and return response -def sendCmd(cmd='', args=''): - if cmd != '': - try: - args = args.replace(' =','=').replace('= ','=') - cmdList = [cmd] + args.split() - #print cmdList - cmdline = Popen(cmdList, stdout=PIPE, stderr=PIPE ) - (so, se) = cmdline.communicate() - if len(so) != 0: - return (so) - else: - return ('Error, %s' % se) - except: - logger.error('Caught %s', str(sys.exc_info()[0])) - logger.error(str(sys.exc_info()[1])) - logger.error('TRACEBACK:\n%s', traceback.format_exc()) - return ('Exception Error') - - return ('') - -# Get Host name -def getHostName(): - try: - host = sendCmd('hostname', '-s') - if host == 'Exception Error': - host = 'Unknown' - except: - host = 'Unknown' - return (host.strip()) - -# file logger -class cLogger: - def __init__(self, logdir, filename, screenPrefix=''): - self.fullFilename = os.path.join(logdir,filename) - self.logfile = open(self.fullFilename, 'w') - self.prefix = screenPrefix - self.starttime = time.time() - - def __del__(self): - self.logfile.close() - - def getFullFileName(self): - return (self.fullFilename) - - def resetStartTime(self, screen=False): - self.starttime = time.time() - self.info("Start time %s" %(time.strftime("%H:%M:%S", time.gmtime(self.starttime))), screen=screen) - - def printBusyTime(self, screen=False): - self.info("Time from start %s" %(time.strftime("%H:%M:%S", (time.gmtime(time.time() - self.starttime)))), screen=screen) - - def printTimeNow(self, screen=False): - self.info("Time %s" %(time.strftime("%H:%M:%S", time.gmtime(time.time()))), screen=screen) - - def info(self, msg, noEnd=False, screen=False): - if len(msg) != 0: - if screen: - print self.prefix+' '+msg - if noEnd == False: - msg += '\n' - self.logfile.write(msg) - self.logfile.flush() - -class cTestLogger(cLogger): - def __init__(self, logdir): - filename = getHostName() + "_StationTest" + '.csv' - cLogger.__init__(self, logdir, filename) - - def addLine(self, info): - cLogger.info(self, info) - -class cStationLogger(cLogger): - def __init__(self, logdir, filetime=time.gmtime()): - filename = "stationtest_" + getHostName() + '.log' - cLogger.__init__(self, logdir, filename) - cLogger.info(self, "StID >: %s" %(getHostName())) - cLogger.info(self, "Lgfl >: %s" %(os.path.join(logdir,filename))) - testdate = time.strftime("%a, %d %b %Y %H:%M:%S", filetime) - cLogger.info(self, "Time >: %s" %(testdate)) - - def addLine(self, info): - cLogger.info(self, info) - - -class cPVSSLogger(cLogger): - def __init__(self, logdir): - filename = getHostName() + "_StationTest_PVSS" + '.log' - cLogger.__init__(self, logdir, filename) - #cLogger.info(self, "# PVSS input file") - - def addLine(self, info): - cLogger.info(self, info) diff --git a/LCU/checkhardware/lib/lofar_lib.py b/LCU/checkhardware/lib/lofar_lib.py deleted file mode 100644 index 28f503c8cc9..00000000000 --- a/LCU/checkhardware/lib/lofar_lib.py +++ /dev/null @@ -1,510 +0,0 @@ -# lofar_lib - -import os -import sys -import time -import logging -import socket -import struct -import string -from general_lib import sendCmd - -os.umask(001) -lofar_version = '0514' - -CoreStations = ('CS001C','CS002C','CS003C','CS004C','CS005C','CS006C','CS007C','CS011C',\ - 'CS013C','CS017C','CS021C','CS024C','CS026C','CS028C','CS030C','CS031C',\ - 'CS032C','CS101C','CS103C','CS201C','CS301C','CS302C','CS401C','CS501C') - -RemoteStations = ('RS106C','RS205C','RS208C','RS210C','RS305C','RS306C','RS307C','RS310C',\ - 'RS406C','RS407C','RS409C','RS503C','RS508C','RS509C') - -InternationalStations = ('DE601C','DE602C','DE603C','DE604C','DE605C','FR606C','SE607C','UK608C') - - -StationType = dict( CS=1, RS=2, IS=3 ) - -logger = None -rcumode = -1 -active_delay_str = ('555,'*16)[:-1] - - -def init_lofar_lib(): - global logger - logger = logging.getLogger() - logger.debug("init logger lofar_lib") - if os.access(dataDir(), os.F_OK): - removeAllDataFiles() - else: - os.mkdir(dataDir()) - - -def dataDir(): - return (r'/localhome/stationtest/sb_data') - -# remove all *.dat -def removeAllDataFiles(): - if os.access(dataDir(), os.F_OK): - files = os.listdir(dataDir()) - #print files - for f in files: - if f[-3:] == 'dat' or f[-3:] == 'nfo': - os.remove(os.path.join(dataDir(),f)) - - -# return station type -def getStationType(StID): - if StID in CoreStations: - return (StationType[CS]) - if StID in RemoteStations: - return (StationType[RS]) - if StID in InternationalStations: - return (StationType[IS]) - - -# read from RemoteStation.conf file number of RSP and TB Boards -""" -# -# THIS FILE IS GENERATED, DO NOT MODIFY IT. -# -# RemoteStation.conf for CS002 -# -# Describes the amount of available hardware on the station. -# - -RS.STATION_ID = 2 -RS.N_RSPBOARDS = 12 -RS.N_TBBOARDS = 6 -RS.N_LBAS = 96 -RS.N_HBAS = 48 -RS.HBA_SPLIT = Yes -RS.WIDE_LBAS = Yes -""" -def readStationConfig(): - f = open('/opt/lofar/etc/RemoteStation.conf', 'r') - lines = f.readlines() - f.close() - - ID = nRSP = nTBB = nLBA = nLBL = nLBH = nHBA = HBA_SPLIT = 0 - - for line in lines: - if (line[0] == '#') or (len(line) < 2): - continue - key, val = line.split('=') - key = key.strip() - val = val.strip() - if key == "RS.STATION_ID": - ID = int(val) - continue - if key == "RS.N_RSPBOARDS": - nRSP = int(val) - continue - if key == "RS.N_TBBOARDS": - nTBB = int(val) - continue - if key == "RS.N_LBAS": - nLBA = int(val) - if nLBA == nRSP * 8: - nLBL = nLBA / 2 - nLBH = nLBA / 2 - else: - nLBL = 0 - nLBH = nLBA - continue - if key == "RS.N_HBAS": - nHBA = int(val) - continue - if key == "RS.HBA_SPLIT": - if string.upper(val) == "YES": - HBA_SPLIT = 1 - continue - return(ID, nRSP, nTBB, nLBL, nLBH, nHBA, HBA_SPLIT) - - -# [lofarsys@RS306C stationtest]$ swlevel 2 -# Going to level 2 -# Starting RSPDriver -# Loading image 4 on RSPboard 0 ... -# Loading image 4 on RSPboard 1 ... -# Loading image 4 on RSPboard 2 ... -# Loading image 4 on RSPboard 3 ... -# Loading image 4 on RSPboard 4 ... -# Loading image 4 on RSPboard 5 ... -# Loading image 4 on RSPboard 6 ... -# Loading image 4 on RSPboard 7 ... -# RSPboard 8: Error requesting active firmware version (communication error) -# Loading image 4 on RSPboard 9 ... -# Loading image 4 on RSPboard 10 ... -# Loading image 4 on RSPboard 11 ... -# One or more boards have a communication problem; try reset the 48V -# root 21470 1 1 10:41 pts/2 00:00:00 /opt/lofar/bin/RSPDriver -# Starting TBBDriver -# root 21492 1 0 10:41 pts/2 00:00:00 /opt/lofar/bin/TBBDriver -# -# Status of all software level: -# 1 : PVSS00pmon 16177 -# 1 : SoftwareMonitor 16227 -# 1 : LogProcessor 16248 -# 1 : ServiceBroker 16278 -# 1 : SASGateway 16299 -# --- -# 2 : RSPDriver 21470 -# 2 : TBBDriver 21492 -# --- -# 3 : CalServer DOWN -# 3 : BeamServer DOWN -# --- -# 4 : HardwareMonitor DOWN -# --- -# 5 : SHMInfoServer DOWN -# --- -# 6 : CTStartDaemon DOWN -# 6 : StationControl DOWN -# 6 : ClockControl DOWN -# 6 : CalibrationControl DOWN -# 6 : BeamControl DOWN -# 6 : TBBControl DOWN -# --- - -def swlevel(level=None): - _level = level - board_errors = list() - if (level != None): - if _level < 0: - _level *= -1 - answer = sendCmd('swlevel', str(_level)) - else: - answer = sendCmd('swlevel') - - current_level = 0 - for line in answer.splitlines(): - if line.find("Going to level") > -1: - current_level = int(line.split()[-1]) - - elif line.find("Currently set level") > -1: - current_level = int(line.split()[-1]) - if current_level < 0: - logger.warn("Current swlevel is %d" %(current_level)) - if line.find("Error requesting active firmware version") > -1: - endpos = line.find(":") - board_errors.append(int(line[:endpos].split()[1])) - logger.warn(line) - return (current_level, board_errors) - -def reset48V(): - logger.info("Try to reset 48V power") - ec_name = socket.gethostname()[:-1]+"ec" - ec_ip = socket.gethostbyname(ec_name) - logger.debug("EC to connect = %s" %(ec_ip)) - - connected = False - - try: - sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - except socket.error: - sck.close() - return - - try: - sck.settimeout(4.0) - sck.connect((ec_ip, 10000)) - connected = True - time.sleep(0.5) - cmd = struct.pack('hhh', 22, 0, 0) - logger.debug("send cmd") - sck.send(cmd) - sck.settimeout(4.0) - logger.debug("recv cmd") - data = sck.recv(6) - sck.close() - logger.debug("reset done") - except socket.error: - print "ec socket connect error" - sck.close() - - -# Run rspctl command with given args and return response -def rspctl(args='', wait=0.0): - if args != '': - logger.debug("rspctl %s" %(args)) - response = sendCmd('rspctl', args) - if wait > 0.0: - time.sleep(wait) - return (response) - return ('No args given') - - -# Run tbbctl command with given args and return response -def tbbctl(args=''): - if args != '': - logger.debug("tbbctl %s" %(args)) - return (sendCmd('tbbctl', args)) - return ('No args given') - -def checkActiveTBBDriver(): - answer = sendCmd('swlevel').strip().splitlines() - for line in answer: - if line.find('TBBDriver') > -1: - if line.find('DOWN') != -1: - return (False) - return (True) - -# wait until all boards have a working image loaded -# returns 1 if ready or 0 if timed_out -def waitTBBready(n_boards=6): - timeout = 90 - logger.info("wait for working TBB boards ") - sys.stdout.flush() - while timeout > 0: - answer = tbbctl('--version') - #print answer - if answer.find('TBBDriver is NOT responding') > 0: - if timeout < 10: - logger.info("TBBDriver is NOT responding, try again in every 5 seconds") - time.sleep(5.0) - timeout -= 5 - if timeout < 60: - return (0) - continue - # check if image_nr > 0 for all boards - if answer.count('V') == (n_boards * 4): - logger.info("All boards in working image") - return (1) - time.sleep(1.0) - timeout -= 1 - logger.warn("Not all TB boards in working image") - return (0) - -def checkActiveRSPDriver(): - answer = sendCmd('swlevel').strip().splitlines() - for line in answer: - if line.find('RSPDriver') > -1: - if line.find('DOWN') != -1: - return (False) - return (True) - -# wait until all boards have a working image loaded -# returns 1 if ready or 0 if timed_out -# -# [lofarsys@RS306C ~]$ rspctl --version -# RSP[ 0] RSP version = 0, BP version = 0.0, AP version = 0.0 -# RSP[ 1] RSP version = 0, BP version = 0.0, AP version = 0.0 -# RSP[ 2] RSP version = 0, BP version = 0.0, AP version = 0.0 -# RSP[ 3] RSP version = 0, BP version = 0.0, AP version = 0.0 -# RSP[ 4] RSP version = 0, BP version = 0.0, AP version = 0.0 -# RSP[ 5] RSP version = 0, BP version = 0.0, AP version = 0.0 -# RSP[ 6] RSP version = 0, BP version = 0.0, AP version = 0.0 -# RSP[ 7] RSP version = 0, BP version = 0.0, AP version = 0.0 -# RSP[ 8] RSP version = 0, BP version = 0.0, AP version = 0.0 -# RSP[ 9] RSP version = 0, BP version = 0.0, AP version = 0.0 -# RSP[10] RSP version = 0, BP version = 0.0, AP version = 0.0 -# RSP[11] RSP version = 0, BP version = 0.0, AP version = 0.0 - -def waitRSPready(): - timeout = 60 - logger.info("wait for working RSP boards ") - sys.stdout.flush() - while timeout > 0: - answer = rspctl('--version') - #print answer - if answer.count('No Response') > 0: - time.sleep(5.0) - timeout -= 5 - if timeout < 60: - return (0) - continue - # check if image_nr > 0 for all boards - if answer.count('0.0') == 0: - logger.info("All boards in working image") - return (1) - else: - logger.warn("Not all RSP boards in working image") - logger.debug(answer) - time.sleep(5.0) - timeout -= 1 - return (0) - -# convert select-list to select-string -def selectStr(sel_list): - last_sel = -2 - set = False - select = "" - for sel in sorted(sel_list): - if sel == last_sel+1: - set = True - else: - if set: - set = False - select += ':%d' %(last_sel) - select += ",%d" %(sel) - last_sel = sel - if set: - select += ':%d' %(last_sel) - return (select[1:]) - - # convert select-string to sel_list -def extractSelectStr(selectStr): - selectStr = selectStr.strip() + '.' - if selectStr.strip() == '.': - return([]) - sel_list = list() - num_str = '' - num = -1 - set_num = -1 - for ch in selectStr: - if ch.isalnum(): - num_str += ch - continue - num_str = num_str.strip() - num = int(num_str) - num_str = '' - - if set_num > -1: - while (set_num < num): - sel_list.append(set_num) - set_num += 1 - set_num = -1 - - if ch == ',': - sel_list.append(num) - - if ch == ':': - set_num = num - - if num > -1: - sel_list.append(num) - return (sorted(sel_list)) - -def getClock(): - answer = rspctl("--clock") - #print answer[-6:-3] - clock = float(answer[-7:-4]) - return (clock) - -# function used for antenna testing -def swap_xy(state): - if state in (0,1): - if state == 1: - logger.info("XY-output swapped") - else: - logger.info("XY-output normal") - rspctl('--swapxy=%d' %(state)) - -def resetRSPsettings(): - if rspctl ('--clock').find('200MHz') < 0: - rspctl ('--clock=200') - logger.info ("Changed Clock to 200MHz") - time.sleep (2.0) - rspctl('--wg=0', wait=0.0) - rspctl('--rcuprsg=0', wait=0.0) - rspctl('--datastream=0', wait=0.0) - rspctl('--splitter=0', wait=0.0) - rspctl('--specinv=0', wait=0.0) - rspctl('--bitmode=16', wait=0.0) - rspctl('--rcumode=0', wait=0.0) - rspctl('--rcuenable=0', wait=0.0) - #rspctl ('--hbadelays=%s' %(('128,'*16)[:-1]), wait=8.0) - -def turnonRCUs(mode, rcus): - #global rcumode - #start_mode = rcumode - select = selectStr(rcus) - logger.info("turn RCU's on, mode %d" %(mode)) - logger.info("enable rcus") - rspctl('--rcuenable=1 --select=%s' %(select), wait=0.0) - logger.info("setweights") - rspctl('--aweights=8000,0', wait=0.0) - - if mode == 5: - rspctl('--specinv=1', wait=0.0) - else: - rspctl('--specinv=0', wait=0.0) - - if mode < 3: - swap_xy(state=1) - else: - swap_xy(state=0) - logger.info("set rcu mode") - rsp_rcu_mode(mode, rcus) - #rcumode = mode - return - -def turnoffRCUs(): - global rcumode - logger.info("RCU's off, mode 0") - rspctl('--rcumode=0', wait=0.0) - rspctl('--rcuenable=0', wait=0.0) - rspctl('--aweights=0,0', wait=1.0) - rcumode = 0 - -# set rcu mode, if mode > 4(hba) turn on hba's in steps to avoid power dips -def rsp_rcu_mode(mode, rcus): - global rcumode - if mode > 0 and mode < 5: # lba modes - rcumode = mode - select = selectStr(rcus) - rspctl('--rcumode=%d --select=%s' %(mode, select), wait=6.0) - return (0) - elif mode < 8: # hba modes - rcumode = mode - # maximum 12 power RCUs each step - steps = int(round(len(rcus) / 24.)) - for step in range(0,(steps*2),2): - rculist = sorted(rcus[step::(steps*2)]+rcus[step+1::(steps*2)]) - select = string.join(list([str(rcu) for rcu in rculist]),',') - rspctl('--rcumode=%d --select=%s' %(mode, select), wait=2.0) - time.sleep(6.0) - return (0) - else: - return (-1) - -# set hba_delays in steps to avoid power dips, and discharge if needed -def rsp_hba_delay(delay, rcus, discharge=True): - global active_delay_str - - if delay == active_delay_str: - logger.debug("requested delay already active, skip hbadelay command") - return (1) - - if discharge == True: - # count number of elements off in last command - n_hba_off = 0 - for i in active_delay_str.split(','): - if int(i,10) & 0x02: - n_hba_off += 1 - - # count number of elements on in new command, and make discharge string - n_hba_on = 0 - if n_hba_off > 0: - discharge_str = '' - for i in delay.split(','): - if int(i,10) & 0x02: - discharge_str += "2," - else: - discharge_str += "0," - n_hba_on += 1 - - # discharge if needed - if n_hba_off > 0 and n_hba_on > 0: - logger.info("set hbadelays to 0 for 1 second") - if n_hba_on > 2: - steps = int(round(len(rcus) / 24.)) - logger.debug("send hbadelay command in %d steps" %(steps)) - for step in range(0,(steps*2),2): - rculist = sorted(rcus[step::(steps*2)]+rcus[step+1::(steps*2)]) - select = string.join(list([str(rcu) for rcu in rculist]),',') - rspctl('--hbadelay=%s --select=%s' %(discharge_str[:-1], select), wait=1.0) - rspctl('--hbadelay=%s --select=%s' %(discharge_str[:-1], select), wait=1.0) - time.sleep(3.0) - else: - rspctl('--hbadelay=%s' %(discharge_str[:-1]), wait=1.0) - rspctl('--hbadelay=%s' %(discharge_str[:-1]), wait=4.0) - - logger.debug("send hbadelay command") - rspctl('--hbadelay=%s' %(delay), wait=1.0) - rspctl('--hbadelay=%s' %(delay), wait=4.0) - - active_delay_str = delay - return (0) - - diff --git a/LCU/checkhardware/lib/search_lib.py b/LCU/checkhardware/lib/search_lib.py deleted file mode 100644 index 85d80dd2329..00000000000 --- a/LCU/checkhardware/lib/search_lib.py +++ /dev/null @@ -1,498 +0,0 @@ -#!/usr/bin/python - -""" -library with all search functions - -P.Donker ASTRON -""" -from numpy import ma, fft, power, arange, asarray, isscalar, NaN, Inf, zeros -from sys import exit -import logging -from time import sleep - -search_version = '0415' - -logger = logging.getLogger() - -""" -search for all peaks (min & max) in spectra -""" -class cSearchPeak: - def __init__(self, data): - self.valid_data = False - if len(data.shape) == 1: - self.valid_data = True - self.data = data.copy() - self.n_data = len(data) - self.max_peaks = [] - self.min_peaks = [] - return - - def search(self, delta, max_width=100, skip_list=[]): - self.max_peaks = [] - self.min_peaks = [] - - x = arange(0,len(self.data),1) - - if not isscalar(delta): - exit('argument delta must be a scalar') - - if delta <= 0: - exit('argument delta must be positive') - - maxval, minval = self.data[1], self.data[1] - maxpos, minpos = 1, 1 - - lookformax = True - - # add subband to skiplist - skiplist = [] - if len(skip_list) > 0: - for max_pos, min_sb, max_sb in skip_list: - for sb in range(min_sb,max_sb+1,1): - skiplist.append(sb) - - # skip subband 0 (always high signal) - for i in range(1,self.n_data,1): - #sleep(0.001) - if ma.count_masked(self.data) > 1 and self.data.mask[i] == True: - continue - - now = self.data[i] - if now > maxval: - maxval = now - maxpos = x[i] - - if now < minval: - minval = now - minpos = x[i] - - if lookformax: - if now < (maxval - delta): - if maxpos not in skiplist: - peakwidth, min_sb, max_sb = self.getPeakWidth(maxpos, delta) - #logger.debug("maxpos=%d, width=%d" %(maxpos, peakwidth)) - if (peakwidth < max_width): - self.max_peaks.append([maxpos, min_sb, max_sb]) - minval = now - minpos = x[i] - lookformax = False - else: - if now > (minval + delta): - if minpos not in skiplist: - self.min_peaks.append(minpos) - maxval = now - maxpos = x[i] - lookformax = True - - # if no peak found with the given delta, return maximum found - if len(self.max_peaks) == 0: - self.max_peaks.append([maxpos, maxpos, maxpos]) - return - - # return data[nr] - def getPeakValue(self, nr): - try: - return (self.data[nr]) - except: - return (NaN) - - def getPeakWidth(self, nr, delta): - peakval = self.data[nr] - minnr = nr - maxnr = nr - for sb in range(nr,0,-1): - if self.data[sb] < peakval: - minnr = sb - if self.data[sb] <= (peakval - delta): - break - for sb in range(nr,self.data.shape[0],1): - if self.data[sb] < peakval: - maxnr = sb - if self.data[sb] <= (peakval - delta): - break - - return (maxnr-minnr, minnr, maxnr) - - # return value and subband nr - def getMaxPeak(self): - maxval = 0.0 - minsb = 0.0 - maxsb = 0.0 - binnr = -1 - - for peak, min_sb, max_sb in self.max_peaks: - if self.data[peak] > maxval: - maxval = self.data[peak] - binnr = peak - minsb = min_sb - maxsb = max_sb - return (maxval, binnr, minsb, maxsb) - - def getSumPeaks(self): - peaksum = 0.0 - for peak, min_sb, max_sb in self.max_peaks: - peaksum += self.data[peak] - return (peaksum) - - # return value and sbband nr - def getMinPeak(self): - minval = Inf - nr_bin = -1 - - for peak in self.min_peaks: - if self.data[peak] < minval: - minval = self.data[peak] - nr_bin = peak - return (minval, nr_bin) - - def nMaxPeaks(self): - return (len(self.max_peaks)) - -# return psd off data and freq bins -def PSD(data, sampletime): - if data.ndim != 1: - return ([],[]) - fft_data = fft.fft(data) - n = fft_data.size - psd_freq = fft.fftfreq(n, sampletime) - psd = power(abs(fft_data),2)/n - return (psd[:n/2], psd_freq[:n/2]) - -def searchFlat(data): - _data = data.getAll().mean(axis=1) - flat_info = list() - for rcu in data.active_rcus: - mean_signal = ma.mean(_data[rcu,:]) - if mean_signal > 61.0 and mean_signal < 64.5: - logger.info("rcu=%d: cable probable off" %(rcu)) - flat_info.append((rcu, mean_signal)) - return(flat_info) - -def searchShort(data): - _data = data.getAll().mean(axis=1) - short_info = list() - for rcu in data.active_rcus: - mean_signal = ma.mean(_data[rcu,:]) - if mean_signal > 55.0 and mean_signal < 61.0: - logger.info("rcu=%d: cable shorted" %(rcu)) - short_info.append((rcu, mean_signal)) - return(short_info) - -def searchDown(data, subband): - _data = data.getAll().mean(axis=1) - down_info = list() - shifted_info = list() - start_sb = subband - 70 - stop_sb = subband + 70 - delta = 3 - - peaks = cSearchPeak(ma.median(_data[:,start_sb:stop_sb], axis=0)) - if not peaks.valid_data: - return (down_info, shifted_info) - - peaks.search(delta=delta) - (median_max_val, median_max_sb, min_sb, max_sb) = peaks.getMaxPeak() - median_bandwidth = max_sb - min_sb - #peakwidth, min_sb, max_sb = peaks.getPeakWidth(median_max_sb, delta) - median_max_sb += start_sb - - median_value = ma.median(_data[:,start_sb:stop_sb]) - logger.debug("reference peak in band %d .. %d : subband=%d, bw(3dB)=%d, median-value-band=%3.1fdB" %\ - (start_sb, stop_sb, (median_max_sb), median_bandwidth, median_value)) - - peaks_array = zeros((_data.shape[0]),'i') - peaks_bw_array = zeros((_data.shape[0]),'i') - - for rcu in data.active_rcus: - peaks = cSearchPeak(_data[rcu,start_sb:stop_sb]) - if peaks.valid_data: - peaks.search(delta=delta) - (maxpeak_val, maxpeak_sb, min_sb, max_sb) = peaks.getMaxPeak() - - if maxpeak_sb > 0: - #peakwidth, min_sb, max_sb = peaks.getPeakWidth(maxpeak_sb, delta) - peaks_bw_array[rcu] = max_sb - min_sb - peaks_array[rcu] = start_sb + maxpeak_sb - else: - peaks_bw_array[rcu] = stop_sb - start_sb - peaks_array[rcu] = subband - - for ant in range(_data.shape[0]/2): - x_rcu = ant * 2 - y_rcu = x_rcu + 1 - - mean_val_trigger = False - down_trigger = False - - ant_x_mean_value = ma.mean(_data[x_rcu,start_sb:stop_sb]) - ant_y_mean_value = ma.mean(_data[y_rcu,start_sb:stop_sb]) - - logger.debug("rcu=%d/%d: X-top, sb%d, bw=%d, mean-value-band=%3.1f Y-top, sb%d, bw=%d, mean-value-band=%3.1f" %\ - (x_rcu, y_rcu, peaks_array[x_rcu], peaks_bw_array[x_rcu], ant_x_mean_value, peaks_array[y_rcu], peaks_bw_array[y_rcu], ant_y_mean_value)) - - if (((ant_x_mean_value < (median_value - 3.0)) and (ant_x_mean_value > 66)) or - ((ant_y_mean_value < (median_value - 3.0)) and (ant_y_mean_value > 66))): - logger.debug("rcu_bin=%d/%d: mean signal in test band for X and Y lower than normal" %(x_rcu, y_rcu)) - mean_val_trigger = True - - if ((((abs(peaks_array[x_rcu] - median_max_sb) > 10) or (abs(median_bandwidth - peaks_bw_array[x_rcu]) > 10)) and (peaks_bw_array[x_rcu] > 3)) or - (((abs(peaks_array[y_rcu] - median_max_sb) > 10) or (abs(median_bandwidth - peaks_bw_array[y_rcu]) > 10)) and (peaks_bw_array[y_rcu] > 3))): - logger.debug("rcu=%d/%d: antenna probable down" %(x_rcu, y_rcu)) - down_trigger = True - - if mean_val_trigger and down_trigger: - down_info.append((ant, peaks_array[x_rcu], peaks_array[y_rcu], median_max_sb)) - - if not down_trigger: - if ((peaks_bw_array[x_rcu] > 20) and (abs(peaks_array[x_rcu] - median_max_sb) > 10)): - logger.debug("rcu=%d: X-top shifted normal=%d, now=%d" %(x_rcu, median_max_sb, peaks_array[x_rcu])) - shifted_info.append((x_rcu, peaks_array[x_rcu], median_max_sb)) - - if ((peaks_bw_array[y_rcu] > 20) and (abs(peaks_array[y_rcu] - median_max_sb) > 10)): - logger.debug("rcu=%d: Y-top shifted normal=%d, now=%d" %(y_rcu, median_max_sb, peaks_array[y_rcu])) - shifted_info.append((y_rcu, peaks_array[y_rcu], median_max_sb)) - - # if more than half de antennes down or shifted, skip test - if len(down_info) > (_data.shape[0] / 2): - down_info = list() - if len(shifted_info) > (_data.shape[0] / 2): - shifted_info = list() - return (down_info, shifted_info) - - -# find oscillation -def search_oscillation(data, pol, delta): - info = list() - _data = data.getAll(pol=pol) - mean_spectras = ma.mean(_data, axis=1) - mean_spectra = ma.mean(mean_spectras, axis=0) - mean_low = ma.mean(_data.min(axis=1)) - info.append((-1, 0, 0, 0)) - - for rcu in data.getActiveRcus(pol): - rcu_bin = rcu - if pol not in ('XY', 'xy'): - rcu_bin /= 2 - #logger.debug("rcu=%d rcu_bin=%d" %(rcu, rcu_bin)) - #max_peak_val = 0 - max_n_peaks = 0 - max_sum_peaks = 0 - peaks = cSearchPeak(mean_spectras[rcu_bin,:] - mean_spectra) - if peaks.valid_data: - peaks.search(delta=delta, max_width=8) - max_val = mean_spectras[rcu_bin,:].max() - max_n_peaks = peaks.nMaxPeaks() - max_sum_peaks = peaks.getSumPeaks() - - bin_low = _data[rcu_bin,:,:].min(axis=0).mean() - if max_n_peaks > 5: - logger.debug("rcu_bin=%d: number-of-peaks=%d max_value=%3.1f peaks_sum=%5.3f low_value=%3.1f" %\ - (rcu_bin, max_n_peaks, max_val, max_sum_peaks, bin_low)) - if bin_low > (mean_low + 2.0): #peaks.getSumPeaks() > (median_sum_peaks * 2.0): - info.append((rcu_bin, max_sum_peaks, max_n_peaks, bin_low)) - - if max_val > 150.0: # only one high peek - info.append((rcu_bin, max_sum_peaks, max_n_peaks, bin_low)) - - return (info) #(sorted(info,reverse=True)) - -# find summator noise -def search_summator_noise(data, pol, min_peak): - sn_info = list() # summator noise - cr_info = list() # cable reflection - _data = data.getAll(pol=pol) - - secs = _data.shape[1] - for rcu in sorted(data.getActiveRcus(pol)): - rcu_bin = rcu - if pol not in ('XY', 'xy'): - rcu_bin /= 2 - #logger.debug("rcu=%d rcu_bin=%d" %(rcu, rcu_bin)) - sum_sn_peaks = 0 - sum_cr_peaks = 0 - max_peaks = 0 - for sec in range(secs): - peaks_ref = cSearchPeak(_data[:,sec,:].mean(axis=0)) - if peaks_ref.valid_data: - peaks_ref.search(delta=min_peak) - - peaks = cSearchPeak(_data[rcu_bin,sec,:]) - if peaks.valid_data: - peaks.search(delta=min_peak, skip_list=peaks_ref.max_peaks) - n_peaks = peaks.nMaxPeaks() - if n_peaks < 3: - continue - sn_peaks = 0 - cr_peaks = 0 - last_sb, min_sb, max_sb = peaks.max_peaks[0] - last_sb_val = peaks.getPeakValue(last_sb) - for sb, min_sb, max_sb in peaks.max_peaks[1:]: - sb_val = peaks.getPeakValue(sb) - - sb_diff = sb - last_sb - sb_val_diff = sb_val - last_sb_val - if sb_diff in (3,4): - if abs(sb_val_diff) < 2.0: - sn_peaks += 1 - elif sn_peaks < 6 and abs(sb_val_diff) > 3.0: - sn_peaks = 0 - if sb_diff in (6,7): - if abs(sb_val_diff) < 2.0: - cr_peaks += 1 - elif cr_peaks < 6 and abs(sb_val_diff) > 3.0: - cr_peaks = 0 - last_sb = sb - last_sb_val = sb_val - - sum_sn_peaks += sn_peaks - sum_cr_peaks += cr_peaks - max_peaks = max(max_peaks, n_peaks) - - if (sum_sn_peaks > (secs * 3.0)): - sn_peaks = sum_sn_peaks / secs - sn_info.append((rcu_bin, sn_peaks, max_peaks)) - if sum_cr_peaks > (secs * 3.0): - cr_peaks = sum_cr_peaks / secs - cr_info.append((rcu_bin, cr_peaks, max_peaks)) - return (sn_info, cr_info) - - -# find noise -# noise looks like the noise floor is going up and down -# -# kijk ook naar op en neer gaande gemiddelden -def search_noise(data, pol, low_deviation, high_deviation, max_diff): - _data = data.getAll(pol=pol) - high_info = list() - low_info = list() - jitter_info = list() - - ref_value = ma.median(_data) - # loop over rcus - for rcu in sorted(data.getActiveRcus(pol)): - rcu_bin = rcu - if pol not in ('XY', 'xy'): - rcu_bin /= 2 - #logger.debug("rcu=%d rcu_bin=%d" %(rcu, rcu_bin)) - rcu_bin_value = ma.median(_data[rcu_bin,:,:]) - if rcu_bin_value < (ref_value + low_deviation): - logger.debug("rcu_bin=%d: masked, low signal, ref=%5.3f val=%5.3f" %(rcu_bin, ref_value, rcu_bin_value)) - low_info.append((rcu_bin, _data[rcu_bin,:,:].min(), -1 , (ref_value+low_deviation), (_data[rcu_bin,:,:].max() - _data[rcu_bin,:,:].min()))) - _data[rcu_bin,:,:] = ma.masked - spec_median = ma.median(_data, axis=2) - spec_max = spec_median.max(axis=1) - spec_min = spec_median.min(axis=1) - ref_value = ma.median(_data) - ref_diff = ma.median(spec_max) - ma.median(spec_min) - ref_std = ma.std(spec_median) - #high_limit = ref_value + min(max((ref_std * 3.0),0.75), high_deviation) - high_limit = ref_value + max((ref_std * 3.0), high_deviation) - low_limit = ref_value + min((ref_std * -3.0), low_deviation) - n_secs = _data.shape[1] - logger.debug("median-signal=%5.3fdB, median-fluctuation=%5.3fdB, std=%5.3f, high_limit=%5.3fdB low_limit=%5.3fdB" %\ - (ref_value, ref_diff, ref_std, high_limit, low_limit)) - # loop over rcus - for rcu in sorted(data.getActiveRcus(pol)): - rcu_bin = rcu - if pol not in ('XY', 'xy'): - rcu_bin /= 2 - #logger.debug("rcu=%d rcu_bin=%d" %(rcu, rcu_bin)) - peaks = cSearchPeak(_data[rcu_bin,0,:]) - if not peaks.valid_data: - return (low_info, high_info, jitter_info) - peaks.search(delta=10.0) - if peaks.nMaxPeaks() >= 30: - logger.debug("rcu_bin=%d: found %d peaks, skip noise test" %(rcu_bin, peaks.nMaxPeaks())) - else: - n_bad_high_secs = 0 - n_bad_low_secs = 0 - if _data.shape[1] == 1: - n_bad_high_secs = 1 - n_bad_low_secs = 1 - n_bad_jitter_secs = 0 - - rcu_bin_max_diff = spec_max[rcu_bin] - spec_min[rcu_bin] - #logger.debug("rcu_bin_max_diff %f" %(rcu_bin_max_diff)) - # loop over secs - for val in spec_median[rcu_bin,:]: - #logger.debug("rcu_bin=%d: high-noise value=%5.3fdB max-ref-value=%5.3fdB" %(rcu_bin, val, ref_value)) - if (val > high_limit): - n_bad_high_secs += 1 - - if (val < low_limit): - n_bad_low_secs += 1 - - if n_bad_high_secs > 1: - high_info.append((rcu_bin, spec_max[rcu_bin], n_bad_high_secs, high_limit, rcu_bin_max_diff)) - logger.debug("rcu_bin=%d: max-noise=%5.3f %d of %d seconds bad" %(rcu_bin, spec_max[rcu_bin], n_bad_high_secs, n_secs)) - - if n_bad_low_secs > 1: - low_info.append((rcu_bin, spec_min[rcu_bin], n_bad_low_secs , low_limit, rcu_bin_max_diff)) - logger.debug("rcu_bin=%d: min-noise=%5.3f %d of %d seconds bad" %(rcu_bin, spec_min[rcu_bin], n_bad_low_secs, n_secs)) - - if (n_bad_high_secs == 0) and (n_bad_low_secs == 0): - max_cnt = 0 - min_cnt = 0 - if rcu_bin_max_diff > (ref_diff + max_diff): - check_high_value = ref_value + (ref_diff / 2.0) - check_low_value = ref_value - (ref_diff / 2.0) - for val in spec_median[rcu_bin,:]: - if val > check_high_value: - max_cnt += 1 - if val < check_low_value: - min_cnt += 1 - - # minimal 20% of the values must be out of the check band - secs = _data.shape[1] - if max_cnt > (secs * 0.10) and min_cnt > (secs * 0.10): - n_bad_jitter_secs = max_cnt + min_cnt - jitter_info.append((rcu_bin, rcu_bin_max_diff, ref_diff, n_bad_jitter_secs)) - logger.debug("rcu_bin=%d: max spectrum fluctuation %5.3f dB" %(rcu_bin, rcu_bin_max_diff)) - return (low_info, high_info, jitter_info) - -# find spurious around normal signals -# -def search_spurious(data, pol, delta): - info = list() - _data = data.getAll(pol=pol) - max_data = ma.max(_data, axis=1) - mean_data = ma.mean(_data, axis=1) - median_spec = ma.mean(max_data, axis=0) - peaks = cSearchPeak(median_spec) - if not peaks.valid_data: - return (info) - - # first mask peaks available in all data - peaks.search(delta=(delta/2.0)) #deta=20 for HBA - for peak, min_sb, max_sb in peaks.max_peaks: - peakwidth = max_sb - min_sb - if peakwidth > 8: - continue - min_sb = max(min_sb-1, 0) - max_sb = min(max_sb+1, peaks.n_data-1) - logger.debug("mask sb %d..%d" %(min_sb, max_sb)) - for i in range(min_sb, max_sb, 1): - mean_data[:,i] = ma.masked - - # search in all data for spurious - for rcu in sorted(data.getActiveRcus(pol)): - rcu_bin = rcu - if pol not in ('XY', 'xy'): - rcu_bin /= 2 - logger.debug("rcu=%d rcu_bin=%d" %(rcu, rcu_bin)) - peaks = cSearchPeak(mean_data[rcu_bin,:]) - if peaks.valid_data: - peaks.search(delta=delta) - for peak, min_sb, max_sb in peaks.max_peaks: - peakwidth = max_sb - min_sb - if peakwidth > 10: - continue - peak_val = peaks.getPeakValue(peak) - if peakwidth < 100 and peak_val != NaN: - logger.debug("rcu_bin=%d: spurious, subband=%d..%d, peak=%3.1fdB" %(rcu_bin, min_sb, max_sb, peak_val)) - if peaks.nMaxPeaks() > 10: - #print rcu_bin, peaks.nMaxPeaks() - info.append(rcu_bin) - return(info) - diff --git a/LCU/checkhardware/lib/test_db.py b/LCU/checkhardware/lib/test_db.py deleted file mode 100644 index ec2ee679766..00000000000 --- a/LCU/checkhardware/lib/test_db.py +++ /dev/null @@ -1,883 +0,0 @@ -#!/usr/bin/python - -from copy import deepcopy -from general_lib import * -from lofar_lib import * -import time -import logging -import string - -db_version = '0415' - -logger = None -def init_test_db(): - global logger - logger = logging.getLogger() - logger.debug("init logger test_db") - -class cDB: - def __init__(self, StID, nRSP, nTBB, nLBL, nLBH, nHBA, HBA_SPLIT): - self.StID = StID - self.nr_rsp = nRSP - self.nr_spu = nRSP / 4 - self.nr_rcu = nRSP * 8 - self.nr_lbl = nLBL - self.nr_lbh = nLBH - self.nr_hba = nHBA - self.hba_split = HBA_SPLIT - self.nr_tbb = nTBB - - self.script_versions = '' - - self.board_errors = list() - self.rcumode = -1 - self.tests_done = list() - self.check_start_time = 0 - self.check_stop_time = 0 - self.rsp_driver_down = False - self.tbb_driver_down = False - - self.station_error = 0 - self.rspdriver_version = "ok" - self.rspctl_version = "ok" - self.tbbdriver_version = "ok" - self.tbbctl_version = "ok" - - self.test_end_time = -1 - - self.spu = list() - for i in range(self.nr_spu): - self.spu.append(self.cSPU(i)) - - self.rsp = list() - for i in range(nRSP): - self.rsp.append(self.cRSP(i)) - - self.tbb = list() - for i in range(nTBB): - self.tbb.append(self.cTBB(i)) - - self.rcu_state = list() - for i in range(self.nr_rcu): - self.rcu_state.append(0) - - self.lbl = deepcopy(self.cLBA_DB(label='LBL', nr_antennas=nLBL, nr_offset=48)) - self.lbh = deepcopy(self.cLBA_DB(label='LBH', nr_antennas=nLBH, nr_offset=0)) - self.hba = deepcopy(self.cHBA_DB(nr_tiles=nHBA, split=self.hba_split)) - - def setTestEndTime(self, end_time): - if end_time > time.time(): - self.test_end_time = end_time - else: - logger.warn("end time in past") - return - - # returns True if before end time - def checkEndTime(self, duration=0.0): - if self.test_end_time == -1: - return (True) - if (time.time() + duration) < self.test_end_time: - return (True) - else: - return (False) - - # add only ones - def addTestDone(self, name): - if name not in self.tests_done: - self.tests_done.append(name) - - # check if already done - def isTestDone(self, name): - if name in self.tests_done: - return (False) - return (True) - - # test - def test(self, logdir): - if self.rspdriver_version != "ok" or self.rspctl_version != "ok": - self.station_error = 1 - - if self.tbbdriver_version != "ok" or self.tbbctl_version != "ok": - self.station_error = 1 - - for _spu in self.spu: - ok = _spu.test() - if not ok: - self.station_error = 1 - - for _rsp in self.rsp: - ok = _rsp.test() - if not ok: - self.station_error = 1 - - for _tbb in self.tbb: - ok = _tbb.test() - if not ok: - self.station_error = 1 - - # test rcu's first - for _rcu in range(self.nr_rcu): - error_count = 0 - - ant_nr = _rcu / 2 - pol_nr = _rcu % 2 # 0=X, 1=Y - - if pol_nr == 0: - if self.nr_lbl > 0 and self.lbl.ant[ant_nr].x.error: error_count += 1 - if self.lbh.ant[ant_nr].x.error: error_count += 1 - if self.hba.tile[ant_nr].x.rcu_error: error_count += 1 - else: - if self.nr_lbl > 0 and self.lbl.ant[ant_nr].y.error: error_count += 1 - if self.lbh.ant[ant_nr].y.error: error_count += 1 - if self.hba.tile[ant_nr].y.rcu_error: error_count += 1 - - if error_count >= 2: - self.rcu_state[_rcu] = 1 - - self.station_error = max(self.station_error, self.lbl.test(), self.lbh.test(), self.hba.test()) - - self.makeLogFile(logdir) - return (self.station_error) - - - # make standard log file - def makeLogFile(self, logdir): - #print logdir - date = getShortDateStr(self.check_start_time) - log = cTestLogger(logdir) - - log.addLine("%s,NFO,---,VERSIONS,%s" %(date, self.script_versions)) - - log.addLine("%s,NFO,---,STATION,NAME=%s" %(date, getHostName())) - - log.addLine("%s,NFO,---,RUNTIME,START=%s,STOP=%s" %(date, getDateTimeStr(self.check_start_time), getDateTimeStr(self.check_stop_time))) - - info = "" - bad = "" - for ant in self.lbl.ant: - if ant.on_bad_list == 1: - bad += "%d " %(ant.nr_pvss) - if len(bad) > 0: - info += "LBL=%s," %(bad[:-1]) - - bad = "" - for ant in self.lbh.ant: - if ant.on_bad_list == 1: - bad += "%d " %(ant.nr_pvss) - if len(bad) > 0: - info += "LBH=%s," %(bad[:-1]) - - bad = "" - for tile in self.hba.tile: - if tile.on_bad_list == 1: - bad += "%d " %(tile.nr) - if len(bad) > 0: - info += "HBA=%s," %(bad[:-1]) - if len(info) > 0: - log.addLine("%s,NFO,---,BADLIST,%s" %(date, info[:-1])) - - if self.rsp_driver_down: - log.addLine("%s,NFO,---,DRIVER,RSPDRIVER=DOWN" %(date)) - - if self.tbb_driver_down: - log.addLine("%s,NFO,---,DRIVER,TBBDRIVER=DOWN" %(date)) - - if len(self.board_errors): - boardstr = '' - for board in self.board_errors: - boardstr += "RSP-%d=DOWN," %(board) - log.addLine("%s,NFO,---,BOARD,%s" %(date, boardstr[:-1])) - - log.addLine("%s,NFO,---,CHECKS,%s" %(date, string.join(self.tests_done, ','))) - - log.addLine("%s,NFO,---,STATISTICS,BAD_LBL=%d,BAD_LBH=%d,BAD_HBA=%d,BAD_HBA0=%d,BAD_HBA1=%d" %\ - (date, self.lbl.nr_bad_antennas, self.lbh.nr_bad_antennas, self.hba.nr_bad_tiles, self.hba.nr_bad_tiles_0, self.hba.nr_bad_tiles_1)) - - - if self.rspdriver_version != "ok" or self.rspctl_version != "ok": - log.addLine("%s,RSP,---,VERSION,RSPDRIVER=%s,RSPCTL=%s" %\ - (date, self.rspdriver_version, self.rspctl_version)) - - for spu in self.spu: - spu.test() - if not spu.voltage_ok: - valstr = '' - if not spu.rcu_ok: valstr += ",RCU-5.0V=%3.1f" %(spu.rcu_5_0V) - if not spu.lba_ok: valstr += ",LBA-8.0V=%3.1f" %(spu.lba_8_0V) - if not spu.hba_ok: valstr += ",HBA-48V=%3.1f" %(spu.hba_48V) - if not spu.spu_ok: valstr += ",SPU-3.3V=%3.1f" %(spu.spu_3_3V) - if len(valstr): - log.addLine("%s,SPU,%03d,VOLTAGE%s" %(date, spu.nr, valstr)) - - if not spu.temp_ok: - log.addLine("%s,SPU,%03d,TEMPERATURE,PCB=%2.0f" %\ - (date, spu.nr, spu.temp)) - - for rsp in self.rsp: - rsp.test() - if not rsp.version_ok: - log.addLine("%s,RSP,%03d,VERSION,BP=%s,AP=%s" %\ - (date, rsp.nr, rsp.bp_version, rsp.ap_version)) - if not rsp.voltage_ok: - log.addLine("%s,RSP,%03d,VOLTAGE,1.2V=%3.2f,2.5V=%3.2f,3.3V=%3.2f" %\ - (date,rsp.nr, rsp.voltage1_2, rsp.voltage2_5, rsp.voltage3_3)) - if not rsp.temp_ok: - log.addLine("%s,RSP,%03d,TEMPERATURE,PCB=%2.0f,BP=%2.0f,AP0=%2.0f,AP1=%2.0f,AP2=%2.0f,AP3=%2.0f" %\ - (date,rsp.nr, rsp.pcb_temp, rsp.bp_temp, rsp.ap0_temp, rsp.ap1_temp, rsp.ap2_temp, rsp.ap3_temp)) - - if self.tbbdriver_version != "ok" or self.tbbctl_version != "ok": - log.addLine("%s,TBB,---,VERSION,TBBDRIVER=%s,TBBCTL=%s" %\ - (date, self.tbbdriver_version, self.tbbctl_version)) - - for tbb in self.tbb: - tbb.test() - if not tbb.version_ok: - log.addLine("%s,TBB,%03d,VERSION,TP=%s,MP=%s" %\ - (date, tbb.nr, tbb.tp_version, tbb.mp_version)) - if not tbb.memory_ok: - log.addLine("%s,TBB,%03d,MEMORY" %(date, tbb.nr)) - - for rcu in range(self.nr_rcu): - if self.rcu_state[rcu]: - log.addLine("%s,RCU,%03d,BROKEN" % (date, rcu)) - - # lbl/lbh - for lba in (self.lbl, self.lbh): - - if lba.signal_check_done: - if lba.test_signal_x == 0 or lba.test_signal_y == 0: - log.addLine("%s,%s,---,NOSIGNAL" %(date, lba.label)) - - elif lba.avg_2_low: - log.addLine("%s,%s,---,TOOLOW,AVGX=%3.1f,AVGY=%3.1f" %(date, lba.label, lba.avg_x, lba.avg_y)) - - else: - if lba.error: - log.addLine("%s,%s,---,TESTSIGNAL,SUBBANDX=%d,SIGNALX=%3.1f,SUBBANDY=%d,SIGNALY=%3.1f" %\ - (date, lba.label, lba.test_subband_x, lba.test_signal_x, lba.test_subband_y, lba.test_signal_y)) - - - if lba.noise_check_done or lba.oscillation_check_done or lba.spurious_check_done or lba.signal_check_done or\ - lba.short_check_done or lba.flat_check_done or lba.down_check_done: - for ant in lba.ant: - if ant.down: - log.addLine("%s,%s,%03d,DOWN,X=%3.1f,Y=%3.1f,Xoff=%d,Yoff=%d" %\ - (date, lba.label, ant.nr_pvss, ant.x.test_signal, ant.y.test_signal, ant.x.offset, ant.y.offset)) - else: - if lba.signal_check_done: - valstr = '' - if ant.x.too_low or ant.x.too_high: valstr += ",X=%3.1f" %(ant.x.test_signal) - if ant.y.too_low or ant.y.too_high: valstr += ",Y=%3.1f" %(ant.y.test_signal) - if len(valstr): - log.addLine("%s,%s,%03d,RF_FAIL%s" %(date, lba.label, ant.nr_pvss, valstr)) - - if lba.flat_check_done: - valstr = '' - if ant.x.flat: valstr += ",Xmean=%3.1f" %(ant.x.flat_val) - if ant.y.flat: valstr += ",Ymean=%3.1f" %(ant.y.flat_val) - if len(valstr): - log.addLine("%s,%s,%03d,FLAT%s" %(date, lba.label, ant.nr_pvss, valstr)) - - if lba.short_check_done: - valstr = '' - if ant.x.short: valstr += ",Xmean=%3.1f" %(ant.x.short_val) - if ant.y.short: valstr += ",Ymean=%3.1f" %(ant.y.short_val) - if len(valstr): - log.addLine("%s,%s,%03d,SHORT%s" %(date, lba.label, ant.nr_pvss, valstr)) - - if lba.oscillation_check_done: - valstr = '' - if ant.x.osc: valstr += ',X=1' - if ant.y.osc: valstr += ',Y=1' - if len(valstr): - log.addLine("%s,%s,%03d,OSCILLATION%s" %(date, lba.label, ant.nr_pvss, valstr)) - - if lba.spurious_check_done: - valstr = '' - if ant.x.spurious: valstr += ',X=1' - if ant.y.spurious: valstr += ',Y=1' - if len(valstr): - log.addLine("%s,%s,%03d,SPURIOUS%s" %(date, lba.label, ant.nr_pvss, valstr)) - - if lba.noise_check_done: - noise = False - valstr = '' - if not ant.x.flat and ant.x.low_noise: - proc = (100.0 / ant.x.low_seconds) * ant.x.low_bad_seconds - valstr += ',Xproc=%5.3f,Xval=%3.1f,Xdiff=%5.3f,Xref=%3.1f' %(proc, ant.x.low_val, ant.x.low_diff, ant.x.low_ref) - if not ant.y.flat and ant.y.low_noise: - proc = (100.0 / ant.y.low_seconds) * ant.y.low_bad_seconds - valstr += ',Yproc=%5.3f,Yval=%3.1f,Ydiff=%5.3f,Yref=%3.1f' %(proc, ant.y.low_val, ant.y.low_diff, ant.y.low_ref) - if len(valstr): - log.addLine("%s,%s,%03d,LOW_NOISE%s" %(date, lba.label, ant.nr_pvss, valstr)) - noise = True - - valstr = '' - if ant.x.high_noise: - proc = (100.0 / ant.x.high_seconds) * ant.x.high_bad_seconds - valstr += ',Xproc=%5.3f,Xval=%3.1f,Xdiff=%5.3f,Xref=%3.1f' %(proc, ant.x.high_val, ant.x.high_diff, ant.x.high_ref) - if ant.y.high_noise: - proc = (100.0 / ant.y.high_seconds) * ant.y.high_bad_seconds - valstr += ',Yproc=%5.3f,Yval=%3.1f,Ydiff=%5.3f,Yref=%3.1f' %(proc, ant.y.high_val, ant.y.high_diff, ant.y.high_ref) - if len(valstr): - log.addLine("%s,%s,%03d,HIGH_NOISE%s" %(date, lba.label, ant.nr_pvss, valstr)) - noise = True - - valstr = '' - if not noise and ant.x.jitter: - proc = (100.0 / ant.x.jitter_seconds) * ant.x.jitter_bad_seconds - valstr += ',Xproc=%5.3f,Xdiff=%5.3f,Xref=%3.1f' %(proc, ant.x.jitter_val, ant.x.jitter_ref) - if not noise and ant.y.jitter: - proc = (100.0 / ant.y.jitter_seconds) * ant.y.jitter_bad_seconds - valstr += ',Xproc=%5.3f,Ydiff=%5.3f,Yref=%3.1f' %(proc, ant.y.jitter_val, ant.y.jitter_ref) - if len(valstr): - log.addLine("%s,%s,%03d,JITTER%s" %(date, lba.label, ant.nr_pvss, valstr)) - lba = None - # end lbl/lbh - - - # hba - if self.hba.signal_check_done: - valstr = '' - if self.hba.ref_signal_x[0] == 0 and self.hba.ref_signal_x[1] == 0: - valstr += ",X" - if self.hba.ref_signal_y[0] == 0 and self.hba.ref_signal_y[1] == 0: - valstr += ",Y" - if len(valstr): - log.addLine("%s,HBA,---,NOSIGNAL%s" %(date, valstr)) - - for tile in self.hba.tile: - if tile.x.error or tile.y.error: - # check for broken summators - if self.hba.modem_check_done: - valstr = '' - if tile.c_summator_error: - log.addLine("%s,HBA,%03d,C_SUMMATOR" %(date, tile.nr)) - else: - for elem in tile.element: - if elem.no_modem: - valstr += ",E%02d=??" %(elem.nr) - - elif elem.modem_error: - valstr += ",E%02d=error" %(elem.nr) - if len(valstr): - log.addLine("%s,HBA,%03d,MODEM%s" %(date, tile.nr, valstr)) - - if self.hba.noise_check_done: - valstr = '' - noise = False - - if tile.x.low_noise: - proc = (100.0 / tile.x.low_seconds) * tile.x.low_bad_seconds - valstr += ',Xproc=%5.3f,Xval=%3.1f,Xdiff=%5.3f,Xref=%3.1f' %(proc, tile.x.low_val, tile.x.low_diff, tile.x.low_ref) - if tile.y.low_noise: - proc = (100.0 / tile.y.low_seconds) * tile.y.low_bad_seconds - valstr += ',Yproc=%5.3f,Yval=%3.1f,Ydiff=%5.3f,Yref=%3.1f' %(proc, tile.y.low_val, tile.y.low_diff, tile.y.low_ref) - if len(valstr): - log.addLine("%s,HBA,%03d,LOW_NOISE%s" %(date, tile.nr, valstr)) - noise = True - - valstr = '' - if tile.x.high_noise: - proc = (100.0 / tile.x.high_seconds) * tile.x.high_bad_seconds - valstr += ',Xproc=%5.3f,Xval=%3.1f,Xdiff=%5.3f,Xref=%3.1f' %(proc, tile.x.high_val, tile.x.high_diff, tile.x.high_ref) - if tile.y.high_noise: - proc = (100.0 / tile.y.high_seconds) * tile.y.high_bad_seconds - valstr += ',Yproc=%5.3f,Yval=%3.1f,Ydiff=%5.3f,Yref=%3.1f' %(proc, tile.y.high_val, tile.y.high_diff, tile.y.high_ref) - if len(valstr): - log.addLine("%s,HBA,%03d,HIGH_NOISE%s" %(date, tile.nr, valstr)) - noise = True - - valstr = '' - if (not noise) and tile.x.jitter: - proc = (100.0 / tile.x.jitter_seconds) * tile.x.jitter_bad_seconds - valstr += ',Xproc=%5.3f,Xdiff=%5.3f,Xref=%3.1f' %(proc, tile.x.jitter_val, tile.x.jitter_ref) - if (not noise) and tile.y.jitter: - proc = (100.0 / tile.y.jitter_seconds) * tile.y.jitter_bad_seconds - valstr += ',Yproc=%5.3f,Ydiff=%5.3f,Yref=%3.1f' %(proc, tile.y.jitter_val, tile.y.jitter_ref) - if len(valstr): - log.addLine("%s,HBA,%03d,JITTER%s" %(date, tile.nr, valstr)) - - if self.hba.oscillation_check_done: - valstr = '' - if tile.x.osc: valstr += ',X=1' - if tile.y.osc: valstr += ',Y=1' - if len(valstr): - log.addLine("%s,HBA,%03d,OSCILLATION%s" %(date, tile.nr, valstr)) - - if tile.p_summator_error: - log.addLine("%s,HBA,%03d,P_SUMMATOR" %(date, tile.nr)) - - - - if self.hba.summatornoise_check_done: - valstr = '' - if tile.x.summator_noise: valstr += ',X=1' - if tile.y.summator_noise: valstr += ',Y=1' - if len(valstr): - log.addLine("%s,HBA,%03d,SUMMATOR_NOISE%s" %(date, tile.nr, valstr)) - - if self.hba.spurious_check_done: - valstr = '' - if tile.x.spurious: valstr += ',X=1' - if tile.y.spurious: valstr += ',Y=1' - if len(valstr): - log.addLine("%s,HBA,%03d,SPURIOUS%s" %(date, tile.nr, valstr)) - - if self.hba.signal_check_done: - valstr = '' - if tile.x.too_low or tile.x.too_high: - valstr += ",X=%3.1f %d %3.1f %3.1f %d %3.1f" %\ - (tile.x.test_signal[0], self.hba.test_subband_x[0], self.hba.ref_signal_x[0],\ - tile.x.test_signal[1], self.hba.test_subband_x[1], self.hba.ref_signal_x[1]) - if tile.y.too_low or tile.y.too_high: - valstr += ",Y=%3.1f %d %3.1f %3.1f %d %3.1f" %\ - (tile.y.test_signal[0], self.hba.test_subband_y[0], self.hba.ref_signal_y[0],\ - tile.y.test_signal[1], self.hba.test_subband_y[1], self.hba.ref_signal_y[1]) - if len(valstr): - log.addLine("%s,HBA,%03d,RF_FAIL%s" %(date, tile.nr, valstr)) - - valstr = '' - if self.hba.element_check_done: - for elem in tile.element: - #if tile.x.rcu_off or tile.y.rcu_off: - # continue - if self.hba.modem_check_done and (elem.no_modem or elem.modem_error): - if not tile.c_summator_error: - if elem.no_modem: - valstr += ",M%d=??" %(elem.nr) - - elif elem.modem_error: - valstr += ",M%d=error" %(elem.nr) - else: - if elem.x.osc or elem.y.osc: - if elem.x.osc: - valstr += ",OX%d=1" %(elem.nr) - if elem.y.osc: - valstr += ",OY%d=1" %(elem.nr) - - elif elem.x.spurious or elem.y.spurious: - if elem.x.spurious: - valstr += ",SPX%d=1" %(elem.nr) - if elem.y.spurious: - valstr += ",SPY%d=1" %(elem.nr) - - elif elem.x.low_noise or elem.x.high_noise or elem.y.low_noise or elem.y.high_noise or elem.x.jitter or elem.y.jitter: - if elem.x.low_noise: - valstr += ",LNX%d=%3.1f %5.3f" %(elem.nr, elem.x.low_val, elem.x.low_diff) - - if elem.x.high_noise: - valstr += ",HNX%d=%3.1f %5.3f" %(elem.nr, elem.x.high_val, elem.x.high_diff) - - if (not elem.x.low_noise) and (not elem.x.high_noise) and (elem.x.jitter > 0.0): - valstr += ",JX%d=%5.3f" %(elem.nr, elem.x.jitter) - - if elem.y.low_noise: - valstr += ",LNY%d=%3.1f %5.3f" %(elem.nr, elem.y.low_val, elem.y.low_diff) - - if elem.y.high_noise: - valstr += ",HNY%d=%3.1f %5.3f" %(elem.nr, elem.y.high_val, elem.y.high_diff) - - if (not elem.y.low_noise) and (not elem.y.high_noise) and (elem.y.jitter > 0.0): - valstr += ",JY%d=%5.3f" %(elem.nr, elem.y.jitter) - else: - if elem.x.ref_signal[0] == 0 and elem.x.ref_signal[1] == 0: - log.addLine("%s,HBA,%03d,NOSIGNAL,E%02dX" %(date, tile.nr, elem.nr)) - else: - if elem.x.error: - valstr += ",X%d=%3.1f %d %3.1f %3.1f %d %3.1f" %\ - (elem.nr,\ - elem.x.test_signal[0], elem.x.test_subband[0], elem.x.ref_signal[0],\ - elem.x.test_signal[1], elem.x.test_subband[1], elem.x.ref_signal[1]) - - if elem.y.ref_signal[0] == 0 and elem.y.ref_signal[1] == 0: - log.addLine("%s,HBA,%03d,NOSIGNAL,E%02dY" %(date, tile.nr, elem.nr)) - else: - if elem.y.error: - valstr += ",Y%d=%3.1f %d %3.1f %3.1f %d %3.1f" %\ - (elem.nr,\ - elem.y.test_signal[0], elem.y.test_subband[0], elem.y.ref_signal[0],\ - elem.y.test_signal[1], elem.y.test_subband[1], elem.y.ref_signal[1]) - - if len(valstr): - log.addLine("%s,HBA,%03d,E_FAIL%s" %(date, tile.nr, valstr)) - # end HBA - return - -#======================================================================================================================= -# database from here - class cSPU: - def __init__(self, nr): - self.nr = nr - self.rcu_5_0V = 0.0 - self.lba_8_0V = 0.0 - self.hba_48V = 0.0 - self.spu_3_3V = 0.0 - self.rcu_ok = 1 - self.lba_ok = 1 - self.hba_ok = 1 - self.spu_ok = 1 - self.voltage_ok = 1 - self.temp = 0.0 - self.temp_ok = 1 - - def test(self): - self.voltage_ok = 0 - if self.rcu_ok and self.lba_ok and self.hba_ok and self.spu_ok: - self.voltage_ok = 1 - return(self.voltage_ok) - - - class cRSP: - def __init__(self, nr): - self.nr = nr - - self.test_done = 0 - self.board_ok = 1 - self.ap_version = 'ok' - self.bp_version = 'ok' - self.version_ok = 1 - self.voltage1_2 = 0.0 - self.voltage2_5 = 0.0 - self.voltage3_3 = 0.0 - self.voltage_ok = 1 - self.pcb_temp = 0.0 - self.bp_temp = 0.0 - self.ap0_temp = 0.0 - self.ap1_temp = 0.0 - self.ap2_temp = 0.0 - self.ap3_temp = 0.0 - self.temp_ok = 1 - - def test(self): - if self.ap_version != 'ok' or self.bp_version != 'ok': - self.version_ok = 0 - return (self.version_ok and self.voltage_ok and self.temp_ok) - - # used by LBA and HBA antenna class - class cPolarity: - def __init__(self, rcu=None): - - self.rcu = rcu - self.rcu_off = 0 # 0 = RCU on, 1 = RCU off - self.rcu_error = 0 - - # status variables 0|1 - self.error = 0 # - self.too_low = 0 # - self.too_high = 0 # - self.low_noise = 0 # - self.high_noise = 0 # - self.jitter = 0 # - self.osc = 0 # - self.no_signal = 0 # signal below 2dB - self.summator_noise = 0 # - self.spurious = 0 # - self.flat = 0 # - self.short = 0 # - - # test result of signal test, - # only for HBA element test, firt value ctrl=129 second value ctrl=253 - self.test_subband = [0, 0] - self.ref_signal = [-1, -1] - self.test_signal = [0.0, 0.0] - self.offset = 0 - - # measured values filled on error - # proc : bad time in meausured time 0..100% - # val : max or min meausured value - self.low_seconds = 0 - self.low_bad_seconds = 0 - self.low_val = 100.0 # - self.low_diff = 0.0 - self.low_ref = 0.0 # - - self.high_seconds = 0 - self.high_bad_seconds = 0 - self.high_val = 0.0 # - self.high_diff = 0.0 - self.high_ref = 0.0 # - - self.jitter_seconds = 0 - self.jitter_bad_seconds = 0 - self.jitter_val = 0.0 - self.jitter_ref = 0.0 - - self.flat_val = 0.0 - self.short_val = 0.0 - - class cLBA_DB: - def __init__(self, label, nr_antennas, nr_offset=0): - self.rsp_driver_down = False - self.noise_check_done = 0 - self.signal_check_done = 0 - self.short_check_done = 0 - self.flat_check_done = 0 - self.down_check_done = 0 - self.spurious_check_done = 0 - self.oscillation_check_done = 0 - - self.noise_low_deviation = 0.0 - self.noise_high_deviation = 0.0 - self.noise_max_fluctuation = 0.0 - - self.rf_low_deviation = 0.0 - self.rf_high_deviation = 0.0 - self.rf_subband = 0 - - self.check_time_noise = 0 - self.nr_antennas = nr_antennas - self.nr_offset = nr_offset - self.label = label - self.error = 0 - self.avg_2_low = 0 - self.avg_x = 0 - self.avg_y = 0 - self.test_subband_x = 0 - self.test_subband_y = 0 - self.test_signal_x = 0 - self.test_signal_y = 0 - self.nr_bad_antennas = -1 - self.ant = list() - for i in range(self.nr_antennas): - self.ant.append(self.cAntenna(i, self.nr_offset)) - return - - def test(self): - if self.rsp_driver_down: - return (self.error) - if self.noise_check_done or self.signal_check_done or self.short_check_done or self.flat_check_done or\ - self.down_check_done or self.signal_check_done or self.spurious_check_done or self.oscillation_check_done: - self.nr_bad_antennas = 0 - - for ant in self.ant: - ant.test() - ant_error = max(ant.x.error, ant.y.error) - self.error = max(self.error, ant_error) - if ant_error: - self.nr_bad_antennas += 1 - return (self.error) - - # return select string for rspctl command - def selectList(self): - select = list() - for ant in self.ant: - if ant.on_bad_list == 0: - select.append(ant.x.rcu) - select.append(ant.y.rcu) - return (select) - - def resetRcuState(self): - for ant in self.ant: - ant.x.rcu_off = 0 - ant.y.rcu_off = 0 - - class cAntenna: - def __init__(self, nr, nr_offset): - self.nr = nr - self.nr_pvss = self.nr + nr_offset - self.on_bad_list = 0 - self.x = cDB.cPolarity(rcu=(self.nr * 2)) - self.y = cDB.cPolarity(rcu=((self.nr * 2) + 1)) - - self.down = 0 - return - - def test(self): - self.x.error = max(self.x.too_low, self.x.too_high, self.x.osc, self.x.high_noise, self.x.low_noise, - self.x.jitter, self.x.spurious, self.down, self.x.flat, self.x.short) - self.y.error = max(self.y.too_low, self.y.too_high, self.y.osc, self.y.high_noise, self.y.low_noise, - self.y.jitter, self.y.spurious, self.down, self.y.flat, self.y.short) - return - - class cHBA_DB: - def __init__(self, nr_tiles, split): - self.rsp_driver_down = False - self.modem_check_done = 0 - self.noise_check_done = 0 - self.signal_check_done = 0 - self.spurious_check_done = 0 - self.oscillation_check_done = 0 - self.summatornoise_check_done = 0 - self.element_check_done = 0 - - self.hba_split = split - self.check_time_noise = 0 - self.check_time_noise_elements = 0 - self.nr_tiles = nr_tiles - self.error = 0 - self.avg_2_low = 0 - # only used for tile RF test - # first value ctrl=129 second value ctrl=253 - self.test_subband_x = [0, 0] - self.test_subband_y = [0, 0] - self.ref_signal_x = [0.0, 0.0] - self.ref_signal_y = [0.0, 0.0] - self.tile = list() - self.nr_bad_tiles = -1 - self.nr_bad_tiles_0 = -1 - self.nr_bad_tiles_1 = -1 - for i in range(self.nr_tiles): - self.tile.append(self.cTile(i)) - return - - def test(self): - if self.rsp_driver_down: - return (self.error) - if self.modem_check_done or self.noise_check_done or self.signal_check_done or self.spurious_check_done \ - or self.oscillation_check_done or self.summatornoise_check_done or self.element_check_done : - if self.hba_split == 1: - self.nr_bad_tiles_0 = 0 - self.nr_bad_tiles_1 = 0 - else: - self.nr_bad_tiles = 0 - - for tile in self.tile: - tile.test() - tile_error = max(tile.x.error, tile.y.error) - self.error = max(self.error, tile_error) - - if tile_error: - if self.hba_split == 1: - if tile.nr < 24: - self.nr_bad_tiles_0 += 1 - else: - self.nr_bad_tiles_1 += 1 - else: - self.nr_bad_tiles += 1 - return (self.error) - - # return select string for rspctl command - def selectList(self): - select = list() - for tile in self.tile: - if tile.on_bad_list == 0: - select.append(tile.x.rcu) - select.append(tile.y.rcu) - return (select) - - def resetRcuState(self): - for tile in self.tile: - tile.x.rcu_off = 0 - tile.y.rcu_off = 0 - - class cTile: - def __init__(self, nr): - self.nr = nr - self.on_bad_list = 0 - self.x = cDB.cPolarity(rcu=(nr*2)) - self.y = cDB.cPolarity(rcu=(nr*2+1)) - - self.noise_low_deviation = 0.0 - self.noise_high_deviation = 0.0 - self.noise_max_fluctuation = 0.0 - - self.rf_low_deviation = 0.0 - self.rf_high_deviation = 0.0 - self.rf_subband = 0 - - self.no_power = 0 # signal around 60dB - self.p_summator_error = 0 - self.c_summator_error = 0 - self.nr_elements = 16 - self.element = list() - for i in range(1,self.nr_elements+1,1): - self.element.append(self.cElement(i)) - return - - def test(self): - no_modem_cnt = 0 - modem_err_cnt = 0 - no_power_cnt = 0 - x_no_signal_cnt = 0 - y_no_signal_cnt = 0 - for elem in self.element: - elem.test() - if elem.x.no_signal: - x_no_signal_cnt += 1 - if elem.y.no_signal: - y_no_signal_cnt += 1 - if elem.no_power: - no_power_cnt += 1 - if elem.no_modem: - no_modem_cnt += 1 - if elem.modem_error: - modem_err_cnt += 1 - - self.x.error = max(self.x.error, elem.x.error) - self.y.error = max(self.y.error, elem.y.error) - - if (no_modem_cnt >= 8) or (modem_err_cnt >= 8): - self.c_summator_error = 1 - if no_power_cnt >= 15: - self.p_summator_error = 1 - if x_no_signal_cnt == 16: - self.x.rcu_error = 1 - if y_no_signal_cnt == 16: - self.y.rcu_error = 1 - - self.x.error = max(self.x.error, self.x.too_low, self.x.too_high, self.x.low_noise, self.x.no_signal, self.x.high_noise, self.x.jitter, self.x.osc, - self.x.summator_noise, self.x.spurious, self.p_summator_error, self.c_summator_error) - - self.y.error = max(self.y.error, self.y.too_low, self.y.too_high, self.y.low_noise, self.y.no_signal, self.y.high_noise, self.y.jitter, self.y.osc, - self.y.summator_noise, self.y.spurious, self.p_summator_error, self.c_summator_error) - return - - - class cElement: - def __init__(self, nr): - self.nr = nr - self.x = cDB.cPolarity() - self.y = cDB.cPolarity() - - self.noise_low_deviation = 0.0 - self.noise_high_deviation = 0.0 - self.noise_max_fluctuation = 0.0 - - self.rf_low_deviation = 0.0 - self.rf_high_deviation = 0.0 - self.rf_subband = 0 - - self.no_power = 0 # signal around 60dB - self.no_modem = 0 # modem reponse = ?? - self.modem_error = 0 # wrong response from modem - - return - - def test(self): - modem_err = 0 - if self.no_modem or self.modem_error: - modem_err = 1 - - self.x.error = max(self.x.too_low, self.x.too_high, self.x.low_noise, self.x.high_noise, self.x.no_signal, - self.x.jitter, self.no_power, self.x.spurious, self.x.osc, modem_err) - - self.y.error = max(self.y.too_low, self.y.too_high, self.y.low_noise, self.y.high_noise, self.y.no_signal, - self.y.jitter, self.no_power, self.y.spurious, self.y.osc, modem_err) - return - - - class cTDS: - def __init__(self): - self.test_done = 0 - self.ok = 1 - - class cTBB: - def __init__(self, nr): - self.nr = nr - self.test_done = 0 - self.tp_version = 'ok' - self.mp_version = 'ok' - self.memory_size = 0 - self.version_ok = 1 - self.memory_ok = 1 - - def test(self): - if self.tp_version != 'ok' or self.mp_version != 'ok': - self.version_ok = 0 - if self.memory_size != 0: - self.memory_ok = 0 - - return - - diff --git a/LCU/checkhardware/lib/test_lib.py b/LCU/checkhardware/lib/test_lib.py deleted file mode 100644 index 7a953b02802..00000000000 --- a/LCU/checkhardware/lib/test_lib.py +++ /dev/null @@ -1,1623 +0,0 @@ -#!/usr/bin/python -# test lib - -from general_lib import * -from lofar_lib import * -from search_lib import * -from data_lib import * -import os -import numpy as np -import logging - -test_version = '0815' - -logger = None -def init_test_lib(): - global logger - logger = logging.getLogger() - logger.debug("init logger test_lib") - - -#HBASubband = dict( DE601C=155, DE602C=155, DE603C=284, DE604C=474, DE605C=479, FR606C=155, SE607C=287, UK608C=155 ) -#DefaultLBASubband = 301 -#DefaultHBASubband = 155 - -class cSPU: - def __init__(self, db): - self.db = db - self.board_info_str = [] - self.board_info_val = [-1, 0.0, 0.0, 0.0, 0.0, 0] - - def extract_board_info(self, line): - li = line.split("|") - if not li[0].strip().isdigit(): - return False - - self.board_info_str = [i.strip() for i in li] - - if li[0].strip().isdigit(): - self.board_info_val[0] = int(li[0].strip()) - else: - self.board_info_val[0] = -1 - - for i in xrange(1, 5, 1): - if li[i].strip().replace('.','').isdigit(): - self.board_info_val[i] = float(li[i].strip()) - else: - self.board_info_val[i] = 0.0 - - if li[5].strip().isdigit(): - self.board_info_val[5] = int(li[5].strip()) - else: - self.board_info_val[5] = 0 - return True - - - def checkStatus(self): - """ - check PSU if boards idle and fully loaded - """ - # in future get all settings from configuration file - max_3_3 = 3.4 - min_3_3 = 3.1 - max_drop_3_3 = 0.3 - - max_5_0 = 5.0 - min_5_0 = 4.5 - max_drop_5_0 = 0.3 - - max_8_0 = 8.0 - min_8_0 = 7.4 - max_drop_8_0 = 0.3 - - max_48 = 48.0 - min_48 = 43.0 - max_drop_48 = 2.0 - - logger.info("=== SPU status check ===") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - noload = [] - fullload_3 = [] - fullload_5 = [] - logger.debug("check spu no load") - answer = rspctl('--spustatus') - - # check if Driver is available - if answer.find('No Response') > 0: - logger.warn("No RSPDriver") - self.db.rspdriver_version = 0 - else: - infolines = answer.splitlines() - for line in infolines: - if self.extract_board_info(line): - bi = self.board_info_val - noload.append([bi[0], bi[1], bi[2], bi[3], bi[4]]) - self.db.spu[bi[0]].temp = bi[5] - bi = self.board_info_str - logger.debug("Subrack %s voltages: rcu=%s lba=%s hba=%s spu=%s temp: %s" % ( - bi[0], bi[1], bi[2], bi[3], bi[4], bi[5])) - - # turn on all hbas - logger.debug("check spu full load mode 3") - rsp_rcu_mode(3, self.db.lbh.selectList()) - answer = rspctl('--spustatus') - infolines = answer.splitlines() - for line in infolines: - if self.extract_board_info(line): - bi = self.board_info_val - fullload_3.append([bi[0], bi[1], bi[2], bi[3], bi[4]]) - bi = self.board_info_str - logger.debug("Subrack %s voltages: rcu=%s lba=%s hba=%s spu=%s temp: %s" % ( - bi[0], bi[1], bi[2], bi[3], bi[4], bi[5])) - - # turn on all hbas - logger.debug("check spu full load mode 5") - rsp_rcu_mode(5, self.db.hba.selectList()) - answer = rspctl('--spustatus') - infolines = answer.splitlines() - for line in infolines: - if self.extract_board_info(line): - bi = self.board_info_val - fullload_5.append([bi[0], bi[1], bi[2], bi[3], bi[4]]) - bi = self.board_info_str - logger.debug("Subrack %s voltages: rcu=%s lba=%s hba=%s spu=%s temp: %s" % ( - bi[0], bi[1], bi[2], bi[3], bi[4], bi[5])) - - for sr in range(self.db.nr_spu): - # calculate mean of noload, fullload_3, fullload_5 - self.db.spu[sr].rcu_5_0V = (noload[sr][1] + fullload_3[sr][1] + fullload_5[sr][1]) / 3.0 - self.db.spu[sr].lba_8_0V = fullload_3[sr][2] - self.db.spu[sr].hba_48V = fullload_5[sr][3] - self.db.spu[sr].spu_3_3V = (noload[sr][4] + fullload_3[sr][4] + fullload_5[sr][4]) / 3.0 - - if (self.db.spu[sr].temp > 35.0): - self.db.spu[sr].temp_ok = 0 - - if (not (min_5_0 <= noload[sr][1] <= max_5_0) - or not (min_5_0 < fullload_3[sr][1] <= max_5_0) - or not (min_5_0 <= fullload_5[sr][1] <= max_5_0) - or (noload[sr][1] - fullload_3[sr][1]) > max_drop_5_0): - self.db.spu[sr].rcu_ok = 0 - logger.info("SPU voltage 5.0V out of range") - - if (not (min_8_0 <= noload[sr][2] <= max_8_0) - or not (min_8_0 <= fullload_3[sr][2] <= max_8_0) - or (noload[sr][2] - fullload_3[sr][2]) > max_drop_8_0): - self.db.spu[sr].lba_ok = 0 - logger.info("SPU voltage 8.0V out of range") - - if (not (min_48 <= noload[sr][3] <= max_48) - or not (min_48 <= fullload_5[sr][3] <= max_48) - or (noload[sr][3] - fullload_5[sr][3]) > max_drop_48): - self.db.spu[sr].hba_ok = 0 - logger.info("SPU voltage 48V out of range") - - if (not (min_3_3 <= noload[sr][4] <= max_3_3) - or not (min_3_3 <= fullload_3[sr][4] <= max_3_3) - or not (min_3_3 <= fullload_5[sr][4] <= max_3_3) - or (noload[sr][4] - fullload_5[sr][4]) > max_drop_3_3): - self.db.spu[sr].spu_ok = 0 - logger.info("SPU voltage 3.3V out of range") - - logger.info("=== Done SPU check ===") - self.db.addTestDone('SPU') - return - -# class for checking TBB boards using tbbctl -class cTBB: - global logger - def __init__(self, db): - self.db = db - self.nr = self.db.nr_tbb - self.driverstate = True - #tbbctl('--free') - - # check software versions of driver, tbbctl and TP/MP firmware - def checkVersions(self, driverV, tbbctlV, tpV, mpV ): - logger.info("=== TBB Version check ===") - answer = tbbctl('--version') - - # check if Driver is available - if answer.find('TBBDriver is NOT responding') > 0: - logger.warn("No TBBDriver") - self.driverstate = False - self.db.tbbdriver_version = 0 - else: - infolines = answer.splitlines() - info = infolines[4:6] + infolines[9:-1] - #print info - if info[0].split()[-1] != driverV: - logger.warn("Not right Driver version") - self.db.tbbdriver_version = info[0].split()[-1] - - if info[1].split()[-1] != tbbctlV: - logger.warn("Not right tbbctl version") - self.db.tbbctl_version = info[1].split()[-1] - - # check if image_nr > 0 for all boards - if str(info).count('V') != (self.nr * 4): - logger.warn("WARNING, Not all boards in working image") - - for tbb in self.db.tbb: - board_info = info[2+tbb.nr].strip().split(' ') - #print board_info - if board_info[3].split()[1] != tpV: - logger.warn("Board %d Not right TP version" %(tbb.nr)) - tbb.tp_version = board_info[3].split()[1] - - if board_info[4].split()[1] != mpV: - logger.warn("Board %d Not right MP version" %(tbb.nr)) - tbb.mp_version = board_info[4].split()[1] - logger.info("=== Done TBB Version check ===") - self.db.addTestDone('TV') - return - - # Check memory address and data lines - def checkMemory(self): - logger.info("=== TBB Memory check ===") - tbbctl('--free') - for tbb in self.db.tbb: - answer = tbbctl('--testddr=%d' %(tbb.nr)) - info = answer.splitlines()[-3:] - ok = True - if info[0].strip() != 'All Addresslines OK': - logger.warn("Board %d Addresline error" %(tbb.nr)) - ok = False - - if info[1].strip() != 'All Datalines OK': - logger.warn("Board %d Datalines error" %(tbb.nr)) - ok = False - - if not ok: - tbb.memory_ok = 0 - logger.info(answer) - # turn on recording again - tbbctl('--alloc') - tbbctl('--record') - rspctl('--tbbmode=transient') - logger.info("=== Done TBB Memory check ===") - self.db.addTestDone('TM') - return -#### end of cTBB class #### - - -# class for checking RSP boards using rspctl -class cRSP: - global logger - def __init__(self, db): - self.db = db - self.nr = self.db.nr_rsp - - # check software versions of driver, tbbctl and TP/MP firmware - def checkVersions(self, bpV, apV ): - logger.info("=== RSP Version check ===") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - answer = rspctl('--version') - - # check if Driver is available - if answer.find('No Response') > 0: - logger.warn("No RSPDriver") - self.db.rspdriver_version = 0 - else: - infolines = answer.splitlines() - info = infolines - - images_ok = True - # check if image_nr > 0 for all boards - if str(info).count('0.0') != 0: - logger.warn("WARNING, Not all boards in working image") - images_ok = False - - for rsp in self.db.rsp: - board_info = info[rsp.nr].split(',') - - if board_info[1].split()[3] != bpV: - logger.warn("Board %d Not right BP version" %(rsp.nr)) - rsp.bp_version = board_info[1].split()[3] - images_ok = False - - if board_info[2].split()[3] != apV: - logger.warn("Board %d Not right AP version" %(rsp.nr)) - rsp.ap_version = board_info[2].split()[3] - images_ok = False - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return (False) - - logger.info("=== Done RSP Version check ===") - self.db.addTestDone('RV') - return (images_ok) - - def checkBoard(self): - max_1_2 = 1.3 - min_1_2 = 1.1 - - max_2_5 = 2.6 - min_2_5 = 2.4 - - max_3_3 = 3.4 - min_3_3 = 3.1 - - ok = True - logger.info("=== RSP Board check ===") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return (False) - answer = rspctl('--status') - p2 = 0 - for rsp in self.db.rsp: - p1 = answer.find("RSP[%2d]" %(rsp.nr), p2) - p2 = answer.find("\n", p1) - d = [float(i.split(":")[1].strip()) for i in answer[p1+7:p2].split(',')] - if len(d) == 3: - logger.debug("RSP board %d: [1.2V]=%3.2fV, [2.5V]=%3.2fV, [3.3V]=%3.2fV" %(rsp.nr, d[0], d[1], d[2])) - rsp.voltage1_2 = d[0] - if not (min_1_2 <= d[0] <= max_1_2): - rsp.voltage_ok = 0 - logger.info("RSP board %d [1.2V]=%3.2fV" %(rsp.nr, d[0])) - rsp.voltage2_5 = d[1] - if not (min_2_5 <= d[1] <= max_2_5): - rsp.voltage_ok = 0 - logger.info("RSP board %d [2.5V]=%3.2fV" %(rsp.nr, d[1])) - rsp.voltage3_3 = d[2] - if not (min_3_3 <= d[2] <= max_3_3): - rsp.voltage_ok = 0 - logger.info("RSP board %d [3.3V]=%3.2fV" %(rsp.nr, d[2])) - - for rsp in self.db.rsp: - p1 = answer.find("RSP[%2d]" %(rsp.nr), p2) - p2 = answer.find("\n", p1) - d = [float(i.split(":")[1].strip()) for i in answer[p1+7:p2].split(',')] - if len(d) == 6: - logger.debug("RSP board %d temperatures: pcb=%3.1f, bp=%3.1f, ap0=%3.1f, ap1=%3.1f, ap2=%3.1f, ap3=%3.1f" %( - rsp.nr, - d[0], d[1], d[2], d[3], d[4], d[5])) - rsp.pcb_temp = d[0] - if d[0] > 45.0: - rsp.temp_ok = 0 - logger.info("RSP board %d [pcb_temp]=%2.0f" %(rsp.nr, d[0])) - rsp.bp_temp = d[1] - if d[1] > 75.0: - rsp.temp_ok = 0 - logger.info("RSP board %d [bp_temp]=%2.0f" %(rsp.nr, d[1])) - rsp.ap0_temp = d[2] - if d[2] > 75.0: - rsp.temp_ok = 0 - logger.info("RSP board %d [ap0_temp]=%2.0f" %(rsp.nr, d[2])) - rsp.ap1_temp = d[3] - if d[3] > 75.0: - rsp.temp_ok = 0 - logger.info("RSP board %d [ap1_temp]=%2.0f" %(rsp.nr, d[3])) - rsp.ap2_temp = d[4] - if d[4] > 75.0: - rsp.temp_ok = 0 - logger.info("RSP board %d [ap2_temp]=%2.0f" %(rsp.nr, d[4])) - rsp.ap3_temp = d[5] - if d[5] > 75.0: - rsp.temp_ok = 0 - logger.info("RSP board %d [ap3_temp]=%2.0f" %(rsp.nr, d[5])) - logger.info("=== Done RSP Board check ===") - self.db.addTestDone('RBC') - return (ok) - -#### end of cRSP class #### - - -# class for testing LBA antennas -class cLBA: - #global logger - # mode='lba_low' or 'lba_high' - def __init__(self, db, lba): - self.db = db - self.lba = lba - self.rcudata = cRCUdata(self.lba.nr_antennas*2) - self.rcudata.setActiveRcus(self.lba.selectList()) - - # Average normal value = 150.000.000 (81.76 dBm) -3dB +3dB - # LOW/HIGH LIMIT is used for calculating mean value - self.lowLimit = -3.0 #dB - self.highLimit = 3.0 #dB - - # MEAN LIMIT is used to check if mean of all antennas is ok - self.meanLimit = 66.0 #dB - - def reset(self): - self.rcudata.reset() - self.rcudata.reset_masks() - self.rcudata.setActiveRcus(self.lba.selectList()) - - def turnOffAnt(self, ant_nr): - ant = self.lba.ant[ant_nr] - ant.x.rcu_off = 1 - ant.y.rcu_off = 1 - self.rcudata.setInActiveRcu(ant.x.rcu) - self.rcudata.setInActiveRcu(ant.y.rcu) - logger.info("turned off antenna %d RCU(%d,%d)" %(ant.nr_pvss, ant.x.rcu, ant.y.rcu)) - rspctl("--rcumode=0 --select=%d,%d" %(ant.x.rcu, ant.y.rcu), wait=2.0) - rspctl("--rcuenable=0 --select=%d,%d" %(ant.x.rcu, ant.y.rcu), wait=2.0) - return - - def set_mode(self, mode): - if self.db.rcumode != mode: - self.db.rcumode = mode - turnoffRCUs() - turnonRCUs(mode=mode, rcus=self.lba.selectList()) - self.lba.resetRcuState() - self.rcudata.reset() - - def record_data(self, rec_time, new_data=False): - if new_data or self.rcudata.isActiveRcusChanged() or self.rcudata.getRecTime() < rec_time: - logger.debug('record info changed') - self.rcudata.resetActiveRcusChanged() - self.rcudata.record(rec_time=rec_time) - - # check for oscillating tiles and turn off RCU - # stop one RCU each run - def checkOscillation(self, mode): - logger.info("=== Start %s oscillation test ===" %(self.lba.label)) - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=28.0) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - - clean = False - while not clean: - if self.db.checkEndTime(duration=18.0) == False: - logger.warn("check stopped, end time reached") - return - - clean = True - self.record_data(rec_time=3) - - for pol_nr, pol in enumerate(('X', 'Y')): - # result is a sorted list on maxvalue - result = search_oscillation(data=self.rcudata, pol=pol, delta=6.0) - if len(result) > 1: - clean = False - ant, peaks_sum, n_peaks, ant_low = sorted(result[1:], reverse=True)[0] #result[1] - #ant = rcu / 2 - #ant_polarity = rcu % 2 - rcu = (ant * 2) + pol_nr - logger.info("RCU %d LBA %d Oscillation sum=%3.1f peaks=%d low=%3.1fdB" %\ - (rcu, self.lba.ant[ant].nr_pvss, peaks_sum, n_peaks, ant_low)) - self.turnOffAnt(ant) - if pol_nr == 0: - self.lba.ant[ant].x.osc = 1 - else: - self.lba.ant[ant].y.osc = 1 - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.lba.oscillation_check_done = 1 - self.db.addTestDone('O%d' %(mode)) - logger.info("=== Done %s oscillation test ===" %(self.lba.label)) - return - - def checkNoise(self, mode, record_time, low_deviation, high_deviation, max_diff): - logger.info("=== Start %s noise test ===" %(self.lba.label)) - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=(record_time+100.0)) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - - for ant in self.lba.ant: - if ant.x.rcu_off or ant.y.rcu_off: - logger.info("skip low-noise test for antenna %d, RCUs turned off" %(ant.nr)) - - self.record_data(rec_time=record_time) - - # result is a sorted list on maxvalue - low_noise, high_noise, jitter = search_noise(self.rcudata, 'XY', low_deviation, high_deviation, max_diff) - - for n in low_noise: - rcu, val, bad_secs, ref, diff = n - ant = rcu / 2 - if self.lba.ant[ant].x.rcu_off or self.lba.ant[ant].y.rcu_off: - continue - #self.turnOffAnt(ant) - logger.info("RCU %d Ant %d Low-Noise value=%3.1f bad=%d(%d) limit=%3.1f diff=%3.3f" %\ - (rcu, self.lba.ant[ant].nr_pvss, val, bad_secs, self.rcudata.frames, ref, diff)) - - self.rcudata.add_to_rcu_mask(rcu) - if rcu%2 == 0: - antenna = self.lba.ant[ant].x - else: - antenna = self.lba.ant[ant].y - - antenna.low_seconds += self.rcudata.frames - antenna.low_bad_seconds += bad_secs - if val < self.lba.ant[ant].x.low_val: - antenna.low_noise = 1 - antenna.low_val = val - antenna.low_ref = ref - antenna.low_diff = diff - - for n in high_noise: - rcu, val, bad_secs, ref, diff = n - ant = rcu / 2 - #self.turnOffAnt(ant) - logger.info("RCU %d Ant %d High-Noise value=%3.1f bad=%d(%d) ref=%3.1f diff=%3.1f" %\ - (rcu, self.lba.ant[ant].nr_pvss, val, bad_secs, self.rcudata.frames, ref, diff)) - - self.rcudata.add_to_rcu_mask(rcu) - if rcu%2 == 0: - antenna = self.lba.ant[ant].x - else: - antenna = self.lba.ant[ant].y - - antenna.high_seconds += self.rcudata.frames - antenna.high_bad_seconds += bad_secs - if val > self.lba.ant[ant].x.high_val: - antenna.high_noise = 1 - antenna.high_val = val - antenna.high_ref = ref - antenna.high_diff = diff - - for n in jitter: - rcu, val, ref, bad_secs = n - ant = rcu / 2 - logger.info("RCU %d Ant %d Jitter, fluctuation=%3.1fdB normal=%3.1fdB" %(rcu, self.lba.ant[ant].nr_pvss, val, ref)) - - self.rcudata.add_to_rcu_mask(rcu) - if rcu%2 == 0: - antenna = self.lba.ant[ant].x - else: - antenna = self.lba.ant[ant].y - - antenna.jitter_seconds += self.rcudata.frames - antenna.jitter_bad_seconds += bad_secs - if val > antenna.jitter_val: - antenna.jitter = 1 - antenna.jitter_val = val - antenna.jitter_ref = ref - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.lba.noise_check_done = 1 - self.db.addTestDone('NS%d=%d' %(mode, record_time)) - logger.info("=== Done %s noise test ===" %(self.lba.label)) - return - - def checkSpurious(self, mode): - logger.info("=== Start %s spurious test ===" %(self.lba.label)) - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=12.0) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - self.record_data(rec_time=3) - - # result is a sorted list on maxvalue - result = search_spurious(self.rcudata, 'XY', delta=3.0) - for rcu in result: - ant = rcu / 2 - ant_polarity = rcu % 2 - #self. turnOffAnt(ant) - logger.info("RCU %d Ant %d pol %d Spurious" %(rcu, self.lba.ant[ant].nr_pvss, ant_polarity)) - - self.rcudata.add_to_rcu_mask(rcu) - if ant_polarity == 0: - self.lba.ant[ant].x.spurious = 1 - else: - self.lba.ant[ant].y.spurious = 1 - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.lba.spurious_check_done = 1 - self.db.addTestDone('SP%d' %(mode)) - logger.info("=== Done %s spurious test ===" %(self.lba.label)) - return - - def checkShort(self, mode): - logger.info("=== Start %s Short test ===" %(self.lba.label)) - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=15.0) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - self.record_data(rec_time=3) - - # search for shorted cable (input), mean signal all subbands between 55 and 61 dB - logger.debug("Check Short") - short = searchShort(self.rcudata) - for i in short: - rcu, mean_val = i - ant = rcu / 2 - pol = rcu % 2 - - logger.info("%s %2d RCU %3d Short, mean value band=%5.1fdB" %\ - (self.lba.label, self.lba.ant[ant].nr_pvss, rcu, mean_val)) - - self.rcudata.add_to_rcu_mask(rcu) - if pol == 0: - self.lba.ant[ant].x.short = 1; - self.lba.ant[ant].x.short_val = mean_val; - else: - self.lba.ant[ant].y.short = 1; - self.lba.ant[ant].y.short_val = mean_val; - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.lba.short_check_done = 1 - self.db.addTestDone('SH%d' %(mode)) - logger.info("=== Done %s Short test ===" %(self.lba.label)) - return - - def checkFlat(self, mode): - logger.info("=== Start %s Flat test ===" %(self.lba.label)) - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=15.0) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - self.record_data(rec_time=3) - - # search for flatliners, mean signal all subbands between 63 and 65 dB - logger.debug("Check Flat") - flat = searchFlat(self.rcudata) - for i in flat: - rcu, mean_val = i - ant = rcu / 2 - pol = rcu % 2 - - logger.info("%s %2d RCU %3d Flat, mean value band=%5.1fdB" %\ - (self.lba.label, self.lba.ant[ant].nr_pvss, rcu, mean_val)) - - self.rcudata.add_to_rcu_mask(rcu) - if pol == 0: - self.lba.ant[ant].x.flat = 1; - self.lba.ant[ant].x.flat_val = mean_val; - else: - self.lba.ant[ant].y.flat = 1; - self.lba.ant[ant].y.flat_val = mean_val; - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.lba.flat_check_done = 1 - self.db.addTestDone('F%d' %(mode)) - logger.info("=== Done %s Flat test ===" %(self.lba.label)) - return - - - def checkDown(self, mode, subband): - logger.info("=== Start %s Down test ===" %(self.lba.label)) - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=15.0) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - self.record_data(rec_time=3) - - # mark lba as down if top of band is lower than normal and top is shifted more than 10 subbands to left or right - logger.debug("Check Down") - down, shifted = searchDown(self.rcudata, subband) - for i in down: - ant, max_x_sb, max_y_sb, mean_max_sb = i - max_x_offset = max_x_sb - mean_max_sb - max_y_offset = max_y_sb - mean_max_sb - - if self.lba.ant[ant].x.flat or self.lba.ant[ant].x.short or self.lba.ant[ant].y.flat or self.lba.ant[ant].y.short: - continue - - self.lba.ant[ant].x.offset = max_x_offset - self.lba.ant[ant].y.offset = max_y_offset - self.lba.ant[ant].down = 1 - logger.info("%s %2d RCU %3d/%3d Down, offset-x=%d offset-y=%d" %\ - (self.lba.label, self.lba.ant[ant].nr_pvss, self.lba.ant[ant].x.rcu, self.lba.ant[ant].y.rcu, max_x_offset, max_y_offset)) - self.rcudata.add_to_rcu_mask(self.lba.ant[ant].x.rcu) - self.rcudata.add_to_rcu_mask(self.lba.ant[ant].y.rcu) - for i in shifted: - rcu, max_sb, mean_max_sb = i - ant = rcu / 2 - logger.info("%s %2d RCU %3d shifted top on sb=%d, normal=sb%d" %(self.lba.label, self.lba.ant[ant].nr_pvss, rcu, max_sb, mean_max_sb)) - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.lba.down_check_done = 1 - self.db.addTestDone('D%d' %(mode)) - logger.info("=== Done %s Down test ===" %(self.lba.label)) - return - - - def checkSignal(self, mode, subband, min_signal, low_deviation, high_deviation): - logger.info("=== Start %s RF test ===" %(self.lba.label)) - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=15.0) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - self.record_data(rec_time=3) - - self.rcudata.searchTestSignal(subband=subband, minsignal=min_signal, maxsignal=90.0) - - logger.info("For X used test subband=%d (%3.1f dB) in mode %d" %\ - (self.rcudata.testSubband_X, self.rcudata.testSignal_X, mode)) - logger.info("For Y used test subband=%d (%3.1f dB) in mode %d" %\ - (self.rcudata.testSubband_Y, self.rcudata.testSignal_Y, mode)) - - if self.rcudata.testSubband_X == 0 or self.rcudata.testSubband_Y == 0: - logger.warn("LBA mode %d, No test signal found" %(mode)) - return - - ssdataX = self.rcudata.getSubbandX() - ssdataY = self.rcudata.getSubbandY() - #if np.ma.count(ssdataX) == 0 or np.ma.count(ssdataY) == 0: - # all zeros (missing settings!!) - # return - - # use only values between lowLimit and highLimit for average calculations - dataInBandX = np.ma.masked_outside(ssdataX, (self.rcudata.testSignal_X + self.lowLimit), (self.rcudata.testSignal_X + self.highLimit)) - medianValX = np.ma.median(dataInBandX) - - dataInBandY = np.ma.masked_outside(ssdataY, (self.rcudata.testSignal_Y + self.lowLimit), (self.rcudata.testSignal_Y + self.highLimit)) - medianValY = np.ma.median(dataInBandY) - - logger.info("used medianValX=%f" %(medianValX)) - logger.info("used medianValY=%f" %(medianValY)) - if medianValX < self.meanLimit or medianValY < self.meanLimit: - self.lba.avg_2_low = 1 - self.lba.avg_x = medianValX - self.lba.avg_y = medianValY - - self.lba.test_signal_x = medianValX - self.lba.test_signal_y = medianValY - self.lba.test_subband_x = self.rcudata.testSubband_X - self.lba.test_subband_y = self.rcudata.testSubband_Y - - logger.debug("Check RF signal") - for ant in self.lba.ant: - ant.x.test_signal = ssdataX[ant.nr] - ant.y.test_signal = ssdataY[ant.nr] - - loginfo = False - if ssdataX[ant.nr] < (medianValX + low_deviation): - if not max(ant.x.flat, ant.x.short, ant.down): - ant.x.too_low = 1 - if ssdataX[ant.nr] < 2.0: - ant.x.rcu_error = 1 - loginfo = True - - if ssdataX[ant.nr] > (medianValX + high_deviation): - ant.x.too_high = 1 - loginfo = True - - if ssdataY[ant.nr] < (medianValY + low_deviation): - if not max(ant.y.flat, ant.y.short, ant.down): - ant.y.too_low = 1 - if ssdataY[ant.nr] < 2.0: - ant.y.rcu_error = 1 - loginfo = True - - if ssdataY[ant.nr] > (medianValY + high_deviation): - ant.y.too_high = 1 - loginfo = True - - if loginfo: - logger.info("%s %2d RCU %3d/%3d X=%5.1fdB Y=%5.1fdB" %(self.lba.label, ant.nr_pvss, ant.x.rcu, ant.y.rcu, ssdataX[ant.nr], ssdataY[ant.nr])) - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.lba.signal_check_done = 1 - self.db.addTestDone('S%d' %(mode)) - logger.info("=== Done %s RF test ===" %(self.lba.label)) - return -#### end of cLBA class #### - - -# class for testing HBA antennas -class cHBA: - #global logger - def __init__(self, db, hba): - self.db = db - self.hba = hba - self.rcudata = cRCUdata(hba.nr_tiles*2) - self.rcudata.setActiveRcus(self.hba.selectList()) - self.rcumode = 0 - - def reset(self): - self.rcudata.reset() - self.rcudata.reset_masks() - self.rcudata.setActiveRcus(self.hba.selectList()) - - def turnOnTiles(self): - pass - - def turnOffTile(self, tile_nr): - tile = self.hba.tile[tile_nr] - tile.x.rcu_off = 1 - tile.y.rcu_off = 1 - logger.info("turned off tile %d RCU(%d,%d)" %(tile.nr, tile.x.rcu, tile.y.rcu)) - rspctl("--rcumode=0 --select=%d,%d" %(tile.x.rcu, tile.y.rcu), wait=2.0) - return - - def turnOffBadTiles(self): - for tile in self.hba.tile: - if tile.x.rcu_off and tile.y.rcu_off: - continue - no_modem = 0 - modem_error = 0 - for elem in tile.element: - if elem.no_modem: - no_modem += 1 - if elem.modem_error: - modem_error += 1 - if tile.x.osc or tile.y.osc or (no_modem >= 8) or (modem_error >= 8): - self.turnOffTile(tile.nr) - return - - def set_mode(self, mode): - if self.db.rcumode != mode: - self.db.rcumode = mode - turnoffRCUs() - turnonRCUs(mode=mode, rcus=self.hba.selectList()) - self.hba.resetRcuState() - self.rcudata.reset() - - def record_data(self, rec_time, new_data=False): - if new_data or self.rcudata.isActiveRcusChanged() or self.rcudata.getRecTime() < rec_time: - logger.debug('record info changed') - self.rcudata.resetActiveRcusChanged() - self.rcudata.record(rec_time=rec_time) - - def checkModem(self, mode): - # setup internal test db - n_elements = 16 - n_tests = 7 - modem_tst = list() - for tile_nr in range(self.db.nr_hba): - tile = list() - for elem_nr in range(n_elements): - test = list() - for tst_nr in range(n_tests): - test.append([0, 0]) - tile.append(test) - modem_tst.append(tile) - # done - - logger.info("=== Start HBA modem test ===") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=50.0) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - - time.sleep(4.0) - ctrlstr = list() - ctrlstr.append(('129,'* 16)[:-1]) # 0ns - ctrlstr.append(('133,'* 16)[:-1]) # 0.5ns - ctrlstr.append(('137,'* 16)[:-1]) # 1ns - ctrlstr.append(('145,'* 16)[:-1]) # 2ns - ctrlstr.append(('161,'* 16)[:-1]) # 4ns - ctrlstr.append(('193,'* 16)[:-1]) # 8ns - ctrlstr.append(('253,'* 16)[:-1]) # 15.5ns - #rsp_hba_delay(delay=ctrlstr[6], rcus=self.hba.selectList(), discharge=False) - tst_nr = 0 - for ctrl in ctrlstr: - - rsp_hba_delay(delay=ctrl, rcus=self.hba.selectList(), discharge=False) - data = rspctl('--realdelays', wait=1.0).splitlines() - - ctrllist = ctrl.split(',') - for line in data: - if line[:3] == 'HBA': - rcu = int(line[line.find('[')+1:line.find(']')]) - hba_nr = rcu / 2 - if self.hba.tile[hba_nr].on_bad_list: - continue - ant_polarity = rcu % 2 - realctrllist = line[line.find('=')+1:].strip().split() - for elem in self.hba.tile[hba_nr].element: - if ctrllist[elem.nr-1] != realctrllist[elem.nr-1]: - logger.info("Modemtest Tile=%d RCU=%d Element=%d ctrlword=%s response=%s" %\ - (hba_nr, rcu, elem.nr, ctrllist[elem.nr-1], realctrllist[elem.nr-1])) - - if realctrllist[elem.nr-1].count('?') == 3: - #elem.no_modem += 1 - modem_tst[hba_nr][elem.nr-1][tst_nr][0] = 1 - else: - #elem.modem_error += 1 - modem_tst[hba_nr][elem.nr-1][tst_nr][1] = 1 - tst_nr += 1 - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - # analyse test results and add to DB - no_modem = dict() - modem_error = dict() - for tile_nr in range(self.db.nr_hba): - n_no_modem = dict() - n_modem_error = dict() - for elem_nr in range(n_elements): - n_no_modem[elem_nr] = 0 - n_modem_error[elem_nr] = 0 - for tst_nr in range(n_tests): - if modem_tst[tile_nr][elem_nr][tst_nr][0]: - n_no_modem[elem_nr] += 1 - if modem_tst[tile_nr][elem_nr][tst_nr][1]: - n_modem_error[elem_nr] += 1 - no_modem[tile_nr] = n_no_modem - modem_error[tile_nr] = n_modem_error - - n_tile_err = 0 - for tile in no_modem: - n_elem_err = 0 - for elem in no_modem[tile]: - if no_modem[tile][elem] == n_tests: - n_elem_err += 1 - if n_elem_err == n_elements: - n_tile_err += 1 - - if n_tile_err < (self.db.nr_hba / 2): - for tile_nr in range(self.db.nr_hba): - for elem_nr in range(n_elements): - if no_modem[tile_nr][elem_nr] >= 2: # 2 or more ctrl values went wrong - self.db.hba.tile[tile_nr].element[elem_nr].no_modem = 1 - - n_tile_err = 0 - for tile in modem_error: - n_elem_err = 0 - for elem in modem_error[tile]: - if modem_error[tile][elem] == n_tests: - n_elem_err += 1 - if n_elem_err == n_elements: - n_tile_err += 1 - - if n_tile_err < (self.db.nr_hba / 2): - for tile_nr in range(self.db.nr_hba): - for elem_nr in range(n_elements): - if no_modem[tile_nr][elem_nr] >= 2: # 2 or more ctrl values went wrong - self.db.hba.tile[tile_nr].element[elem_nr].modem_error = 1 - - self.hba.modem_check_done = 1 - self.db.addTestDone('M%d' % mode) - logger.info("=== Done HBA modem test ===") - #self.db.rcumode = 0 - return - - # check for summator noise and turn off RCU - def checkSummatorNoise(self, mode): - logger.info("=== Start HBA tile based summator-noise test ===") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=25.0) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - - delay_str = ('253,'* 16)[:-1] - rsp_hba_delay(delay=delay_str, rcus=self.hba.selectList()) - - self.record_data(rec_time=12) - - for pol_nr, pol in enumerate(('X', 'Y')): - sum_noise, cable_reflection = search_summator_noise(data=self.rcudata, pol=pol, min_peak=0.8) - for n in sum_noise: - bin_nr, cnt, n_peaks = n - tile = bin_nr - logger.info("RCU %d Tile %d Summator-Noise cnt=%3.1f peaks=%3.1f" %(self.hba.tile[tile].x.rcu, tile, cnt, n_peaks)) - if pol == 'X': - self.hba.tile[tile].x.summator_noise = 1 - else: - self.hba.tile[tile].y.summator_noise = 1 - self.turnOffTile(tile) - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.hba.summatornoise_check_done = 1 - self.db.addTestDone('SN%d' %(mode)) - logger.info("=== Done HBA tile based summator-noise test ===") - return - - # check for oscillating tiles and turn off RCU - # stop one RCU each run - def checkOscillation(self, mode): - logger.info("=== Start HBA tile based oscillation test ===") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=35.0) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - - delay_str = ('253,'* 16)[:-1] - get_new_data = rsp_hba_delay(delay=delay_str, rcus=self.hba.selectList()) - - clean = False - while not clean: - if self.db.checkEndTime(duration=25.0) == False: - logger.warn("check stopped, end time reached") - return - - clean = True - - self.record_data(rec_time=12, new_data=get_new_data) - - for pol_nr, pol in enumerate(('X', 'Y')): - # result is a sorted list on maxvalue - result = search_oscillation(data=self.rcudata, pol=pol, delta=6.0) # start_sb=45, stop_sb=350 - if len(result) > 1: - if len(result) == 2: - tile, max_sum, n_peaks, rcu_low = result[1] - else: - ref_low = result[0][3] - max_low_tile = (-1, -1) - max_sum_tile = (-1, -1) - for i in result[1:]: - tile, max_sum, n_peaks, tile_low = i - #rcu = (tile * 2) + pol_nr - if max_sum > max_sum_tile[0]: - max_sum_tile = (max_sum, tile) - if (tile_low - ref_low) > max_low_tile[0]: - max_low_tile = (tile_low, tile) - - rcu_low, tile = max_low_tile - - clean = False - get_new_data = True - #tile = rcu / 2 - #tile_polarity = rcu % 2 - rcu = (tile * 2) + pol_nr - logger.info("RCU %d Tile %d Oscillation sum=%3.1f peaks=%d low=%3.1f" %\ - (rcu, tile, max_sum, n_peaks, tile_low)) - self.turnOffTile(tile) - if pol_nr == 0: - self.hba.tile[tile].x.osc = 1 - else: - self.hba.tile[tile].y.osc = 1 - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.hba.oscillation_check_done = 1 - self.db.addTestDone('O%d' %(mode)) - logger.info("=== Done HBA tile based oscillation test ===") - return - - def checkNoise(self, mode, record_time, low_deviation, high_deviation, max_diff): - logger.info("=== Start HBA tile based noise test ===") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=(record_time+60.0)) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - - for tile in self.hba.tile: - if tile.x.rcu_off or tile.y.rcu_off: - logger.info("skip low-noise test for tile %d, RCUs turned off" %(tile.nr)) - - delay_str = ('253,'* 16)[:-1] - get_new_data = rsp_hba_delay(delay=delay_str, rcus=self.hba.selectList()) - - self.record_data(rec_time=record_time, new_data=get_new_data) - - for pol_nr, pol in enumerate(('X', 'Y')): - # result is a sorted list on maxvalue - low_noise, high_noise, jitter = search_noise(self.rcudata, pol, low_deviation, high_deviation, max_diff) - - for n in low_noise: - bin_nr, val, bad_secs, ref, diff = n - tile = bin_nr - rcu = (tile * 2) + pol_nr - if self.hba.tile[tile].x.rcu_off or self.hba.tile[tile].y.rcu_off: - continue - logger.info("RCU %d Tile %d Low-Noise value=%3.1f bad=%d(%d) limit=%3.1f diff=%3.3f" %\ - (rcu, tile, val, bad_secs, self.rcudata.frames, ref, diff)) - - if pol == 'X': - tile_polarity = self.hba.tile[tile].x - else: - tile_polarity = self.hba.tile[tile].y - - tile_polarity.low_seconds += self.rcudata.frames - tile_polarity.low_bad_seconds += bad_secs - if val < tile_polarity.low_val: - tile_polarity.low_noise = 1 - tile_polarity.low_val = val - tile_polarity.low_ref = ref - tile_polarity.low_diff = diff - - for n in high_noise: - bin_nr, val, bad_secs, ref, diff = n - tile = bin_nr - rcu = (tile * 2) + pol_nr - logger.info("RCU %d Tile %d High-Noise value=%3.1f bad=%d(%d) limit=%3.1f diff=%3.1f" %\ - (rcu, tile, val, bad_secs, self.rcudata.frames, ref, diff)) - - if pol == 'X': - tile_polarity = self.hba.tile[tile].x - else: - tile_polarity = self.hba.tile[tile].y - - tile_polarity.high_seconds += self.rcudata.frames - tile_polarity.high_bad_seconds += bad_secs - if val > tile_polarity.high_val: - tile_polarity.high_noise = 1 - tile_polarity.high_val = val - tile_polarity.high_ref = ref - tile_polarity.high_diff = diff - - for n in jitter: - bin_nr, val, ref, bad_secs = n - tile = bin_nr - rcu = (tile * 2) + pol_nr - logger.info("RCU %d Tile %d Jitter, fluctuation=%3.1fdB normal=%3.1fdB" %(rcu, tile, val, ref)) - - if pol == 'X': - tile_polarity = self.hba.tile[tile].x - else: - tile_polarity = self.hba.tile[tile].y - - tile_polarity.jitter_seconds += self.rcudata.frames - tile_polarity.jitter_bad_seconds += bad_secs - if val > tile_polarity.jitter_val: - tile_polarity.jitter = 1 - tile_polarity.jitter_val = val - tile_polarity.jitter_ref = ref - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.hba.noise_check_done = 1 - self.db.addTestDone('NS%d=%d' %(mode, record_time)) - logger.info("=== Done HBA tile based noise test ===") - return - - def checkSpurious(self, mode): - logger.info("=== Start HBA tile based spurious test ===") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=12.0) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - - delay_str = ('253,'* 16)[:-1] - get_new_data = rsp_hba_delay(delay=delay_str, rcus=self.hba.selectList()) - - self.record_data(rec_time=12, new_data=get_new_data) - - for pol_nr, pol in enumerate(('X', 'Y')): - # result is a sorted list on maxvalue - result = search_spurious(self.rcudata, pol, delta=3.0) - for bin_nr in result: - tile = bin_nr - rcu = (tile * 2) + pol_nr - logger.info("RCU %d Tile %d pol %c Spurious" %(rcu, tile, pol)) - if pol == 'X': - self.hba.tile[tile].x.spurious = 1 - else: - self.hba.tile[tile].y.spurious = 1 - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.hba.spurious_check_done = 1 - self.db.addTestDone('SP%d' %(mode)) - logger.info("=== Done HBA spurious test ===") - return - - def checkSignal(self, mode, subband, min_signal, low_deviation, high_deviation): - logger.info("=== Start HBA tile based RF test ===") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - if self.db.checkEndTime(duration=37.0) == False: - logger.warn("check stopped, end time reached") - return - - self.set_mode(mode) - - # check twice - # 2 ... check if all elements are turned off, normal value between 60.0 and 62.0 - # 128 ... - # 253 ... - for tile in self.hba.tile: - if tile.x.rcu_off or tile.y.rcu_off: - logger.info("skip signal test for tile %d, RCUs turned off" %(tile.nr)) - - logger.info("start test") - for ctrl in ('2,', '128,', '253,'): - if self.db.checkEndTime(duration=20.0) == False: - logger.warn("check stopped, end time reached") - return - - if ctrl == '128,': ctrl_nr = 0 - elif ctrl == '253,': ctrl_nr = 1 - - logger.info("HBA signal test, ctrl word %s" %(ctrl[:-1])) - - delay_str = (ctrl*16)[:-1] - rsp_hba_delay(delay=delay_str, rcus=self.hba.selectList()) - - self.record_data(rec_time=2, new_data=True) - self.rcudata.searchTestSignal(subband=subband, minsignal=min_signal, maxsignal=150.0) - logger.info("HBA, X used test subband=%d avg_signal=%3.1f" %(self.rcudata.testSubband_X, self.rcudata.testSignal_X)) - logger.info("HBA, Y used test subband=%d avg_signal=%3.1f" %(self.rcudata.testSubband_Y, self.rcudata.testSignal_Y)) - - if ctrl == '2,': - ssdataX = self.rcudata.getSubbandX(subband=subband) - ssdataY = self.rcudata.getSubbandY(subband=subband) - else: - ssdataX = self.rcudata.getSubbandX() - ssdataY = self.rcudata.getSubbandY() - - for tile in self.hba.tile: - if tile.x.rcu_off or tile.y.rcu_off: - continue - logger.debug("HBA Tile=%d : X=%3.1fdB Y=%3.1fdB" %\ - (tile.nr, ssdataX[tile.nr], ssdataY[tile.nr])) - if ctrl == '2,': - continue - - if (self.rcudata.testSignal_X != -1) and (self.rcudata.testSignal_Y != -1): - - self.hba.ref_signal_x[ctrl_nr] = self.rcudata.testSignal_X - self.hba.ref_signal_y[ctrl_nr] = self.rcudata.testSignal_Y - self.hba.test_subband_x[ctrl_nr] = self.rcudata.testSubband_X - self.hba.test_subband_y[ctrl_nr] = self.rcudata.testSubband_Y - - avgX = self.rcudata.testSignal_X - avgY = self.rcudata.testSignal_Y - minX = ssdataX.min() - minY = ssdataY.min() - - # if all elements in range - #if minX < (avgX + self.min_dB) and minY < (avgY + self.min_dB): - # continue - - logger.debug("X data: min=%5.3f max=%5.3f avg=%5.3f" %(minX, ssdataX.max(), avgX)) - logger.debug("Y data: min=%5.3f max=%5.3f avg=%5.3f" %(minY, ssdataY.max(), avgY)) - - for tile in self.hba.tile: - if tile.x.rcu_off or tile.y.rcu_off: - continue - - tile.x.test_signal[ctrl_nr] = ssdataX[tile.nr] - tile.y.test_signal[ctrl_nr] = ssdataY[tile.nr] - - loginfo = False - if ssdataX[tile.nr] < (avgX + low_deviation): - if ssdataX[tile.nr] < 2.0: - tile.x.no_signal = 1 - elif ssdataX[tile.nr] > 55.0 and ssdataX[tile.nr] < 65.0: - tile.no_power = 1 - else: - tile.x.too_low = 1 - loginfo = True - - if ssdataX[tile.nr] > (avgX + high_deviation): - tile.x.too_high = 1 - loginfo = True - - if ssdataY[tile.nr] < (avgY + low_deviation): - if ssdataY[tile.nr] < 2.0: - tile.y.no_signal = 1 - elif ssdataY[tile.nr] > 55.0 and ssdataY[tile.nr] < 65.0: - tile.no_power = 1 - else: - tile.y.too_low = 1 - loginfo = True - - if ssdataY[tile.nr] > (avgY + high_deviation): - tile.y.too_high = 1 - loginfo = True - - if loginfo: - logger.info("HBA Tile=%d Error: X=%3.1fdB Y=%3.1fdB" %\ - (tile.nr, ssdataX[tile.nr], ssdataY[tile.nr])) - else: - logger.warn("HBA, No valid test signal") - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.hba.signal_check_done = 1 - self.db.addTestDone('S%d' %(mode)) - logger.info("=== Done HBA signal test ===") - return - - # Next tests are element based - # - # 8bit control word - # - # bit-7 RF on/off 1 = on - # bit-6 delay 1 = 8 ns - # bit-5 delay 1 = 4 ns - # bit-4 delay 1 = 2 ns - # bit-3 delay 1 = 1 ns - # bit-2 delay 1 = 0.5 ns - # bit-1 LNA on/off 1 = off - # bit-0 LED on/off 1 = on - # - # control word = 0 (signal - 30 db) - # control word = 2 (signal - 40 db) - # - def checkElements(self, mode, record_time, subband, - noise_low_deviation, noise_high_deviation, noise_max_diff, - rf_min_signal, rf_low_deviation, rf_high_deviation): - - logger.info("=== Start HBA element based tests ===") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - - self.set_mode(mode) - - n_rcus_off = 0 - for ctrl in ('128', '253'): - if ctrl == '128': ctrl_nr = 0 - elif ctrl == '253': ctrl_nr = 1 - - for elem in range(self.hba.tile[0].nr_elements): - logger.info("check elements %d, ctrlword=%s" %(elem+1, ctrl)) - - if self.db.checkEndTime(duration=45.0) == False: - logger.warn("check stopped, end time reached") - return - - if n_rcus_off > 0: - rsp_rcu_mode(mode=mode, rcus=self.hba.selectList()) - n_rcus_off = 0 - for tile in self.hba.tile: - if tile.element[elem].no_modem or tile.element[elem].modem_error: - self.turnOffTile(tile.nr) - n_rcus_off += 1 - logger.info("skip tile %d, modem error" %(tile.nr)) - - delay_str = ('2,'*elem + ctrl + ',' + '2,'*15)[:33] - rsp_hba_delay(delay=delay_str, rcus=self.hba.selectList()) - - clean = False - while not clean: - if self.db.checkEndTime(duration=(record_time+45.0)) == False: - logger.warn("check stopped, end time reached") - return - - clean = True - self.record_data(rec_time=record_time, new_data=True) - - clean, n_off = self.checkOscillationElements(elem) - n_rcus_off += n_off - if n_off > 0: continue - n_off = self.checkSpuriousElements(elem) - n_rcus_off += n_off - if n_off > 0: continue - self.checkNoiseElements(elem, noise_low_deviation, noise_high_deviation, noise_max_diff) - self.checkSignalElements(elem, ctrl_nr, subband, rf_min_signal, rf_low_deviation, rf_high_deviation) - - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down while testing, skip result") - return - - self.hba.element_check_done = 1 - self.db.addTestDone('E%d' %(mode)) - logger.info("=== Done HBA element tests ===") - return - - # check for oscillating tiles and turn off RCU - # stop one RCU each run - # elem counts from 0..15 (for user output use 1..16) - def checkOscillationElements(self, elem): - logger.info("--- oscillation test --") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - clean = True - n_rcus_off = 0 - # result is a sorted list on maxvalue - result = search_oscillation(data=self.rcudata, pol='XY', delta=3.0) - if len(result) > 1: - clean = False - rcu, peaks_sum, n_peaks, rcu_low = sorted(result[1:], reverse=True)[0] #result[1] - tile = rcu / 2 - if self.hba.tile[tile].element[elem].no_modem or self.hba.tile[tile].element[elem].modem_error: - return(True, 0) - tile_polarity = rcu % 2 - logger.info("RCU %d Tile %d Element %d Oscillation sum=%3.1f peaks=%d, low=%3.1f" %\ - (rcu, tile, elem+1, peaks_sum, n_peaks, rcu_low)) - self.turnOffTile(tile) - n_rcus_off += 1 - if tile_polarity == 0: - self.hba.tile[tile].element[elem].x.osc = 1 - else: - self.hba.tile[tile].element[elem].y.osc = 1 - return (clean, n_rcus_off) - - def checkSpuriousElements(self, elem): - logger.info("--- spurious test ---") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - n_rcus_off = 0 - # result is a sorted list on maxvalue - result = search_spurious(data=self.rcudata, pol='XY', delta=3.0) - for rcu in result: - tile = rcu / 2 - tile_polarity = rcu % 2 - logger.info("RCU %d Tile %d Element %d pol %d Spurious" %(rcu, tile, elem+1, tile_polarity)) - self.turnOffTile(tile) - n_rcus_off += 1 - if tile_polarity == 0: - self.hba.tile[tile].element[elem].x.spurious = 1 - else: - self.hba.tile[tile].element[elem].y.spurious = 1 - return (n_rcus_off) - - def checkNoiseElements(self, elem, low_deviation, high_deviation, max_diff): - logger.info("--- noise test ---") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - # result is a sorted list on maxvalue - low_noise, high_noise, jitter = search_noise(self.rcudata, 'XY', low_deviation, high_deviation, max_diff) - - for n in low_noise: - rcu, val, bad_secs, ref, diff = n - tile = rcu / 2 - logger.info("RCU %d Tile %d Element %d Low-Noise value=%3.1f bad=%d(%d) limit=%3.1f diff=%3.3f" %\ - (rcu, tile, elem+1, val, bad_secs, self.rcudata.frames, ref, diff)) - - if rcu%2 == 0: - elem_polarity = self.hba.tile[tile].element[elem].x - else: - elem_polarity = self.hba.tile[tile].element[elem].y - - elem_polarity.low_seconds += self.rcudata.frames - elem_polarity.low_bad_seconds += bad_secs - if val < elem_polarity.low_val: - elem_polarity.low_noise = 1 - elem_polarity.low_val = val - elem_polarity.low_ref = ref - elem_polarity.low_diff = diff - - for n in high_noise: - rcu, val, bad_secs, ref, diff = n - tile = rcu / 2 - logger.info("RCU %d Tile %d Element %d High-Noise value=%3.1f bad=%d(%d) ref=%3.1f diff=%3.1f" %\ - (rcu, tile, elem+1, val, bad_secs, self.rcudata.frames, ref, diff)) - - if rcu%2 == 0: - elem_polarity = self.hba.tile[tile].element[elem].x - else: - elem_polarity = self.hba.tile[tile].element[elem].y - - elem_polarity.high_seconds += self.rcudata.frames - elem_polarity.high_bad_seconds += bad_secs - if val > elem_polarity.high_val: - elem_polarity.high_noise = 1 - elem_polarity.high_val = val - elem_polarity.high_ref = ref - elem_polarity.high_diff = diff - - for n in jitter: - rcu, val, ref, bad_secs = n - tile = rcu / 2 - logger.info("RCU %d Tile %d Element %d Jitter, fluctuation=%3.1fdB normal=%3.1fdB" %\ - (rcu, tile, elem+1, val, ref)) - - if rcu%2 == 0: - elem_polarity = self.hba.tile[tile].element[elem].x - else: - elem_polarity = self.hba.tile[tile].element[elem].y - - elem_polarity.jitter_seconds += self.rcudata.frames - elem_polarity.jitter_bad_seconds += bad_secs - if val > elem_polarity.jitter_val: - elem_polarity.jitter = 1 - elem_polarity.jitter_val = val - elem_polarity.jitter_ref = ref - return - - def checkSignalElements(self, elem, ctrl_nr, subband, min_signal, low_deviation, high_deviation): - - logger.info("--- RF test ---") - if not checkActiveRSPDriver(): - logger.warn("RSPDriver down, skip test") - return - self.rcudata.searchTestSignal(subband=subband, minsignal=min_signal, maxsignal=120.0) - logger.info("HBA, X used test subband=%d avg_signal=%3.1f" %(self.rcudata.testSubband_X, self.rcudata.testSignal_X)) - logger.info("HBA, Y used test subband=%d avg_signal=%3.1f" %(self.rcudata.testSubband_Y, self.rcudata.testSignal_Y)) - - ssdataX = self.rcudata.getSubbandX() - ssdataY = self.rcudata.getSubbandY() - avgX = self.rcudata.testSignal_X - avgY = self.rcudata.testSignal_Y - minX = ssdataX.min() - minY = ssdataY.min() - - logger.debug("X data: min=%5.3f max=%5.3f avg=%5.3f" %(minX, ssdataX.max(), avgX)) - logger.debug("Y data: min=%5.3f max=%5.3f avg=%5.3f" %(minY, ssdataY.max(), avgY)) - - for tile in self.hba.tile: - if tile.x.rcu_off or tile.y.rcu_off: - logger.info("skip signal test for tile %d, RCUs are turned off" %(tile.nr)) - - if self.rcudata.testSubband_X == 0 or self.rcudata.testSubband_Y == 0: - logger.warn("HBA, No valid test signal") - for tile in self.hba.tile: - tile.element[elem].x.ref_signal[ctrl_nr] = 0 - tile.element[elem].y.ref_signal[ctrl_nr] = 0 - return - - for tile in self.hba.tile: - if tile.x.rcu_off or tile.y.rcu_off: - continue - tile.element[elem].x.ref_signal[ctrl_nr] = avgX - tile.element[elem].y.ref_signal[ctrl_nr] = avgY - tile.element[elem].x.test_subband[ctrl_nr] = self.rcudata.testSubband_X - tile.element[elem].y.test_subband[ctrl_nr] = self.rcudata.testSubband_Y - tile.element[elem].x.test_signal[ctrl_nr] = ssdataX[tile.nr] - tile.element[elem].y.test_signal[ctrl_nr] = ssdataY[tile.nr] - - #logger.debug("HBA Tile=%d Element=%d: X=%3.1fdB Y=%3.1fdB" %\ - # (tile.nr, elem+1, ssdataX[tile.nr], ssdataY[tile.nr])) - - loginfo = False - if ssdataX[tile.nr] < (avgX + low_deviation): - if ssdataX[tile.nr] < 2.0: - tile.element[elem].x.no_signal = 1 - elif ssdataX[tile.nr] > 55.0 and ssdataX[tile.nr] < 65.0: - tile.element[elem].no_power = 1 - else: - tile.element[elem].x.too_low = 1 - loginfo = True - - if ssdataX[tile.nr] > (avgX + high_deviation): - tile.element[elem].x.too_high = 1 - loginfo = True - - if ssdataY[tile.nr] < (avgY + low_deviation): - if ssdataY[tile.nr] < 2.0: - tile.element[elem].y.no_signal = 1 - elif ssdataY[tile.nr] > 55.0 and ssdataY[tile.nr] < 65.0: - tile.element[elem].no_power = 1 - else: - tile.element[elem].y.too_low = 1 - loginfo = True - - if ssdataY[tile.nr] > (avgY + high_deviation): - tile.element[elem].y.too_high = 1 - loginfo = True - - if loginfo: - logger.info("HBA Tile=%d Element=%d Error: X=%3.1fdB Y=%3.1fdB" %\ - (tile.nr, elem+1, ssdataX[tile.nr], ssdataY[tile.nr])) - return -#### end of cHBA class #### - - diff --git a/LCU/checkhardware/rtsm.py b/LCU/checkhardware/rtsm.py index 1f9d114fd00..a390bc979f3 100755 --- a/LCU/checkhardware/rtsm.py +++ b/LCU/checkhardware/rtsm.py @@ -2,235 +2,142 @@ check_version = '0714' -from threading import Thread import sys -import traceback import os -import numpy as np import time -#import datetime +import traceback +from socket import gethostname +from threading import Thread +import numpy as np + +os.umask(001) +os.nice(15) + +conf_file = r'/localhome/stationtest/config/check_hardware.conf' + +mainpath = r'/opt/stationtest' +maindatapath = r'/localhome/stationtest' +observationspath = r'/opt/lofar/var/run' +BEAMLETPATH = r'/localhome/data/Beamlets' + +confpath = os.path.join(maindatapath, 'config') +logpath = os.path.join(maindatapath, 'log') +rtsmpath = os.path.join(maindatapath, 'rtsm_data') + +# if not exists make path +if not os.access(logpath, os.F_OK): + os.mkdir(logpath) +if not os.access(rtsmpath, os.F_OK): + os.mkdir(rtsmpath) + +hostname = gethostname().split('.')[0].upper() + +# first start main logging before including checkhardware_lib import logging -mainPath = r'/opt/stationtest' -mainDataPath = r'/localhome/stationtest' -observationsPath = r'/opt/lofar/var/run' +# backup log files +for nr in xrange(8, -1, -1): + if nr == 0: + full_filename = os.path.join(logpath, '%s_rtsm.log' % hostname) + else: + full_filename = os.path.join(logpath, '%s_rtsm.log.%d' % (hostname, nr)) + full_filename_new = os.path.join(logpath, '%s_rtsm.log.%d' % (hostname, (nr + 1))) + if os.path.exists(full_filename): + os.rename(full_filename, full_filename_new) -beamletPath = r'/localhome/data/Beamlets' +# make and setup logger +logger = logging.getLogger('main') +logger.setLevel(logging.DEBUG) +# create file handler +filename = '%s_rtsm.log' % hostname +full_filename = os.path.join(logpath, filename) +file_handler = logging.FileHandler(full_filename, mode='w') +formatter = logging.Formatter('%(asctime)s %(name)-14s %(levelname)-8s %(message)s') +file_handler.setFormatter(formatter) +file_handler.setLevel(logging.DEBUG) +logger.addHandler(file_handler) +time.sleep(2.0) +logger.debug("logger is working") -libPath = os.path.join(mainPath, 'lib') -sys.path.insert(0, libPath) +# now include checkhardware library +from checkhardware_lib import * +from checkhardware_lib.spectrum_checks import * +from checkhardware_lib.data import AntennaData -confPath = os.path.join(mainDataPath, 'config') -logPath = os.path.join(mainDataPath, 'log') -rtsmPath = os.path.join(mainDataPath, 'rtsm_data') -from general_lib import * -from lofar_lib import * -from search_lib import * -from data_lib import * +def lba_mode(mode): + return mode in (1, 2, 3, 4) -os.umask(001) -os.nice(15) -# make path if not exists -if not os.access(logPath, os.F_OK): - os.mkdir(logPath) -if not os.access(rtsmPath, os.F_OK): - os.mkdir(rtsmPath) - -logger = None - -def lbaMode(mode): - if mode in (1, 2, 3, 4): - return (True) - return (False) - -def lbaLowMode(mode): - if mode in (1, 2): - return (True) - return (False) - -def lbaHighMode(mode): - if mode in (3, 4): - return (True) - return (False) - -def hbaMode(mode): - if mode in (5, 6, 7): - return (True) - return (False) - -def checkStr(key): - checks = dict({'OSC':"Oscillation", 'HN':"High-noise", 'LN':"Low-noise", 'J':"Jitter", 'SN':"Summator-noise",\ - 'CR':"Cable-reflection", 'M':"Modem-failure", 'DOWN':"Antenna-fallen", 'SHIFT':"Shifted-band"}) - return (checks.get(key, 'Unknown')) - -def printHelp(): - print "----------------------------------------------------------------------------" - print "Usage of arguments" - print - print "Set logging level, can be: debug|info|warning|error" - print "-ls=debug : print all information on screen, default=info" - print "-lf=info : print debug|warning|error information to log file, default=debug" - print - - print "----------------------------------------------------------------------------" - - -def getArguments(): - args = dict() - key = '' - value = '-' - for arg in sys.argv[1:]: - if arg[0] == '-': - opt = arg[1:].upper() - valpos = opt.find('=') - if valpos != -1: - key, value = opt.strip().split('=') - else: - key, value = opt, '-' +def lba_low_mode(mode): + return mode in (1, 2) - if key in ('H','LS','LF'): - if value != '-': - args[key] = value - else: - args[key] = '-' - else: - sys.exit("Unknown key %s" %(key)) - return (args) + +def lba_high_mode(mode): + return mode in (3, 4) + + +def hba_mode(mode): + return mode in (5, 6, 7) + + +def abbr_to_str(key): + checks = {'OSC': "Oscillation", 'HN': "High-noise", 'LN': "Low-noise", 'J': "Jitter", 'SN': "Summator-noise", + 'CR': "Cable-reflection", 'M': "Modem-failure", 'DOWN': "Antenna-fallen", 'SHIFT': "Shifted-band"} + return checks.get(key, 'Unknown') # get and unpack configuration file -class cConfiguration: +class Configuration: def __init__(self): self.conf = dict() - full_filename = os.path.join(confPath, 'checkHardware.conf') + full_filename = os.path.join(confpath, 'checkHardware.conf') f = open(full_filename, 'r') data = f.readlines() f.close() for line in data: - if line[0] in ('#','\n',' '): + if line[0] in ('#', '\n', ' '): continue if line.find('#') > 0: line = line[:line.find('#')] try: key, value = line.strip().split('=') - key = key.replace('_','-') + key = key.replace('_', '-') self.conf[key] = value + except ValueError: + print "Not a valid key, value pair: %s" % line except: - print "Not a valid configuration setting: %s" %(line) - - def getInt(self,key, default=0): - return (int(self.conf.get(key, str(default)))) - - def getFloat(self,key, default=0.0): - return (float(self.conf.get(key, str(default)))) - - def getStr(self,key): - return (self.conf.get(key, '')) - - -# setup default python logging system -# logstream for screen output -# filestream for program log file -def init_logging(args): - log_levels = {'DEBUG' : logging.DEBUG, - 'INFO' : logging.INFO, - 'WARNING': logging.WARNING, - 'ERROR' : logging.ERROR} - - try: - screen_log_level = args.get('LS', 'INFO') - file_log_level = args.get('LF', 'DEBUG') - except: - print "Not a legal log level, try again" - sys.exit(-1) - - station = getHostName() - - # create logger - _logger = logging.getLogger() - _logger.setLevel(logging.DEBUG) - - # create file handler - filename = '%s_rtsm.log' %(getHostName()) - full_filename = os.path.join(logPath, filename) - file_handler = logging.FileHandler(full_filename, mode='w') - formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') - file_handler.setFormatter(formatter) - file_handler.setLevel(log_levels[file_log_level]) - _logger.addHandler(file_handler) - - if (len(_logger.handlers) == 1) and ('LS' in args): - # create console handler - stream_handler = logging.StreamHandler() - fmt = '%s %%(levelname)-8s %%(message)s' %(station) - formatter = logging.Formatter(fmt) - stream_handler.setFormatter(formatter) - stream_handler.setLevel(log_levels[screen_log_level]) - _logger.addHandler(stream_handler) - return (_logger) - -def getRcuMode(n_rcus): -# RCU[ 0].control=0x10337a9c => ON, mode:3, delay=28, att=06 - rcumode = -1 - rcu_info = {} - answer = rspctl("--rcu") - if answer.count('mode:') == n_rcus: - for line in answer.splitlines(): - if line.find('mode:') == -1: - continue - rcu = line[line.find('[')+1 : line.find(']')].strip() - state = line[line.find('=>')+2 : line.find(',')].strip() - mode = line[line.find('mode:')+5] - if rcu.isdigit() and state in ("OFF", "ON") and mode.isdigit(): - rcu_info[int(rcu)] = (state, int(mode)) - - for mode in range(8): - mode_cnt = answer.count("mode:%d" %(mode)) - if mode == 0: - if mode_cnt == n_rcus: - logger.debug("Not observing") - elif mode_cnt > (n_rcus / 3) and answer.count("mode:0") == (n_rcus - mode_cnt): - logger.debug("Now observing in rcumode %d" %(mode)) - rcumode = mode - return (rcumode, rcu_info) - -def getAntPol(rcumode, rcu): - pol_str = ('X','Y') - ant = rcu / 2 - if rcumode == 1: - pol_str = ('Y','X') - ant += 48 - pol = pol_str[rcu % 2] - return (ant, pol) + raise + + def get_int(self, key, default=0): + return int(self.conf.get(key, str(default))) + + def get_float(self, key, default=0.0): + return float(self.conf.get(key, str(default))) + + def get_str(self, key): + return self.conf.get(key, '') + class CSV: - station = "" - obs_id = "" - filename = "" - rcu_mode = 0 - record_timestamp = 0 - @staticmethod - def setObsID(obs_id): - CSV.station = getHostName() - CSV.obs_id = obs_id - CSV.filename = "%s_%s_open.dat" %(CSV.station, CSV.obs_id) - CSV.rcu_mode = 0 - CSV.rec_timestamp = 0 - CSV.writeHeader() - return - @staticmethod - def setRcuMode(rcumode): - CSV.rcu_mode = rcumode + def __init__(self, obsid): + self.obsid = obsid + self.station = hostname + self.filename = "%s_%s_open.dat" %(self.station, self.obsid) + self.rcumode = 0 + self.record_timestamp = 0 + self.write_header() + + def set_rcu_mode(self, rcumode): + self.rcumode = rcumode return - @staticmethod - def setRecordTimestamp(timestamp): - CSV.record_timestamp = timestamp + + def set_record_timestamp(self, timestamp): + self.record_timestamp = timestamp return - @staticmethod - def writeHeader(): - full_filename = os.path.join(rtsmPath, CSV.filename) + + def write_header(self): + full_filename = os.path.join(rtsmpath, self.filename) # write only if new file if not os.path.exists(full_filename): f = open(full_filename, 'w') @@ -239,37 +146,38 @@ class CSV: f.flush() f.close() return - @staticmethod - def writeSpectra(data, rcu, check): - #dumpTime = time.gmtime(CSV.record_timestamp) - #date_str = time.strftime("%Y%m%d", dumpTime) - full_filename = os.path.join(rtsmPath, CSV.filename) + def write_spectra(self, data, rcu, check): + # dumpTime = time.gmtime(self.record_timestamp) + # date_str = time.strftime("%Y%m%d", dumpTime) - logger.debug("start dumping data to %s" %(full_filename)) + full_filename = os.path.join(rtsmpath, self.filename) + + logger.debug("start dumping data to %s" % full_filename) f = open(full_filename, 'a') - if CSV.rcu_mode in (1, 2, 3, 4): - freq = (0 , 100) - elif CSV.rcu_mode in (5,): + freq = (0, 0) + if self.rcumode in (1, 2, 3, 4): + freq = (0 , 100) + elif self.rcumode in (5,): freq = (100, 200) - elif CSV.rcu_mode in (6,): + elif self.rcumode in (6,): freq = (160, 240) - elif CSV.rcu_mode in (7,): + elif self.rcumode in (7,): freq = (200, 300) - spectra_info = "SPECTRA-INFO=%d,%d,%s,%s,%d,%d,%f\n" %\ - (rcu, CSV.rcu_mode, CSV.obs_id, check, freq[0], freq[1], CSV.record_timestamp) - + spectra_info = "SPECTRA-INFO=%d,%d,%s,%s,%d,%d,%f\n" % ( + rcu, self.rcumode, self.obsid, check, freq[0], freq[1], self.record_timestamp) mean_spectra = "MEAN-SPECTRA=[" - for i in np.nan_to_num(data.getMeanSpectra(rcu%2)): - mean_spectra += "%3.1f " %(i) + for i in data.median_all_spectras(freq_band=mode_to_band(self.rcumode), polarity=data.polarity(rcu), masked=True): + mean_spectra += "%3.1f " % np.nan_to_num(i) mean_spectra += "]\n" bad_spectra = "BAD-SPECTRA=[" - for i in np.nan_to_num(data.getSpectra(rcu)): - bad_spectra += "%3.1f " %(i) + for i in data.rcu_mean_spectra(rcu=rcu, masked=True): + #logger.debug("BAD-SPECTRA=%s" % str(i)) + bad_spectra += "%3.1f " % np.nan_to_num(i) bad_spectra += "]\n\n" f.write(spectra_info) @@ -278,222 +186,282 @@ class CSV: f.close() return - @staticmethod - def writeInfo(start_time, stop_time, obsid_samples): - full_filename = os.path.join(rtsmPath, CSV.filename) - logger.debug("add obs_info to %s" %(full_filename)) + + def write_info(self, start_time, stop_time, obsid_samples): + full_filename = os.path.join(rtsmpath, self.filename) + logger.debug("add obs_info to %s" % full_filename) f = open(full_filename, 'a') - f.write('# OBS-ID-INFO=obs_id,start_time,stop_time,obsid_samples\n') - f.write('OBS-ID-INFO=%s,%5.3f,%5.3f,%d\n\n' %(CSV.obs_id, start_time, stop_time, obsid_samples)) + f.write('# OBS-ID-INFO=obsid,start_time,stop_time,obsid_samples\n') + f.write('OBS-ID-INFO=%s,%5.3f,%5.3f,%d\n\n' % (self.obsid, start_time, stop_time, obsid_samples)) f.flush() f.close() return - @staticmethod - def closeFile(): - full_filename = os.path.join(rtsmPath, CSV.filename) - filename_new = CSV.filename.replace('open','closed') - full_filename_new = os.path.join(rtsmPath, filename_new) - logger.debug("rename file from %s to %s" %(full_filename, full_filename_new)) + + def close_file(self): + full_filename = os.path.join(rtsmpath, self.filename) + filename_new = self.filename.replace('open', 'closed') + full_filename_new = os.path.join(rtsmpath, filename_new) + logger.debug("rename file from %s to %s" % (full_filename, full_filename_new)) os.rename(full_filename, full_filename_new) - CSV.obs_id = "" - CSV.filename = "" + self.obsid = "" + self.filename = "" return -def checkForOscillation(data, rcumode, error_list, delta): + +def check_oscillation(data, band, error_list, settings, cvs): logger.debug("start oscillation check") - for pol_nr, pol in enumerate(('X', 'Y')): - #test_data = data.getAll()[:,:1,:] - result = search_oscillation(data, pol, delta) + for pol in ('X', 'Y'): + # test_data = data.getAll()[:,:1,:] + result = check_for_oscillation(data, band, pol, settings) if len(result) > 1: # get mean values from all rcu's (rcu = -1) - bin_nr, ref_max_sum, ref_n_peaks, ref_rcu_low = result[0] + rcu, ref_max_sum, ref_n_peaks, ref_rcu_low = result[0] - #rcu, max_sum, n_peaks, rcu_low = sorted(result[1:], reverse=True)[0] + # rcu, max_sum, n_peaks, rcu_low = sorted(result[1:], reverse=True)[0] if len(result) == 2: - bin_nr, max_sum, n_peaks, rcu_low = result[1] + rcu, max_sum, n_peaks, rcu_low = result[1] else: ref_low = result[0][3] max_low_rcu = (-1, -1) max_sum_rcu = (-1, -1) for i in result[1:]: - bin_nr, max_sum, n_peaks, rcu_low = i - if max_sum > max_sum_rcu[0]: max_sum_rcu = (max_sum, bin_nr) - if (rcu_low - ref_low) > max_low_rcu[0]: max_low_rcu = (rcu_low, bin_nr) + rcu, max_sum, n_peaks, rcu_low = i + if max_sum > max_sum_rcu[0]: + max_sum_rcu = (max_sum, rcu) + if (rcu_low - ref_low) > max_low_rcu[0]: + max_low_rcu = (rcu_low, rcu) - rcu_low, bin_nr = max_low_rcu + rcu_low, rcu = max_low_rcu - rcu = (bin_nr * 2) + pol_nr - ant, pol = getAntPol(rcumode, rcu) + ant = data.antenna(rcu) + mode = data.mode(rcu) - if lbaMode(rcumode): - logger.info("Mode-%d RCU-%03d Ant-%03d %c Oscillation, sum=%3.1f(%3.1f) peaks=%d(%d) low=%3.1fdB(%3.1f) (=ref)" %\ - (rcumode, rcu, ant, pol, max_sum, ref_max_sum, n_peaks, ref_n_peaks, rcu_low, ref_rcu_low)) + if lba_mode(mode): + logger.info("Mode-%d RCU-%03d Ant-%03d %c " + "Oscillation, sum=%3.1f(%3.1f) peaks=%d(%d) low=%3.1fdB(%3.1f) (=ref)" % ( + mode, rcu, ant, pol, max_sum, ref_max_sum, n_peaks, ref_n_peaks, rcu_low, ref_rcu_low)) if rcu not in error_list: error_list.append(rcu) - CSV.writeSpectra(data, rcu, "OSC") - - if hbaMode(rcumode): - if ((max_sum > 5000.0) or (n_peaks > 40)): - logger.info("Mode-%d RCU-%03d Tile-%02d %c Oscillation, sum=%3.1f(%3.1f) peaks=%d(%d) low=%3.1fdB(%3.1f) ref=()" %\ - (rcumode, rcu, ant, pol, max_sum, ref_max_sum, n_peaks, ref_n_peaks, rcu_low, ref_rcu_low)) + cvs.set_rcu_mode(mode) + cvs.write_spectra(data, rcu, "OSC") + + if hba_mode(mode): + if max_sum > 5000.0 or n_peaks > 40: + logger.info("Mode-%d RCU-%03d Tile-%02d %c " + "Oscillation, sum=%3.1f(%3.1f) peaks=%d(%d) low=%3.1fdB(%3.1f) ref=()" % ( + mode, rcu, ant, pol, max_sum, ref_max_sum, n_peaks, ref_n_peaks, rcu_low, ref_rcu_low)) if rcu not in error_list: error_list.append(rcu) - CSV.writeSpectra(data, rcu, "OSC") + cvs.set_rcu_mode(mode) + cvs.write_spectra(data, rcu, "OSC") return -def checkForNoise(data, rcumode, error_list, low_deviation, high_deviation, max_diff): + +def check_noise(data, band, error_list, settings, cvs): logger.debug("start noise check") - for pol_nr, pol in enumerate(('X', 'Y')): - low_noise, high_noise, jitter = search_noise(data, pol, low_deviation, high_deviation*1.5, max_diff) + for pol in ('X', 'Y'): + low_noise, high_noise, jitter = check_for_noise(data, band, pol, settings) for err in high_noise: - bin_nr, val, bad_secs, ref, diff = err - rcu = (bin_nr * 2) + pol_nr - ant, pol = getAntPol(rcumode, rcu) - if lbaMode(rcumode): - logger.info("Mode-%d RCU-%03d Ant-%03d %c High-noise, value=%3.1fdB bad=%d(%d) limit=%3.1fdB diff=%3.1fdB" %\ - (rcumode, rcu, ant, pol, val, bad_secs, data.frames, ref, diff)) - if rcu not in error_list: - error_list.append(rcu) - CSV.writeSpectra(data, rcu, "HN") + rcu, val, bad_secs, ref, diff = err - if hbaMode(rcumode): - logger.info("Mode-%d RCU-%03d Tile-%02d %c High-noise, value=%3.1fdB bad=%d(%d) limit=%3.1fdB diff=%3.1fdB" %\ - (rcumode, rcu, ant, pol, val, bad_secs, data.frames, ref, diff)) - if rcu not in error_list: - error_list.append(rcu) - CSV.writeSpectra(data, rcu, "HN") + ant = data.antenna(rcu) + mode = data.mode(rcu) + + if lba_mode(mode): + logger.info("Mode-%d RCU-%03d Ant-%03d %c " + "High-noise, value=%3.1fdB bad=%d(%d) limit=%3.1fdB diff=%3.1fdB" % ( + mode, rcu, ant, pol, val, bad_secs, data.seconds(), ref, diff)) + + if hba_mode(mode): + logger.info("Mode-%d RCU-%03d Tile-%02d %c " + "High-noise, value=%3.1fdB bad=%d(%d) limit=%3.1fdB diff=%3.1fdB" % ( + mode, rcu, ant, pol, val, bad_secs, data.seconds(), ref, diff)) + + if rcu not in error_list: + error_list.append(rcu) + cvs.set_rcu_mode(mode) + cvs.write_spectra(data, rcu, "HN") for err in low_noise: - bin_nr, val, bad_secs, ref, diff = err - rcu = (bin_nr * 2) + pol_nr - ant, pol = getAntPol(rcumode, rcu) - if lbaMode(rcumode): - logger.info("Mode-%d RCU-%03d Ant-%03d %c Low-noise, value=%3.1fdB bad=%d(%d) limit=%3.1fdB diff=%3.1fdB" %\ - (rcumode, rcu, ant, pol, val, bad_secs, data.frames, ref, diff)) - if rcu not in error_list: - error_list.append(rcu) - CSV.writeSpectra(data, rcu, "LN") + rcu, val, bad_secs, ref, diff = err - if hbaMode(rcumode): - logger.info("Mode-%d RCU-%03d Tile-%02d %c Low-noise, value=%3.1fdB bad=%d(%d) limit=%3.1fdB diff=%3.1fdB" %\ - (rcumode, rcu, ant, pol, val, bad_secs, data.frames, ref, diff)) - if rcu not in error_list: - error_list.append(rcu) - CSV.writeSpectra(data, rcu, "LN") + ant = data.antenna(rcu) + mode = data.mode(rcu) + + if lba_mode(mode): + logger.info("Mode-%d RCU-%03d Ant-%03d %c " + "Low-noise, value=%3.1fdB bad=%d(%d) limit=%3.1fdB diff=%3.1fdB" % ( + mode, rcu, ant, pol, val, bad_secs, data.seconds(), ref, diff)) + + if hba_mode(mode): + logger.info("Mode-%d RCU-%03d Tile-%02d %c " + "Low-noise, value=%3.1fdB bad=%d(%d) limit=%3.1fdB diff=%3.1fdB" % ( + mode, rcu, ant, pol, val, bad_secs, data.seconds(), ref, diff)) + + if rcu not in error_list: + error_list.append(rcu) + cvs.set_rcu_mode(mode) + cvs.write_spectra(data, rcu, "LN") return -def checkForSummatorNoise(data, rcumode, error_list): + +def check_summator_noise(data, band, error_list, settings, cvs): logger.debug("start summator-noise check") - for pol_nr, pol in enumerate(('X', 'Y')): + for pol in ('X', 'Y'): # sn=SummatorNoise cr=CableReflections - sn, cr = search_summator_noise(data=data, pol=pol, min_peak=2.0) + sn = check_for_summator_noise(data=data, band=band, pol=pol, parset=settings) + cr = check_for_cable_reflection(data=data, band=band, pol=pol, parset=settings) for msg in sn: - bin_nr, peaks, max_peaks = msg - rcu = (bin_nr * 2) + pol_nr - tile, pol = getAntPol(rcumode, rcu) - logger.info("Mode-%d RCU-%03d Tile-%02d %c Summator-noise, cnt=%d peaks=%d" %\ - (rcumode, rcu, tile, pol, peaks, max_peaks)) + rcu, peaks, max_peaks = msg + + tile = data.antenna(rcu) + mode = data.mode(rcu) + + logger.info("Mode-%d RCU-%03d Tile-%02d %c " + "Summator-noise, cnt=%d peaks=%d" % ( + mode, rcu, tile, pol, peaks, max_peaks)) + if rcu not in error_list: error_list.append(rcu) - CSV.writeSpectra(data, rcu, "SN") + cvs.set_rcu_mode(mode) + cvs.write_spectra(data, rcu, "SN") + for msg in cr: - bin_nr, peaks, max_peaks = msg - rcu = (bin_nr * 2) + pol_nr - tile, pol = getAntPol(rcumode, rcu) - logger.info("Mode-%d RCU-%03d Tile-%02d %c Cable-reflections, cnt=%d peaks=%d" %\ - (rcumode, rcu, tile, pol, peaks, max_peaks)) - #if rcu not in error_list: - #error_list.append(rcu) - #CSV.writeSpectra(data, rcu, "CR") + rcu, peaks, max_peaks = msg + + tile = data.antenna(rcu) + mode = data.mode(rcu) + + logger.info("Mode-%d RCU-%03d Tile-%02d %c " + "Cable-reflections, cnt=%d peaks=%d" % ( + mode, rcu, tile, pol, peaks, max_peaks)) + # if rcu not in error_list: + # error_list.append(rcu) + # cvs.set_rcu_mode(mode) + # cvs.write_spectra(data, rcu, "CR") return -def checkForDown(data, rcumode, error_list, subband): + +def check_down(data, band, error_list, settings, cvs): logger.debug("start down check") - down, shifted = searchDown(data, subband) + down, shifted = check_for_down(data, band, settings) + + ref_max_sb = 0 + ref_max_val = 0.0 + ref_max_bw = 0 + ref_band_pwr = 0.0 + for msg in down: - ant, max_x_sb, max_y_sb, mean_max_sb = msg - rcu = ant * 2 - max_x_offset = max_x_sb - mean_max_sb - max_y_offset = max_y_sb - mean_max_sb - ant, pol = getAntPol(rcumode, rcu) - logger.info("Mode-%d RCU-%02d/%02d Ant-%02d Down, x-offset=%d y-offset=%d" %\ - (rcumode, rcu, (rcu+1), ant, max_x_offset, max_y_offset)) + rcu, max_sb, max_val, max_bw, band_pwr = msg + if rcu == 'Xref': + logger.info("down test X rcus's median values: sb=%d, pwr=%3.1fdB, bw=%d, band-pwr=%3.1fdB" % ( + max_sb, max_val, max_bw, band_pwr)) + continue + if rcu == 'Yref': + logger.info("down test Y rcu's median values: sb=%d, pwr=%3.1fdB, bw=%d, band-pwr=%3.1fdB" % ( + max_sb, max_val, max_bw, band_pwr)) + continue + + max_offset = 292 - max_sb + + ant = data.antenna(rcu) + mode = data.mode(rcu) + pol = data.polarity(rcu) + + logger.info("Mode-%d RCU-%02d Ant-%02d %s Down: sb=%d, pwr=%3.1fdB, bw=%d, band-pwr=%3.1fdB" % ( + mode, rcu, ant, pol, max_sb, max_val, max_bw, band_pwr)) + if rcu not in error_list: error_list.append(rcu) - error_list.append(rcu+1) - CSV.writeSpectra(data, rcu, "DOWN") - CSV.writeSpectra(data, rcu+1, "DOWN") + cvs.set_rcu_mode(mode) + cvs.write_spectra(data, rcu, "DOWN") return -def checkForFlat(data, rcumode, error_list): + +def check_flat(data, band, error_list, settings, cvs): logger.debug("start flat check") - flat = searchFlat(data) + flat = check_for_flat(data, band, settings) for msg in flat: rcu, mean_val = msg - ant, pol = getAntPol(rcumode, rcu) - logger.info("Mode-%d RCU-%02d Ant-%02d Flat, value=%5.1fdB" %\ - (rcumode, rcu, ant, mean_val)) + + ant = data.antenna(rcu) + mode = data.mode(rcu) + pol = data.polarity(rcu) + + logger.info("Mode-%d RCU-%02d Ant-%02d %s " + "Flat, value=%5.1fdB" % ( + mode, rcu, ant, pol, mean_val)) if rcu not in error_list: error_list.append(rcu) - CSV.writeSpectra(data, rcu, "FLAT") + cvs.set_rcu_mode(mode) + cvs.write_spectra(data, rcu, "FLAT") return -def checkForShort(data, rcumode, error_list): + +def check_short(data, band, error_list, settings, cvs): logger.debug("start short check") - short = searchShort(data) + short = check_for_short(data, band, settings) for msg in short: rcu, mean_val = msg - ant, pol = getAntPol(rcumode, rcu) - logger.info("Mode-%d RCU-%02d Ant-%02d Short, value=%5.1fdB" %\ - (rcumode, rcu, ant, mean_val)) + + ant = data.antenna(rcu) + mode = data.mode(rcu) + pol = data.polarity(rcu) + + logger.info("Mode-%d RCU-%02d Ant-%02d %s " + "Short, value=%5.1fdB" % ( + mode, rcu, ant, pol, mean_val)) if rcu not in error_list: error_list.append(rcu) - CSV.writeSpectra(data, rcu, "SHORT") + cvs.set_rcu_mode(mode) + cvs.write_spectra(data, rcu, "SHORT") return -def closeAllOpenFiles(): - files = os.listdir(rtsmPath) + +def close_all_open_files(): + files = os.listdir(rtsmpath) for filename in files: if filename.find('open') > -1: - full_filename = os.path.join(rtsmPath, filename) - filename_new = filename.replace('open','closed') - full_filename_new = os.path.join(rtsmPath, filename_new) + full_filename = os.path.join(rtsmpath, filename) + filename_new = filename.replace('open', 'closed') + full_filename_new = os.path.join(rtsmpath, filename_new) os.rename(full_filename, full_filename_new) return -class cDayInfo: + +class DayInfo: def __init__(self): self.date = time.strftime("%Y%m%d", time.gmtime(time.time())) - self.filename = "%s_%s_dayinfo.dat" %(getHostName(), self.date) - self.samples = [0,0,0,0,0,0,0] # RCU-mode 1..7 + self.filename = "%s_%s_dayinfo.dat" % (hostname, self.date) + self.samples = [0, 0, 0, 0, 0, 0, 0] # RCU-mode 1..7 self.obs_info = list() - self.deleteOldDays() - self.readFile() + self.delete_old_days() + self.read_file() - def addSample(self, rcumode=-1): + def add_sample(self, rcumode=-1): date = time.strftime("%Y%m%d", time.gmtime(time.time())) # new day reset data and set new filename if self.date != date: self.date = date self.reset() - if rcumode in range(1,8,1): - self.samples[rcumode-1] += 1 - self.writeFile() + if rcumode in range(1, 8, 1): + self.samples[rcumode - 1] += 1 + self.write_file() - def addObsInfo(self, obs_id, start_time, stop_time, rcu_mode, samples): + def add_obs_info(self, obs_id, start_time, stop_time, rcu_mode, samples): self.obs_info.append([obs_id, start_time, stop_time, rcu_mode, samples]) def reset(self): - self.filename = "%s_%s_dayinfo.dat" %(getHostName(), self.date) - self.samples = [0,0,0,0,0,0,0] # RCU-mode 1..7 + self.filename = "%s_%s_dayinfo.dat" % (hostname, self.date) + self.samples = [0, 0, 0, 0, 0, 0, 0] # RCU-mode 1..7 self.obs_info = list() - self.deleteOldDays() + self.delete_old_days() # after a restart, earlier data is imported - def readFile(self): - full_filename = os.path.join(rtsmPath, self.filename) + def read_file(self): + full_filename = os.path.join(rtsmpath, self.filename) if os.path.exists(full_filename): f = open(full_filename, 'r') lines = f.readlines() @@ -501,319 +469,308 @@ class cDayInfo: for line in lines: if len(line.strip()) == 0 or line.strip()[0] == '#': continue - key,data = line.split('=') + key, data = line.split('=') if key == 'DAY-INFO': self.samples = [int(i) for i in data.split(',')[1:]] if key == 'OBSID-INFO': d = data.split(',') - self.obs_info.append([d[0],float(d[1]),float(d[2]),int(d[3]), int(d[4])]) + self.obs_info.append([d[0], float(d[1]), float(d[2]), int(d[3]), int(d[4])]) # rewrite file every sample - def writeFile(self): - full_filename = os.path.join(rtsmPath, self.filename) + def write_file(self): + full_filename = os.path.join(rtsmpath, self.filename) f = open(full_filename, 'w') f.write('#DAY-INFO date,M1,M2,M3,M4,M5,M6,M7\n') - f.write('DAY-INFO=%s,%d,%d,%d,%d,%d,%d,%d\n' %\ - (self.date, self.samples[0], self.samples[1], self.samples[2], self.samples[3], self.samples[4], self.samples[5], self.samples[6])) + f.write('DAY-INFO=%s,%d,%d,%d,%d,%d,%d,%d\n' % ( + self.date, self.samples[0], self.samples[1], self.samples[2], self.samples[3], + self.samples[4], self.samples[5], self.samples[6])) f.write('\n#OBS-ID-INFO obs_id, start_time, stop_time, rcu_mode, samples\n') for i in self.obs_info: - f.write('OBS-ID-INFO=%s,%5.3f,%5.3f,%d,%d\n' %\ - (i[0],i[1],i[2],i[3],i[4])) + f.write('OBS-ID-INFO=%s,%5.3f,%5.3f,%d,%d\n' % ( + i[0], i[1], i[2], i[3], i[4])) f.close() - def deleteOldDays(self): - files = os.listdir(rtsmPath) + def delete_old_days(self): + files = os.listdir(rtsmpath) backup = True for filename in files: if filename.find('closed') != -1: backup = False - if backup == True: + if backup: for filename in files: if filename.find('dayinfo') != -1: if filename.split('.')[0].split('_')[1] != self.date: - full_filename = os.path.join(rtsmPath, filename) + full_filename = os.path.join(rtsmpath, filename) os.remove(full_filename) - -def getObsId(): - #obs_start_str = "" - #obs_stop_str = "" - #obs_start_time = 0.0 - #obs_stop_time = 0.0 - obsids = "" - answer = sendCmd('swlevel') + +def get_obs_id(): + """ check swlevel for active obsid, + return: list with active obsids + """ + obsids = [] + answer = run_cmd('swlevel') if answer.find("ObsID") > -1: - s1 = answer.find("ObsID:")+6 + s1 = answer.find("ObsID:") + 6 s2 = answer.find("]") obsids = answer[s1:s2].strip().split() - return (obsids) + return obsids + - -def getObsIdInfo(obsid): - filename = "Observation%s" %(obsid.strip()) - fullfilename = os.path.join(observationsPath, filename) +def get_obs_id_info(obsid): + filename = "Observation%s" % obsid.strip() + fullfilename = os.path.join(observationspath, filename) f = open(fullfilename, 'r') obsinfo = f.read() f.close() + + m1 = obsinfo.find("Observation.receiverList") + m2 = obsinfo.find("\n", m1) + obs_rcu_str = obsinfo[m1:m2].split("=")[1].strip() + #print obs_rcu_str + obs_rcu_list = extract_select_str(obs_rcu_str[1:-1]) + m1 = obsinfo.find("Observation.startTime") m2 = obsinfo.find("\n", m1) - obs_start_str = obsinfo[m1:m2].split("=")[1].strip() + obs_start_str = obsinfo[m1:m2].split("=")[1].strip() + #print obs_start_str obs_start_time = time.mktime(time.strptime(obs_start_str, "%Y-%m-%d %H:%M:%S")) - - m1 = obsinfo.find("Observation.stopTime",m2) + + m1 = obsinfo.find("Observation.stopTime") m2 = obsinfo.find("\n", m1) - obs_stop_str = obsinfo[m1:m2].split("=")[1].strip() + obs_stop_str = obsinfo[m1:m2].split("=")[1].strip() + #print obs_stop_str obs_stop_time = time.mktime(time.strptime(obs_stop_str, "%Y-%m-%d %H:%M:%S")) - - logger.debug("obsid %s %s .. %s" %(obsid, obs_start_str, obs_stop_str)) - return(obsid, obs_start_time, obs_stop_time) + + logger.debug("obsid=%s time=%s..%s rcus=%s" % (obsid, obs_start_str, obs_stop_str, obs_rcu_str)) + #print obs_start_time, obs_stop_time, time.time() + return obsid, obs_start_time, obs_stop_time, obs_rcu_list + class RecordBeamletStatistics(Thread): - def __init__(self): + def __init__(self, obsid, starttime, duration): Thread.__init__(self) self.running = False - self.reset() - - def reset(self): - self.dump_dir = '' - self.obsid = '' - self.duration = 0 - - def set_obsid(self, obsid): - self.dump_dir = os.path.join(beamletPath, obsid) + self.obsid = obsid + self.starttime = starttime + self.duration = duration + self.dump_dir = os.path.join(BEAMLETPATH, self.obsid) try: os.mkdir(self.dump_dir) + self.ready = True + except OSError: + self.ready = False except: - pass - self.obsid = obsid - - def set_duration(self, duration): - self.duration = duration - + raise + def is_running(self): return self.running - + def kill_recording(self): if self.running: logger.debug("kill recording beamlet statistics") - sendCmd(cmd='pkill', args='rspctl') + run_cmd(cmd='killall rspctl') logger.debug("recording killed") - #self.running = False - #self.make_plots() - - def make_plots(self): - if self.obsid: - try: - response = sendCmd(cmd='/home/fallows/inspect_bsts.bash', args=self.obsid) - logger.debug('response "inspect.bsts.bash" = {%s}' % response) - except: - logger.debug('exception while running "inspect.bsts.bash"') - self.reset() - + def run(self): + sleeptime = self.starttime - time.time() + if sleeptime > 0.0: + time.sleep(sleeptime) if self.duration: self.running = True logger.debug("start recording beamlet statistics for %d seconds" % self.duration) rspctl('--statistics=beamlet --duration=%d --integration=1 --directory=%s' % (self.duration, self.dump_dir)) logger.debug("recording done") - self.make_plots() self.running = False - + def main(): - global logger - obs_id = "" - active_obs_id = "" - rcumode = 0 - #station = getHostName() - DI = cDayInfo() - - args = getArguments() - if args.has_key('H'): - printHelp() - sys.exit() - - logger = init_logging(args) - init_lofar_lib() - init_data_lib() + obs_id = "" + active_obs_id = "" + rcumode = 0 + dayinfo = DayInfo() - conf = cConfiguration() + init_lofar_lib() - #StID = getHostName() + # get test settings from configuration file + conf = TestSettings(filename=conf_file) logger.info('== Start rtsm (Real Time Station Monitor) ==') - removeAllDataFiles() - # Read in RemoteStation.conf - ID, nRSP, nTBB, nLBL, nLBH, nHBA, HBA_SPLIT = readStationConfig() + st_id, n_rsp, n_tbb, n_lbl, n_lbh, n_hba, hba_split = read_station_config() - n_rcus = nRSP * 8 + n_rcus = n_rsp * 8 - data = cRCUdata(n_rcus) + data = AntennaData({'n_rcus': n_rcus, 'data-dir': data_dir()}) - obs_start_time = 0 - obs_stop_time = 0 - obsid_samples = 0 - - beamlet_recording = RecordBeamletStatistics() - - + obs_info = {} + obs_info_to_delete = [] + + close_all_open_files() + + sleep_counter = 0 while True: try: - # get active obsid from swlevel - obsids = getObsId() - time_now = time.time() - # stop if no more obsids or observation is stoped - if obs_stop_time > 0.0: - if active_obs_id not in obsids or len(obsids) == 0 or time_now > obs_stop_time: - logger.debug("save obs_id %s" %(obs_id)) - DI.addObsInfo(obs_id, obs_start_time, obs_stop_time, rcumode, obsid_samples) - DI.writeFile() - CSV.writeInfo(obs_start_time, obs_stop_time, obsid_samples) - CSV.closeFile() - active_obs_id = "" - obs_start_time = 0.0 - obs_stop_time = 0.0 - # if still running kill recording - if beamlet_recording: - if beamlet_recording.is_running(): - beamlet_recording.kill_recording() - beamlet_recording = 0 - - # if no active observation get obs info if obsid available - if active_obs_id == "": - # if still running kill recording - if beamlet_recording: - if beamlet_recording.is_running(): - beamlet_recording.kill_recording() - beamlet_recording = 0 - - for id in obsids: - obsid, start, stop = getObsIdInfo(id) - if time_now >= (start - 60.0) and (time_now + 15) < stop: - active_obs_id = obsid - obs_start_time = start - obs_stop_time = stop - break - - if time_now < obs_start_time: - logger.debug("waiting %d seconds for start of observation" %(int(obs_start_time - time_now))) - time.sleep((obs_start_time - time_now) + 1.0) - - # start recording beamlets - if not beamlet_recording: - if obs_start_time > 0.0 and time.time() >= obs_start_time: - duration = obs_stop_time - time.time() - 10 - if duration > 2: - beamlet_recording = RecordBeamletStatistics() - beamlet_recording.set_obsid(active_obs_id) - beamlet_recording.set_duration(duration) - beamlet_recording.start() - - check_start = time.time() - # if new obs_id save data and reset settings - if obs_id != active_obs_id: - # start new file and set new obsid - obs_id = active_obs_id - obsid_samples = 0 - CSV.setObsID(obs_id) - - # it takes about 11 seconds to record data, for safety use 15 - if (time.time() + 15.0) < obs_stop_time: - # observing, so check mode now - rcumode, rcu_info = getRcuMode(n_rcus) - if rcumode <= 0: - continue - active_rcus = [] - for rcu in rcu_info: - state, mode = rcu_info[rcu] - if state == 'ON': - active_rcus.append(rcu) - data.setActiveRcus(active_rcus) - - rec_timestamp = time.time()+3.0 - data.record(rec_time=1, read=True, slow=True) - #data.fetch() - - CSV.setRcuMode(rcumode) - CSV.setRecordTimestamp(rec_timestamp) - DI.addSample(rcumode) - obsid_samples += 1 - logger.debug("do tests") - mask = extractSelectStr(conf.getStr('mask-rcumode-%d' %(rcumode))) - data.setMask(mask) - if len(mask) > 0: - logger.debug("mask=%s" %(str(mask))) - - error_list = [] - # do LBA tests - if lbaMode(rcumode): - checkForDown(data, rcumode, error_list, - conf.getInt('lbh-test-sb',301)) - checkForShort(data, rcumode, error_list) - checkForFlat(data, rcumode, error_list) - checkForOscillation(data, rcumode, error_list, 6.0) - checkForNoise(data, rcumode, error_list, - conf.getFloat('lba-noise-min-deviation', -3.0), - conf.getFloat('lba-noise-max-deviation', 2.5), - conf.getFloat('lba-noise-max-difference', 1.5)) - # do HBA tests - if hbaMode(rcumode): - checkForOscillation(data, rcumode, error_list, 9.0) - checkForSummatorNoise(data, rcumode, error_list) - checkForNoise(data, rcumode, error_list, - conf.getFloat('hba-noise-min-deviation', -3.0), - conf.getFloat('hba-noise-max-deviation', 2.5), - conf.getFloat('hba-noise-max-difference', 2.0)) - else: - - closeAllOpenFiles() - - if active_obs_id == "": + # get list with active obsids from swlevel, [] if none + obsids = get_obs_id() + + # loop over obsids and start new proces for each obsid, asuming more than one observation can be run + # get also used rcus from parameterset + if obsids: + for _obsid in obsids: + if not _obsid in obs_info: + # new obsid, setup and start recording beamlet statistics + obs_info[_obsid] = {} + obsid, start, stop, rcus = get_obs_id_info(_obsid) + obs_info[_obsid]['starttime'] = start + obs_info[_obsid]['stoptime'] = stop + obs_info[_obsid]['rcus'] = rcus + obs_info[_obsid]['state'] = 'new' + obs_info[_obsid]['csv'] = CSV(_obsid) + if time_now > (start + 60.0): + starttime = time_now + else: + starttime = start + 60.0 + duration = stop - starttime - 10.0 + #print starttime, duration + rbc = RecordBeamletStatistics(_obsid, starttime, duration) + obs_info[_obsid]['beamlet-recording'] = rbc + obs_info[_obsid]['beamlet-recording'].start() + obs_info[_obsid]['next-check-time'] = starttime + obs_info[_obsid]['last-check-time'] = stop - 15.0 + obs_info[_obsid]['samples'] = 0 + #print str(obs_info[_obsid]) + + # close finished obsids + for _obsid in obs_info_to_delete: + if obs_info[_obsid]['state'] == 'finished': + del obs_info[_obsid] + obs_info_to_delete = [] + + # mark stopped obsids as stopped + for _obsid in obs_info.iterkeys(): + if not _obsid in obsids: + obs_info[_obsid]['state'] = 'stopped' + obs_info_to_delete.append(_obsid) + + check_now = False + for _obsid in obs_info.iterkeys(): + if time_now >= obs_info[_obsid]['next-check-time']: + check_now = True + + if check_now: + # observing, so check mode now + rec_timestamp = time.time() + 3.0 + data.collect(n_seconds=1, slow=True) + # data.fetch() + + for _obsid in obs_info.iterkeys(): + # finish stopped obsid, and stop recording if needed + if obs_info[_obsid]['state'] == 'stopped': + # dayinfo.add_obs_info(obs_id, obs_start_time, obs_stop_time, rcumode, obsid_samples) + # dayinfo.write_file() + obs_info[_obsid]['csv'].write_info(obs_info[_obsid]['starttime'], + obs_info[_obsid]['stoptime'], + obs_info[_obsid]['samples']) + obs_info[_obsid]['csv'].close_file() + if obs_info[_obsid]['beamlet-recording'].is_running(): + obs_info[_obsid]['beamlet-recording'].kill_recording() + time.sleep(0.2) + if not obs_info[_obsid]['beamlet-recording'].is_running(): + obs_info[_obsid]['state'] = 'finished' + continue # finished, next obsid + + if time_now > obs_info[_obsid]['last-check-time']: + continue + + if time_now < obs_info[_obsid]['next-check-time']: + continue + + obs_info[_obsid]['csv'].set_record_timestamp(rec_timestamp) + obs_info[_obsid]['samples'] += 1 + logger.debug("do tests") + + error_list = [] + + for band in AntennaData.bands: + if data.band_active(band): + # TODO: DI.add_sample(rcumode) + # mask = extract_select_str(conf.get_str('mask-band-%d' % band)) + # data.mask_sb(mask) + # if len(mask) > 0: + # logger.debug("mask=%s" %(str(mask))) + data.reset_masked_rcus() + masked_rcus = [] + for i in xrange(n_rcus): + if not i in obs_info[_obsid]['rcus']: + masked_rcus.append(i) + data.mask_rcu(masked_rcus) + # check rcumode of first rcu + mode = data.mode(obs_info[_obsid]['rcus'][0]) + # do LBA tests + logger.debug("band= %s, mode= %d" % (band, mode)) + if mode in (1, 2, 3, 4) and band in ('10_90', '30_90'): + settings = conf.rcumode(mode) + check_down(data, band, error_list, settings, obs_info[_obsid]['csv']) + check_short(data, band, error_list, settings, obs_info[_obsid]['csv']) + check_flat(data, band, error_list, settings, obs_info[_obsid]['csv']) + check_oscillation(data, band, error_list, settings, obs_info[_obsid]['csv']) + check_noise(data, band, error_list, settings, obs_info[_obsid]['csv']) + + # do HBA tests + if mode in (5, 6, 7) and band in ('110_190', '170_210', '210_250'): + settings = conf.group('rcumode.%d.tile' % mode) + check_oscillation(data, band, error_list, settings, obs_info[_obsid]['csv']) + check_summator_noise(data, band, error_list, settings, obs_info[_obsid]['csv']) + check_noise(data, band, error_list, settings, obs_info[_obsid]['csv']) + + next_check_time = obs_info[_obsid]['next-check-time'] + obs_info[_obsid]['next-check-time'] = next_check_time + 60.0 + logger.debug("next check obsid %s on %s" %(_obsid, time.ctime(next_check_time + 60.0))) + #print str(obs_info[_obsid]) + + if len(obs_info) == 0: # if not observing check every 30 seconds for observation start - sleeptime = 30.0 - logger.debug("no observation, sleep %1.0f seconds" %(sleeptime)) + if sleep_counter == 0: + logger.debug("no observation, sleep mode activated") + sleep_counter += 1 + if (sleep_counter % 60) == 0: + logger.debug("no observation, still sleeping") + time.sleep(10.0) else: - # if observing do check every 60 seconds - check_stop = time.time() - sleeptime = 60.0 - (check_stop - check_start) - logger.debug("sleep %1.0f seconds till next check" %(sleeptime)) - while sleeptime > 0.0: - wait = min(1.0, sleeptime) - sleeptime -= wait - time.sleep(wait) + # if observing do check every 1 seconds + sleep_counter = 0 + time.sleep(1.0) except KeyboardInterrupt: logger.info("stopped by user") - sys.exit() + return 0 except: logger.error('Caught %s', str(sys.exc_info()[0])) logger.error(str(sys.exc_info()[1])) logger.error('TRACEBACK:\n%s', traceback.format_exc()) logger.error('Aborting NOW') - sys.exit(0) - + return 1 # do test and write result files to log directory - log_dir = conf.getStr('log-dir-local') + log_dir = conf.get_str('log-dir-local') if os.path.exists(log_dir): logger.info("write result data") # write result else: - logger.warn("not a valid log directory") + logger.warning("not a valid log directory") logger.info("Test ready.") # if still running kill recording if beamlet_recording: if beamlet_recording.is_running(): beamlet_recording.kill_recording() - beamlet_recording = 0 # delete files from data directory - removeAllDataFiles() - sys.exit(0) + remove_all_data_files() + return 0 if __name__ == '__main__': - main() + sys.exit(main()) diff --git a/LCU/checkhardware/showBadSpectra.py b/LCU/checkhardware/show_bad_spectra.py similarity index 92% rename from LCU/checkhardware/showBadSpectra.py rename to LCU/checkhardware/show_bad_spectra.py index 3a98ba22f7b..63e990dcbb3 100755 --- a/LCU/checkhardware/showBadSpectra.py +++ b/LCU/checkhardware/show_bad_spectra.py @@ -5,10 +5,11 @@ import numpy as np import matplotlib.pyplot as plt import time -obs_id_to_plot = '' +obs_id_to_plot = '' station_to_plot = '' -spectraPath = r'/localhome/stationtest/bad_spectra' +spectraPath = r'/localhome/stationtest/bad_spectra' + def main(): files = full_listdir(spectraPath) @@ -41,13 +42,13 @@ def main(): rcu = val_data if val_name == 'frequency': freq = str2array(val_data[1:-1]) - #print len(freq) + # print len(freq) if val_name == 'mean-spectra': mean_spectra = str2array(val_data[1:-1]) - #print len(mean_spectra) + # print len(mean_spectra) if val_name == 'rcu-spectra': bad_rcu = str2array(val_data[1:-1]) - #print len(bad_rcu) + # print len(bad_rcu) plt.plot(freq,mean_spectra,'k',freq,bad_rcu,'r') plt.legend(('median-spectra', 'rcu-%s' %(rcu)), fancybox=True) diff --git a/LCU/checkhardware/showTestResult.py b/LCU/checkhardware/show_test_result.py similarity index 91% rename from LCU/checkhardware/showTestResult.py rename to LCU/checkhardware/show_test_result.py index 1900b69c923..f74297b7c4d 100755 --- a/LCU/checkhardware/showTestResult.py +++ b/LCU/checkhardware/show_test_result.py @@ -8,7 +8,8 @@ import string # import datetime # import time -logdir = '' +report_dir = '' +conf_file = r'/localhome/stationtest/config/check_hardware.conf' def print_help(): """ print help """ @@ -42,38 +43,36 @@ if 'P' in args: lib_path = run_path+r'/lib' sys.path.insert(0, lib_path) -from general_lib import * -from lofar_lib import * +from checkhardware_lib import * -station_id = getHostName().upper() +hostname = get_hostname() +if hostname == 'Unknown': + sys.exit('hostname unknown') +station_id = hostname.upper() def main(): - global logdir - + global report_dir + """ main function """ - fd = open(run_path+r'/checkHardware.conf', 'r') - data = fd.readlines() - fd.close() - for line in data: - if line.find('log-dir-local') != -1: - key, logdir = line.strip().split('=') + conf = TestSettings(filename=conf_file) + report_dir = conf().as_string('paths.local-report-dir') data_sets = [] - + if 'F' in args: fullfilename = args.get('F') else: if 'L2' in args: - testfilename = '%s_L2_StationTestHistory.csv' % station_id + testfilename = '%s_L2_station_test_history.csv' % station_id data_sets.append( ['L2', get_data(testfilename, int(args.get('L2', '1')))] ) - - if 'S' in args: - testfilename = '%s_S_StationTestHistory.csv' % station_id + + if 'S' in args: + testfilename = '%s_S_station_test_history.csv' % station_id data_sets.append( ['S', get_data(testfilename, int(args.get('S', '1')))] ) - + if not 'L2' in args and not 'S' in args: - testfilename = '%s_StationTest.csv' % station_id + testfilename = '%s_station_test.csv' % station_id data_sets.append( ['', get_data(testfilename, 1)] ) rcu_x = rcu_y = 0 @@ -84,13 +83,13 @@ def main(): # print data for all sets print "\n\n\n" for check_type, data in data_sets: - message = "STATION-CHECK RESULTS %s for last %s checks" % (check_type, args.get('%s' % check_type, '1')) + message = "STATION-CHECK RESULTS %s for last %s checks" % (check_type, args.get('%s' % check_type, '1')) banner_len = 100 msg_len = len(message) print "-" * banner_len print ">" * ((banner_len - msg_len - 6) / 2) + " %s " % message + "<" * ((banner_len - msg_len - 6) / 2) print "-" * banner_len - + check_nr = int(args.get('%s' % check_type, '1')) - 1 for line in data: partnumber = -1 @@ -107,9 +106,9 @@ def main(): message = "= csv -%s- (last - %d) =" % (check_type, check_nr) else: message = "= csv -%s- (last) =" % check_type - print ' ' + '=' * len(message) + print ' ' + '=' * len(message) print ' ' + message - print ' ' + '=' * len(message) + print ' ' + '=' * len(message) check_nr -= 1 part = d[1] @@ -200,8 +199,8 @@ def main(): def get_data(filename, n_checks): if not filename.startswith('/'): - if os.path.exists(logdir): - fullfilename = os.path.join(logdir, filename) + if os.path.exists(report_dir): + fullfilename = os.path.join(report_dir, filename) else: print "not a valid log dir" sys.exit(-1) @@ -210,9 +209,9 @@ def get_data(filename, n_checks): data = fd.readlines() fd.close() except: - print "%s not found in %s" % (filename, logdir) + print "%s not found in %s" % (filename, report_dir) sys.exit(-1) - + first_line = 0 check_cnt = 0 for i in range(len(data) - 1, -1, -1): @@ -255,19 +254,24 @@ def print_info(msg, keyvalue, msg_info): if msg == 'CHECKS': """E5""" checks = msg_info.split() - info = [] + info1 = [] + info2 = [] if 'RV' in checks: - info.append('RSP-version') + info1.append('RSP-firmware-version') if 'TV' in checks: - info.append('TBB-version') + info1.append('TB-firmware-version') if 'TM' in checks: - info.append('TBB-memory') + info1.append('TB-memory-check') if 'SPU' in checks: - info.append('SPU-check') - if 'RBV' in checks: - info.append('RSP-voltage') - if len(info): - print "-- Checks done : %s" % string.join(info, ', ') + info2.append('SPU-board-check') + if 'RBC' in checks: + info2.append('RSP-board-checks') + if 'TBC' in checks: + info2.append('TB-board-checks') + if len(info1) or len(info2): + print "-- Checks done : %s" % string.join(info1, ', ') + if len(info2): + print " : %s" % string.join(info2, ', ') info = [] for mode in '1234567': if 'M%s' % mode in checks: @@ -279,9 +283,9 @@ def print_info(msg, keyvalue, msg_info): if 'D%s' % mode in checks: info.append('Down') if 'S%s' % mode in checks: - info.append('RF') + info.append('RF') if 'O%s' % mode in checks: - info.append('Oscillation') + info.append('Oscillation') if 'SP%s' % mode in checks: info.append('Spurious') if 'SN%s' % mode in checks: @@ -290,8 +294,8 @@ def print_info(msg, keyvalue, msg_info): if 'NS%s' % mode in check.split('='): info.append('Noise[%ssec]' % check.split('=')[1]) if 'E%s' % mode in checks: - info.append('Elements') - if len(info): + info.append('Elements') + if len(info): print "-- Checks done M%s : %s" % (mode, string.join(info, ', ')) info = [] @@ -362,7 +366,13 @@ def print_tbb(partnumber, msg, keyvalue): if 'TP' in keyvalue or 'MP' in keyvalue: print " Board %2d wrong firmware version: TP=%s MP=%s" % ( partnumber, keyvalue.get('TP'), keyvalue.get('MP')) - + if msg == 'VOLTAGE': + print " Board %2d wrong voltage: 1.2V=%s 2.5V=%s 3.3V=%s" % ( + partnumber, keyvalue.get('1.2V'), keyvalue.get('2.5V'), keyvalue.get('3.3V')) + if msg == 'TEMPERATURE': + print " Board %2d high temperature: PCB=%s TP=%s MP0=%s MP1=%s MP2=%s MP3=%s" % ( + partnumber, keyvalue.get('PCB'), keyvalue.get('TP'), keyvalue.get('MP0'), keyvalue.get('MP1'), + keyvalue.get('MP2'), keyvalue.get('MP3')) if msg == 'MEMORY': print " Board %2d Memory address or dataline error" % partnumber return diff --git a/LCU/checkhardware/updatePVSS.py b/LCU/checkhardware/update_pvss.py similarity index 87% rename from LCU/checkhardware/updatePVSS.py rename to LCU/checkhardware/update_pvss.py index e6db9a742dd..3a4b4955098 100755 --- a/LCU/checkhardware/updatePVSS.py +++ b/LCU/checkhardware/update_pvss.py @@ -10,14 +10,12 @@ import sys import os from time import sleep -libPath = '/opt/stationtest/lib' -sys.path.insert(0, libPath) +from checkhardware_lib import * -from general_lib import * -from lofar_lib import * +conf_file = r'/localhome/stationtest/config/check_hardware.conf' args = dict() -logdir = "" +conf = None logger = 0 nLBL = 0 nLBH = 0 @@ -27,34 +25,40 @@ nRSP = 0 # PVSS states State = dict({'OFF':0, 'OPERATIONAL':1, 'MAINTENANCE':2, 'TEST':3, 'SUSPICIOUS':4, 'BROKEN':5}) +hostname = get_hostname().upper() +if hostname == 'Unknown': + sys.exit('hostname unknown') def main(): - global args, logdir, logger, nRSP, nLBL, nLBH, nHBA + global conf, args, logger, nRSP, nLBL, nLBH, nHBA getArguments() - logdir = getLogDir() - ID, nRSP, nTBB, nLBL, nLBH, nHBA, HBA_SPLIT = readStationConfig() - logger = cPVSSLogger(logdir) - + + conf = TestSettings(filename=conf_file) + report_dir = conf().as_string('paths.local-report-dir') + + ID, nRSP, nTBB, nLBL, nLBH, nHBA, HBA_SPLIT = read_station_config() + logger = MyPVSSLogger(report_dir, hostname) + if args.has_key('RESET'): resetPVSS(state=0) - + if args.has_key('NO_UPDATE'): - print "skip PVSS update" - + print "skip PVSS update" + addManualDataToPVSS() - + # read last log file from checkhardware - testfilename = '%s_StationTest.csv' %(getHostName()) - fullFilename = os.path.join(logdir, testfilename) + testfilename = '%s_station_test.csv' % hostname + fullFilename = os.path.join(report_dir, testfilename) if args.has_key('FILE'): fullFilename = args.get('FILE') - + try: f = open(fullFilename, 'r') except IOError: print "file not found %s" %(fullFilename) return - + testdata = f.readlines() f.close() bad_lba, bad_hba = addDataToPVSS(testdata) @@ -66,7 +70,7 @@ def printHelp(): print "Usage of arguments" print "Output of last stationcheck is always send to pvss also the bad_rcu file is made" print "-h : this help screen" - + print "-reset[=type] : set all state fields to ok for type if given" print " type = all | lba | lbl | lbh | hba (all=default)" print "-no_update : skip pvss update" @@ -96,7 +100,7 @@ def printHelp(): print " or available more than y% of time and fluctuation > z dB" print "-LBHJ=x,y,z : jitter, flag only if available more than x% of time (x=0..100)" print " or available more than y% of time and fluctuation > z dB" - + # get command line arguments def getArguments(): global args @@ -104,7 +108,7 @@ def getArguments(): if sys.argv[i][0] == '-': if sys.argv[i].find('=') != -1: valpos = sys.argv[i].find('=') - key = sys.argv[i][1:valpos].upper() + key = sys.argv[i][1:valpos].upper() val = sys.argv[i][valpos+1:].split(',') if len(val) > 1: args[key] = val @@ -118,35 +122,25 @@ def getArguments(): sys.exit() return -# get logdir from configuration file -def getLogDir(): - logdir = "" - # look for log directory - f = open("/opt/stationtest/checkHardware.conf", 'r') - data = f.readlines() - f.close() - for line in data: - if line.find('log-dir-local') != -1: - key, logdir = line.strip().split('=') - return(logdir) - # send comment, key and value to PVSS and write to file def sendToPVSS(comment, pvss_key, value): global logger, args if args.has_key('NO_UPDATE'): return("") - + if len(comment) > 0: comment = 'stationtest::'+comment else: comment = 'stationtest' + + # add extra argument to setObjectState force=true to reset failure. arguments = '%s %s %d' %(comment, pvss_key, value) - logger.addLine(arguments[11:]) + logger.add_line(arguments[11:]) if args.has_key('TEST'): print arguments else: - response = sendCmd('setObjectState', arguments) + response = run_cmd('setObjectState %s' % arguments) sleep(0.2) return(response) return("") @@ -154,19 +148,19 @@ def sendToPVSS(comment, pvss_key, value): # set all antenna info to ok def resetPVSS(state=0): global args, libPath, State, nRSP, nLBL, nLBH, nHBA - + reset_type = args.get('RESET','ALL').upper() filename = "reset_pvss.log" full_filename = os.path.join(libPath, filename) f = open(full_filename, 'w') - - if reset_type == 'ALL': + + if reset_type == 'ALL': for rcu in range(nRSP*8): board = int(rcu / 8) rack = int(board / 4) - cabinet = int(rack / 2) + cabinet = int(rack / 2) f.write("LOFAR_PIC_Cabinet%d_Subrack%d_RSPBoard%d_RCU%d %d\n" %(cabinet, rack, board, rcu, state)) - + if reset_type in ('ALL','LBA','LBH'): for ant in range(nLBH): f.write("LOFAR_PIC_LBA%03d %d\n" %(ant, state)) @@ -175,10 +169,10 @@ def resetPVSS(state=0): for ant in range(nLBL): f.write("LOFAR_PIC_LBA%03d %d\n" %(ant+48, state)) - if reset_type in ('ALL','HBA'): + if reset_type in ('ALL','HBA'): for tile in range(nHBA): f.write("LOFAR_PIC_HBA%02d %d\n" %(tile, state)) - + for elem in range(16): f.write("LOFAR_PIC_HBA%02d.element%02d %d\n" %(tile, elem, state)) f.write("LOFAR_PIC_HBA%02d.element%02d.comm %d\n" %(tile, elem, state)) @@ -186,16 +180,14 @@ def resetPVSS(state=0): f.write("LOFAR_PIC_HBA%02d.element%02d.Y %d\n" %(tile, elem, state)) f.close() if not args.has_key('TEST'): - sendCmd("setObjectState", "stationtest:reset %s" %(full_filename)) + run_cmd("setObjectState stationtest:reset %s" % full_filename) sleep(5.0) - + # add manual filled list with bad antennas to pvss def addManualDataToPVSS(): - global State, logdir - filename = "bad_antenna_list.txt" - full_filename = "/globalhome/log/bad_antenna_list.txt" + full_filename = conf().get('files.bad-antenna-list') try: f = open(full_filename, 'r') except IOError: @@ -206,7 +198,7 @@ def addManualDataToPVSS(): for line in data: if line[0] == '#': continue - if line.upper().find(getHostName()) > -1: + if line.upper().find(hostname) > -1: bad_antenna_list = line.strip().split(' ')[1:] for ant in bad_antenna_list: part = ant[:3].upper() @@ -216,18 +208,15 @@ def addManualDataToPVSS(): if part == 'HBA': sendToPVSS("manualy-marked", "LOFAR_PIC_HBA%02d" %(part_nr), State['BROKEN']) return - + # add result data from checkhardware to PVSS def addDataToPVSS(data): - global args - global State bad_lba = dict() - bad_hba = dict() - + bad_hba = dict() + RFrefX = 0.0 RFrefY = 0.0 - - + for line in data: if line[0] == '#': continue @@ -250,8 +239,8 @@ def addDataToPVSS(data): keyinfo[key] = vallist else: keyinfo[info[i]] = '-' - - + + if part == 'LBL': lban_limits = args.get('LBLN','0.0') lbaj_limits = args.get('LBLJ','0.0') @@ -260,12 +249,12 @@ def addDataToPVSS(data): lban_limits = args.get('LBHN','0.0') lbaj_limits = args.get('LBHJ','0.0') lbas_limit = args.get('LBHS','0.0') - + if part in ('LBL', 'LBH'): if msgType == 'TESTSIGNAL': RFrefX = float(keyinfo.get('SIGNALX','0.0')) RFrefY = float(keyinfo.get('SIGNALY','0.0')) - + if msgType == 'LOW_NOISE': if float(keyinfo.get('Xproc','0.0')) >= 100.0 or float(keyinfo.get('Yproc','0.0')) >= 100.0: sendToPVSS("low-noise", "LOFAR_PIC_LBA%03d" %(partNr), State['BROKEN']) @@ -280,7 +269,7 @@ def addDataToPVSS(data): diff_limit = float(lban_limits[2]) else: proc_limit_1 = float(lban_limits) - + if float(keyinfo.get('Xproc','0.0')) >= proc_limit_1 or float(keyinfo.get('Yproc','0.0')) >= proc_limit_1: if ((float(keyinfo.get('Xproc','0.0')) < proc_limit_2 and (float(keyinfo.get('Xval','0.0')) - float(keyinfo.get('Xref','0.0'))) < diff_limit) and (float(keyinfo.get('Yproc','0.0')) < proc_limit_2 and (float(keyinfo.get('Yval','0.0')) - float(keyinfo.get('Yref','0.0'))) < diff_limit)): @@ -298,7 +287,7 @@ def addDataToPVSS(data): diff_limit = float(lbaj_limits[2]) else: proc_limit_1 = float(lbaj_limits) - + if float(keyinfo.get('Xproc','0.0')) >= proc_limit_1 or float(keyinfo.get('Yproc','0.0')) >= proc_limit_1: if ((float(keyinfo.get('Xproc','0.0')) < proc_limit_2 and float(keyinfo.get('Xdiff','0.0')) < diff_limit) and (float(keyinfo.get('Yproc','0.0')) < proc_limit_2 and float(keyinfo.get('Ydiff','0.0')) < diff_limit)): @@ -306,7 +295,7 @@ def addDataToPVSS(data): else: sendToPVSS("jitter", "LOFAR_PIC_LBA%03d" %(partNr), State['BROKEN']) bad_lba[partNr] = 1 - + elif msgType == 'OSCILLATION': sendToPVSS("oscillating", "LOFAR_PIC_LBA%03d" %(partNr), State['BROKEN']) bad_lba[partNr] = 1 @@ -323,22 +312,22 @@ def addDataToPVSS(data): if Y > 0.0: if abs(Y - RFrefY) > float(lbas_limit): comment += "Y" - flag = True + flag = True if flag: #print 'LBL %3.1f (%3.1f) %3.1f (%3.1f)' %(X, RFrefX, Y, RFrefY) sendToPVSS(comment, "LOFAR_PIC_LBA%03d" %(partNr), State['BROKEN']) bad_lba[partNr] = 1 - + elif msgType == 'DOWN': sendToPVSS("down", "LOFAR_PIC_LBA%03d" %(partNr), State['BROKEN']) bad_lba[partNr] = 1 - + if part == 'HBA': if msgType == 'LOW_NOISE': if float(keyinfo.get('Xproc','0.0')) >= 100.0 or float(keyinfo.get('Yproc','0.0')) >= 100.0: sendToPVSS("low-noise", "LOFAR_PIC_HBA%02d" %(partNr), State['BROKEN']) bad_hba[partNr] = 1 - + elif msgType == 'HIGH_NOISE': limits = args.get('N','0.0') proc_limit_2 = 0.0 @@ -349,7 +338,7 @@ def addDataToPVSS(data): diff_limit = float(limits[2]) else: proc_limit_1 = float(limits) - + if float(keyinfo.get('Xproc','0.0')) >= proc_limit_1 or float(keyinfo.get('Yproc','0.0')) >= proc_limit_1: if ((float(keyinfo.get('Xproc','0.0')) < proc_limit_2 and (float(keyinfo.get('Xval','0.0')) - float(keyinfo.get('Xref','0.0'))) < diff_limit) and (float(keyinfo.get('Yproc','0.0')) < proc_limit_2 and (float(keyinfo.get('Yval','0.0')) - float(keyinfo.get('Yref','0.0'))) < diff_limit)): @@ -368,7 +357,7 @@ def addDataToPVSS(data): diff_limit = float(limits[2]) else: proc_limit_1 = float(limits) - + if float(keyinfo.get('Xproc','0.0')) >= proc_limit_1 or float(keyinfo.get('Yproc','0.0')) >= proc_limit_1: if ((float(keyinfo.get('Xproc','0.0')) < proc_limit_2 and float(keyinfo.get('Xdiff','0.0')) < diff_limit) and (float(keyinfo.get('Yproc','0.0')) < proc_limit_2 and float(keyinfo.get('Ydiff','0.0')) < diff_limit)): @@ -376,26 +365,26 @@ def addDataToPVSS(data): else: sendToPVSS("jitter", "LOFAR_PIC_HBA%02d" %(partNr), State['BROKEN']) bad_hba[partNr] = 1 - + elif msgType == 'OSCILLATION': if not args.has_key('O'): sendToPVSS("oscillating", "LOFAR_PIC_HBA%02d" %(partNr), State['BROKEN']) bad_hba[partNr] = 1 - + elif msgType == 'C_SUMMATOR': sendToPVSS("modem-fail", "LOFAR_PIC_HBA%02d" %(partNr), State['BROKEN']) bad_hba[partNr] = 1 - + elif msgType == 'SUMMATOR_NOISE': if not args.has_key('SN'): sendToPVSS("summator-noise", "LOFAR_PIC_HBA%02d" %(partNr), State['BROKEN']) bad_hba[partNr] = 1 - + elif msgType == 'SPURIOUS': if not args.has_key('SP'): sendToPVSS("spurious-signals", "LOFAR_PIC_HBA%02d" %(partNr), State['BROKEN']) bad_hba[partNr] = 1 - + elif msgType == 'RF_FAIL': flag = False limit = float(args.get('S','0')) @@ -406,11 +395,11 @@ def addDataToPVSS(data): flag = True if len(Y): if abs(float(Y[0]) - float(Y[2])) > limit: - flag = True - if flag: + flag = True + if flag: sendToPVSS("rf-tile-fail", "LOFAR_PIC_HBA%02d" %(partNr), State['BROKEN']) bad_hba[partNr] = 1 - + elif msgType == 'E_FAIL': if args.has_key('E') == False: max_errors = 2 @@ -419,7 +408,7 @@ def addDataToPVSS(data): LNY_errors = 0 RFX_errors = 0 RFY_errors = 0 - + # check first total number of errors in tile for elem_nr in range(1,17,1): if keyinfo.has_key('M%d' %(elem_nr)): @@ -431,65 +420,64 @@ def addDataToPVSS(data): if keyinfo.has_key('X%d' %(elem_nr)): RFX_errors += 1 if keyinfo.has_key('Y%d' %(elem_nr)): - RFY_errors += 1 - + RFY_errors += 1 - send_tile_errors = 0 + + send_tile_errors = 0 for elem_nr in range(1,17,1): send_elem_errors = 0 - + if modem_errors > max_errors and keyinfo.has_key('M%d' %(elem_nr)): sendToPVSS("rf-fail", "LOFAR_PIC_HBA%02d.element%02d.comm" %(partNr, elem_nr-1), State['BROKEN']) send_elem_errors += 1 - + comment = "" if (RFX_errors > max_errors) and keyinfo.has_key('X%d' %(elem_nr)): comment += "rf-fail&" - + if (LNX_errors > max_errors) and keyinfo.has_key('LNX%d' %(elem_nr)): comment += "low-noise&" - + if keyinfo.has_key('HNX%d' %(elem_nr)) or keyinfo.has_key('JX%d' %(elem_nr)): comment += "noise&" - + if len(comment) > 0: sendToPVSS(comment[:-1], "LOFAR_PIC_HBA%02d.element%02d.X" %(partNr, elem_nr-1), State['BROKEN']) send_elem_errors += 1 - - + + comment = "" if (RFY_errors > max_errors) and keyinfo.has_key('Y%d' %(elem_nr)): comment += "rf-fail&" - + if (LNY_errors > max_errors) and keyinfo.has_key('LNY%d' %(elem_nr)): comment += "low-noise&" - + if keyinfo.has_key('HNY%d' %(elem_nr)) or keyinfo.has_key('JY%d' %(elem_nr)): comment += "noise&" - - if len(comment) > 0: + + if len(comment) > 0: sendToPVSS(comment[:-1], "LOFAR_PIC_HBA%02d.element%02d.Y" %(partNr, elem_nr-1), State['BROKEN']) send_elem_errors += 1 - - + + if send_elem_errors > 0: sendToPVSS("rf-fail", "LOFAR_PIC_HBA%02d.element%02d" %(partNr, elem_nr-1), State['BROKEN']) send_tile_errors += 1 - + if send_tile_errors > 0: sendToPVSS("", "LOFAR_PIC_HBA%02d" %(partNr), State['BROKEN']) bad_hba[partNr] = 1 - - return (list(bad_lba), list(bad_hba)) -# write bad rcu's to file in logdir + return (list(bad_lba), list(bad_hba)) + +# write bad rcu's to file in report_dir def addDataToBadRcuFile(bad_lba, bad_hba): - global nLBL - global nLBH - - # add bad rcus to file - filename = '%s_bad_rcus.txt' %(getHostName()) - full_filename = os.path.join(logdir, filename) + # add bad rcus to file + report_dir = conf().as_string('paths.local-report-dir') + filename = '%s_bad_rcus.txt' % hostname + full_filename = os.path.join(report_dir, filename) + print "bad_rcus filename = %s" % full_filename f = open(full_filename, 'w') lbl = "" @@ -501,7 +489,7 @@ def addDataToBadRcuFile(bad_lba, bad_hba): else: lbh += "%d," %(ant*2) lbh += "%d," %(ant*2+1) - + if len(lbl): lbl = lbl[:-1] lbl = "LBL=[" + lbl + "]\n" @@ -520,9 +508,9 @@ def addDataToBadRcuFile(bad_lba, bad_hba): hba = hba[:-1] hba = "HBA=[" + hba + "]\n" f.write(hba) - + f.close() if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/MAC/APL/APLCommon/src/swlevel b/MAC/APL/APLCommon/src/swlevel index 6ec96f8081e..24849dd71dd 100755 --- a/MAC/APL/APLCommon/src/swlevel +++ b/MAC/APL/APLCommon/src/swlevel @@ -26,8 +26,8 @@ # if [ "$LOFARROOT" == "" ]; then - # default value until all MAC controlled systems provide $LOFARROOT - LOFARROOT=/opt/lofar + # default value until all MAC controlled systems provide $LOFARROOT + LOFARROOT=/opt/lofar fi BINDIR=$LOFARROOT/bin @@ -36,7 +36,7 @@ LOGDIR=$LOFARROOT/var/log ETCDIR=$LOFARROOT/etc LEVELTABLE=${ETCDIR}/swlevel.conf -# Make sure all files are user/group writeable (needed for Int. +# Make sure all files are user/group writeable (needed for Int. # Stations) umask 002 @@ -56,10 +56,10 @@ SyntaxError() echo "-v: Show running LOFAR version, exit (-V: only print version)" echo "-u: Show users owning running processes (-U: same)" echo "-i: Load RSP firmware from image [x] (default image is 1)" - echo "-l: Set to level as provided (optional)" - echo "-q: Quit process with name processname" - echo "-r: Run process with name processname" - echo + echo "-l: Set to level as provided (optional)" + echo "-q: Quit process with name processname" + echo "-r: Run process with name processname" + echo echo "Levels:" echo "0: Stop all lofar software" echo "1: Run Lofar daemons and PVSS" @@ -76,34 +76,34 @@ SyntaxError() # First argument is timeout, next arguments are the command and its parameters -# Find which image to load on a given station; uses file +# Find which image to load on a given station; uses file # ${ETCDIR}/RSPImage.conf # # returns $image with image number findImage() -{ - findstation=$1 - RSPImageFile=${ETCDIR}/RSPImage.conf - if [ ! -e $RSPImageFile ] ; then - echo "Cannot find Image file ${ETCDIR}/RSPImage.conf" - exit; - fi - image=0 - while read line - do - first=`echo $line | awk '{print $1}'` - if [ "$first" == "$findstation" ]; then - image=`echo $line | awk '{print $2}'` - break - fi - done < ${ETCDIR}/RSPImage.conf - if [ $image -eq 0 ]; then - echo "Could not find station $findstation in file ${ETCDIR}/RSPImage.conf" - exit; - fi - return -} +{ + findstation=$1 + RSPImageFile=${ETCDIR}/RSPImage.conf + if [ ! -e $RSPImageFile ] ; then + echo "Cannot find Image file ${ETCDIR}/RSPImage.conf" + exit; + fi + image=0 + while read line + do + first=`echo $line | awk '{print $1}'` + if [ "$first" == "$findstation" ]; then + image=`echo $line | awk '{print $2}'` + break + fi + done < ${ETCDIR}/RSPImage.conf + if [ $image -eq 0 ]; then + echo "Could not find station $findstation in file ${ETCDIR}/RSPImage.conf" + exit; + fi + return +} # # selectImage(); load an image on the RSP boards @@ -111,45 +111,36 @@ findImage() selectImage() { let nrRSPs=`grep RSPBOARDS ${ETCDIR}/RemoteStation.conf | cut -d'=' -f2 | sed 's/ //g'` - let offset=0x`grep RSPDriver.MAC_ADDR_0 ${ETCDIR}/RSPDriver.conf | cut -d'=' -f2 | cut -d':' -f5` + let offset=0x`grep RSPDriver.MAC_ADDR_0 ${ETCDIR}/RSPDriver.conf | cut -d'=' -f2 | cut -d':' -f5` let board=0 - # Assume no errors with board communication + # Assume no errors with board communication boardError=0 - errorBoards="" + errorBoards="" # Make sure we have an image number in parameter $image - if [ -z $image ] && [ $imageForced -eq 0 ]; then - findImage `hostname -s` - fi + if [ -z $image ] && [ $imageForced -eq 0 ]; then + findImage `hostname -s` + fi while [ $board -lt $nrRSPs ] do # get major version of active firmware on RSPboard $board - #boardHex=`echo $board | awk '{ printf "%02x", $1 }'` + #boardHex=`echo $board | awk '{ printf "%02x", $1 }'` boardHex=`echo $board | awk -v firstBoard=$offset '{ printf "%02x", firstBoard+$1 }'` # Uncomment next lines only for testing purposes! - #if [ "$boardHex" == "03" ]; then + #if [ "$boardHex" == "03" ]; then # boardHex="1F" - #fi - rsu=`run_timeout.sh 5 sudo ${SBINDIR}/rsuctl3 -m 10:fa:00:00:$boardHex:00 -qV 2>&1 | grep BP | cut -d':' -f2 | sed 's/ //g' | cut -d'.' -f1` - - # If not a single number, something weird must have happened - if [ ${#rsu} -ne 1 ]; then - echo "RSPboard $boardHex: Error requesting active firmware version (communication error)" - boardError=1 - errorBoards=${errorBoards}${boardHex}"," - else - echo "Loading image $image on RSPboard $boardHex ..." - run_timeout.sh 5 sudo ${SBINDIR}/rsuctl3_reset -m 10:fa:00:00:$boardHex:00 -q -x -p $image 1>/dev/null 2>&1 - if [ $? -ne 0 ]; then - boardError=1 - errorBoards=${errorBoards}${boardHex}"," - fi - fi - # Next board + #fi + echo "Loading image $image on RSPboard $boardHex ..." + run_timeout.sh 5 sudo ${SBINDIR}/rsuctl3_reset -m 10:fa:00:00:$boardHex:00 -q -x -p $image 1>/dev/null 2>&1 + if [ $? -ne 0 ]; then + boardError=1 + errorBoards=${errorBoards}${boardHex}"," + fi let board+=1 done + if [ $boardError -eq 1 ]; then - echo "Board(s) $errorBoards have a communication problem; try reset the 48V" + echo "Board(s) $errorBoards have a communication problem; try reset the 48V" fi } @@ -159,58 +150,58 @@ selectImage() # check_images() { -if [ -e $BINDIR/rspctl ]; then - # First make sure RSP images are properly loaded - # Introduce a timeout of 60 sec for images to initialize - echo "Waiting for RSP and TBB images to be initialized" - timeout=60 - for (( s=0 ; s<timeout; s++ )) - do - rsu_ready=`( rspctl --version | grep "0.0" ) >& /dev/null; echo $?` - if [ $rsu_ready == 1 ]; then - echo "RSP Images are loaded" - break - fi - sleep 1 - done - if [ $s == $timeout ]; then - echo "Could not load RSP images in time; Reset RSP boards" - exit 1 - fi +if [ -e $BINDIR/rspctl ]; then + # First make sure RSP images are properly loaded + # Introduce a timeout of 60 sec for images to initialize + echo "Waiting for RSP and TBB images to be initialized" + timeout=60 + for (( s=0 ; s<timeout; s++ )) + do + rsu_ready=`( rspctl --version | grep "0.0" ) >& /dev/null; echo $?` + if [ $rsu_ready == 1 ]; then + echo "RSP Images are loaded" + break + fi + sleep 1 + done + if [ $s == $timeout ]; then + echo "Could not load RSP images in time; Reset RSP boards" + exit 1 + fi fi if [ -e $BINDIR/tbbctl ]; then - # Now make sure TBB images are properly loaded - # Introduce a timeout of 60 sec for images to initialize - echo "Waiting for TBB images to be initialized" - timeout=60 - sleep 10 - for (( s=0 ; s<timeout; s++ )) - do - tbb_respons=`tbbctl --version` - tbb_ready=`( echo $tbb_respons | grep "\ V\ " ) >& /dev/null; echo $?` - if [ $tbb_ready -eq 0 ]; then - sleep 10 # additional delay to let TBB boards end their init phase - echo "TBB Images are loaded" - break - fi - tbb_down=`( echo $tbb_respons | grep "TBBDriver is NOT responding" )>& /dev/null; echo $?` - if [ $tbb_down -eq 0 ]; then - echo "TBBDriver is not responding; cannot continue start of TBBs" - # Trigger message furtheron in the code - s=$timeout - break - fi - sleep 1 - done - if [ $s == $timeout ]; then - echo "Could not load TBB images; Reset TBB boards" - else - # Start TBB recording mode - if [ -e $SBINDIR/startTBB.sh ]; then - $SBINDIR/startTBB.sh - fi - fi + # Now make sure TBB images are properly loaded + # Introduce a timeout of 60 sec for images to initialize + echo "Waiting for TBB images to be initialized" + timeout=60 + sleep 10 + for (( s=0 ; s<timeout; s++ )) + do + tbb_respons=`tbbctl --version` + tbb_ready=`( echo $tbb_respons | grep "\ V\ " ) >& /dev/null; echo $?` + if [ $tbb_ready -eq 0 ]; then + sleep 10 # additional delay to let TBB boards end their init phase + echo "TBB Images are loaded" + break + fi + tbb_down=`( echo $tbb_respons | grep "TBBDriver is NOT responding" )>& /dev/null; echo $?` + if [ $tbb_down -eq 0 ]; then + echo "TBBDriver is not responding; cannot continue start of TBBs" + # Trigger message furtheron in the code + s=$timeout + break + fi + sleep 1 + done + if [ $s == $timeout ]; then + echo "Could not load TBB images; Reset TBB boards" + else + # Start TBB recording mode + if [ -e $SBINDIR/startTBB.sh ]; then + $SBINDIR/startTBB.sh + fi + fi fi } @@ -226,7 +217,7 @@ start_prog() # special check for logging-daemons [ $prog == $logProgToSkip ] && return - + # check existance [ -x $BINDIR/$prog ] || [ -x $BINDIR/${prog}.sh ] || return @@ -241,49 +232,48 @@ start_prog() if [ $? -ne 0 ]; then curdate=`date +%Y%m%dT%H%M%S` # PVSS needs special treatment - if [ "$prog" = "PVSS00pmon" ]; then - echo Starting $prog - start_pvss2 1>/dev/null 2>&1 & - sleep 3 - elif [ "$prog" = "SASGateway" ]; then - # Foreign stations not under central control should not - # connect to the SAS database; this prevents SAS main- - # tenance etc. - if [ "$user" = "lofarsys" ]; then + if [ "$prog" = "PVSS00pmon" ]; then echo Starting $prog - rm -f $LOGDIR/$prog.log*.? 1>/dev/null 2>&1 - $BINDIR/$prog 1>>${LOGDIR}/${prog}.stdout.${curdate} 2>&1 & - else - echo "Local use, not starting $prog" - fi - else - if [ -n "$asroot" ]; then - echo Starting $prog - sudo rm -f $LOGDIR/$prog.log.? 1>/dev/null 2>&1 - if [ "$prog" = "RSPDriver" ]; then - selectImage - if [ $boardError -eq 1 ]; then - exit - fi - fi - if [ "$prog" = "TBBDriver" ]; then - # Check if RSPDriver is already running; if not, do not start either! - /sbin/pidof RSPDriver 1>/dev/null 2>&1 - if [ $? -ne 0 ]; then - echo "RSPDriver not running, so not starting TBBDriver either" - exit - fi - fi - sudo -b $BINDIR/$prog 1>>$LOGDIR/$prog.stdout.${curdate} 2>&1 - if [ "$prog" = "TBBDriver" ]; then - check_images - fi - else - - echo Starting $prog - rm -f $LOGDIR/$prog.log*.? 1>/dev/null 2>&1 - $BINDIR/$prog 1>>$LOGDIR/$prog.stdout.${curdate} 2>&1 & - fi + start_pvss2 1>/dev/null 2>&1 & + sleep 3 + elif [ "$prog" = "SASGateway" ]; then + # Foreign stations not under central control should not + # connect to the SAS database; this prevents SAS main- + # tenance etc. + if [ "$user" = "lofarsys" ]; then + echo Starting $prog + rm -f $LOGDIR/$prog.log*.? 1>/dev/null 2>&1 + $BINDIR/$prog 1>>${LOGDIR}/${prog}.stdout.${curdate} 2>&1 & + else + echo "Local use, not starting $prog" + fi + else + if [ -n "$asroot" ]; then + echo Starting $prog + sudo rm -f $LOGDIR/$prog.log.? 1>/dev/null 2>&1 + if [ "$prog" = "RSPDriver" ]; then + selectImage + if [ $boardError -eq 1 ]; then + exit + fi + fi + if [ "$prog" = "TBBDriver" ]; then + # Check if RSPDriver is already running; if not, do not start either! + /sbin/pidof RSPDriver 1>/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "RSPDriver not running, so not starting TBBDriver either" + exit + fi + fi + sudo -b $BINDIR/$prog 1>>$LOGDIR/$prog.stdout.${curdate} 2>&1 + if [ "$prog" = "TBBDriver" ]; then + check_images + fi + else + echo Starting $prog + rm -f $LOGDIR/$prog.log*.? 1>/dev/null 2>&1 + $BINDIR/$prog 1>>$LOGDIR/$prog.stdout.${curdate} 2>&1 & + fi fi usleep 250000 ps -ef | grep -v grep | egrep '[0-9][0-9] [a-zA-Z0-9/_.]*/'${prog} @@ -299,14 +289,14 @@ stop_prog() prog=$1 asroot=${2:1} withmpi=${3:1} - [ ! -z "$asroot" ] && asroot=sudo + [ ! -z "$asroot" ] && asroot=sudo # special check for logging-daemons [ $prog == $logProgToSkip ] && return - + # check existance [ -x $BINDIR/$prog ] || [ -x $BINDIR/${prog}.sh ] || return - + # if it is a shell script call the script if [ -f $BINDIR/${prog}.sh ]; then $BINDIR/${prog}.sh stop @@ -319,6 +309,14 @@ stop_prog() return fi + # if RSPDriver disable external clock first (use 125MHz board clock) + if [ "$prog" = "RSPDriver" ]; then + echo "disable clock output on clock board" + rspctl --clock=0 1>/dev/null 2>&1 + # wait while setting is done + sleep 15 + fi + # PVSS needs special treatment if [ "$prog" = "PVSS00pmon" ]; then echo "Stopping PVSS database" @@ -334,7 +332,7 @@ stop_prog() # first try normal kill for pid in `/sbin/pidof -x ${prog}` - do + do echo "Softly killing ${prog}(${pid})" $asroot kill $pid 1>/dev/null 2>&1 usleep 500000 @@ -342,41 +340,52 @@ stop_prog() # when normal kill did not work, kill is with -9 for pid in `/sbin/pidof -x ${prog}` - do + do echo "Hard killing ${prog}(${pid})" $asroot kill -9 $pid 1>/dev/null 2>&1 usleep 500000 done - # if user0 or lofarsys, try normal kill as root - - for pid in `/sbin/pidof -x ${prog}` - do - if [ "$user" == "user0" -o "$user" == "lofarsys" ]; then - sudo kill $pid 1>/dev/null 2>&1 - usleep 50000 - fi - done - - # if user0 or lofarsys, try hard kill as root - for pid in `/sbin/pidof -x ${prog}` - do - if [ "$user" == "user0" -o "$user" == "lofarsys" ]; then - sudo kill -9 $pid 1>/dev/null 2>&1 - usleep 50000 - fi - done - - # if still alive, write a message - for pid in `/sbin/pidof -x ${prog}` - do - echo -n "Could not kill ${prog}(${pid}); " - if [ "$user" == "user0" -o "$user" == "lofarsys" ]; then - echo "tried it as root as well, giving up..." - else - echo "probably run by another user, contact your system administrator" - fi + # if user0 or lofarsys, try normal kill as root + + for pid in `/sbin/pidof -x ${prog}` + do + if [ "$user" == "user0" -o "$user" == "lofarsys" ]; then + sudo kill $pid 1>/dev/null 2>&1 + usleep 50000 + fi done + # if user0 or lofarsys, try hard kill as root + for pid in `/sbin/pidof -x ${prog}` + do + if [ "$user" == "user0" -o "$user" == "lofarsys" ]; then + sudo kill -9 $pid 1>/dev/null 2>&1 + usleep 50000 + fi + done + + # if still alive, write a message + for pid in `/sbin/pidof -x ${prog}` + do + echo -n "Could not kill ${prog}(${pid}); " + if [ "$user" == "user0" -o "$user" == "lofarsys" ]; then + echo "tried it as root as well, giving up..." + else + echo "probably run by another user, contact your system administrator" + fi + done + + # if RSPDriver set rsp firmware to image 0 (factory image) + if [ "$prog" = "RSPDriver" ]; then + image=0 + imageForced=1 + selectImage + if [ $boardError -eq 1 ]; then + exit + fi + echo "RSP image set to image 0, wait.." + sleep 12 + fi } # @@ -391,17 +400,17 @@ status_prog() list=( `cat $LEVELTABLE | cut -d"#" -f1 | awk '{ if (NF>0) print $0 }' ` ) for line in ${list[@]} do - # expected process and swlevel it should run in + # expected process and swlevel it should run in levelnr=`echo $line | cut -d":" -f1` prog=`echo $line | cut -d":" -f6` - pid=("") + pid=("") # special check for logging-daemons [ $prog == $logProgToSkip ] && continue - + # check existance [ -x $BINDIR/$prog ] || [ -x $BINDIR/${prog}.sh ] || continue - + if [ $prevlevel -ne $levelnr ]; then echo "---" prevlevel=$levelnr @@ -422,11 +431,11 @@ status_prog() i=0 for apid in ${pid[@]} do - obsid[i]=`ps -p $apid --no-heading -o command | awk -F{ '{print $2}' | awk -F} '{print $1}'` - if [ $show_users -eq 1 ]; then - pid_user[i]=`ps -p $apid -o user=` - fi - i=$i+1 + obsid[i]=`ps -p $apid --no-heading -o command | awk -F{ '{print $2}' | awk -F} '{print $1}'` + if [ $show_users -eq 1 ]; then + pid_user[i]=`ps -p $apid -o user=` + fi + i=$i+1 done # If a program is running in a level higher than the level # that should be active, raise the active level to indicate @@ -436,47 +445,47 @@ status_prog() pid="DOWN" fi - if [ "$pid" != "DOWN" ] && [ ${#obsid[0]} != 0 ]; then - echo ${levelnr}:${prog}:${pid[*]}:${obsid[*]} | awk -F: '{ printf "%s : %-25s %s [ObsID: %s]\n", $1, $2, $3, $4 }' + if [ "$pid" != "DOWN" ] && [ ${#obsid[0]} != 0 ]; then + echo ${levelnr}:${prog}:${pid[*]}:${obsid[*]} | awk -F: '{ printf "%s : %-25s %s [ObsID: %s]\n", $1, $2, $3, $4 }' elif [ "$pid" != "DOWN" ] && [ ${show_users} -eq 1 ]; then - echo ${levelnr}:${prog}:${pid[*]}:${pid_user[*]} | awk -F: '{ printf "%s : %-25s %s [%s]\n", $1, $2, $3, $4 }' + echo ${levelnr}:${prog}:${pid[*]}:${pid_user[*]} | awk -F: '{ printf "%s : %-25s %s [%s]\n", $1, $2, $3, $4 }' else - echo ${levelnr}:${prog}:${pid[*]} | awk -F: '{ printf "%s : %-25s %s\n", $1, $2, $3}' + echo ${levelnr}:${prog}:${pid[*]} | awk -F: '{ printf "%s : %-25s %s\n", $1, $2, $3}' fi # Some Checks # Controllers must have one instance, only. Some programs may have more instances. - if [ ${#pid[@]} -ge 2 ]; then - if [ "$prog" != "ObservationControl" \ - -a "$prog" != "PythonControl" \ - -a "$prog" != "OnlineControl" ]; then - toomany="$toomany ${prog}[$levelnr]" - fi + if [ ${#pid[@]} -ge 2 ]; then + if [ "$prog" != "ObservationControl" \ + -a "$prog" != "PythonControl" \ + -a "$prog" != "OnlineControl" ]; then + toomany="$toomany ${prog}[$levelnr]" + fi fi - - # Check for missing controllers - if [ "$pid" = "DOWN" -o "$pid" = "0" ]; then - if [ $levelnr -le $level ]; then - if [ $levelnr -le 5 ]; then - missing="$missing ${prog}[$levelnr]" - else - # LCU level 6 has two permanent controllers running - if [ "$prog" == "StationControl" \ - -o "$prog" == "ClockControl" ]; then - missing="$missing ${prog}[$levelnr]" - fi - # MCU level 6 must have MACScheduler running - if [ "$prog" == "MACScheduler" ]; then - missing="$missing ${prog}[$levelnr]" + + # Check for missing controllers + if [ "$pid" = "DOWN" -o "$pid" = "0" ]; then + if [ $levelnr -le $level ]; then + if [ $levelnr -le 5 ]; then + missing="$missing ${prog}[$levelnr]" + else + # LCU level 6 has two permanent controllers running + if [ "$prog" == "StationControl" \ + -o "$prog" == "ClockControl" ]; then + missing="$missing ${prog}[$levelnr]" + fi + # MCU level 6 must have MACScheduler running + if [ "$prog" == "MACScheduler" ]; then + missing="$missing ${prog}[$levelnr]" + fi + fi fi - fi - fi fi done echo "---" - if [ "$missing" ]; then + if [ "$missing" ]; then echo "Missing :"$missing fi - if [ "$toomany" ]; then + if [ "$toomany" ]; then echo "Too many:"$toomany fi } @@ -488,210 +497,217 @@ goto_level() { #first power down to new level newlevel=$1 - + if [ -e /tmp/level.admin ]; then + curlevel=`cat /tmp/level.admin` + else + curlevel=0 + fi # set rcumode to 0 (power save) when entering level 1 if [ ${newlevel} -le 1 ]; then - if [ -e /tmp/level.admin ]; then - curlevel=`cat /tmp/level.admin` - else - curlevel=-1 - fi - if [ ${curlevel} -ge 2 ]; then - /sbin/pidof RSPDriver 1>/dev/null 2>&1 - if [ $? -eq 0 ]; then - status=`( rspctl --version | grep "0.0" ) >& /dev/null; echo $?` - if [ $status == 1 ]; then - echo "set rcumode to 0 (power save)" - rspctl --rcumode=0 1>/dev/null 2>&1 - # Wait for setting to take effect before killing RSPDriver - sleep 2 - else + if [ ${curlevel} -ge 2 ]; then + /sbin/pidof RSPDriver 1>/dev/null 2>&1 + if [ $? -eq 0 ]; then + status=`( rspctl --version | grep "0.0" ) >& /dev/null; echo $?` + if [ $status == 1 ]; then + echo "set rcumode to 0 (power save)" + rspctl --rcumode=0 1>/dev/null 2>&1 + # Wait for setting to take effect before killing RSPDriver + sleep 2 + else echo "Beware: NOT going to rcumode 0 as images are still being initialized" - fi - fi - if [ -e $SBINDIR/stopTBB.sh ]; then - echo "Stopping TBB recording mode" - $SBINDIR/stopTBB.sh - fi - fi + fi + fi + if [ -e $SBINDIR/stopTBB.sh ]; then + echo "Stopping TBB recording mode" + $SBINDIR/stopTBB.sh + fi + fi fi - for (( l=6 ; l>newlevel ; l-- )) + for (( l=6 ; l > newlevel ; l-- )) do - tac $LEVELTABLE | cut -d"#" -f1 | awk '{ if (NF>0) print $0 }' | \ + if [ $curlevel -gt $l ]; then + # echo "Write new swlevel $l to /tmp/level.admin" + echo $l > /tmp/level.admin + fi + tac $LEVELTABLE | cut -d"#" -f1 | awk '{ if (NF>0) print $0 }' | \ grep "^${l}:" | grep ":d:" | while read line do - ( + ( asroot=`echo $line | cut -d":" -f4` withmpi=`echo $line | cut -d":" -f5` program=`echo $line | cut -d":" -f6` stop_prog $program x$asroot x$withmpi - ) <&- # cant have programs reading from stdin - # as that would mess up 'while read line' + ) <&- # cant have programs reading from stdin + # as that would mess up 'while read line' done + done - # then power up to new level - for (( l=1 ; l<=newlevel ; l++ )) + # then power up to new level + for (( l=1 ; l <= newlevel ; l++ )) do - - # Start programs for level $l - cat $LEVELTABLE | cut -d"#" -f1 | awk '{ if (NF>0) print $0 }' | grep "^${l}:" | grep ":u:" | while read line + # Start programs for level $l + cat $LEVELTABLE | cut -d"#" -f1 | awk '{ if (NF>0) print $0 }' | grep "^${l}:" | grep ":u:" | while read line do - ( + ( asroot=`echo $line | cut -d":" -f4` withmpi=`echo $line | cut -d":" -f5` program=`echo $line | cut -d":" -f6` start_prog $program x$asroot x$withmpi - ) <&- # cant have programs reading from stdin - # as that would mess up 'while read line' - done + ) <&- # cant have programs reading from stdin + # as that would mess up 'while read line' + done + if [ $curlevel -le $l ]; then + # echo "Write new swlevel $l to /tmp/level.admin" + echo $l > /tmp/level.admin + fi done } show_level() { - if [ -e /tmp/level.admin ]; then - level=`cat /tmp/level.admin` - status_prog >& /dev/null - if [ $highest_level_running -gt $level ]; then - level=$highest_level_running - status_prog >& /dev/null - fi - if [ "$missing" != "" ]; then - let level=0-$level - fi - - if [ "$1" != "S" ]; then - echo -n "Currently set level is " - fi - echo $level - if [ "$1" = "S" -o "$1" = "s" ]; then - exit - fi - else - level=-1 - if [ "$1" != "S" ]; then - echo "Currently set level unknown" - fi - fi - # argument -s/-S only returns level, no list - if [ -z "$1" ]; then - status_prog - fi - exit $level + if [ -e /tmp/level.admin ]; then + level=`cat /tmp/level.admin` + status_prog >& /dev/null + if [ $highest_level_running -gt $level ]; then + level=$highest_level_running + status_prog >& /dev/null + fi + if [ "$missing" != "" ]; then + let level=0-$level + fi + + if [ "$1" != "S" ]; then + echo -n "Currently set level is " + fi + echo $level + if [ "$1" = "S" -o "$1" = "s" ]; then + exit + fi + else + level=-1 + if [ "$1" != "S" ]; then + echo "Currently set level unknown" + fi + fi + # argument -s/-S only returns level, no list + if [ -z "$1" ]; then + status_prog + fi + exit $level } print_level() { - if [ -e /tmp/level.admin ]; then - level=`cat /tmp/level.admin` - if [ "$1" != "P" ]; then - echo -n "Last set level is " - fi - echo $level - if [ "$1" = "P" -o "$1" = "p" ]; then - exit - fi - else - level=-1 - if [ "$1" != "S" ]; then - echo "Last set level unknown" - fi - fi - # argument -s/-S only returns level, no list - exit $level + if [ -e /tmp/level.admin ]; then + level=`cat /tmp/level.admin` + if [ "$1" != "P" ]; then + echo -n "Last set level is " + fi + echo $level + if [ "$1" = "P" -o "$1" = "p" ]; then + exit + fi + else + level=-1 + if [ "$1" != "S" ]; then + echo "Last set level unknown" + fi + fi + # argument -s/-S only returns level, no list + exit $level } show_lofar_version() { - if [ -e $LOFARROOT/Version.txt ]; then - version=`cat $LOFARROOT/Version.txt` - if [ "$1" = "v" ]; then - echo -n "Current LOFAR version is " - fi - echo $version - else - version="-1" - if [ "$1" = "v" ]; then - echo "Current LOFAR version unknown" - fi - fi - if [ "$version" != "-1" ]; then - exit - else - exit $version - fi + if [ -e $LOFARROOT/Version.txt ]; then + version=`cat $LOFARROOT/Version.txt` + if [ "$1" = "v" ]; then + echo -n "Current LOFAR version is " + fi + echo $version + else + version="-1" + if [ "$1" = "v" ]; then + echo "Current LOFAR version unknown" + fi + fi + if [ "$version" != "-1" ]; then + exit + else + exit $version + fi } handle_args() { - # Handle arguments - if [ ${#} -gt 1 ]; then - if [[ $1 != \-* ]]; then - echo "Warning: all arguments except level $1 will be ignored" - fi - fi - while getopts "hUuSsPpVvi:l:q:r:" flag - do - case "$flag" in - [uU]) - show_users=1 - show_level - ;; - [sS]) - show_level $flag - ;; - [pP]) - print_level $flag - ;; - [vV]) - show_lofar_version $flag - ;; - i) - imageForced=1 - image=$OPTARG - # This is needed to be able to retrieve the requested swlevel - # when it is not provided with option -l - shift $((OPTIND-1)); OPTIND=1 - ;; - q) - procesname=$OPTARG - stop_prog $procesname - exit - ;; - r) - procesname=$OPTARG - start_prog $procesname - exit - ;; - l) - level=$OPTARG - ;; - h) - SyntaxError - ;; - *) - exit - ;; - esac - done - if [ -z $level ]; then - if [ "$1" != "" ]; then - level=$1 - else - level=-1 - fi - fi + # Handle arguments + if [ ${#} -gt 1 ]; then + if [[ $1 != \-* ]]; then + echo "Warning: all arguments except level $1 will be ignored" + fi + fi + while getopts "hUuSsPpVvi:l:q:r:" flag + do + case "$flag" in + [uU]) + show_users=1 + show_level + ;; + [sS]) + show_level $flag + ;; + [pP]) + print_level $flag + ;; + [vV]) + show_lofar_version $flag + ;; + i) + imageForced=1 + image=$OPTARG + # This is needed to be able to retrieve the requested swlevel + # when it is not provided with option -l + shift $((OPTIND-1)); OPTIND=1 + ;; + q) + procesname=$OPTARG + stop_prog $procesname + exit + ;; + r) + procesname=$OPTARG + start_prog $procesname + exit + ;; + l) + level=$OPTARG + ;; + h) + SyntaxError + ;; + *) + exit + ;; + esac + done + if [ -z $level ]; then + if [ "$1" != "" ]; then + level=$1 + else + level=-1 + fi + fi - if [ "$user" != "lofarsys" -a $level -gt 3 ]; then - echo "Will only start up to level 3 as this appears to be local use" - level=3 - fi + if [ "$user" != "lofarsys" -a $level -gt 3 ]; then + echo "Will only start up to level 3 as this appears to be local use" + level=3 + fi - return + return } @@ -701,7 +717,7 @@ handle_args() # Find out if we are running on a PVSS system -# Note: on PVSS systems LoggingClient must be ignored, +# Note: on PVSS systems LoggingClient must be ignored, # On non-PVSS system LoggingProcessor. logProgToSkip=LoggingProcessor @@ -712,8 +728,8 @@ fi # All users can ask for current level show_users=0 -if [ -z $1 ]; then - show_level +if [ -z $1 ]; then + show_level fi user=`id | cut -d'(' -f2 | cut -d')' -f1` @@ -724,14 +740,14 @@ handle_args $* # All other options that act on the station status are for lofarsys only # Don't allow root to run swlevel because all logfile get root access. -if [ "$LOFARROOT" == "/opt/lofar" -a "$user" != "lofarsys" -a "$group" != "local" ]; then - echo "swlevel must be run by user lofarsys or group local members!" - exit +if [ "$LOFARROOT" == "/opt/lofar" -a "$user" != "lofarsys" -a "$group" != "local" ]; then + echo "swlevel must be run by user lofarsys or group local members!" + exit fi # first power down to this level case $level in - 0|1|2|3|4|5|6) + 0|1|2|3|4|5|6) ;; *) SyntaxError esac @@ -741,8 +757,8 @@ cd ${BINDIR} goto_level $level cd ${cwd} status_prog -if [ $highest_level_running -gt $level ]; then - echo "Could not go to level $level. Level is $highest_level_running" +if [ $highest_level_running -gt $level ]; then + echo "Could not go to level $level. Level is $highest_level_running" fi # save for later echo $level > /tmp/level.admin diff --git a/MAC/Tools/Power/ecSetObserving.py b/MAC/Tools/Power/ecSetObserving.py deleted file mode 100755 index 07c6a09c347..00000000000 --- a/MAC/Tools/Power/ecSetObserving.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/python -# -# Look for RCUs in ON mode and set EC to observing -# -# 2012 P.Donker - -from isEcLib import * -import time -import subprocess -import os,sys - -def main(): - host = getIP() - if host == None: - print "===============================================" - print "ERROR, this script can only run on a IS station" - print "===============================================" - - ec = EC(host) - ec.printInfo(False) - - ec.connectToHost() - time.sleep(1.0) - # version is used to check if function is available in firmware - version,versionstr = ec.getVersion() - ec.disconnectHost() - - # run each minute - while True: - RSPobserving = 0 - TBBobserving = 0 - - response = cmd('rspctl', '--rcu').splitlines() - for line in response: - pos = line.find('RCU[') - if pos != -1: - data = line.strip().split(',')[0].split()[2] - if data == 'ON': - RSPobserving = 1 - - ec.connectToHost() - time.sleep(1.0) - ec.setObserving(RSPobserving) - ec.disconnectHost() - time.sleep(60.0) - -# excecute commandline cmd -def cmd(cmd, args=''): - if len(args) > 0: - proc = subprocess.Popen([cmd, args], stdout=subprocess.PIPE, stderr = subprocess.STDOUT ) - else: - proc = subprocess.Popen([cmd], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - (so, se) = proc.communicate() - return(so) - -# start main() -if __name__ == "__main__": - - # Fork the process, so we can run it as a daemon - # using /etc/init.d/ecSetObserving [start/stop/status] - fpid = os.fork() - if fpid!=0: - # Running as daemon now. PID is fpid - sys.exit(0) - - main() - - - diff --git a/MAC/Tools/Power/isResetTrip.py b/MAC/Tools/Power/ec_reset_trip.py similarity index 61% rename from MAC/Tools/Power/isResetTrip.py rename to MAC/Tools/Power/ec_reset_trip.py index dd11302771e..54110c4cdb2 100755 --- a/MAC/Tools/Power/isResetTrip.py +++ b/MAC/Tools/Power/ec_reset_trip.py @@ -1,19 +1,18 @@ #!/usr/bin/python -## "isReset48V.py" -## RESET 48V powersupply on IS (international station) -## can only be used on IS (international) LCU +## Reset trip system in EC unit +## can only be used on LCU ## -## usage: ./isReset48V.py +## usage: ./ec_reset_trip.py ## ## Author: Pieter Donker (ASTRON) -## Last change: november 2011 +## Last change: september 2014 -from isEcLib import * +from st_ec_lib import * import sys import time -VERSION = '1.1.0' # version of this script +VERSION = '1.2.0' # version of this script # used variables version = 0 # EC version @@ -25,9 +24,9 @@ versionstr = 'V-.-.-' if __name__ == '__main__': host = getIP() if host == None: - print "===============================================" - print "ERROR, this script can only run on a IS station" - print "===============================================" + print "============================================" + print "ERROR, this script can only run on a station" + print "============================================" else: ec = EC(host) ec.connectToHost() diff --git a/MAC/Tools/Power/ec_set_observing.py b/MAC/Tools/Power/ec_set_observing.py new file mode 100755 index 00000000000..0916b1dbd17 --- /dev/null +++ b/MAC/Tools/Power/ec_set_observing.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# +# Look for RCUs in ON mode and set EC to observing +# +# 2016 P.Donker +# +# this script is called by a cron job every minute + +import os +os.chdir('/opt/lofar/sbin/') + +import socket +import time +import subprocess + + +from st_ec_lib import * + +def main(): + host = getIP() + if host == None: + print "===============================================" + print "ERROR, this script can only run on a station" + print "===============================================" + + ec = EC(host) + ec.printInfo(False) + + observing = 0 + + # look for RCU's in ON state + response = cmd('/opt/lofar/bin/rspctl --rcu') + for line in response.splitlines(): + if 'RCU[' in line: + data = line.strip().split(',')[0].split()[-1] + if data == 'ON': + observing = 1 + + #print "set observing %d" % observing + ec.connectToHost() + ec.setObserving(observing) + time.sleep(0.5) + ec.disconnectHost() + +# excecute commandline cmd +def cmd(command): + cmd_list = command.split() + proc = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + (so, se) = proc.communicate() + return(so) + +# start main() +if __name__ == "__main__": + main() + + + diff --git a/MAC/Tools/Power/isEcLib.py b/MAC/Tools/Power/isEcLib.py deleted file mode 100755 index 64c847d84f9..00000000000 --- a/MAC/Tools/Power/isEcLib.py +++ /dev/null @@ -1,259 +0,0 @@ -## P.Donker ASTRON februari 2011 -## EC IS status module - -import socket -import struct -import time - -def getIP(): - # get ip-adres of LCU - local_host = socket.gethostbyname(socket.gethostname()) - ip = local_host.split('.') - if ip[0] == '10' and ip[1] == '209': - # if LCU adress make it EC adress - return(local_host[:local_host.rfind('.')+1]+'3') - elif ip[0] == '192' and ip[1] == '168': - # if LCU adress make it EC adress - return(local_host[:local_host.rfind('.')+1]+'103') - else: - return(None) - -class EC: - # cmdIDs from TCP PROTOCOL ec-controller - EC_NONE = 0 - EC_STATUS = 1 - EC_CTRL_TEMP = 3 - EC_VERSION = 5 - EC_SET_HEATER = 17 - EC_SET_48 = 20 - EC_RESET_48 = 22 - EC_SET_LCU = 25 - EC_RESET_LCU = 27 - EC_RESET_TRIP = 28 - SET_OBSERVING = 120 - - PWR_OFF = 0 - PWR_ON = 1 - P_48 = 0 - P_LCU = 1 - P_ALL = 2 - - printToScreen = False - host = None - station = None - port = 10000 - sck = None - logger = False - info = '' - version = 0 - versionstr = 'V-.-.-' - - def __init__(self, addr='0.0.0.0'): - self.host = addr - try: - (hostname,a,b) = socket.gethostbyaddr(addr) - self.station = hostname.split('.')[0] - except: - self.station = 'Unknown' - - def setInfo(self, info): - self.info = info - if self.printToScreen: - print self.info - self.info = '' - else: self.info += '\n' - return - - def addInfo(self, info): - self.info += info - if self.printToScreen: - print self.info - self.info = '' - else: self.info += '\n' - return - - def printInfo(self, state=True): - self.printToScreen = state - return - - def hex2bit(self, val=0, bits=16): - bit = '' - for i in range(bits-1,-1,-1): - if val & (1 << i): - bit += '1' - else: - bit += '0' - return(bit) - - #--------------------------------------- - def connectToHost(self): - self.setInfo("connecting to %s on port %d" %(self.host, self.port)) - connected = False - - try: - self.sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - except socket.error: - self.sck.close() - return(connected) - - try: - self.sck.settimeout(3.0) - self.sck.connect((self.host, self.port)) - connected = True - #time.sleep(0.1) - except socket.error: - self.sck.close() - return(connected) - - #--------------------------------------- - def disconnectHost(self): - self.setInfo("closing %s" %(self.host)) - self.sck.close() - time.sleep(0.5) - return - - #--------------------------------------- - def sendCmd(self, cmdId=0, cab=-1, value=0): - if (cmdId == self.EC_NONE): - return (false) - try: - cmd = struct.pack('hhh', cmdId, cab, int(value)) - self.sck.send(cmd) - except socket.error: - self.setInfo("socket error, try to reconnect") - self.disconnectHost() - time.sleep(10.0) - self.connectToHost() - return - #--------------------------------------- - def recvAck(self): - socketError = False - try: - self.sck.settimeout(1.0) - data = self.sck.recv(6) - except socket.error: - socketError = True - self.setInfo("socket error, try to reconnect") - #self.disconnectHost() - #self.connectToHost() - if socketError: - return(0,0,[]) - - header = struct.unpack('hhh', data) - cmdId = header[0] - status = header[1] - PLSize = header[2] - if (PLSize > 0): - data = self.sck.recv(PLSize) - fmt = 'h' * int(PLSize / 2) - PL = struct.unpack(fmt, data) - else: - PL = [] - return (cmdId, status, PL) - #--------------------------------------- - def setPower(self, pwr=P_ALL, state=PWR_ON): - if ((pwr == self.P_48) or (pwr == self.P_ALL)): - self.sendCmd(self.EC_SET_48, 0, state) - (cmdId, status, PL) = self.recvAck() - self.setInfo('Power Set 48V to %d' %(state)) - if ((pwr == self.P_LCU) or (pwr == self.P_ALL)): - self.sendCmd(self.EC_SET_LCU, 0, state) - (cmdId, status, PL) = self.recvAck() - self.setInfo('Power Set LCU to %d' %(state)) - return(self.info) - #--------------------------------------- - def resetPower(self, pwr=P_ALL): - if ((pwr == self.P_48) or (pwr == self.P_ALL)): - self.sendCmd(self.EC_RESET_48, 0, 0) - (cmdId, status, PL) = self.recvAck() - self.setInfo('PowerReset 48V') - if ((pwr == self.P_LCU) or (pwr == self.P_ALL)): - self.sendCmd(self.EC_RESET_LCU, 0, 0) - (cmdId, status, PL) = self.recvAck() - self.setInfo('PowerReset LCU') - return(self.info) - #--------------------------------------- - def resetTrip(self): - self.sendCmd(self.EC_RESET_TRIP, -1, 0) - (cmdId, status, PL) = self.recvAck() - self.setInfo('Reset Trip System') - return(self.info) - #--------------------------------------- - def setHeater(self, mode=0): - self.sendCmd(self.EC_SET_HEATER, -1, mode) - (cmdId, status, payload) = self.recvAck() - - if (mode == self.MODE_ON): self.setInfo('heater is turned ON') - if (mode == self.MODE_OFF): self.setInfo('heater is turned OFF') - if (mode == self.MODE_AUTO): self.setInfo('heater set to AUTO') - - #--------------------------------------- - def getVersion(self): - self.sendCmd(self.EC_VERSION) - (cmdId, status, PL) = self.recvAck() - try: - version = int((PL[0]*100)+(PL[1]*10)+PL[2]) - versionstr = 'V%d.%d.%d' %(PL) - self.version = version - self.versionstr = versionstr - self.setInfo('EC software version %d.%d.%d' %(PL)) - except: - version = 0 - versionstr = 'V0.0.0' - self.version = version - self.versionstr = versionstr - self.setInfo('EC software version 0.0.0') - - return version, versionstr - - #--------------------------------------- - def getStatusData(self): - self.sendCmd(self.EC_STATUS) - (cmdId, status, PL2) = self.recvAck() - return PL2 - - def getStatus(self): - onoff = ('OFF','ON') - badok = ('N.A.','OK') - - # get information from EC - self.sendCmd(self.EC_CTRL_TEMP) - (cmdId, status, PL1) = self.recvAck() - self.sendCmd(self.EC_STATUS) - (cmdId, status, PL2) = self.recvAck() - if len(PL1) == 0 or len(PL2) == 0: return - # fill lines with data - lines = [] - lines.append('temperature = %5.2f' %(PL2[2]/100.)) - lines.append('humidity = %5.2f' %(PL2[3]/100.)) - lines.append('heater state = %s' %(onoff[PL2[(3*7)+6]])) - lines.append('power 48V state = %s' %(onoff[(PL2[28] & 1)])) - lines.append('power LCU state = %s' %(onoff[(PL2[28] >> 1) & 1])) - lines.append('lightning state = %s' %(badok[(PL2[29] & 1)])) - - # print lines to screen or file, see printInfo - info1 = ' %s (EC %s)' %(self.station, self.versionstr) - info2 = ' %s ' %(time.asctime()) - self.setInfo('-' * len(info2)) - self.addInfo(info1) - self.addInfo(info2) - self.addInfo('-' * len(info2)) - for line in lines: - self.addInfo(line) - - #--------------------------------------- - def getPowerStatus(self): - state = ('OFF','ON') - # get information from EC - self.sendCmd(self.EC_STATUS) - (cmdId, status, PL) = self.recvAck() - - self.addInfo('Power: 48V = %s, LCU = %s' %(state[(PL[28] & 1)], state[(PL[28] >> 1)])) - - #--------------------------------------- - ## set observing active(1) or not active(0) - def setObserving(self, state=0): - self.sendCmd(self.SET_OBSERVING, -1, state) - (cmdId, status, PL) = self.recvAck() - self.setInfo('SetObserving to %d' %(state)) - return(self.info) diff --git a/MAC/Tools/Power/nlStatus.py b/MAC/Tools/Power/nlStatus.py deleted file mode 100755 index 0e51c2006b4..00000000000 --- a/MAC/Tools/Power/nlStatus.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/python - -## "nlStatus.py" -## Print EC status of NL(dutch) station -## can only be used on NL (dutch) LCU -## -## usage: ./nlStatus.py -## -## Author: Pieter Donker (ASTRON) -## Last change: november 2011 - -from nlEcLib import * -import sys -import time - -VERSION = '1.1.0' # version of this script - -# used variables -version = 0 # EC version -versionstr = 'V-.-.-' - -##======================================================================= -## start of main program -##======================================================================= -if __name__ == '__main__': - host = getIP() - #print host - if host == None: - print "===============================================" - print "ERROR, this script can only run on a NL station" - print "===============================================" - else: - ec = EC(host) - ec.connectToHost() - time.sleep(1.0) - # version is used to check if function is available in firmware - version,versionstr = ec.getVersion() - ec.printInfo(True) - print "" - ec.getStationInfo() - ec.getStatus() - print "" - ec.getTripStatus() - print "" - ec.printInfo(False) - ec.disconnectHost() - - diff --git a/MAC/Tools/Power/nlStatusData.py b/MAC/Tools/Power/nlStatusData.py deleted file mode 100755 index dbd6a302ea7..00000000000 --- a/MAC/Tools/Power/nlStatusData.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/python - -""" -write status-data to stdout -stdout format: - time [0] data_cab0 [1] data_cab1 [3] data_cab3 - -values in data_cabx: - temperature humidity fansstate heaterstate - temperature : actual temperature in cabinet - humidity : actual humidity in cabinet - fanstate : which fans are on - bit 0 outer fan front - bit 1 inner fan front - bit 2 inner fan back - bit 4 outer fan back - heaterstate : only available in cabinet 3 - 0 = off - 1 = on - -example, returned data: -1333702601 [0] 24.71 16.81 4 0 [1] 24.72 43.36 4 0 [3] 14.69 41.73 2 0 - -""" - -#from eccontrol import * -#from stations import * -import nlEcLib as eclib -import sys -import time - -VERSION = '1.0.0' # version of this script -Station = '' - -##======================================================================= -## start of main program -##======================================================================= -def main(): - ec = eclib.EC(eclib.getIP()) - ec.connectToHost() - ec.printInfo(False) # print NOT to screen - - # version is used to check if function is available in firmware - PL2 = ec.getStatusData() - print '%1.0f' %(time.time()), - cabs = [0,1,3] - for cab in cabs: - # print cabnr, temperature, humidity, fansstate, heaterstate - print '[%d] %1.2f %1.2f %d %d' %\ - ( cab, PL2[(cab*7)+2]/100., PL2[(cab*7)+3]/100., - PL2[(cab*7)+4] & 0x0f, PL2[(cab*7)+6]), - print - - ##---------------------------------------------------------------------- - ## do not delete next lines - ec.disconnectHost() - - -if __name__ == '__main__': - main() - diff --git a/MAC/Tools/Power/isReset48V.py b/MAC/Tools/Power/reset_48v.py old mode 100755 new mode 100644 similarity index 64% rename from MAC/Tools/Power/isReset48V.py rename to MAC/Tools/Power/reset_48v.py index 6a2cb38d66a..4e050945dc8 --- a/MAC/Tools/Power/isReset48V.py +++ b/MAC/Tools/Power/reset_48v.py @@ -1,19 +1,18 @@ #!/usr/bin/python -## "isReset48V.py" -## RESET 48V powersupply on IS (international station) -## can only be used on IS (international) LCU +## RESET 48V powersupply +## can only be used on LCU ## -## usage: ./isReset48V.py +## usage: ./reset_48v.py ## ## Author: Pieter Donker (ASTRON) -## Last change: november 2011 +## Last change: september 2014 -from isEcLib import * +from st_ec_lib import * import sys import time -VERSION = '1.1.0' # version of this script +VERSION = '1.2.0' # version of this script # used variables version = 0 # EC version @@ -25,9 +24,9 @@ versionstr = 'V-.-.-' if __name__ == '__main__': host = getIP() if host == None: - print "===============================================" - print "ERROR, this script can only run on a IS station" - print "===============================================" + print "============================================" + print "ERROR, this script can only run on a station" + print "============================================" else: ec = EC(host) ec.connectToHost() diff --git a/MAC/Tools/Power/isResetLCU.py b/MAC/Tools/Power/reset_lcu.py similarity index 83% rename from MAC/Tools/Power/isResetLCU.py rename to MAC/Tools/Power/reset_lcu.py index 46e7c732700..138d57f223c 100755 --- a/MAC/Tools/Power/isResetLCU.py +++ b/MAC/Tools/Power/reset_lcu.py @@ -1,20 +1,19 @@ #!/usr/bin/python -## "isResetLCU.py" -## RESET LCU power on IS (international station) -## can only be used on IS LCU +## RESET LCU power +## can only be used on LCU ## ## USE ONLY IF NORMAL SHUTDOWN IS NOT POSSIBLE -## usage: ./isResetLCU.py +## usage: ./reset_lcu.py ## ## Author: Pieter Donker (ASTRON) -## Last change: november 2011 +## Last change: september 2014 -from isEcLib import * +from st_ec_lib import * import sys import time -VERSION = '1.1.0' # version of this script +VERSION = '1.2.0' # version of this script # used variables version = 0 # EC version @@ -27,7 +26,7 @@ if __name__ == '__main__': host = getIP() if host == None: print "===============================================" - print "ERROR, this script can only run on a IS station" + print "ERROR, this script can only run on a station" print "===============================================" else: ec = EC(host) diff --git a/MAC/Tools/Power/nlEcLib.py b/MAC/Tools/Power/st_ec_lib.py similarity index 64% rename from MAC/Tools/Power/nlEcLib.py rename to MAC/Tools/Power/st_ec_lib.py index 33fcf0d2f4c..a900e915e0d 100755 --- a/MAC/Tools/Power/nlEcLib.py +++ b/MAC/Tools/Power/st_ec_lib.py @@ -1,5 +1,5 @@ ## P.Donker ASTRON februari 2011 -## EC NL status module +## station EC lib module import socket import struct @@ -9,7 +9,7 @@ def getIP(): # get ip-adres of LCU local_host = socket.gethostbyname(socket.gethostname()) ip = local_host.split('.') - if ip[0] == '10' and ip[1] == '151': + if ip[0] == '10' and (ip[1] == '151' or ip[1] == '209'): # if LCU adress make it EC adress return(local_host[:local_host.rfind('.')+1]+'3') return(None) @@ -21,6 +21,19 @@ class EC: EC_CTRL_TEMP = 3 EC_VERSION = 5 EC_STATION_INFO = 6 + EC_SET_HEATER = 17 + EC_SET_48 = 20 + EC_RESET_48 = 22 + EC_SET_LCU = 25 + EC_RESET_LCU = 27 + EC_RESET_TRIP = 28 + SET_OBSERVING = 120 + + PWR_OFF = 0 + PWR_ON = 1 + P_48 = 0 + P_LCU = 1 + P_ALL = 2 printToScreen = False host = None @@ -31,7 +44,7 @@ class EC: info = '' version = 0 versionstr = 'V-.-.-' - + def __init__(self, addr='0.0.0.0'): self.host = addr try: @@ -39,7 +52,7 @@ class EC: self.station = hostname.split('.')[0] except: self.station = 'Unknown' - + def setInfo(self, info): self.info = info if self.printToScreen: @@ -55,19 +68,10 @@ class EC: self.info = '' else: self.info += '\n' return - + def printInfo(self, state=True): self.printToScreen = state return - - def hex2bit(self, val=0, bits=16): - bit = '' - for i in range(bits-1,-1,-1): - if val & (1 << i): - bit += '1' - else: - bit += '0' - return(bit) #--------------------------------------- def connectToHost(self): @@ -84,18 +88,21 @@ class EC: self.sck.settimeout(3.0) self.sck.connect((self.host, self.port)) connected = True - #time.sleep(0.1) + time.sleep(0.5) + self.getVersion() + if self.version > 200: + self.getStationInfo() except socket.error: self.sck.close() return(connected) - + #--------------------------------------- def disconnectHost(self): self.setInfo("closing %s" %(self.host)) self.sck.close() - time.sleep(1.0) + time.sleep(0.5) return - + #--------------------------------------- def sendCmd(self, cmdId=0, cab=-1, value=0): if (cmdId == self.EC_NONE): @@ -111,7 +118,7 @@ class EC: return #--------------------------------------- def recvAck(self): - socketError = False + socketError = False try: self.sck.settimeout(1.0) data = self.sck.recv(6) @@ -122,7 +129,7 @@ class EC: #self.connectToHost() if socketError: return(0,0,[]) - + header = struct.unpack('hhh', data) cmdId = header[0] status = header[1] @@ -133,7 +140,43 @@ class EC: PL = struct.unpack(fmt, data) else: PL = [] - return (cmdId, status, PL) + return (cmdId, status, PL) + #--------------------------------------- + def setPower(self, pwr=P_ALL, state=PWR_ON): + if ((pwr == self.P_48) or (pwr == self.P_ALL)): + self.sendCmd(self.EC_SET_48, 0, state) + (cmdId, status, PL) = self.recvAck() + self.setInfo('Power Set 48V to %d' %(state)) + if ((pwr == self.P_LCU) or (pwr == self.P_ALL)): + self.sendCmd(self.EC_SET_LCU, 0, state) + (cmdId, status, PL) = self.recvAck() + self.setInfo('Power Set LCU to %d' %(state)) + return(self.info) + #--------------------------------------- + def resetPower(self, pwr=P_ALL): + if ((pwr == self.P_48) or (pwr == self.P_ALL)): + self.sendCmd(self.EC_RESET_48, 0, 0) + (cmdId, status, PL) = self.recvAck() + self.setInfo('PowerReset 48V') + if ((pwr == self.P_LCU) or (pwr == self.P_ALL)): + self.sendCmd(self.EC_RESET_LCU, 0, 0) + (cmdId, status, PL) = self.recvAck() + self.setInfo('PowerReset LCU') + return(self.info) + #--------------------------------------- + def resetTrip(self): + self.sendCmd(self.EC_RESET_TRIP, -1, 0) + (cmdId, status, PL) = self.recvAck() + self.setInfo('Reset Trip System') + return(self.info) + #--------------------------------------- + def setHeater(self, mode=0): + self.sendCmd(self.EC_SET_HEATER, -1, mode) + (cmdId, status, payload) = self.recvAck() + + if (mode == self.MODE_ON): self.setInfo('heater is turned ON') + if (mode == self.MODE_OFF): self.setInfo('heater is turned OFF') + if (mode == self.MODE_AUTO): self.setInfo('heater set to AUTO') #--------------------------------------- def getVersion(self): @@ -159,7 +202,7 @@ class EC: wxt520Text = ('not available','available') self.sendCmd(self.EC_STATION_INFO) (cmdId, status, PL) = self.recvAck() - + type = int(PL[0]) wxt520 = int(PL[1]) self.stationtype = type @@ -167,32 +210,42 @@ class EC: self.setInfo('station type: %s, wxt520: %s' %\ (stationType[type], wxt520Text[wxt520])) return type, wxt520 + #--------------------------------------- def getStatusData(self): self.sendCmd(self.EC_STATUS) (cmdId, status, PL2) = self.recvAck() return PL2 - + def getStatus(self): + if self.stationtype == 3: + self.statusNL() + return() + if self.stationtype == 4: + self.statusIS() + return() + self.setInfo("Unsupported station type") + + def statusNL(self): ec_mode = ('OFF','ON','AUTO','MANUAL','STARTUP','AUTO-SEEK','ABSENT','TEST') fan = ('. . . .','. . . .','. 2 . .','1 2 . .',\ '. . 3 .','. . . .','. 2 3 .','1 2 3 .',\ '. . . .','. . . .','. . . .','. . . .',\ '. . 3 4','. . . .','. 2 3 4','1 2 3 4') - + door = ('CLOSED','FRONT_OPEN','BACK_OPEN','ALL_OPEN') fanstate = ('BAD | BAD ','GOOD| BAD ','BAD | GOOD','GOOD| GOOD') fanestate= ('OFF | OFF ','ON | OFF ','OFF | ON ','ON | ON ') onoff = ('OFF','ON') badok = ('N.A.','OK') - + # get information from EC self.sendCmd(self.EC_CTRL_TEMP) (cmdId, status, PL1) = self.recvAck() self.sendCmd(self.EC_STATUS) (cmdId, status, PL2) = self.recvAck() - if len(PL1) == 0 or len(PL2) == 0: return - # fill lines with data + if len(PL1) == 0 or len(PL2) == 0: return + # fill lines with data lines = [] lines.append(' |') @@ -216,8 +269,8 @@ class EC: lines[4] += '%11.2f |' %(PL2[(cab*7)+2]/100.) lines[5] += '%11.2f |' %(PL2[(cab*7)+3]/100.) lines[6] += '%11s |' %(fan[(PL2[(cab*7)+4]&0x0f)]) - lines[7] += '%11s |' %(fanestate[(PL2[(cab*7)+4]>>4)&0x3]) - lines[8] += '%11s |' %(fanstate[(PL2[(cab*7)+4]>>6)&0x3]) + lines[7] += '%11s |' %(fanestate[(PL2[(cab*7)+4]>>4)&0x3]) + lines[8] += '%11s |' %(fanstate[(PL2[(cab*7)+4]>>6)&0x3]) lines[9] += '%11s |' %(door[(PL2[(cab*7)+5]&0x03)]) if (cab != 3): lines[10] += '%11s |' %('none') @@ -228,16 +281,54 @@ class EC: lines.append('power 48V state = %s' %(onoff[(PL2[i] & 1)])) lines.append('power LCU state = %s' %(onoff[(PL2[i] >> 1)])) lines.append('lightning state = %s' %(badok[(PL2[i+1] & 1)])) - + # print lines to screen or file, see printInfo info = 'status %s (%s) %s ' %(self.station, self.versionstr, time.asctime()) self.setInfo('-' * len(info)) self.addInfo(info) self.addInfo('-' * len(info)) - + + for line in lines: + self.addInfo(line) + + def statusIS(self): + onoff = ('OFF','ON') + badok = ('N.A.','OK') + + # get information from EC + self.sendCmd(self.EC_CTRL_TEMP) + (cmdId, status, PL1) = self.recvAck() + self.sendCmd(self.EC_STATUS) + (cmdId, status, PL2) = self.recvAck() + if len(PL1) == 0 or len(PL2) == 0: return + # fill lines with data + lines = [] + lines.append('temperature = %5.2f' %(PL2[2]/100.)) + lines.append('humidity = %5.2f' %(PL2[3]/100.)) + lines.append('heater state = %s' %(onoff[PL2[(3*7)+6]])) + lines.append('power 48V state = %s' %(onoff[(PL2[28] & 1)])) + lines.append('power LCU state = %s' %(onoff[(PL2[28] >> 1) & 1])) + lines.append('lightning state = %s' %(badok[(PL2[29] & 1)])) + + # print lines to screen or file, see printInfo + info1 = ' %s (EC %s)' %(self.station, self.versionstr) + info2 = ' %s ' %(time.asctime()) + self.setInfo('-' * len(info2)) + self.addInfo(info1) + self.addInfo(info2) + self.addInfo('-' * len(info2)) for line in lines: self.addInfo(line) + #--------------------------------------- + def getPowerStatus(self): + state = ('OFF','ON') + # get information from EC + self.sendCmd(self.EC_STATUS) + (cmdId, status, PL) = self.recvAck() + + self.addInfo('Power: 48V = %s, LCU = %s' %(state[(PL[28] & 1)], state[(PL[28] >> 1)])) + #--------------------------------------- def getTripStatus(self): # get information from EC @@ -253,7 +344,7 @@ class EC: if (PL[22] & 0x1000): self.addInfo('trip in cabinet 3') state = True - + if (PL[1] & 0x6000): self.addInfo('warning in cabinet 0') state = True @@ -267,3 +358,11 @@ class EC: if (state == False): self.addInfo('NO trips available') return(state) + + #--------------------------------------- + ## set observing active(1) or not active(0) + def setObserving(self, state=0): + self.sendCmd(self.SET_OBSERVING, -1, state) + (cmdId, status, PL) = self.recvAck() + self.setInfo('SetObserving to %d' %(state)) + return(self.info) diff --git a/MAC/Tools/Power/isStatus.py b/MAC/Tools/Power/status.py similarity index 63% rename from MAC/Tools/Power/isStatus.py rename to MAC/Tools/Power/status.py index 574b72f5647..788de2f4dfe 100755 --- a/MAC/Tools/Power/isStatus.py +++ b/MAC/Tools/Power/status.py @@ -1,19 +1,18 @@ #!/usr/bin/python -## "isStatus.py" -## Print EC status of IS (international station) -## can only be used on IS LCU +## Print EC status +## can only be used on LCU ## -## usage: ./isStatus.py +## usage: ./status.py ## ## Author: Pieter Donker (ASTRON) -## Last change: november 2011 +## Last change: September 2014 -from isEcLib import * +from st_ec_lib import * import sys import time -VERSION = '1.1.0' # version of this script +VERSION = '1.2.0' # version of this script # used variables version = 0 # EC version @@ -25,9 +24,9 @@ versionstr = 'V-.-.-' if __name__ == '__main__': host = getIP() if host == None: - print "===============================================" - print "ERROR, this script can only run on a IS station" - print "===============================================" + print "============================================" + print "ERROR, this script can only run on a station" + print "============================================" else: ec = EC(host) ec.connectToHost() diff --git a/MAC/Tools/Power/isStatusData.py b/MAC/Tools/Power/status_data.py similarity index 95% rename from MAC/Tools/Power/isStatusData.py rename to MAC/Tools/Power/status_data.py index 7d3f474448d..9a398f8cea6 100755 --- a/MAC/Tools/Power/isStatusData.py +++ b/MAC/Tools/Power/status_data.py @@ -25,11 +25,11 @@ example, returned data: #from eccontrol import * #from stations import * -import isEcLib as eclib +import st_ec_lib as eclib import sys import time -VERSION = '1.0.0' # version of this script +VERSION = '1.1.0' # version of this script Station = '' ##======================================================================= diff --git a/MAC/Tools/Power/isTurnOff48V.py b/MAC/Tools/Power/turn_off_48v.py similarity index 62% rename from MAC/Tools/Power/isTurnOff48V.py rename to MAC/Tools/Power/turn_off_48v.py index bf28be0a5b5..e1568c691e9 100755 --- a/MAC/Tools/Power/isTurnOff48V.py +++ b/MAC/Tools/Power/turn_off_48v.py @@ -1,19 +1,18 @@ #!/usr/bin/python -## "isOff48V.py" -## Turn off 48V powersupply on IS (international station) -## can only be used on IS (international) LCU +## Turn off 48V powersupply +## can only be used on LCU ## -## usage: ./isOff48V.py +## usage: ./turn_off_48v.py ## ## Author: Pieter Donker (ASTRON) -## Last change: May 2013 +## Last change: September 2014 -from isEcLib import * +from st_ec_lib import * import sys import time -VERSION = '0.0.1' # version of this script +VERSION = '1.0.0' # version of this script # used variables version = 0 # EC version @@ -25,9 +24,9 @@ versionstr = 'V-.-.-' if __name__ == '__main__': host = getIP() if host == None: - print "===============================================" - print "ERROR, this script can only run on a IS station" - print "===============================================" + print "============================================" + print "ERROR, this script can only run on a station" + print "============================================" else: ec = EC(host) ec.connectToHost() diff --git a/MAC/Tools/Power/isTurnOffLCU.py b/MAC/Tools/Power/turn_off_lcu.py similarity index 71% rename from MAC/Tools/Power/isTurnOffLCU.py rename to MAC/Tools/Power/turn_off_lcu.py index b8f67bbeb27..b9874176728 100755 --- a/MAC/Tools/Power/isTurnOffLCU.py +++ b/MAC/Tools/Power/turn_off_lcu.py @@ -1,19 +1,18 @@ #!/usr/bin/python -## "isOff48V.py" ## Turn off 48V powersupply on IS (international station) ## can only be used on IS (international) LCU ## -## usage: ./isOff48V.py +## usage: ./turn_off_lcu.py ## ## Author: Pieter Donker (ASTRON) -## Last change: May 2013 +## Last change: September 2014 -from isEcLib import * +from st_ec_lib import * import sys import time -VERSION = '0.0.1' # version of this script +VERSION = '1.0.0' # version of this script # used variables version = 0 # EC version @@ -25,9 +24,9 @@ versionstr = 'V-.-.-' if __name__ == '__main__': host = getIP() if host == None: - print "===============================================" - print "ERROR, this script can only run on a IS station" - print "===============================================" + print "============================================" + print "ERROR, this script can only run on a station" + print "============================================" else: ec = EC(host) ec.connectToHost() diff --git a/MAC/Tools/Power/isTurnOn48V.py b/MAC/Tools/Power/turn_on_48v.py similarity index 62% rename from MAC/Tools/Power/isTurnOn48V.py rename to MAC/Tools/Power/turn_on_48v.py index 91216df6ae5..3fe593af817 100755 --- a/MAC/Tools/Power/isTurnOn48V.py +++ b/MAC/Tools/Power/turn_on_48v.py @@ -1,19 +1,18 @@ #!/usr/bin/python -## "isOff48V.py" -## Turn off 48V powersupply on IS (international station) -## can only be used on IS (international) LCU +## Turn on 48V powersupply +## can only be used on LCU ## -## usage: ./isOff48V.py +## usage: ./turn_on_48v.py ## ## Author: Pieter Donker (ASTRON) -## Last change: May 2013 +## Last change: September 2014 -from isEcLib import * +from st_ec_lib import * import sys import time -VERSION = '0.0.1' # version of this script +VERSION = '1.0.0' # version of this script # used variables version = 0 # EC version @@ -25,9 +24,9 @@ versionstr = 'V-.-.-' if __name__ == '__main__': host = getIP() if host == None: - print "===============================================" - print "ERROR, this script can only run on a IS station" - print "===============================================" + print "============================================" + print "ERROR, this script can only run on a station" + print "============================================" else: ec = EC(host) ec.connectToHost() diff --git a/MAC/Tools/Power/isTurnOnLCU.py b/MAC/Tools/Power/turn_on_lcu.py similarity index 62% rename from MAC/Tools/Power/isTurnOnLCU.py rename to MAC/Tools/Power/turn_on_lcu.py index 6df46ce50fb..6128edaf5df 100755 --- a/MAC/Tools/Power/isTurnOnLCU.py +++ b/MAC/Tools/Power/turn_on_lcu.py @@ -1,19 +1,18 @@ #!/usr/bin/python -## "isOff48V.py" -## Turn off 48V powersupply on IS (international station) -## can only be used on IS (international) LCU +## Turn on LCU power +## can only be used on LCU ## -## usage: ./isOff48V.py +## usage: ./turn_on_lcu.py ## ## Author: Pieter Donker (ASTRON) -## Last change: May 2013 +## Last change: September 2014 -from isEcLib import * +from st_ec_lib import * import sys import time -VERSION = '0.0.1' # version of this script +VERSION = '1.0.0' # version of this script # used variables version = 0 # EC version @@ -25,9 +24,9 @@ versionstr = 'V-.-.-' if __name__ == '__main__': host = getIP() if host == None: - print "===============================================" - print "ERROR, this script can only run on a IS station" - print "===============================================" + print "============================================" + print "ERROR, this script can only run on a station" + print "============================================" else: ec = EC(host) ec.connectToHost() -- GitLab From 183aae555b5ef77c83ecda8175b5d6da26a445eb Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 9 Sep 2016 19:53:41 +0000 Subject: [PATCH 782/933] Task #9273: DPPP applycal now accepts ScalarPhase as alias for CommonScalarPhase --- CEP/DP3/DPPP/src/ApplyCal.cc | 23 ++++++++++++++++------- CEP/DP3/DPPP/test/tApplyCal2.run | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CEP/DP3/DPPP/src/ApplyCal.cc b/CEP/DP3/DPPP/src/ApplyCal.cc index 104f9fbeb0f..ecbe7168017 100644 --- a/CEP/DP3/DPPP/src/ApplyCal.cc +++ b/CEP/DP3/DPPP/src/ApplyCal.cc @@ -70,6 +70,15 @@ namespace LOFAR { if (itsCorrectType=="fulljones" && itsUpdateWeights) { ASSERTSTR (itsInvert, "Updating weights has not been implemented for invert=false and fulljones"); } + ASSERT(itsCorrectType=="gain" || itsCorrectType=="fulljones" || + itsCorrectType=="tec" || itsCorrectType=="clock" || + itsCorrectType=="scalarphase" || itsCorrectType=="commonscalarphase" || + itsCorrectType=="scalaramplitude" || itsCorrectType=="commonscalaramplitude" || + itsCorrectType=="rotationangle" || itsCorrectType=="commonrotationangle" || + itsCorrectType=="rotationmeasure"); + if (itsCorrectType.substr(0,6)=="common") { + itsCorrectType=itsCorrectType.substr(6); + } } ApplyCal::ApplyCal() @@ -172,13 +181,13 @@ namespace LOFAR { itsParmExprs.push_back("Clock:0"); itsParmExprs.push_back("Clock:1"); } - } else if (itsCorrectType == "commonrotationangle") { + } else if (itsCorrectType == "rotationangle") { itsParmExprs.push_back("{Common,}RotationAngle"); - } else if (itsCorrectType == "commonscalarphase") { + } else if (itsCorrectType == "scalarphase") { itsParmExprs.push_back("{Common,}ScalarPhase"); } else if (itsCorrectType == "rotationmeasure") { itsParmExprs.push_back("RotationMeasure"); - } else if (itsCorrectType == "commonscalaramplitude") { + } else if (itsCorrectType == "scalaramplitude") { itsParmExprs.push_back("{Common,}ScalarAmplitude"); } else { @@ -448,7 +457,7 @@ namespace LOFAR { parmvalues[1][ant][tf] * freq * casa::C::_2pi); } } - else if (itsCorrectType=="commonrotationangle") { + else if (itsCorrectType=="rotationangle") { double phi=parmvalues[0][ant][tf]; if (itsInvert) { phi = -phi; @@ -474,11 +483,11 @@ namespace LOFAR { itsParms(2, ant, tf) = sinv; itsParms(3, ant, tf) = cosv; } - else if (itsCorrectType=="commonscalarphase") { + else if (itsCorrectType=="scalarphase") { itsParms(0, ant, tf) = polar(1., parmvalues[0][ant][tf]); itsParms(1, ant, tf) = polar(1., parmvalues[0][ant][tf]); } - else if (itsCorrectType=="commonscalaramplitude") { + else if (itsCorrectType=="scalaramplitude") { itsParms(0, ant, tf) = parmvalues[0][ant][tf]; itsParms(1, ant, tf) = parmvalues[0][ant][tf]; } @@ -501,7 +510,7 @@ namespace LOFAR { uint numParms; if (itsCorrectType=="fulljones" || - itsCorrectType=="commonrotationangle" || + itsCorrectType=="rotationangle" || itsCorrectType=="rotationmeasure") { numParms = 4; } diff --git a/CEP/DP3/DPPP/test/tApplyCal2.run b/CEP/DP3/DPPP/test/tApplyCal2.run index 54a2594dea1..9aa6f4c4032 100755 --- a/CEP/DP3/DPPP/test/tApplyCal2.run +++ b/CEP/DP3/DPPP/test/tApplyCal2.run @@ -79,3 +79,21 @@ EOL ../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DATA3 steps=[applycal] applycal.parmdb=tApplyCal.parmdb applycal.correction=commonscalarphase showcounts=false $taqlexe 'select from tNDPPP-generic.MS where not(all(DATA~=DATA3))' > taql.out diff taql.out taql.ref || exit 1 + +echo; echo "Testing ScalarAmplitude values" +rm -rf tApplyCal.parmdb +../../../../ParmDB/src/parmdbm <<EOL +open table="tApplyCal.parmdb" +add ScalarAmplitude:CS001HBA0 values=[3.,3.,3.,3.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarAmplitude:CS002HBA0 values=[3.,3.,3.,3.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarAmplitude:CS002HBA1 values=[3.,3.,3.,3.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarAmplitude:CS004HBA1 values=[3.,3.,3.,3.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarAmplitude:RS106HBA values=[3.,3.,3.,3.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarAmplitude:RS208HBA values=[3.,3.,3.,3.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarAmplitude:RS305HBA values=[3.,3.,3.,3.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +add ScalarAmplitude:RS307HBA values=[3.,3.,3.,3.], domain=[10e6, 200e6, 4472025735, 4972025795], shape=[2,2], shape=[2,2] +EOL +../../src/NDPPP msin=tNDPPP-generic.MS msout=. msout.datacolumn=DATA3 steps=[applycal] applycal.parmdb=tApplyCal.parmdb applycal.correction=scalaramplitude showcounts=false +$taqlexe 'select from tNDPPP-generic.MS where not(all(DATA~=9*DATA3))' > taql.out +diff taql.out taql.ref || exit 1 + -- GitLab From 06a9b1e71ac5cb1bce82b1705a77dd4b9ba31c0c Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Mon, 12 Sep 2016 06:57:13 +0000 Subject: [PATCH 783/933] TAsk #9612: Fixing build on CEP2 --- CMake/LofarPackageList.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index de8392dd784..3c1e320f5a9 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -38,6 +38,7 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(DPPP_AOFlag_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/DPPP_AOFlag) set(SPW_Combine_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/SPWCombine) set(LofarFT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Imager/LofarFT) + set(AOFlagger_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/AOFlagger) set(AWImager2_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Imager/AWImager2) set(Laps-GRIDInterface_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/LAPS/GRIDInterface) set(Laps-ParsetCombiner_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/LAPS/ParsetCombiner) -- GitLab From e78e0fd224d17e8bb1af565518aa283bfebc6dcf Mon Sep 17 00:00:00 2001 From: Pieter Donker <donker@astron.nl> Date: Tue, 13 Sep 2016 10:00:51 +0000 Subject: [PATCH 784/933] Task #9612: made symlinks to new tool names in Tools/Power dir --- .gitattributes | 15 ++++++++++-- LCU/checkhardware/checkhardware_lib/db_new.py | 0 .../checkhardware_lib/reporting.py | 24 +++++++++---------- .../checkhardware_lib/test_db.py | 0 MAC/Tools/Power/ecSetObserving.py | 1 + MAC/Tools/Power/isEcLib.py | 1 + MAC/Tools/Power/isReset48V.py | 1 + MAC/Tools/Power/isResetLCU.py | 1 + MAC/Tools/Power/isResetTrip.py | 1 + MAC/Tools/Power/isStatus.py | 1 + MAC/Tools/Power/isStatusData.py | 1 + MAC/Tools/Power/isTurnOff48V.py | 1 + MAC/Tools/Power/isTurnOffLCU.py | 1 + MAC/Tools/Power/isTurnOn48V.py | 1 + MAC/Tools/Power/nlEcLib.py | 1 + MAC/Tools/Power/nlStatus.py | 1 + MAC/Tools/Power/nlStatusData.py | 1 + 17 files changed, 38 insertions(+), 14 deletions(-) delete mode 100644 LCU/checkhardware/checkhardware_lib/db_new.py delete mode 100644 LCU/checkhardware/checkhardware_lib/test_db.py create mode 120000 MAC/Tools/Power/ecSetObserving.py create mode 120000 MAC/Tools/Power/isEcLib.py create mode 120000 MAC/Tools/Power/isReset48V.py create mode 120000 MAC/Tools/Power/isResetLCU.py create mode 120000 MAC/Tools/Power/isResetTrip.py create mode 120000 MAC/Tools/Power/isStatus.py create mode 120000 MAC/Tools/Power/isStatusData.py create mode 120000 MAC/Tools/Power/isTurnOff48V.py create mode 120000 MAC/Tools/Power/isTurnOffLCU.py create mode 120000 MAC/Tools/Power/isTurnOn48V.py create mode 120000 MAC/Tools/Power/nlEcLib.py create mode 120000 MAC/Tools/Power/nlStatus.py create mode 120000 MAC/Tools/Power/nlStatusData.py diff --git a/.gitattributes b/.gitattributes index d4dd52253b0..cfb6a87b178 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2934,7 +2934,6 @@ LCU/checkhardware/check_hardware.py -text LCU/checkhardware/checkhardware_lib/__init__.py -text LCU/checkhardware/checkhardware_lib/data.py -text LCU/checkhardware/checkhardware_lib/db.py -text -LCU/checkhardware/checkhardware_lib/db_new.py -text LCU/checkhardware/checkhardware_lib/general.py -text LCU/checkhardware/checkhardware_lib/hardware_tests.py -text LCU/checkhardware/checkhardware_lib/hba.py -text @@ -2958,7 +2957,6 @@ LCU/checkhardware/checkhardware_lib/spectrum_checks/summator_noise.py -text LCU/checkhardware/checkhardware_lib/spectrum_checks/tools.py -text LCU/checkhardware/checkhardware_lib/spu.py -text LCU/checkhardware/checkhardware_lib/tbb.py -text -LCU/checkhardware/checkhardware_lib/test_db.py -text LCU/checkhardware/do_station_test.sh -text svneol=unset#application/x-shellscript LCU/checkhardware/rtsm.py -text LCU/checkhardware/show_bad_spectra.py -text @@ -4357,8 +4355,21 @@ MAC/Tools/NTP/timepps.h -text MAC/Tools/NTP/timer.c -text MAC/Tools/NTP/timer.c.patch -text MAC/Tools/NTP/timex.h -text +MAC/Tools/Power/ecSetObserving.py -text MAC/Tools/Power/ec_reset_trip.py -text MAC/Tools/Power/ec_set_observing.py -text +MAC/Tools/Power/isEcLib.py -text +MAC/Tools/Power/isReset48V.py -text +MAC/Tools/Power/isResetLCU.py -text +MAC/Tools/Power/isResetTrip.py -text +MAC/Tools/Power/isStatus.py -text +MAC/Tools/Power/isStatusData.py -text +MAC/Tools/Power/isTurnOff48V.py -text +MAC/Tools/Power/isTurnOffLCU.py -text +MAC/Tools/Power/isTurnOn48V.py -text +MAC/Tools/Power/nlEcLib.py -text +MAC/Tools/Power/nlStatus.py -text +MAC/Tools/Power/nlStatusData.py -text MAC/Tools/Power/reset_48v.py -text MAC/Tools/Power/reset_lcu.py -text MAC/Tools/Power/st_ec_lib.py -text diff --git a/LCU/checkhardware/checkhardware_lib/db_new.py b/LCU/checkhardware/checkhardware_lib/db_new.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/LCU/checkhardware/checkhardware_lib/reporting.py b/LCU/checkhardware/checkhardware_lib/reporting.py index 0d3b51b3f75..428fdb7d99c 100644 --- a/LCU/checkhardware/checkhardware_lib/reporting.py +++ b/LCU/checkhardware/checkhardware_lib/reporting.py @@ -80,18 +80,18 @@ def spu_report(db, date, log): logger.debug("generate spu report") for spu in db.spu: spu.test() - if not spu.voltage_ok: - valstr = '' - if not spu.rcu_ok: - valstr += ",RCU-5.0V=%3.1f" % spu.rcu_5_0_volt - if not spu.lba_ok: - valstr += ",LBA-8.0V=%3.1f" % spu.lba_8_0_volt - if not spu.hba_ok: - valstr += ",HBA-48V=%3.1f" % spu.hba_48_volt - if not spu.spu_ok: - valstr += ",SPU-3.3V=%3.1f" % spu.spu_3_3_volt - if len(valstr): - log.add_line("%s,SPU,%03d,VOLTAGE%s" % (date, spu.nr, valstr)) + if not spu.voltage_ok: + valstr = '' + if not spu.rcu_ok: + valstr += ",RCU-5.0V=%3.1f" % spu.rcu_5_0_volt + if not spu.lba_ok: + valstr += ",LBA-8.0V=%3.1f" % spu.lba_8_0_volt + if not spu.hba_ok: + valstr += ",HBA-48V=%3.1f" % spu.hba_48_volt + if not spu.spu_ok: + valstr += ",SPU-3.3V=%3.1f" % spu.spu_3_3_volt + if len(valstr): + log.add_line("%s,SPU,%03d,VOLTAGE%s" % (date, spu.nr, valstr)) if not spu.temp_ok: log.add_line("%s,SPU,%03d,TEMPERATURE,PCB=%2.0f" % ( diff --git a/LCU/checkhardware/checkhardware_lib/test_db.py b/LCU/checkhardware/checkhardware_lib/test_db.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/MAC/Tools/Power/ecSetObserving.py b/MAC/Tools/Power/ecSetObserving.py new file mode 120000 index 00000000000..28676a0ccd6 --- /dev/null +++ b/MAC/Tools/Power/ecSetObserving.py @@ -0,0 +1 @@ +ec_set_observing.py \ No newline at end of file diff --git a/MAC/Tools/Power/isEcLib.py b/MAC/Tools/Power/isEcLib.py new file mode 120000 index 00000000000..1e65dd3e223 --- /dev/null +++ b/MAC/Tools/Power/isEcLib.py @@ -0,0 +1 @@ +st_ec_lib.py \ No newline at end of file diff --git a/MAC/Tools/Power/isReset48V.py b/MAC/Tools/Power/isReset48V.py new file mode 120000 index 00000000000..6987f263f17 --- /dev/null +++ b/MAC/Tools/Power/isReset48V.py @@ -0,0 +1 @@ +reset_48v.py \ No newline at end of file diff --git a/MAC/Tools/Power/isResetLCU.py b/MAC/Tools/Power/isResetLCU.py new file mode 120000 index 00000000000..729a66dac82 --- /dev/null +++ b/MAC/Tools/Power/isResetLCU.py @@ -0,0 +1 @@ +reset_lcu.py \ No newline at end of file diff --git a/MAC/Tools/Power/isResetTrip.py b/MAC/Tools/Power/isResetTrip.py new file mode 120000 index 00000000000..ca55cad4cda --- /dev/null +++ b/MAC/Tools/Power/isResetTrip.py @@ -0,0 +1 @@ +ec_reset_trip.py \ No newline at end of file diff --git a/MAC/Tools/Power/isStatus.py b/MAC/Tools/Power/isStatus.py new file mode 120000 index 00000000000..65773e96ec5 --- /dev/null +++ b/MAC/Tools/Power/isStatus.py @@ -0,0 +1 @@ +status.py \ No newline at end of file diff --git a/MAC/Tools/Power/isStatusData.py b/MAC/Tools/Power/isStatusData.py new file mode 120000 index 00000000000..95d9f7941c0 --- /dev/null +++ b/MAC/Tools/Power/isStatusData.py @@ -0,0 +1 @@ +status_data.py \ No newline at end of file diff --git a/MAC/Tools/Power/isTurnOff48V.py b/MAC/Tools/Power/isTurnOff48V.py new file mode 120000 index 00000000000..26117b17a3f --- /dev/null +++ b/MAC/Tools/Power/isTurnOff48V.py @@ -0,0 +1 @@ +turn_off_48v.py \ No newline at end of file diff --git a/MAC/Tools/Power/isTurnOffLCU.py b/MAC/Tools/Power/isTurnOffLCU.py new file mode 120000 index 00000000000..11caae5ac99 --- /dev/null +++ b/MAC/Tools/Power/isTurnOffLCU.py @@ -0,0 +1 @@ +turn_off_lcu.py \ No newline at end of file diff --git a/MAC/Tools/Power/isTurnOn48V.py b/MAC/Tools/Power/isTurnOn48V.py new file mode 120000 index 00000000000..b8c3c7c03fd --- /dev/null +++ b/MAC/Tools/Power/isTurnOn48V.py @@ -0,0 +1 @@ +turn_on_48v.py \ No newline at end of file diff --git a/MAC/Tools/Power/nlEcLib.py b/MAC/Tools/Power/nlEcLib.py new file mode 120000 index 00000000000..1e65dd3e223 --- /dev/null +++ b/MAC/Tools/Power/nlEcLib.py @@ -0,0 +1 @@ +st_ec_lib.py \ No newline at end of file diff --git a/MAC/Tools/Power/nlStatus.py b/MAC/Tools/Power/nlStatus.py new file mode 120000 index 00000000000..65773e96ec5 --- /dev/null +++ b/MAC/Tools/Power/nlStatus.py @@ -0,0 +1 @@ +status.py \ No newline at end of file diff --git a/MAC/Tools/Power/nlStatusData.py b/MAC/Tools/Power/nlStatusData.py new file mode 120000 index 00000000000..95d9f7941c0 --- /dev/null +++ b/MAC/Tools/Power/nlStatusData.py @@ -0,0 +1 @@ +status_data.py \ No newline at end of file -- GitLab From c84aa42472f0232bf986f4a288453bd572d65ad8 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 13 Sep 2016 11:17:44 +0000 Subject: [PATCH 785/933] Fix nrCoresPerTask to 21, since the user or MoM not specify it correctly in all cases, and we want to use full nodes for now anyway. --- MAC/Services/src/PipelineControl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index ce5f9e06857..12c19e86f50 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -143,10 +143,10 @@ class Parset(dict): return result def processingNumberOfCoresPerTask(self): - result = int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfCoresPerTask"]) or "20" - if result < 1 or result > 20: - logger.warn('Invalid Observation.Cluster.ProcessingCluster.numberOfCoresPerTask: %s, defaulting to %s', result, max(1, min(20, result))) - return max(1, min(20, result)) + result = int(self[PARSET_PREFIX + "Observation.Cluster.ProcessingCluster.numberOfCoresPerTask"]) or "21" + if result != 21: + logger.warn('Invalid Observation.Cluster.ProcessingCluster.numberOfCoresPerTask: %s, defaulting to %s', result, 21) + return 21 def processingNumberOfTasks(self): """ Parse the number of nodes to allocate from "Observation.Cluster.ProcessingCluster.numberOfTasks", -- GitLab From e1910af7ddf5a8f86ff583b395009f4cf1151ce0 Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Tue, 13 Sep 2016 12:44:43 +0000 Subject: [PATCH 786/933] Task #9655: progs changes for snmp and 3.14 --- MAC/Deployment/data/OTDB/createPICfile | 4 ++-- MAC/Navigator2/config/progs.dist.station | 2 ++ MAC/Navigator2/config/progs.maincu | 1 + MAC/Navigator2/config/progs.standalone.station | 2 ++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/MAC/Deployment/data/OTDB/createPICfile b/MAC/Deployment/data/OTDB/createPICfile index ce027947bf3..a79fa63ff14 100755 --- a/MAC/Deployment/data/OTDB/createPICfile +++ b/MAC/Deployment/data/OTDB/createPICfile @@ -120,7 +120,7 @@ stations = [] for line in filledLine.findall(open(StationFile).read()): # print "line: =",line if (line.strip() and len(line.split()) == 13): - (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, HBAsplit, LBAcal, AartFaac ) = line.split() + (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, nrPowec, HBAsplit, LBAcal, AartFaac ) = line.split() if (height != "0" and int(stationID) < 900): ringStations.append(ringDict[stnType]+"_"+name) stations.append(name) @@ -199,7 +199,7 @@ for line in software.findall(open(PVSSbasefile).read()): for line in filledLine.findall(open(StationFile).read()): # print "line: =",line if (line.strip() and len(line.split()) == 13): - (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, HBAsplit, LBAcal, AartFaac ) = line.split() + (name, stationID, stnType, long, lat, height, nrRSP, nrTBB, nrLBA, nrHBA, nrPowec, BAsplit, LBAcal, AartFaac ) = line.split() if ( height == "0" or int(stationID) >= 900): continue diff --git a/MAC/Navigator2/config/progs.dist.station b/MAC/Navigator2/config/progs.dist.station index 34192b8fc51..3035cde2d06 100644 --- a/MAC/Navigator2/config/progs.dist.station +++ b/MAC/Navigator2/config/progs.dist.station @@ -16,4 +16,6 @@ PVSS00ctrl | always | 30 | 2 | 2 |monitorStateChanges.c PVSS00ctrl | once | 30 | 2 | 2 |readStationConfigs.ctl PVSS00ctrl | always | 30 | 2 | 2 |monitorStateReset.ctl PVSS00ctrl | always | 30 | 2 | 2 |transferMPs.ctl +PVSS00ctrl | always | 30 | 2 | 2 |setSumAlerts.ctl +PVSS00snmp | always | 30 | 2 | 2 |-num 2 PVSS00ui | manual | 30 | 2 | 2 |-m para -display localhost:10.0 diff --git a/MAC/Navigator2/config/progs.maincu b/MAC/Navigator2/config/progs.maincu index 5e72d998956..0724854b418 100644 --- a/MAC/Navigator2/config/progs.maincu +++ b/MAC/Navigator2/config/progs.maincu @@ -16,6 +16,7 @@ PVSS00ctrl | always | 30 | 2 | 2 |monitorStateReset.ctl PVSS00ctrl | always | 30 | 2 | 2 |monitorAlarms.ctl PVSS00ctrl | always | 30 | 2 | 2 |gcf_cwd.ctl PVSS00ctrl | always | 30 | 2 | 2 |transferMPs.ctl +PVSS00ctrl | always | 30 | 2 | 2 |setSumAlerts.ctl PVSS00ui | manual | 30 | 2 | 2 |-m para -display localhost:10.0 diff --git a/MAC/Navigator2/config/progs.standalone.station b/MAC/Navigator2/config/progs.standalone.station index c279f585a98..73018a603fe 100644 --- a/MAC/Navigator2/config/progs.standalone.station +++ b/MAC/Navigator2/config/progs.standalone.station @@ -16,4 +16,6 @@ PVSS00ctrl | once | 30 | 2 | 2 |readStationConfigs.ct PVSS00ctrl | always | 30 | 2 | 2 |monitorStateReset.ctl PVSS00ctrl | always | 30 | 2 | 2 |monitorStationAlarms.ctl PVSS00ctrl | always | 30 | 2 | 2 |transferMPs.ctl +PVSS00ctrl | always | 30 | 2 | 2 |setSumAlerts.ctl +PVSS00snmp | always | 30 | 2 | 2 |-num 2 PVSS00ui | manual | 30 | 2 | 2 |-m para -display localhost:10.0 -- GitLab From bf5c603edfbfd04e01d2729375268f1d9422fcf1 Mon Sep 17 00:00:00 2001 From: Pieter Donker <donker@astron.nl> Date: Tue, 13 Sep 2016 14:24:52 +0000 Subject: [PATCH 787/933] Task #9612: checkhardware, some small tbb test changes --- .gitattributes | 1 - LCU/checkhardware/check_hardware.conf | 244 --------------------- LCU/checkhardware/check_hardware.py | 19 +- LCU/checkhardware/checkhardware_lib/tbb.py | 6 +- 4 files changed, 17 insertions(+), 253 deletions(-) delete mode 100755 LCU/checkhardware/check_hardware.conf diff --git a/.gitattributes b/.gitattributes index cfb6a87b178..1d7391a1253 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2929,7 +2929,6 @@ LCU/StationTest/xc_160_setup.sh eol=lf LCU/StationTest/xc_160_verify.sh eol=lf LCU/StationTest/xc_200_setup.sh eol=lf LCU/StationTest/xc_200_verify.sh eol=lf -LCU/checkhardware/check_hardware.conf -text LCU/checkhardware/check_hardware.py -text LCU/checkhardware/checkhardware_lib/__init__.py -text LCU/checkhardware/checkhardware_lib/data.py -text diff --git a/LCU/checkhardware/check_hardware.conf b/LCU/checkhardware/check_hardware.conf deleted file mode 100755 index 1ae6e3b070e..00000000000 --- a/LCU/checkhardware/check_hardware.conf +++ /dev/null @@ -1,244 +0,0 @@ -# -# configuration file for check_hardware.py -# - -[configuration] -version= 00.01 -station= CS001C - -# checks to do if '-l=x' argument is given, all checks behind list.x are executed -# always checks will be done always -# -# S(rcumode) : signal check for rcumode (also down and flat-check in rcumode 1..4). -# O(rcumode) : oscillation check for rcumode. -# SP(rcumode) : spurious check for rcumode. -# N(rcumode)[= 300]: noise check for rcumode, optional data time in seconds -# default data time= 120 sec. -# E(rcumode)[= 60] : do all RCU5 element tests, optional data time in seconds. -# default data time= 10 sec. -# M(rcumode) : do modem -# SN(rcumode) : do summator noise -# -# RCU(mode) : do all rcu checks for given mode, no element tests done. -# -# RBC : RSP voltage/temperature check -# TBC : TBB voltage/temperature check -# SPU : SPU voltage -# TM : TBB memmory -[check] -always= RV, TV, RBC, TBC -list.0= -list.1= SPU,TM,RCU1,RCU3,RCU5 -list.2= SPU,TM,RCU1,RCU3,RCU5,E7 -list.3= S1,S3 - -[spu] -temperature.min= 10.0 -temperature.max= 35.0 -voltage.3_3.min= 3.1 -voltage.3_3.max= 3.4 -voltage.3_3.max-drop= 0.3 -voltage.5_0.min= 4.5 -voltage.5_0.max= 5.0 -voltage.5_0.max-drop= 0.3 -voltage.8_0.min= 7.4 -voltage.8_0.max= 8.0 -voltage.8_0.max-drop= 0.3 -voltage.48_0.min= 43.0 -voltage.48_0.max= 48.0 -voltage.48_0.max-drop= 2.0 - -[tbb] -version.tp= 2.4 -version.mp= 3.0 -temperature.min= 10.0 -temperature.max= 45.0 -temperature.tp.min= 10.0 -temperature.tp.max= 75.0 -temperature.mp.min= 10.0 -temperature.mp.max= 75.0 -temperature.mp.max_delta= 10.0 -voltage.1_2.min= 1.1 -voltage.1_2.max= 1.3 -voltage.2_5.min= 2.4 -voltage.2_5.max= 2.6 -voltage.3_3.min= 3.1 -voltage.3_3.max= 3.4 - -[rsp] -version.ap= 9.3 -version.bp= 9.3 -temperature.min= 10.0 -temperature.max= 50.0 -temperature.ap.min= 10.0 -temperature.ap.max= 80.0 -temperature.ap.max_delta= 10.0 -temperature.bp.min= 10.0 -temperature.bp.max= 80.0 -temperature.bp.max_delta= 10.0 -voltage.1_2.min= 1.1 -voltage.1_2.max= 1.3 -voltage.2_5.min= 2.4 -voltage.2_5.max= 2.6 -voltage.3_3.min= 3.1 -voltage.3_3.max= 3.4 - -[rcumode.1-3] -short.mean-pwr.min= 55.0 -short.mean-pwr.max= 61.0 -flat.mean-pwr.min= 61.0 -flat.mean-pwr.max= 64.5 -rf.subbands= 231:371 -rf.min-sb-pwr= 75.0 -rf.negative-deviation= -3.0 -rf.positive-deviation= 3.0 -noise.negative-deviation= -2.5 -noise.positive-deviation= 2.5 -noise.max-difference= 1.5 -noise.passband= 1:511 -oscillation.min-peak-pwr= 6.0 -oscillation.passband= 1:511 -cable-reflection.min-peak-pwr= 0.8 -cable-reflection.passband= 1:511 -spurious.min-peak-pwr= 3.0 -spurious.passband= 1:511 -down.passband= 231:371 - -[rcumode.2-4] -short.mean-pwr.min= 55.0 -short.mean-pwr.max= 61.0 -flat.mean-pwr.min= 61.0 -flat.mean-pwr.max= 64.5 -rf.subbands= 231:371 -rf.min-sb-pwr= 75.0 -rf.negative-deviation= -3.0 -rf.positive-deviation= 3.0 -noise.negative-deviation= -2.5 -noise.positive-deviation= 2.5 -noise.max-difference= 1.5 -noise.passband= 1:511 -oscillation.min-peak-pwr= 6.0 -oscillation.passband= 1:511 -cable-reflection.min-peak-pwr= 0.8 -cable-reflection.passband= 1:511 -spurious.min-peak-pwr= 3.0 -spurious.passband= 1:511 -down.passband= 231:371 - -[rcumode.5.tile] -short.mean-pwr.min= 55.0 -short.mean-pwr.max= 61.0 -flat.mean-pwr.min= 61.0 -flat.mean-pwr.max= 64.5 -rf.subbands= 105 -rf.min-sb-pwr= 80.0 -rf.negative-deviation= -24.0 -rf.positive-deviation= 12.0 -noise.negative-deviation= -3.0 -noise.positive-deviation= 1.5 -noise.max-difference= 1.5 -noise.passband= 1:511 -summator-noise.min-peak-pwr= 0.8 -summator-noise.passband= 45:135 -cable-reflection.min-peak-pwr= 0.8 -cable-reflection.passband= 1:511 -oscillation.min-peak-pwr= 6.0 -oscillation.passband= 1:511 -spurious.min-peak-pwr= 3.0 -spurious.passband= 1:511 - -[rcumode.5.element] -rf.subbands= 105 -rf.min-sb-pwr= 70.0 -rf.negative-deviation= -24.0 -rf.positive-deviation= 12.0 -noise.negative-deviation= -3.0 -noise.positive-deviation= 1.5 -noise.max-difference= 1.5 -noise.passband= 1:511 -oscillation.min-peak-pwr= 6.0 -oscillation.passband= 1:511 -spurious.min-peak-pwr= 3.0 -spurious.passband= 1:511 - -[rcumode.6.tile] -short.mean-pwr.min= 55.0 -short.mean-pwr.max= 61.0 -flat.mean-pwr.min= 61.0 -flat.mean-pwr.max= 64.5 -rf.subbands= 105 -rf.min-sb-pwr= 80.0 -rf.negative-deviation= -24.0 -rf.positive-deviation= 12.0 -noise.negative-deviation= -3.0 -noise.positive-deviation= 1.5 -noise.max-difference= 1.5 -noise.passband= 1:511 -summator-noise.min-peak-pwr= 0.8 -summator-noise.passband= 45:135 -cable-reflection.min-peak-pwr= 0.8 -cable-reflection.passband= 1:511 -oscillation.min-peak-pwr= 6.0 -oscillation.passband= 1:511 -spurious.min-peak-pwr= 3.0 -spurious.passband= 1:511 - -[rcumode.6.element] -rf.subbands= 105 -rf.min-sb-pwr= 70.0 -rf.negative-deviation= -24.0 -rf.positive-deviation= 12.0 -noise.negative-deviation= -3.0 -noise.positive-deviation= 1.5 -noise.max-difference= 1.5 -noise.passband= 1:511 -oscillation.min-peak-pwr= 6.0 -oscillation.passband= 1:511 -spurious.min-peak-pwr= 3.0 -spurious.passband= 1:511 - -[rcumode.7.tile] -short.mean-pwr.min= 55.0 -short.mean-pwr.max= 61.0 -flat.mean-pwr.min= 61.0 -flat.mean-pwr.max= 64.5 -rf.subbands= 105 -rf.min-sb-pwr= 80.0 -rf.negative-deviation= -24.0 -rf.positive-deviation= 12.0 -noise.negative-deviation= -3.0 -noise.positive-deviation= 1.5 -noise.max-difference= 1.5 -noise.passband= 1:511 -summator-noise.min-peak-pwr= 0.8 -summator-noise.passband= 45:135 -cable-reflection.min-peak-pwr= 0.8 -cable-reflection.passband= 1:511 -oscillation.min-peak-pwr= 6.0 -oscillation.passband= 1:511 -spurious.min-peak-pwr= 3.0 -spurious.passband= 1:511 - -[rcumode.7.element] -rf.subbands= 105 -rf.min-sb-pwr= 70.0 -rf.negative-deviation= -24.0 -rf.positive-deviation= 12.0 -noise.negative-deviation= -3.0 -noise.positive-deviation= 1.5 -noise.max-difference= 1.5 -noise.passband= 1:511 -oscillation.min-peak-pwr= 6.0 -oscillation.passband= 1:511 -spurious.min-peak-pwr= 3.0 -spurious.passband= 1:511 - -# General settings -[paths] -global-data= /globalhome/log/stationtest -local-data= /opt/stationtest/data -local-report-dir= /localhome/stationtest/data -global-report-dir= /globalhome/log/stationtest - -[files] -bad-antenna-list= /globalhome/log/stationtest/bad_antenna_list.txt diff --git a/LCU/checkhardware/check_hardware.py b/LCU/checkhardware/check_hardware.py index 95b91f5392e..d58676cc2bc 100755 --- a/LCU/checkhardware/check_hardware.py +++ b/LCU/checkhardware/check_hardware.py @@ -610,11 +610,24 @@ def main(): logger.error('TRACEBACK:\n%s', traceback.format_exc()) logger.error('Aborting NOW') - logger.info('Check if boards are still ok') - check_active_boards(db, n_rsp, n_tbb, 1) - if not db.rsp_driver_down: + if len(sys.argv) > 1: + logger.info('Check if boards are still ok') + check_active_boards(db, n_rsp, n_tbb, 1) + + if check_active_tbbdriver() == True: + # set mode to transient (also needed to set the right ethernet header to cep), + # free all memory allocations, reallocate again and start recording + tbbctl('--mode=transient') + tbbctl('--free') + tbbctl('--alloc') + tbbctl('--record') + + if check_active_rspdriver() == True: + # activate datastream from rsp to tbb in transient mode + rspctl('--tbbmode=transient') logger.info('Going back to swlevel %d' % start_level) swlevel(start_level) + logger.info('Test ready.') write_message('!!! The test is ready and the station can be used again! !!!') diff --git a/LCU/checkhardware/checkhardware_lib/tbb.py b/LCU/checkhardware/checkhardware_lib/tbb.py index 28fe500c1f5..1f4ac0bbb03 100644 --- a/LCU/checkhardware/checkhardware_lib/tbb.py +++ b/LCU/checkhardware/checkhardware_lib/tbb.py @@ -11,7 +11,7 @@ class TBB(object): self.db = db self.nr = self.db.nr_tbb self.driverstate = True - # tbbctl('--free') + #tbbctl('--free') # check software versions of driver, tbbctl and TP/MP firmware def check_versions(self, parset): @@ -67,10 +67,6 @@ class TBB(object): if not memory_ok: tbb.memory_ok = 0 logger.info(answer) - # turn on recording again - tbbctl('--alloc') - tbbctl('--record') - rspctl('--tbbmode=transient') logger.info("=== Done TBB Memory check ===") self.db.add_test_done('TM') return -- GitLab From a4b9f9e6b556d520d422266373c27047f7a1be0a Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Tue, 13 Sep 2016 15:17:41 +0000 Subject: [PATCH 788/933] Task #9612: Updating two shield files to reflect files at systems --- MAC/Deployment/data/PVSS/License/shield.CCU099.txt | 2 +- MAC/Deployment/data/PVSS/License/shield.MCU099.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MAC/Deployment/data/PVSS/License/shield.CCU099.txt b/MAC/Deployment/data/PVSS/License/shield.CCU099.txt index 5eef98bf118..1ade2af8ce3 100644 --- a/MAC/Deployment/data/PVSS/License/shield.CCU099.txt +++ b/MAC/Deployment/data/PVSS/License/shield.CCU099.txt @@ -1,5 +1,5 @@ [license] -code = "CCU099 00473561460" +code = "CCU099.control.lofar 00473561460" version = 30800002 sn = "471_3031_2_Astron_Gen_II_1_38/54" date = 2012.02.09;09:59:23,000 diff --git a/MAC/Deployment/data/PVSS/License/shield.MCU099.txt b/MAC/Deployment/data/PVSS/License/shield.MCU099.txt index abf9a1bb13a..d0e1fc094db 100644 --- a/MAC/Deployment/data/PVSS/License/shield.MCU099.txt +++ b/MAC/Deployment/data/PVSS/License/shield.MCU099.txt @@ -1,6 +1,6 @@ [license] -#hw = "MCU099 02772147255" -code = "MCU099 60315490059" +#hw = "MCU099.control.lofar 02772147255" +code = "MCU099.control.lofar 60315490059" version = 30800002 sn = "471_3031_1_Astron_Gen_I_1_38/3" date = 2012.02.09;09:58:38,000 -- GitLab From 203124de2c333232dca626724cf16c06ca27fa25 Mon Sep 17 00:00:00 2001 From: Pieter Donker <donker@astron.nl> Date: Wed, 14 Sep 2016 09:33:45 +0000 Subject: [PATCH 789/933] Task #9612: checkhardware, 2 typos in making report file --- LCU/checkhardware/checkhardware_lib/spectrum_checks/down.py | 2 +- LCU/checkhardware/do_station_test.sh | 2 +- LCU/checkhardware/show_test_result.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LCU/checkhardware/checkhardware_lib/spectrum_checks/down.py b/LCU/checkhardware/checkhardware_lib/spectrum_checks/down.py index d92f54f833b..2137c774ad0 100644 --- a/LCU/checkhardware/checkhardware_lib/spectrum_checks/down.py +++ b/LCU/checkhardware/checkhardware_lib/spectrum_checks/down.py @@ -222,7 +222,7 @@ def check_for_down(data, band, parset): logger.debug("rcu=%d: Y broken or antenna down" % y_rcu) y_down_trigger = True - if (x_value_trigger and x_down_trigger) or (y_value_trigger and y_down_trigger): + if (x_value_trigger and x_down_trigger) and (y_value_trigger and y_down_trigger): down_info.append((x_rcu, peaks_sb_array[x_rcu], peaks_pwr_array[x_rcu], peaks_bw_array[x_rcu], mean_pwr_array[x_rcu])) down_info.append((y_rcu, peaks_sb_array[y_rcu], peaks_pwr_array[y_rcu], peaks_bw_array[y_rcu], mean_pwr_array[y_rcu])) #logger.debug("down_info=%s" % str(down_info)) diff --git a/LCU/checkhardware/do_station_test.sh b/LCU/checkhardware/do_station_test.sh index e71eac95099..bb49098a4b2 100755 --- a/LCU/checkhardware/do_station_test.sh +++ b/LCU/checkhardware/do_station_test.sh @@ -60,7 +60,7 @@ then filenameLocalHistory=$host"_S_StationTestHistory.csv" else filenameLocal=$host"_L"$LEVEL"_StationTest.csv" - filenameLocalHistory=$host"_L"$LEVEL"_stationTestHistory.csv" + filenameLocalHistory=$host"_L"$LEVEL"_StationTestHistory.csv" fi filenameBadRCUs=$host"_bad_rcus.txt" diff --git a/LCU/checkhardware/show_test_result.py b/LCU/checkhardware/show_test_result.py index f74297b7c4d..6835d1f4230 100755 --- a/LCU/checkhardware/show_test_result.py +++ b/LCU/checkhardware/show_test_result.py @@ -64,11 +64,11 @@ def main(): fullfilename = args.get('F') else: if 'L2' in args: - testfilename = '%s_L2_station_test_history.csv' % station_id + testfilename = '%s_L2_StationTestHistory.csv' % station_id data_sets.append( ['L2', get_data(testfilename, int(args.get('L2', '1')))] ) if 'S' in args: - testfilename = '%s_S_station_test_history.csv' % station_id + testfilename = '%s_S_StationTestHistory.csv' % station_id data_sets.append( ['S', get_data(testfilename, int(args.get('S', '1')))] ) if not 'L2' in args and not 'S' in args: -- GitLab From 6d0129be2da95048141e74267d78233b8ab2eb35 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 14 Sep 2016 11:33:10 +0000 Subject: [PATCH 790/933] Task #9607: fix when claiming 0 resources, go to error --- .../ResourceAssigner/lib/assignment.py | 13 +++++++++---- .../ResourceAssignmentDatabase/radb.py | 8 ++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index f2117648b52..a85e30fcb72 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -259,9 +259,14 @@ class ResourceAssigner(): # another service sets the parset spec in otdb, and updated otdb task status to scheduled, which is then synced to radb self._sendNotification(task, 'scheduled') else: - logger.warning('doAssignment: Not all claims could be inserted. Setting task %s status to conflict' % (taskId)) - self.radbrpc.updateTask(taskId, status='conflict') - self._sendNotification(task, 'conflict') + if claim_ids: + logger.warning('doAssignment: Not all claims could be inserted. Setting task %s status to conflict' % (taskId)) + self.radbrpc.updateTask(taskId, status='conflict') + self._sendNotification(task, 'conflict') + else: + logger.warning('doAssignment: No claims could be made. Setting task %s status to error' % (taskId)) + self.radbrpc.updateTask(taskId, status='error') + self._sendNotification(task, 'error') def _sendNotification(self, task, status): try: @@ -436,4 +441,4 @@ class ResourceAssigner(): logger.info('claimResources: inserting %d claims in the radb' % len(claims)) claim_ids = self.radbrpc.insertResourceClaims(task['id'], claims, 1, 'anonymous', -1)['ids'] logger.info('claimResources: %d claims were inserted in the radb' % len(claim_ids)) - return len(claim_ids) == len(claims), claim_ids + return (len(claim_ids) > 0 and len(claim_ids) == len(claims)), claim_ids diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 4026758ce6e..3d1c0ea38c7 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -1062,7 +1062,7 @@ class RADatabase: except Exception as e: logger.error("Invalid claim dict, rolling back. %s" % e) self.rollback() - return None + return [] try: # use psycopg2 mogrify to parse and build the insert values @@ -1072,7 +1072,7 @@ class RADatabase: except Exception as e: logger.error("Invalid input, rolling back: %s\n%s" % (claim_values, e)) self.rollback() - return None + return [] query = '''INSERT INTO resource_allocation.resource_claim (resource_id, task_id, starttime, endtime, status_id, session_id, claim_size, username, user_id) @@ -1084,7 +1084,7 @@ class RADatabase: if not claimIds or [x for x in claimIds if x < 0]: logger.error("One or more claims cloud not be inserted. Rolling back.") self.rollback() - return None + return [] # gather all properties for all claims # store them as list of (claim_id, prop_type, prop_value, io_type, sap_nr) tuples @@ -1097,7 +1097,7 @@ class RADatabase: if properties: property_ids = self.insertResourceClaimProperties(properties, False) if property_ids == None: - return None + return [] # get the claims as they were inserted # and validate them against all other claims -- GitLab From 255b3e536f4a63508d57f9a1c89b1ef7db573f96 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 14 Sep 2016 11:33:41 +0000 Subject: [PATCH 791/933] Task #9607: check for None --- .../ResourceAssignmentDatabase/radbpglistener.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py index 8548c6281e7..4da8de503b0 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py @@ -157,9 +157,10 @@ class RADBPGListener(PostgresListener): def _sendNotification(self, subject, contentDict): try: - msg = EventMessage(context=self.notification_prefix + subject, content=contentDict) - logger.info('Sending notification %s: %s' % (subject, str(contentDict).replace('\n', ' '))) - self.event_bus.send(msg) + if subject and contentDict: + msg = EventMessage(context=self.notification_prefix + subject, content=contentDict) + logger.info('Sending notification %s: %s' % (subject, str(contentDict).replace('\n', ' '))) + self.event_bus.send(msg) except Exception as e: logger.error(str(e)) -- GitLab From c1f9b1c2539cdf2eee2491156ed562cad262a44d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 14 Sep 2016 11:34:39 +0000 Subject: [PATCH 792/933] Task #9607: sort by cluster, type, starttime --- .../lib/static/app/controllers/gridcontroller.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 6ec2e9e35cb..6cfa82daf48 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -33,7 +33,7 @@ $scope.columns = [ enableCellEdit: false, enableCellEditOnFocus: false, cellTemplate:'<div style=\'text-align:left\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>', - sort: { direction: uiGridConstants.ASC, priority: 2 } + sort: { direction: uiGridConstants.ASC, priority: 3 } }, { field: 'endtime', displayName: 'End', @@ -75,7 +75,7 @@ $scope.columns = [ type: uiGridConstants.filter.SELECT, selectOptions: [] }, - sort: { direction: uiGridConstants.ASC, priority: 1 } + sort: { direction: uiGridConstants.ASC, priority: 2 } }, { field: 'mom_object_group_id', displayName: 'Group', @@ -117,7 +117,8 @@ $scope.columns = [ cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) { var value = grid.getCellValue(row,col); return "grid-cluster-" + value; - } + }, + sort: { direction: uiGridConstants.ASC, priority: 1 } }]; $scope.gridOptions = { enableGridMenu: false, -- GitLab From e13615ca18d730ffaef4b925396ec8ddba96d1fe Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 14 Sep 2016 11:35:22 +0000 Subject: [PATCH 793/933] Task #9607: log errors --- SAS/ResourceAssignment/RAScripts/povero | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/RAScripts/povero b/SAS/ResourceAssignment/RAScripts/povero index c98253a363a..de23a38d276 100755 --- a/SAS/ResourceAssignment/RAScripts/povero +++ b/SAS/ResourceAssignment/RAScripts/povero @@ -52,8 +52,10 @@ def getSlurmStats(otdb_id): nnodes = int(parts[1]) clusterusage = nnodes / 50.0 return cputimeraw, nnodes, clusterusage - except Exception: - pass + except Exception as e: + logger.error(e) + else: + logger.error(err) return 0, 0, 0 -- GitLab From 53fde39822670f8a93b81a8c662335448e50e35c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 14 Sep 2016 11:44:24 +0000 Subject: [PATCH 794/933] Task #9607: disable dependencies when there are too many tasks in view --- .../lib/static/app/controllers/ganttprojectcontroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index c14bb17ea27..ecc42fb4bda 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -149,7 +149,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS } //only enable dependencies (arrows between tasks) in detailed view - $scope.options.dependencies = (fullTimespanInMinutes <= 6*60) || numTasks < 20; + $scope.options.dependencies = (fullTimespanInMinutes <= 6*60 && numTasks <= 100) || numTasks < 20; //start with aggregating all tasks per type, //and plot these in the upper rows, -- GitLab From 2f6c453cd8fff644f08960070737a7c88e2ec55a Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 14 Sep 2016 13:29:57 +0000 Subject: [PATCH 795/933] Task #9607: (work by ruud beukema, commit by schaap) Added visual indication to tasks that are blocked by predecessors, and added functionality for selecting them (e.g. for rescheduling) --- .../radbpglistener.py | 12 +- .../sql/create_database.sql | 7 +- .../static/app/controllers/datacontroller.js | 15 +- .../app/controllers/ganttprojectcontroller.js | 16 +- .../static/app/controllers/gridcontroller.js | 41 +++++- .../angular-gantt-contextmenu-plugin.js | 27 +++- .../lib/static/css/main.css | 138 ++++++++---------- 7 files changed, 163 insertions(+), 93 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py index 4da8de503b0..d6fbb82c38b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py @@ -79,7 +79,17 @@ class RADBPGListener(PostgresListener): self.subscribe('resource_capacity_update', self.onResourceCapacityUpdated) def onTaskUpdated(self, payload = None): - self._sendNotification('TaskUpdated', self.rarpc.getTask(payload)) + # Send notification for the given updated task + task_id = payload + task = self.rarpc.getTask(task_id) + self._sendNotification('TaskUpdated', task) + + # The "blocked_by_ids" property of the given task's successors might have been updated due to the given task + # status being updated. Therefore also send a notification for these successors - lazily ignoring that they + # might not have changed. + suc_sched_tasks = self.rarpc.getTasks(task_ids=task['successor_ids'], task_status='scheduled') + for suc_sched_task in suc_sched_tasks: + self._sendNotification('TaskUpdated', suc_sched_task) def onTaskInserted(self, payload = None): self._sendNotification('TaskInserted', self.rarpc.getTask(payload)) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql index 0dbaed1f40b..643de3fa1e0 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql @@ -307,7 +307,12 @@ CREATE OR REPLACE VIEW resource_allocation.task_view AS SELECT t.id, t.mom_id, t.otdb_id, t.status_id, t.type_id, t.specification_id, ts.name AS status, tt.name AS type, s.starttime, s.endtime, extract(epoch from age(s.endtime, s.starttime)) as duration, s.cluster, (SELECT array_agg(tp.predecessor_id) FROM resource_allocation.task_predecessor tp where tp.task_id=t.id) as predecessor_ids, - (SELECT array_agg(tp.task_id) FROM resource_allocation.task_predecessor tp where tp.predecessor_id=t.id) as successor_ids + (SELECT array_agg(tp.task_id) FROM resource_allocation.task_predecessor tp where tp.predecessor_id=t.id) as successor_ids, + (SELECT DISTINCT ARRAY ( + SELECT _tp.predecessor_id + FROM resource_allocation.task_predecessor _tp, resource_allocation.task _t + WHERE t.status_id = 400 AND _tp.task_id = t.id AND _t.id = _tp.predecessor_id AND (_t.status_id = 1100 OR _t.status_id = 1150) ) as array_agg + ) as blocked_by_ids FROM resource_allocation.task t JOIN resource_allocation.task_status ts ON ts.id = t.status_id JOIN resource_allocation.task_type tt ON tt.id = t.type_id diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 0ebfd32d4f2..0a4346b3783 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -355,7 +355,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, id = parseInt(id); } - var foundTask = self.tasks.find(function(t) { return t['id_name'] == id; }); + var foundTask = id_name == 'id' ? self.taskDict[id] : self.tasks.find(function(t) { return t[id_name] == id; }); if(foundTask) { defer.resolve(foundTask); @@ -892,6 +892,19 @@ dataControllerMod.controller('DataController', }; $scope.jumpToNow(); + + $scope.loadTasksSelectAndJumpIntoView = function(task_ids) { + var list_of_promises = task_ids.map(function(t_id) { return $scope.dataService.getTask(t_id); }); + var defer = $q.defer(); + $q.all(list_of_promises).then(function(in_tasks) { + var loaded_tasks = in_tasks.filter(function(t) { return t != undefined; }); + var loaded_tasks_ids = loaded_tasks.map(function(t) { return t.id; }); + $scope.dataService.setSelectedTaskIds(loaded_tasks_ids); + $scope.jumpToSelectedTasks(); + defer.resolve(loaded_tasks); + }); + return defer.promise; + }; $scope.loadTaskByOTDBIdSelectAndJumpIntoView = function(otdb_id) { var defer = $q.defer(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index ecc42fb4bda..071293a4536 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -261,6 +261,15 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS ganntProjectTypeRows.push(availableRow); ganntRows.push(availableRow); } + + // Scheduled tasks that are blocked tasks are shown differently and use a tooltip + var css_class = "task-status-"; + if (task.blocked_by_ids.length > 0) { + css_class += "blocked"; + } + else { + css_class += task.status; + } var rowTask = { id: task.id.toString(), @@ -268,8 +277,11 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS from: task.starttime, to: task.endtime, raTask: task, - color: self.taskStatusColors[task.status], - classes: 'task-status-' + task.status, + + // Leave color property undefined; it is now defined by CSS + //color: self.taskStatusColors[task.status], + + classes: css_class, movable: $.inArray(task.status_id, editableTaskStatusIds) > -1 }; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 6cfa82daf48..10991e728fe 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -63,8 +63,15 @@ $scope.columns = [ editableCellTemplate: 'ui-grid/dropdownEditor', editDropdownOptionsArray: [], cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) { - var value = grid.getCellValue(row,col); - return "grid-status-" + value; + // Scheduled tasks that are blocked tasks use a different make-up style + var css_class = "grid-status-"; + if (row.entity.blocked_by_ids.length > 0) { + css_class += "blocked"; + } + else { + css_class += grid.getCellValue(row,col); + } + return css_class; } }, { field: 'type', @@ -261,7 +268,8 @@ $scope.columns = [ mom_object_group_id: task.mom_object_group_id, mom_object_group_name: task.mom_object_group_name, mom_object_group_mom2object_id: task.mom_object_group_mom2object_id, - cluster: task.cluster + cluster: task.cluster, + blocked_by_ids: task.blocked_by_ids, }; gridTasks.push(gridTask); } @@ -588,20 +596,39 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } - var finished_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'finished' || t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; }); + var aborted_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; }); - if(finished_selected_cep4_pipelines.length > 0) { - var liContent = '<li><a href="#">Reschedule finished/aborted CEP4 pipelines</a></li>' + if(aborted_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Reschedule aborted/error CEP4 pipelines</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - for(var pl of finished_selected_cep4_pipelines) { + for(var pl of aborted_selected_cep4_pipelines) { var newTask = { id: pl.id, status: 'prescheduled' }; dataService.putTask(newTask); } }); } + + var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); }); + + if(blocked_selected_cep4_tasks.length > 0) { + var liContent = '<li><a href="#">Select blocking predecessors</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + + var blocking_predecessors = [] + for(var task of blocked_selected_cep4_tasks) { + blocking_predecessors = blocking_predecessors.concat(task.blocked_by_ids); + } + + var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; + dataCtrlScope.loadTasksSelectAndJumpIntoView(blocking_predecessors); + }); + } var closeContextMenu = function(cme) { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index 1e05df07db0..ead27ced7b0 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -162,21 +162,40 @@ }); } - var finished_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'finished' || t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; }); + var aborted_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; }); - if(finished_selected_cep4_pipelines.length > 0) { - var liContent = '<li><a href="#">Reschedule finished/aborted CEP4 pipelines</a></li>' + if(aborted_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Reschedule aborted/error CEP4 pipelines</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - for(var pl of finished_selected_cep4_pipelines) { + for(var pl of aborted_selected_cep4_pipelines) { var newTask = { id: pl.id, status: 'prescheduled' }; dataService.putTask(newTask); } }); } + var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); }); + + if(blocked_selected_cep4_tasks.length > 0) { + var liContent = '<li><a href="#">Select blocking predecessors</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + + var blocking_predecessors = [] + for(var task of blocked_selected_cep4_tasks) { + blocking_predecessors = blocking_predecessors.concat(task.blocked_by_ids); + } + + dataCtrlScope.loadTasksSelectAndJumpIntoView(blocking_predecessors); + }); + } + + var closeContextMenu = function() { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index 04e51917753..9ef3d56d527 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -82,7 +82,8 @@ table.uib-timepicker td.uib-time { } .gantt-task-content { - margin-top: 2.2px; +/* margin-top: 2.2px; */ + padding-top: 2.2px; } .gantt-task.task-selected-task { @@ -104,132 +105,115 @@ div.gantt-task span { padding: 0px 10px; } -div.gantt-task.task-status-on_hold span, div.gantt-task.task-status-prescheduled span, div.gantt-task.task-status-scheduled span, div.gantt-task.task-status-aborted span, div.gantt-task.task-status-error span { - color: #ffffff; -} - -div.gantt-task.claim-task-status-on_hold span, div.gantt-task.claim-task-status-prescheduled span, div.gantt-task.claim-task-status-scheduled span, div.gantt-task.claim-task-status-aborted span, div.gantt-task.claim-task-status-error span { - color: #ffffff; -} +/* + The same status coloring is used for both the grid items and the gantt-chart items (a.k.a. tasks) +*/ +.grid-status-prepared, +div.gantt-task.task-status-prepared div, div.gantt-task.claim-task-status-prepared span { - background: #cccccc; -} - -div.gantt-task.claim-task-status-approved span { - background: #8cb3d9; -} - -div.gantt-task.claim-task-status-on_hold span { - background: #b34700; -} - -div.gantt-task.claim-task-status-conflict span { - background: #ff0000; -} - -div.gantt-task.claim-task-status-prescheduled span { - background: #6666ff; -} - -div.gantt-task.claim-task-status-scheduled span { - background: #0000ff; -} - -div.gantt-task.claim-task-status-queued span { - background: #ccffff; -} - -div.gantt-task.claim-task-status-active span { - background: #ffff00; -} - -div.gantt-task.claim-task-status-completing span { - background: #ffdd99; -} - -div.gantt-task.claim-task-status-finished span { - background: #00ff00; -} - -div.gantt-task.claim-task-status-aborted span { - background: #cc0000; -} - -div.gantt-task.claim-task-status-error span { - background: #990033; -} - -div.gantt-task.claim-task-status-opened span { - background: #d9e5f2; -} - -div.gantt-task.claim-task-status-suspended span { - background: #996666; -} - -.grid-status-prepared { background-color: #cccccc !important; } -.grid-status-opened { +.grid-status-opened, +div.gantt-task.task-status-opened div, +div.gantt-task.claim-task-status-opened span { background-color: #aaaaaa !important; } -.grid-status-suspended { +.grid-status-suspended, +div.gantt-task.task-status-suspended div, +div.gantt-task.claim-task-status-suspended span { background-color: #776666 !important; } -.grid-status-approved { +.grid-status-approved, +div.gantt-task.task-status-approved div, +div.gantt-task.claim-task-status-approved span { background-color: #8cb3d9 !important; } -.grid-status-on_hold { +.grid-status-on_hold, +div.gantt-task.task-status-on_hold div, +div.gantt-task.claim-task-status-on_hold span { background-color: #b34700 !important; color: #ffffff; } -.grid-status-conflict { +.grid-status-conflict, +div.gantt-task.task-status-conflict div, +div.gantt-task.claim-task-status-conflict span { background-color: #ff0000 !important; } -.grid-status-prescheduled { +.grid-status-prescheduled, +div.gantt-task.task-status-prescheduled div, +div.gantt-task.claim-task-status-prescheduled span { background-color: #6666ff !important; color: #ffffff; } -.grid-status-scheduled { +.grid-status-scheduled, .grid-status-blocked, +div.gantt-task.task-status-scheduled div, +div.gantt-task.task-status-blocked div, +div.gantt-task.claim-task-status-scheduled span { background-color: #0000ff !important; color: #ffffff; } -.grid-status-queued { +.grid-status-queued, +div.gantt-task.task-status-queued div, +div.gantt-task.claim-task-status-queued span { background-color: #ccffff !important; } -.grid-status-active { +.grid-status-active, +div.gantt-task.task-status-active div, +div.gantt-task.claim-task-status-active span { background-color: #ffff00 !important; } -.grid-status-completing { +.grid-status-completing, +div.gantt-task.task-status-completing div, +div.gantt-task.claim-task-status-completing span { background-color: #ffdd99 !important; } -.grid-status-finished { +.grid-status-finished, +div.gantt-task.task-status-finished div, +div.gantt-task.claim-task-status-finished span { background-color: #00ff00 !important; } -.grid-status-aborted { +.grid-status-aborted, +div.gantt-task.task-status-aborted div, +div.gantt-task.claim-task-status-aborted span { background-color: #cc0000 !important; color: #ffffff; } -.grid-status-error { +.grid-status-error, +div.gantt-task.task-status-error div, +div.gantt-task.claim-task-status-error span { background-color: #990033 !important; color: #ffffff; } +.grid-status-blocked, div.gantt-task.task-status-blocked div { + background-image: url(/static/icons/warning.png); + background-repeat: no-repeat; +} + +.grid-status-blocked { + background-position: right 50%; + padding-right: 18px; +} + +div.gantt-task.task-status-blocked div{ + background-position: left 50%; +} + .grid-cluster-CEP2 { background-color: #ccffff !important; -- GitLab From 8d620c9fda0c97d0d3f5b8411b2b34f9931b7d19 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 14 Sep 2016 13:41:53 +0000 Subject: [PATCH 796/933] Task #9607: (work by ruud beukema, commit by schaap) Added missing icon --- .gitattributes | 1 + .../lib/static/icons/warning.png | Bin 0 -> 692 bytes 2 files changed, 1 insertion(+) create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/warning.png diff --git a/.gitattributes b/.gitattributes index 1d7391a1253..ee3b5f8c719 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5168,6 +5168,7 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-half SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-halflings-regular.ttf -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-halflings-regular.woff -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-halflings-regular.woff2 -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/warning.png -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-animate/angular-animate.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-aria/angular-aria.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-gantt/.bower.json -text diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/warning.png b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/warning.png new file mode 100644 index 0000000000000000000000000000000000000000..e8c259292940ff503711382478f088dcf2224105 GIT binary patch literal 692 zcmeAS@N?(olHy`uVBq!ia0y~yU=RXf4mJh`hOl#e;S3B6Y)RhkE)4%caKYZ?lNlHo zI14-?iy0W_TZ1s8^5zMG3=9nHC7!;n><>73_za}BzUj+gU|@3eba4!^IK6j@w?{~# zMBDuTMdxN(rmwp<D=p(#iWi5zYxDL`K~X`15|dh-Ty&K<7KQHMy5)YwDk}2AD&O4V zL>A6g(GaIHXU^L*j>gQnXsOE;l)Xad*L3~+|7-N$|F5<G)1REdxPW1=W#T{4(}jCJ zxtv&kj`^Qy@D!0)XZEI@rw>mwDacwoXVp5j^e6wm_P%@ac1iUP^8#^?DO%H1O9CU8 zMN4PfI-mU-cgnfTcJ02h(^HyN?@qt)*m~Pd@$+)J{qeiMPLwWx_R_=h()$w+eq4Uz z{NV3<i%Xp=Hm=`(_U6o<**$&BC1uYGeDbymE?Z@l_T~SZd;LvI(!6_3YpOQ%I2}xl zZ9VqhV~?Dt|LT6{yN!$sK8Gt9Z@gzww?==;hC^Iy6<<uw;Fa?h=#eT}@+KvC@Amqy z7CP*l^L3?XN$*K#<6~0{KJv|DRkGSft>&%?O+FL<zO^eewRCS-7nB$+t@P{E(&CA` zGLAH;1_T-W-`Np%;qIdaEE>^?TyI|9Y^pJhm0-SfT9)&)NcHbB_FvnU%V_LSNPKr$ z=(}8M#RrwsPiEU>Fmk(EMakUScI)rd#JhVBO-w#r@K^2piL2Zfwfc4U=T3KFHs0K= zq52}T@I&j@>({0&*mY@w{{3g#^L%xC7>lJGxRMq&tf~H^<X*L?gCWL&gM(ePcdMB2 zWXn6xcfPlbnDoYJW3a=OR!znS6{!|84g8NOyPYU43~yQC;H4oF=DW$sZ|`aU{qs)V v_esCYpY-!G^ELT5ix`X!JhK10dmn?iXXi3gS0-@=1_lOCS3j3^P6<r_<o7hd literal 0 HcmV?d00001 -- GitLab From 41f51e9b60b82bc943a24fe05ec822555a8a39b2 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 14 Sep 2016 13:53:40 +0000 Subject: [PATCH 797/933] Task #9607: set task to error if estimator fails --- .../ResourceAssigner/lib/assignment.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index a85e30fcb72..d399f5f049b 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -207,8 +207,13 @@ class ResourceAssigner(): logger.info('skipping resource assignment for CEP4 task otdb_id=%s because status=%s' % (otdb_id, status)) return - needed = self.getNeededResouces(specification_tree) - logger.info('doAssignment: getNeededResouces=%s' % (needed,)) + try: + needed = self.getNeededResouces(specification_tree) + logger.info('doAssignment: getNeededResouces=%s' % (needed,)) + except Exception as e: + logger.error(e) + self.radbrpc.updateTask(taskId, status='error') + self._sendNotification(task, 'error') if not str(otdb_id) in needed: logger.error("no otdb_id %s found in estimator results %s" % (otdb_id, needed)) -- GitLab From f07714ee69b2dc0290425e28f39550800863e948 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 14 Sep 2016 14:03:32 +0000 Subject: [PATCH 798/933] Task #9607: added icon to cmake --- .../ResourceAssignmentEditor/lib/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt index d72973220ff..661e51d5558 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt @@ -41,6 +41,7 @@ set(app_files static/app/controllers/ganttprojectcontroller.js static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js static/css/main.css + static/icons/warning.png templates/index.html) set(web_files -- GitLab From 46db9df06c7ac738cf4410ba7b5a6f8b9b0f3a20 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 14 Sep 2016 14:22:44 +0000 Subject: [PATCH 799/933] Task #9607: added missing return --- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index d399f5f049b..65a00d60478 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -214,6 +214,7 @@ class ResourceAssigner(): logger.error(e) self.radbrpc.updateTask(taskId, status='error') self._sendNotification(task, 'error') + return if not str(otdb_id) in needed: logger.error("no otdb_id %s found in estimator results %s" % (otdb_id, needed)) -- GitLab From 68a1d512721cfe14e92ec3f4e18c1775b1ff28db Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Thu, 15 Sep 2016 08:05:40 +0000 Subject: [PATCH 800/933] Task #6517: Creating shield file for MCU002 --- .gitattributes | 3 ++ .../License/471_3031_1_Astron_Gen_I_3_314.log | 30 +++++++++++ .../PVSS/License/Astron_Central_1_shield.txt | 53 +++++++++---------- .../data/PVSS/License/MCU002_option.txt | 27 ++++++++++ .../data/PVSS/License/shield.MCU002.txt | 28 ++++++++++ 5 files changed, 114 insertions(+), 27 deletions(-) create mode 100644 MAC/Deployment/data/PVSS/License/471_3031_1_Astron_Gen_I_3_314.log create mode 100644 MAC/Deployment/data/PVSS/License/MCU002_option.txt create mode 100644 MAC/Deployment/data/PVSS/License/shield.MCU002.txt diff --git a/.gitattributes b/.gitattributes index 5113243e693..2313004a7f5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3480,6 +3480,7 @@ MAC/Deployment/data/OTDB/genArrayC++.py -text MAC/Deployment/data/OTDB/genArrayJava.py -text MAC/Deployment/data/OTDB/genArrayTable.py -text MAC/Deployment/data/OTDB/genArrayTest.py -text +MAC/Deployment/data/PVSS/License/471_3031_1_Astron_Gen_I_3_314.log -text MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_2_311.log -text MAC/Deployment/data/PVSS/License/CCU099_option.txt -text MAC/Deployment/data/PVSS/License/CS001C_option_lcu037.txt -text @@ -3525,6 +3526,7 @@ MAC/Deployment/data/PVSS/License/HwCode.cs016c -text MAC/Deployment/data/PVSS/License/HwCode.mcu001 -text MAC/Deployment/data/PVSS/License/License[!!-~]administration.xlsx -text MAC/Deployment/data/PVSS/License/MCU001_option.old.txt -text +MAC/Deployment/data/PVSS/License/MCU002_option.txt -text MAC/Deployment/data/PVSS/License/MCU099_option.txt -text MAC/Deployment/data/PVSS/License/PL610C_option.txt -text MAC/Deployment/data/PVSS/License/PL611C_option.txt -text @@ -3588,6 +3590,7 @@ MAC/Deployment/data/PVSS/License/shield.DE605C.txt -text MAC/Deployment/data/PVSS/License/shield.DE609C.txt -text MAC/Deployment/data/PVSS/License/shield.FR606C.txt -text MAC/Deployment/data/PVSS/License/shield.FR606C_lcu101.txt -text +MAC/Deployment/data/PVSS/License/shield.MCU002.txt -text MAC/Deployment/data/PVSS/License/shield.MCU099.txt -text MAC/Deployment/data/PVSS/License/shield.PL610C.txt -text MAC/Deployment/data/PVSS/License/shield.PL611C.txt -text diff --git a/MAC/Deployment/data/PVSS/License/471_3031_1_Astron_Gen_I_3_314.log b/MAC/Deployment/data/PVSS/License/471_3031_1_Astron_Gen_I_3_314.log new file mode 100644 index 00000000000..78659da440f --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/471_3031_1_Astron_Gen_I_3_314.log @@ -0,0 +1,30 @@ + +--------------------------------------------------- +[license] +code = "mcu002.control.lofar 20329200053" +version = 31400002 +sn = "471_3031_1_Astron_Gen_I_3_314/1" +date = 2016.09.15;10:04:35,000 +comment = "CentOS7 MainCU system MCU002" +expire = 0000.00.00;00:00:00,000 +redundancy = 1 +ui = 15 +para = 4 +dde = 5 +event = 1 +ios = 100000 +ssi = 0 +api = 80 +excelreport = 5 +http = 15 +infoserver = 5000 +comcenter = 5 +maintenance = 0 +scheduler = 0 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + diff --git a/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt b/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt index 2f8cdc681b7..850425c82d7 100644 --- a/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt +++ b/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt @@ -1,29 +1,28 @@ [license] -#hw = 00825320842 -code = "dongleHost 10552290714" -version = 31400002 -sn = "471_3031_1_Astron_Gen_I_3_314" -expire = 0000.00.00;00:00:00,000 -redundancy = 1 -ui = 15 -para = 4 -dde = 5 -event = 1 -api = 80 -excelreport = 5 -http = 15 -infoserver = 5000 -ios = 100000 -comcenter = 5 -maintenance = 0 -scheduler = 0 -ssi = 0 -distributed = 255 -uifix = 0 -parafix = 0 -pararemote = 0 -ctrlext = 1 -update = 0 -licenseMax = 8 -licenseLeft = 8 +code = "dongleHost 10387235999" +version = 31400002 +sn = "471_3031_1_Astron_Gen_I_3_314" +expire = 0000.00.00;00:00:00,000 +redundancy = 1 +ui = 15 +para = 4 +dde = 5 +event = 1 +ios = 100000 +ssi = 0 +api = 80 +excelreport = 5 +http = 15 +infoserver = 5000 +comcenter = 5 +maintenance = 0 +scheduler = 0 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 +licenseMax = 8 +licenseLeft = 7 diff --git a/MAC/Deployment/data/PVSS/License/MCU002_option.txt b/MAC/Deployment/data/PVSS/License/MCU002_option.txt new file mode 100644 index 00000000000..366fd978375 --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/MCU002_option.txt @@ -0,0 +1,27 @@ +[license] +code = "mcu002.control.lofar 51476810367" +version = 31400002 +comment = "CentOS7 MainCU system MCU002" +sn = "471_3031_1_Astron_Gen_I_1_314" +expire = 0000.00.00;00:00:00,000 +redundancy = 1 +ui = 15 +para = 4 +dde = 5 +event = 1 +api = 80 +excelreport = 5 +http = 15 +infoserver = 5000 +ios = 100000 +comcenter = 5 +maintenance = 0 +scheduler = 0 +ssi = 0 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + diff --git a/MAC/Deployment/data/PVSS/License/shield.MCU002.txt b/MAC/Deployment/data/PVSS/License/shield.MCU002.txt new file mode 100644 index 00000000000..932a68b0004 --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/shield.MCU002.txt @@ -0,0 +1,28 @@ +[license] +code = "mcu002.control.lofar 20329200053" +version = 31400002 +sn = "471_3031_1_Astron_Gen_I_3_314/1" +date = 2016.09.15;10:04:35,000 +comment = "CentOS7 MainCU system MCU002" +expire = 0000.00.00;00:00:00,000 +redundancy = 1 +ui = 15 +para = 4 +dde = 5 +event = 1 +ios = 100000 +ssi = 0 +api = 80 +excelreport = 5 +http = 15 +infoserver = 5000 +comcenter = 5 +maintenance = 0 +scheduler = 0 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + -- GitLab From c639f0d35ad7f613e5980688a103fdb1f664a50b Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 15 Sep 2016 09:57:20 +0000 Subject: [PATCH 801/933] Task #9522: Use purge in autoremove to clean up more, fixing a build issue for Yan --- Docker/lofar-base/Dockerfile.tmpl | 12 ++++++------ Docker/lofar-outputproc/Dockerfile.tmpl | 2 +- Docker/lofar-pipeline/Dockerfile.tmpl | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 34e2d90f3f4..9b6e80d0beb 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -43,7 +43,7 @@ RUN apt-get update && \ apt-get install -y python-pip && \ pip install numpy && \ apt-get purge -y python-pip && \ - apt-get autoremove -y && \ + apt-get autoremove -y --purge && \ apt-get install -y nano sudo # @@ -76,7 +76,7 @@ RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison bash -c "strip ${INSTALLDIR}/casacore/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casacore/{build,src}" && \ apt-get purge -y wget git cmake g++ gfortran flex bison libopenblas-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge # # ******************* @@ -93,7 +93,7 @@ RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system- bash -c "strip ${INSTALLDIR}/casarest/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casarest/{build,src}" && \ apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge # # ******************* @@ -111,7 +111,7 @@ RUN apt-get update && apt-get install -y git make g++ python-setuptools libboost bash -c "find ${INSTALLDIR}/python-casacore/lib -name '*.so' | xargs strip || true" && \ bash -c "rm -rf ${INSTALLDIR}/python-casacore/python-casacore" && \ apt-get purge -y git make g++ python-setuptools libboost-python-dev libcfitsio3-dev wcslib-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge # # ******************* @@ -132,7 +132,7 @@ RUN apt-get update && apt-get install -y subversion swig ruby ruby-dev python-de bash -c "strip /opt/qpid/{bin,lib}/* || true" && \ bash -c "rm -rf ~/sources" && \ apt-get purge -y subversion swig ruby ruby-dev python-dev libsasl2-dev pkg-config cmake libtool uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man fakeroot build-essential debhelper libsslcommon2-dev libxqilla-dev python-setuptools libboost-program-options${BOOST_VERSION}-dev libboost-filesystem${BOOST_VERSION}-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge # # ******************* # DAL @@ -147,7 +147,7 @@ RUN apt-get update && apt-get install -y git cmake g++ swig python-dev libhdf5-d bash -c "find ${INSTALLDIR}/DAL/lib -name '*.so' | xargs strip || true" && \ bash -c "rm -rf ${INSTALLDIR}/DAL/{src,build}" && \ apt-get purge -y git cmake g++ swig python-dev libhdf5-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge # # entry diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index 22a882cea6e..818e940c861 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -33,5 +33,5 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ setcap cap_sys_nice,cap_sys_admin=ep ${INSTALLDIR}/lofar/bin/outputProc && \ apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index c8b9303eed9..ae3f7ee87d4 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cp apt-get -y install python-pip python-dev && \ pip install pyfits pywcs python-monetdb && \ apt-get -y purge python-pip python-dev && \ - apt-get -y autoremove + apt-get -y autoremove --purge # # ******************* @@ -29,7 +29,7 @@ RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12 bash -c "rm -rf ${INSTALLDIR}/aoflagger/{build,aoflagger-${AOFLAGGER_VERSION}}" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libopenblas-dev && \ - apt-get -y autoremove + apt-get -y autoremove --purge # # ******************* @@ -58,4 +58,4 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge -- GitLab From 328ea4083959f2e26aa453c273130c338e3e7728 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 15 Sep 2016 10:01:39 +0000 Subject: [PATCH 802/933] Task #9522: Remove obsolete USER/UID settings in Dockerfile --- Docker/lofar-base/Dockerfile.tmpl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 9b6e80d0beb..a5679fe25f5 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -6,7 +6,6 @@ FROM ubuntu:16.04 # # common-environment # -ENV USER=lofar ENV INSTALLDIR=/opt # @@ -23,11 +22,6 @@ ENV CASACORE_VERSION=latest \ PYTHON_CASACORE_VERSION=2.1.2 \ BOOST_VERSION=1.58 -# -# set-uid -# -ENV UID=${BUILD_UID} - # # set-build-options # -- GitLab From ad8294c78b146223f9c2fd43b181dc859d0d5b21 Mon Sep 17 00:00:00 2001 From: Pieter Donker <donker@astron.nl> Date: Thu, 15 Sep 2016 13:25:20 +0000 Subject: [PATCH 803/933] Task #9612: checkhardware, if no tests done skip creating report --- LCU/checkhardware/check_hardware.py | 35 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/LCU/checkhardware/check_hardware.py b/LCU/checkhardware/check_hardware.py index d58676cc2bc..a267b3a5465 100755 --- a/LCU/checkhardware/check_hardware.py +++ b/LCU/checkhardware/check_hardware.py @@ -592,25 +592,26 @@ def main(): db.check_stop_time = time.gmtime() - try: - # do db test and write result files to log directory - report_dir = conf().as_string('paths.local-report-dir') - if os.path.exists(report_dir): - logger.info('write result data') - db.test() - make_report(db, report_dir) - else: - logger.warning('not a valid report directory') - # delete files from data directory - remove_all_data_files() - except: - logger.error('Program fault, reporting and cleanup') - logger.error('Caught %s', str(sys.exc_info()[0])) - logger.error(str(sys.exc_info()[1])) - logger.error('TRACEBACK:\n%s', traceback.format_exc()) - logger.error('Aborting NOW') if len(sys.argv) > 1: + try: + # do db test and write result files to log directory + report_dir = conf().as_string('paths.local-report-dir') + if os.path.exists(report_dir): + logger.info('write result data') + db.test() + make_report(db, report_dir) + else: + logger.warning('not a valid report directory') + # delete files from data directory + remove_all_data_files() + except: + logger.error('Program fault, reporting and cleanup') + logger.error('Caught %s', str(sys.exc_info()[0])) + logger.error(str(sys.exc_info()[1])) + logger.error('TRACEBACK:\n%s', traceback.format_exc()) + logger.error('Aborting NOW') + logger.info('Check if boards are still ok') check_active_boards(db, n_rsp, n_tbb, 1) -- GitLab From 5a6e42d444f0fefbcf2ec87309d3a6c821e1bc93 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 15 Sep 2016 13:59:35 +0000 Subject: [PATCH 804/933] Task #9522: Do not set UID, as bash will to that for us --- Docker/lofar-base/chuser.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/Docker/lofar-base/chuser.sh b/Docker/lofar-base/chuser.sh index 9ac3603c1a0..46872545a87 100755 --- a/Docker/lofar-base/chuser.sh +++ b/Docker/lofar-base/chuser.sh @@ -1,8 +1,5 @@ #!/usr/bin/env bash -# Correct UID -export UID=`id -u` - # Configure user if [ -z "${USER}" ]; then export USER=${UID} -- GitLab From f1876b64cb5391466fba3bb57b67860a5e908bef Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 16 Sep 2016 07:24:54 +0000 Subject: [PATCH 805/933] Task #9859: automatically reschedule all scheduled successor tasks --- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 65a00d60478..e2c0af39114 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -333,6 +333,12 @@ class ResourceAssigner(): else: logger.info('no successors for otdb_id=%s', task['otdb_id']) + #reschedule all scheduled successor tasks + #because they might need to update their specification due to this scheduled task + successor_tasks = self.radbrpc.getTasks(task['successor_ids'], task_status='scheduled') + for successor_task in successor_tasks: + self.radbrpc.updateTask(successor_task['id'], status='prescheduled') + except Exception as e: logger.error(e) -- GitLab From 6d673aa32d36f6822dfc0bf736959c709eaa0da7 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 16 Sep 2016 07:36:14 +0000 Subject: [PATCH 806/933] Task #9859: automatically reschedule all scheduled successor tasks --- .../OTDBtoRATaskStatusPropagator/propagator.py | 9 +++++++++ .../ResourceAssigner/lib/assignment.py | 6 ------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index b326523f592..eff0ec3ca20 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -99,6 +99,15 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): self._update_radb_task_status(treeId, 'scheduled') self._updateStartStopTimesFromSpecification(treeId) + radb_task = self.radb.getTask(otdb_id=treeId) + if radb_task: + #reschedule all scheduled successor tasks + #because they might need to update their specification due to this scheduled task + successor_tasks = self.radb.getTasks(radb_task['successor_ids'], task_status='scheduled') + for successor_task in successor_tasks: + logger.info('rescheduling otdb_id=%s because it is a successor of the just scheduled task otdb_id=%s', successor_task['otdb_id'], radb_task['otdb_id']) + self.radb.updateTask(successor_task['id'], status='prescheduled') + def onObservationQueued(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'queued') diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index e2c0af39114..65a00d60478 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -333,12 +333,6 @@ class ResourceAssigner(): else: logger.info('no successors for otdb_id=%s', task['otdb_id']) - #reschedule all scheduled successor tasks - #because they might need to update their specification due to this scheduled task - successor_tasks = self.radbrpc.getTasks(task['successor_ids'], task_status='scheduled') - for successor_task in successor_tasks: - self.radbrpc.updateTask(successor_task['id'], status='prescheduled') - except Exception as e: logger.error(e) -- GitLab From 06b99ce948373607a2de2d121d1af8054b86cca0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 16 Sep 2016 07:42:51 +0000 Subject: [PATCH 807/933] Task #9859: automatically reschedule all scheduled successor tasks --- .../OTDBtoRATaskStatusPropagator/propagator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index eff0ec3ca20..78a4d216ec2 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -103,7 +103,7 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): if radb_task: #reschedule all scheduled successor tasks #because they might need to update their specification due to this scheduled task - successor_tasks = self.radb.getTasks(radb_task['successor_ids'], task_status='scheduled') + successor_tasks = self.radb.getTasks(task_ids=radb_task['successor_ids'], task_status=['scheduled', 'queued']) for successor_task in successor_tasks: logger.info('rescheduling otdb_id=%s because it is a successor of the just scheduled task otdb_id=%s', successor_task['otdb_id'], radb_task['otdb_id']) self.radb.updateTask(successor_task['id'], status='prescheduled') -- GitLab From bddb4ad64a3d1c4b489fb4de01bba339a9b81427 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 16 Sep 2016 07:59:54 +0000 Subject: [PATCH 808/933] Task #9859: set status in otdb, not in radb --- .../OTDBtoRATaskStatusPropagator/propagator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index 78a4d216ec2..f6ac8a163e1 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -106,7 +106,7 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): successor_tasks = self.radb.getTasks(task_ids=radb_task['successor_ids'], task_status=['scheduled', 'queued']) for successor_task in successor_tasks: logger.info('rescheduling otdb_id=%s because it is a successor of the just scheduled task otdb_id=%s', successor_task['otdb_id'], radb_task['otdb_id']) - self.radb.updateTask(successor_task['id'], status='prescheduled') + self.otdb.taskSetStatus(successor_task['otdb_id'], 'prescheduled') def onObservationQueued(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'queued') -- GitLab From 6e9b855f0726d295dda963ad869cbc1e984a4666 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 16 Sep 2016 08:41:27 +0000 Subject: [PATCH 809/933] Task #9607: always put queued/active/completing tasks to aborted first when rescheduling --- .../propagator.py | 77 ++++++++++++------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index f6ac8a163e1..4e357edcd2e 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -46,11 +46,24 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): super(OTDBtoRATaskStatusPropagator, self).stop_listening(**kwargs) def _update_radb_task_status(self, otdb_id, task_status): - logger.info("updating radb-task with otdb_id %s to status %s" % (otdb_id, task_status)) - result = self.radb.updateTaskStatusForOtdbId(otdb_id=otdb_id, status=task_status) - - if not result or 'updated' not in result or not result['updated']: - logger.warning("could not update task with otdb_id %s to status %s" % (otdb_id, task_status)) + try: + if task_status in ['approved', 'prescheduled']: + radb_task = self.radb.getTask(otdb_id=treeId) + if (radb_task and + radb_task['cluster'] == 'CEP4' and + radb_task['status'] in ['queued', 'active', 'completing']): + # set task to aborted first, so other controls (e.g. pipelinecontrol) + # can respond to the aborted event + logger.info("aborting radb-task with otdb_id %s from status %s" % (otdb_id, radb_task['status'])) + result = self.radb.updateTaskStatusForOtdbId(otdb_id=otdb_id, status='aborted') + + logger.info("updating radb-task with otdb_id %s to status %s" % (otdb_id, task_status)) + result = self.radb.updateTaskStatusForOtdbId(otdb_id=otdb_id, status=task_status) + + if not result or 'updated' not in result or not result['updated']: + logger.warning("could not update task with otdb_id %s to status %s" % (otdb_id, task_status)) + except Exception as e: + logger.error(e) def _updateStartStopTimesFromSpecification(self, treeId): # cep2 jobs still get scheduled via old scheduler @@ -99,34 +112,40 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): self._update_radb_task_status(treeId, 'scheduled') self._updateStartStopTimesFromSpecification(treeId) - radb_task = self.radb.getTask(otdb_id=treeId) - if radb_task: - #reschedule all scheduled successor tasks - #because they might need to update their specification due to this scheduled task - successor_tasks = self.radb.getTasks(task_ids=radb_task['successor_ids'], task_status=['scheduled', 'queued']) - for successor_task in successor_tasks: - logger.info('rescheduling otdb_id=%s because it is a successor of the just scheduled task otdb_id=%s', successor_task['otdb_id'], radb_task['otdb_id']) - self.otdb.taskSetStatus(successor_task['otdb_id'], 'prescheduled') + try: + radb_task = self.radb.getTask(otdb_id=treeId) + if radb_task: + #reschedule all scheduled successor tasks + #because they might need to update their specification due to this scheduled task + successor_tasks = self.radb.getTasks(task_ids=radb_task['successor_ids'], task_status=['scheduled', 'queued']) + for successor_task in successor_tasks: + logger.info('rescheduling otdb_id=%s because it is a successor of the just scheduled task otdb_id=%s', successor_task['otdb_id'], radb_task['otdb_id']) + self.otdb.taskSetStatus(successor_task['otdb_id'], 'prescheduled') + except Exception as e: + logger.error(e) def onObservationQueued(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'queued') - # pipeline control puts tasks in queued state for pipelines, - # and gives the pipeline to slurm - # from that moment it is not known exactly when the task will run. - # we do know however that it will run after its predecessors, and after 'now' - # reflect that in radb for pipelines - task = self.radb.getTask(otdb_id=treeId) - if task and task['type'] == 'pipeline' and 'predecessor_ids' in task: - predecessor_tasks = [self.radb.getTask(pid) for pid in task['predecessor_ids']] - if predecessor_tasks: - pred_endtimes = [t['endtime'] for t in predecessor_tasks] - max_pred_endtime = max(pred_endtimes) - new_startime = max([max_pred_endtime, datetime.utcnow()]) - new_endtime = new_startime + timedelta(seconds=task['duration']) - - logger.info("Updating task %s (otdb_id=%s, status=queued) startime to \'%s\' and endtime to \'%s\'", task['id'], treeId, new_startime, new_endtime) - self.radb.updateTaskAndResourceClaims(task['id'], starttime=new_startime, endtime=new_endtime) + try: + # pipeline control puts tasks in queued state for pipelines, + # and gives the pipeline to slurm + # from that moment it is not known exactly when the task will run. + # we do know however that it will run after its predecessors, and after 'now' + # reflect that in radb for pipelines + task = self.radb.getTask(otdb_id=treeId) + if task and task['type'] == 'pipeline' and 'predecessor_ids' in task: + predecessor_tasks = [self.radb.getTask(pid) for pid in task['predecessor_ids']] + if predecessor_tasks: + pred_endtimes = [t['endtime'] for t in predecessor_tasks] + max_pred_endtime = max(pred_endtimes) + new_startime = max([max_pred_endtime, datetime.utcnow()]) + new_endtime = new_startime + timedelta(seconds=task['duration']) + + logger.info("Updating task %s (otdb_id=%s, status=queued) startime to \'%s\' and endtime to \'%s\'", task['id'], treeId, new_startime, new_endtime) + self.radb.updateTaskAndResourceClaims(task['id'], starttime=new_startime, endtime=new_endtime) + except Exception as e: + logger.error(e) def onObservationStarted(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'active') -- GitLab From 44712d351f449fb9fca3e42ef3e67f51a7c2c258 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 16 Sep 2016 08:42:09 +0000 Subject: [PATCH 810/933] Task #9607: timezone utc --- .../RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py | 4 ++++ .../ResourceAssignmentDatabase/radbpglistener.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py index 36acbcfefab..e66822e21b6 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/rotspservice.py @@ -92,6 +92,10 @@ class RATaskStatusChangedListener(RABusListener): __all__ = ["RATaskStatusChangedListener"] def main(): + # make sure we run in UTC timezone + import os + os.environ['TZ'] = 'UTC' + from optparse import OptionParser from lofar.messaging import setQpidLogLevel from lofar.common.util import waitForInterrupt diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py index d6fbb82c38b..5fedde628a3 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py @@ -175,6 +175,10 @@ class RADBPGListener(PostgresListener): logger.error(str(e)) def main(): + # make sure we run in UTC timezone + import os + os.environ['TZ'] = 'UTC' + # Check the invocation arguments parser = OptionParser("%prog [options]", description='runs the radb postgres listener which listens to changes on some tables in the radb and publishes the changes as notifications on the bus.') -- GitLab From 1511bb7a9dca71b7acb550be6d836ba7e79dd6bd Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 16 Sep 2016 08:46:47 +0000 Subject: [PATCH 811/933] Task #9607: use proper datetime conversion for newly inserted tasks/claims --- .../lib/static/app/controllers/datacontroller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 0a4346b3783..8e28b28b9f5 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -777,8 +777,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } else if(change.changeType == 'insert') { var task = self.taskDict[changedTask.id]; if(!task) { - changedTask.starttime = new Date(changedTask.starttime); - changedTask.endtime = new Date(changedTask.endtime); + changedTask.starttime = self.convertDatestringToLocalUTCDate(changedTask.starttime); + changedTask.endtime = self.convertDatestringToLocalUTCDate(changedTask.endtime); self.tasks.push(changedTask); self.taskDict[changedTask.id] = changedTask; } @@ -806,8 +806,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } else if(change.changeType == 'insert') { var claim = self.resourceClaimDict[changedClaim.id]; if(!claim) { - changedClaim.starttime = new Date(changedClaim.starttime); - changedClaim.endtime = new Date(changedClaim.endtime); + changedClaim.starttime = self.convertDatestringToLocalUTCDate(changedClaim.starttime); + changedClaim.endtime = self.convertDatestringToLocalUTCDate(changedClaim.endtime); self.resourceClaims.push(changedClaim); self.resourceClaimDict[changedClaim.id] = changedClaim; } -- GitLab From 44496e29c1aed5c65197359f56a3993aa4d5dab4 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 16 Sep 2016 09:49:28 +0000 Subject: [PATCH 812/933] Task #9607: return result zipped --- .../lib/webservice.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 01ea4db44f7..3240698ed8b 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -115,6 +115,7 @@ radbchangeshandler = None @app.route('/') @app.route('/index.htm') @app.route('/index.html') +@gzipped def index(): '''Serves the ResourceAssignmentEditor's index page''' return render_template('index.html', title='Scheduler') @@ -222,6 +223,7 @@ def getTasksFromUntil(fromTimestamp=None, untilTimestamp=None): return jsonify({'tasks': tasks}) @app.route('/rest/tasks/<int:task_id>', methods=['GET']) +@gzipped def getTask(task_id): try: task = rarpc.getTask(task_id) @@ -238,6 +240,7 @@ def getTask(task_id): return jsonify({'task': None}) @app.route('/rest/tasks/otdb/<int:otdb_id>', methods=['GET']) +@gzipped def getTaskByOTDBId(otdb_id): try: task = rarpc.getTask(otdb_id=otdb_id) @@ -254,6 +257,7 @@ def getTaskByOTDBId(otdb_id): return jsonify({'task': None}) @app.route('/rest/tasks/mom/<int:mom_id>', methods=['GET']) +@gzipped def getTaskByMoMId(mom_id): try: task = rarpc.getTask(mom_id=mom_id) @@ -270,6 +274,7 @@ def getTaskByMoMId(mom_id): return jsonify({'task': None}) @app.route('/rest/tasks/mom/group/<int:mom_group_id>', methods=['GET']) +@gzipped def getTasksByMoMGroupId(mom_group_id): try: mom_ids = momqueryrpc.getTaskIdsInGroup(mom_group_id)[str(mom_group_id)] @@ -356,6 +361,7 @@ def cleanupTaskData(task_id): abort(500) @app.route('/rest/tasks/<int:task_id>/datapath', methods=['GET']) +@gzipped def getTaskDataPath(task_id): try: task = rarpc.getTask(task_id) @@ -372,6 +378,7 @@ def getTaskDataPath(task_id): abort(404, result['message'] if result and 'message' in result else '') @app.route('/rest/tasks/otdb/<int:otdb_id>/diskusage', methods=['GET']) +@gzipped def getTaskDiskUsageByOTDBId(otdb_id): try: result = sqrpc.getDiskUsageForTaskAndSubDirectories(otdb_id=otdb_id) @@ -383,6 +390,7 @@ def getTaskDiskUsageByOTDBId(otdb_id): abort(404, result['message'] if result and 'message' in result else '') @app.route('/rest/tasks/<int:task_id>/diskusage', methods=['GET']) +@gzipped def getTaskDiskUsage(task_id): try: result = sqrpc.getDiskUsageForTaskAndSubDirectories(radb_id=task_id) @@ -392,6 +400,7 @@ def getTaskDiskUsage(task_id): if result['found']: return jsonify(result) abort(404, result['message'] if result and 'message' in result else '') + @app.route('/rest/tasks/<int:task_id>/copy', methods=['PUT']) def copyTask(task_id): if isProductionEnvironment(): @@ -414,34 +423,40 @@ def copyTask(task_id): return jsonify({'copied':False}) @app.route('/rest/tasks/<int:task_id>/resourceclaims') +@gzipped def taskResourceClaims(task_id): return jsonify({'taskResourceClaims': rarpc.getResourceClaims(task_id=task_id, include_properties=True)}) @app.route('/rest/tasktypes') +@gzipped def tasktypes(): result = rarpc.getTaskTypes() result = sorted(result, key=lambda q: q['id']) return jsonify({'tasktypes': result}) @app.route('/rest/taskstatustypes') +@gzipped def getTaskStatusTypes(): result = rarpc.getTaskStatuses() result = sorted(result, key=lambda q: q['id']) return jsonify({'taskstatustypes': result}) @app.route('/rest/resourcetypes') +@gzipped def resourcetypes(): result = rarpc.getResourceTypes() result = sorted(result, key=lambda q: q['id']) return jsonify({'resourcetypes': result}) @app.route('/rest/resourceclaimpropertytypes') +@gzipped def resourceclaimpropertytypes(): result = rarpc.getResourceClaimPropertyTypes() result = sorted(result, key=lambda q: q['id']) return jsonify({'resourceclaimpropertytypes': result}) @app.route('/rest/momprojects') +@gzipped def getMoMProjects(): projects = [] try: @@ -457,6 +472,7 @@ def getMoMProjects(): return jsonify({'momprojects': projects}) @app.route('/rest/momprojects/<int:project_mom2id>') +@gzipped def getMoMProject(project_mom2id): try: projects = momqueryrpc.getProjects() @@ -470,6 +486,7 @@ def getMoMProject(project_mom2id): abort(404, str(e)) @app.route('/rest/momprojects/<int:project_mom2id>/diskusage') +@gzipped def getMoMProjectDiskUsageById(project_mom2id): try: projects = momqueryrpc.getProjects() @@ -483,6 +500,7 @@ def getMoMProjectDiskUsageById(project_mom2id): abort(404, str(e)) @app.route('/rest/momprojects/<string:project_name>/diskusage') +@gzipped def getMoMProjectDiskUsageByName(project_name): try: result = sqrpc.getDiskUsageForProjectDirAndSubDirectories(project_name=project_name) @@ -492,6 +510,7 @@ def getMoMProjectDiskUsageByName(project_name): abort(404, str(e)) @app.route('/rest/momprojects/diskusage') +@gzipped def getMoMProjectsDiskUsage(): try: result = sqrpc.getDiskUsageForProjectsDirAndSubDirectories() @@ -501,6 +520,7 @@ def getMoMProjectsDiskUsage(): abort(404, str(e)) @app.route('/rest/momobjectdetails/<int:mom2id>') +@gzipped def getMoMObjectDetails(mom2id): details = momqueryrpc.getProjectDetails(mom2id) details = details.values()[0] if details else None @@ -511,11 +531,13 @@ def getMoMObjectDetails(mom2id): return jsonify({'momobjectdetails': details}) @app.route('/rest/updates/<int:sinceChangeNumber>') +@gzipped def getUpdateEventsSince(sinceChangeNumber): changesSince = radbchangeshandler.getChangesSince(sinceChangeNumber) return jsonify({'changes': changesSince}) @app.route('/rest/mostRecentChangeNumber') +@gzipped def getMostRecentChangeNumber(): mrcn = radbchangeshandler.getMostRecentChangeNumber() return jsonify({'mostRecentChangeNumber': mrcn}) @@ -525,12 +547,14 @@ def getUpdateEvents(): return getUpdateEventsSince(-1L) @app.route('/rest/lofarTime') +@gzipped def getLofarTime(): return jsonify({'lofarTime': asIsoFormat(datetime.utcnow())}) #ugly method to generate html tables for all tasks @app.route('/tasks.html') +@gzipped def getTasksHtml(): tasks = rarpc.getTasks() if not tasks: @@ -559,6 +583,7 @@ def getTasksHtml(): #ugly method to generate html tables for the task and it's claims @app.route('/tasks/<int:task_id>.html', methods=['GET']) +@gzipped def getTaskHtml(task_id): task = rarpc.getTask(task_id) @@ -660,6 +685,7 @@ def resourceClaimsForTaskHtml(task_id): return html @app.route('/tasks/<int:task_id>/log.html', methods=['GET']) +@gzipped def getTaskLogHtml(task_id): task = rarpc.getTask(task_id) -- GitLab From 0006f4481d76ce9f0ed217d7c8151020fa47550b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 16 Sep 2016 09:49:59 +0000 Subject: [PATCH 813/933] Task #9607: fixed race condition --- .../ResourceAssigner/lib/schedulechecker.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index 06670458914..2e16e1b6831 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -149,7 +149,12 @@ class ScheduleChecker(): pipelines.sort(key=lambda pl: pl['starttime'], reverse=True) for task in pipelines: - movePipelineAfterItsPredecessors(task, self._radbrpc, min_start_timestamp) + # moving pipelines might take a while + # so this task might have changed status to active + # in that case we don't want to move it + uptodate_task = radbrpc.getTask(task['id']) + if uptodate_task['status'] in ['scheduled', 'queued']: + movePipelineAfterItsPredecessors(uptodate_task, self._radbrpc, min_start_timestamp) except Exception as e: logger.error("Error while checking scheduled pipelines: %s", e) -- GitLab From 16dcd60215fd7d00c241363cf0aeebf9aabd4f84 Mon Sep 17 00:00:00 2001 From: Pieter Donker <donker@astron.nl> Date: Fri, 16 Sep 2016 18:57:12 +0000 Subject: [PATCH 814/933] Task #9612: checkhardware, index bug in hba.py --- LCU/checkhardware/checkhardware_lib/hba.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/LCU/checkhardware/checkhardware_lib/hba.py b/LCU/checkhardware/checkhardware_lib/hba.py index ea1bc08af6f..60cb30ef448 100644 --- a/LCU/checkhardware/checkhardware_lib/hba.py +++ b/LCU/checkhardware/checkhardware_lib/hba.py @@ -546,10 +546,12 @@ class HBA(object): for tile in self.hba.tile: if tile.x.rcu_off or tile.y.rcu_off: continue - if signal_info_x[str(tile.x.rcu)]['status'] in ('high',): - logger.warning("Tile %d rcu %d not switched off" % (tile.nr, tile.x.rcu)) - if signal_info_y[str(tile.y.rcu)]['status'] in ('high',): - logger.warning("Tile %d rcu %d not switched off" % (tile.nr, tile.y.rcu)) + if str(tile.x.rcu) in signal_info_x: + if signal_info_x[str(tile.x.rcu)]['status'] in ('high',): + logger.warning("Tile %d rcu %d not switched off" % (tile.nr, tile.x.rcu)) + if str(tile.y.rcu) in signal_info_y: + if signal_info_y[str(tile.y.rcu)]['status'] in ('high',): + logger.warning("Tile %d rcu %d not switched off" % (tile.nr, tile.y.rcu)) if not check_active_rspdriver(): logger.warning("RSPDriver down while testing, skip result") -- GitLab From 5efc7b905e28c21763dc1777599915f4f8f94b0f Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Fri, 16 Sep 2016 21:45:22 +0000 Subject: [PATCH 815/933] Annotate jobs with their project and task names to show them in summaries. Lower max job runtime to a week. --- MAC/Services/src/PipelineControl.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 12c19e86f50..afa562a7c0c 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -78,6 +78,7 @@ from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAUL import subprocess import datetime +import pipes import os import re from socket import getfqdn @@ -205,6 +206,10 @@ class Parset(dict): def otdbId(self): return int(self[PARSET_PREFIX + "Observation.otdbID"]) + def description(self): + return "%s - %s" % (self[PARSET_PREFIX + "Observation.Campaign.name"],self[PARSET_PREFIX + "Observation.Scheduler.taskName"]) + + class Slurm(object): def __init__(self, headnode="head01.cep4.control.lofar"): self.headnode = headnode @@ -422,8 +427,11 @@ class PipelineControl(OTDBBusListener): # Enforce the dependencies, instead of creating lingering jobs "--kill-on-invalid-dep=yes", - # Maximum run time for job (31 days) - "--time=31-0", + # Maximum run time for job (7 days) + "--time=7-0", + + # Annotate the job + "--comment=%s" % pipes.quote(parset.description()), # Lower priority to drop below inspection plots "--nice=1000", -- GitLab From b4660faec511f787d657040651778d6004f669eb Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 19 Sep 2016 07:54:21 +0000 Subject: [PATCH 816/933] Task #9522: Fail if we cannot set capabilities --- RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh index 3d246001f8e..109c94d97b2 100755 --- a/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh +++ b/RTCP/Cobalt/GPUProc/src/scripts/Cobalt_install.sh @@ -52,7 +52,7 @@ for HOST in ${HOSTS:-cbm001 cbm002 cbm003 cbm004 cbm005 cbm006 cbm007 cbm008 cbm OUTPUTPROC_CAPABILITIES='cap_sys_nice,cap_ipc_lock' sudo /sbin/setcap \"${OUTPUTPROC_CAPABILITIES}\"=ep bin/outputProc || true RTCP_CAPABILITIES='cap_net_raw,cap_sys_nice,cap_ipc_lock' - sudo /sbin/setcap \"${RTCP_CAPABILITIES}\"=ep bin/rtcp || true + sudo /sbin/setcap \"${RTCP_CAPABILITIES}\"=ep bin/rtcp " || exit 1 done -- GitLab From 8a6da15ea18e8993a5e06f454e30c0557bb76fc8 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 19 Sep 2016 08:28:47 +0000 Subject: [PATCH 817/933] Task #9522: Allocate most queue items while running to be able to receive data earlier --- RTCP/Cobalt/OutputProc/src/SubbandWriter.cc | 37 +++++++++++++++------ RTCP/Cobalt/OutputProc/src/SubbandWriter.h | 5 ++- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc index 2d5900eb69a..234dd072f76 100644 --- a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc +++ b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc @@ -42,34 +42,49 @@ namespace LOFAR itsAllocator(0), itsOutputPool(str(format("SubbandWriter::itsOutputPool [stream %u]") % streamNr), parset.settings.realTime), itsInputThread(parset, streamNr, itsOutputPool, logPrefix), - itsOutputThread(parset, streamNr, itsOutputPool, mdLogger, mdKeyPrefix, logPrefix) + itsOutputThread(parset, streamNr, itsOutputPool, mdLogger, mdKeyPrefix, logPrefix), + itsAlignment(512), + itsNrStations(parset.settings.correlator.stations.size()), + itsNrChannels(parset.settings.correlator.nrChannels), + itsNrSamples(parset.settings.correlator.nrSamplesPerIntegration()) { - const unsigned alignment = 512; - const unsigned nrStations = parset.settings.correlator.stations.size(); - const unsigned nrChannels = parset.settings.correlator.nrChannels; - const unsigned nrSamples = parset.settings.correlator.nrSamplesPerIntegration(); + ASSERT(preAllocateReceiveQueue <= maxReceiveQueueSize); // We alloc all memory at once to avoid maxReceiveQueueSize malloc() calls, which occasionally stall on CEP4 LOG_INFO_STR("Allocating memory..."); - itsArena = new MallocedArena(maxReceiveQueueSize * CorrelatedData::size(nrStations, nrChannels, nrSamples, alignment), alignment); + itsArena = new MallocedArena(maxReceiveQueueSize * CorrelatedData::size(itsNrStations, itsNrChannels, itsNrSamples, itsAlignment), itsAlignment); LOG_INFO_STR("Allocated " << itsArena->size() << " bytes"); itsAllocator = new SparseSetAllocator(*itsArena); - for (unsigned i = 0; i < maxReceiveQueueSize; i++) { - LOG_INFO_STR("new CorrelatedData " << i); - CorrelatedData *data = new CorrelatedData(nrStations, nrChannels, nrSamples, *itsAllocator, alignment); - LOG_INFO_STR("itsOutputPool.free.append " << i); + + for (unsigned i = 0; i < preAllocateReceiveQueue; i++) { + CorrelatedData *data = new CorrelatedData(itsNrStations, itsNrChannels, itsNrSamples, itsAlignment); itsOutputPool.free.append(data); } LOG_INFO_STR("End of constructor"); } + void SubbandWriter::addInputElement() + { + } + void SubbandWriter::process() { -# pragma omp parallel sections num_threads(2) +# pragma omp parallel sections num_threads(3) { +# pragma omp section + { + OMPThread::ScopedName sn(str(format("allocator %u") % itsStreamNr)); + NSTimer timer(str(format("allocator %u") % itsStreamNr), false, true); + + for (unsigned i = preAllocateReceiveQueue; i < maxReceiveQueueSize; i++) { + CorrelatedData *data = new CorrelatedData(itsNrStations, itsNrChannels, itsNrSamples, itsAlignment); + itsOutputPool.free.append(data); + } + } + # pragma omp section { OMPThread::ScopedName sn(str(format("input %u") % itsStreamNr)); diff --git a/RTCP/Cobalt/OutputProc/src/SubbandWriter.h b/RTCP/Cobalt/OutputProc/src/SubbandWriter.h index 6ba94d811a2..79f6c9a3a58 100644 --- a/RTCP/Cobalt/OutputProc/src/SubbandWriter.h +++ b/RTCP/Cobalt/OutputProc/src/SubbandWriter.h @@ -62,7 +62,8 @@ namespace LOFAR unsigned streamNr() const { return itsStreamNr; } private: - static const unsigned maxReceiveQueueSize = 60; + static const unsigned preAllocateReceiveQueue = 2; // number of elements to construct before starting + static const unsigned maxReceiveQueueSize = 60; // number of elements desired in the queue const unsigned itsStreamNr; @@ -72,6 +73,8 @@ namespace LOFAR InputThread itsInputThread; SubbandOutputThread itsOutputThread; + + const unsigned itsAlignment, itsNrStations, itsNrChannels, itsNrSamples; }; } // namespace Cobalt } // namespace LOFAR -- GitLab From aa5642589383215754cc122c13eec04acdb90cd9 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 19 Sep 2016 11:41:21 +0000 Subject: [PATCH 818/933] Task #9522: Fixed typo --- RTCP/Cobalt/OutputProc/src/SubbandWriter.cc | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc index 234dd072f76..535f7822a10 100644 --- a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc +++ b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc @@ -25,6 +25,7 @@ #include <CoInterface/CorrelatedData.h> #include <CoInterface/Allocator.h> #include <CoInterface/OMPThread.h> +#include <Common/Timer.h> #include <boost/format.hpp> using boost::format; @@ -58,17 +59,13 @@ namespace LOFAR itsAllocator = new SparseSetAllocator(*itsArena); for (unsigned i = 0; i < preAllocateReceiveQueue; i++) { - CorrelatedData *data = new CorrelatedData(itsNrStations, itsNrChannels, itsNrSamples, itsAlignment); + CorrelatedData *data = new CorrelatedData(itsNrStations, itsNrChannels, itsNrSamples, *itsAllocator, itsAlignment); itsOutputPool.free.append(data); } LOG_INFO_STR("End of constructor"); } - void SubbandWriter::addInputElement() - { - } - void SubbandWriter::process() { @@ -80,7 +77,7 @@ namespace LOFAR NSTimer timer(str(format("allocator %u") % itsStreamNr), false, true); for (unsigned i = preAllocateReceiveQueue; i < maxReceiveQueueSize; i++) { - CorrelatedData *data = new CorrelatedData(itsNrStations, itsNrChannels, itsNrSamples, itsAlignment); + CorrelatedData *data = new CorrelatedData(itsNrStations, itsNrChannels, itsNrSamples, *itsAllocator, itsAlignment); itsOutputPool.free.append(data); } } -- GitLab From 2a6b82a4f431d7dc116cbaced8f8bd0662968ea7 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 19 Sep 2016 13:10:05 +0000 Subject: [PATCH 819/933] Task #9522: Enlarge preallocation buffer, print timers --- RTCP/Cobalt/OutputProc/src/GPUProcIO.cc | 3 --- RTCP/Cobalt/OutputProc/src/SubbandWriter.cc | 6 +----- RTCP/Cobalt/OutputProc/src/SubbandWriter.h | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc index 848bf6a9f34..0491edb1815 100644 --- a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc +++ b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc @@ -171,10 +171,7 @@ bool process(Stream &controlStream) string logPrefix = str(format("[obs %u correlated stream %3u] ") % parset.settings.observationID % fileIdx); - LOG_INFO_STR("Setting up writer for " << file.location.filename); - SubbandWriter *writer = new SubbandWriter(parset, fileIdx, mdLogger, mdKeyPrefix, logPrefix); - LOG_INFO_STR("push_back()"); subbandWriters.push_back(writer); LOG_INFO_STR("done with fileIdx " << fileIdx); diff --git a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc index 535f7822a10..fdb5855fecd 100644 --- a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc +++ b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc @@ -52,9 +52,7 @@ namespace LOFAR ASSERT(preAllocateReceiveQueue <= maxReceiveQueueSize); // We alloc all memory at once to avoid maxReceiveQueueSize malloc() calls, which occasionally stall on CEP4 - LOG_INFO_STR("Allocating memory..."); itsArena = new MallocedArena(maxReceiveQueueSize * CorrelatedData::size(itsNrStations, itsNrChannels, itsNrSamples, itsAlignment), itsAlignment); - LOG_INFO_STR("Allocated " << itsArena->size() << " bytes"); itsAllocator = new SparseSetAllocator(*itsArena); @@ -62,8 +60,6 @@ namespace LOFAR CorrelatedData *data = new CorrelatedData(itsNrStations, itsNrChannels, itsNrSamples, *itsAllocator, itsAlignment); itsOutputPool.free.append(data); } - - LOG_INFO_STR("End of constructor"); } @@ -74,7 +70,7 @@ namespace LOFAR # pragma omp section { OMPThread::ScopedName sn(str(format("allocator %u") % itsStreamNr)); - NSTimer timer(str(format("allocator %u") % itsStreamNr), false, true); + NSTimer timer(str(format("allocator %u") % itsStreamNr), true, true); for (unsigned i = preAllocateReceiveQueue; i < maxReceiveQueueSize; i++) { CorrelatedData *data = new CorrelatedData(itsNrStations, itsNrChannels, itsNrSamples, *itsAllocator, itsAlignment); diff --git a/RTCP/Cobalt/OutputProc/src/SubbandWriter.h b/RTCP/Cobalt/OutputProc/src/SubbandWriter.h index 79f6c9a3a58..c65240167de 100644 --- a/RTCP/Cobalt/OutputProc/src/SubbandWriter.h +++ b/RTCP/Cobalt/OutputProc/src/SubbandWriter.h @@ -62,7 +62,7 @@ namespace LOFAR unsigned streamNr() const { return itsStreamNr; } private: - static const unsigned preAllocateReceiveQueue = 2; // number of elements to construct before starting + static const unsigned preAllocateReceiveQueue = 4; // number of elements to construct before starting static const unsigned maxReceiveQueueSize = 60; // number of elements desired in the queue const unsigned itsStreamNr; -- GitLab From e0d9f15ef43e3e629121b2347d3ec424799ed6ef Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 19 Sep 2016 13:11:01 +0000 Subject: [PATCH 820/933] Task #9522: Enlarge preallocation buffer, print timers --- RTCP/Cobalt/OutputProc/src/SubbandWriter.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc index fdb5855fecd..19847a4a0e9 100644 --- a/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc +++ b/RTCP/Cobalt/OutputProc/src/SubbandWriter.cc @@ -73,6 +73,7 @@ namespace LOFAR NSTimer timer(str(format("allocator %u") % itsStreamNr), true, true); for (unsigned i = preAllocateReceiveQueue; i < maxReceiveQueueSize; i++) { + LOG_INFO_STR(str(format("[stream %u] Allocating element %u") % itsStreamNr % i)); CorrelatedData *data = new CorrelatedData(itsNrStations, itsNrChannels, itsNrSamples, *itsAllocator, itsAlignment); itsOutputPool.free.append(data); } -- GitLab From 6c52e1a982860eaba4d205c4d49d879325008610 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 19 Sep 2016 14:24:58 +0000 Subject: [PATCH 821/933] Fix annotation of pipelines --- MAC/Services/src/PipelineControl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index afa562a7c0c..5da7d909db1 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -431,7 +431,7 @@ class PipelineControl(OTDBBusListener): "--time=7-0", # Annotate the job - "--comment=%s" % pipes.quote(parset.description()), + "--comment=%s" % pipes.quote(pipes.quote(parset.description())), # Lower priority to drop below inspection plots "--nice=1000", -- GitLab From 0458c30640c867878101a90806ed4e6a079072f7 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 20 Sep 2016 11:53:41 +0000 Subject: [PATCH 822/933] Task #9607: only queue slurm jobs if both slurm submissions succeeded, else set status aborted --- MAC/Services/src/PipelineControl.py | 125 +++++++++++++++------------- 1 file changed, 65 insertions(+), 60 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 5da7d909db1..e8d97a0341a 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -458,27 +458,26 @@ class PipelineControl(OTDBBusListener): status_bus = self.otdb_service_busname, )) - logger.info("Handing over pipeline %s to SLURM, setting status to QUEUED", otdbId) - self._setStatus(otdbId, "queued") + try: + logger.info("Handing over pipeline %s to SLURM", otdbId) - # Schedule runPipeline.sh - logger.info("Scheduling SLURM job for runPipeline.sh") - slurm_job_id = self.slurm.submit(self._jobName(otdbId), -""" + # Schedule runPipeline.sh + slurm_job_id = self.slurm.submit(self._jobName(otdbId), + """ # Run a command, but propagate SIGINT and SIGTERM function runcmd {{ - trap 'kill -s SIGTERM $PID' SIGTERM - trap 'kill -s SIGINT $PID' SIGINT +trap 'kill -s SIGTERM $PID' SIGTERM +trap 'kill -s SIGINT $PID' SIGINT - "$@" & - PID=$! - wait $PID # returns the exit status of "wait" if interrupted - wait $PID # returns the exit status of $PID - CMDRESULT=$? +"$@" & +PID=$! +wait $PID # returns the exit status of "wait" if interrupted +wait $PID # returns the exit status of $PID +CMDRESULT=$? - trap - SIGTERM SIGINT +trap - SIGTERM SIGINT - return $CMDRESULT +return $CMDRESULT }} # print some info @@ -492,12 +491,12 @@ wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&sta # run the pipeline runcmd docker-run-slurm.sh --rm --net=host \ - -e LOFARENV={lofarenv} \ - -v $HOME/.ssh:$HOME/.ssh:ro \ - -e SLURM_JOB_ID=$SLURM_JOB_ID \ - -v /data:/data \ - {image} \ - runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -P {parset_dir} + -e LOFARENV={lofarenv} \ + -v $HOME/.ssh:$HOME/.ssh:ro \ + -e SLURM_JOB_ID=$SLURM_JOB_ID \ + -v /data:/data \ + {image} \ +runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -P {parset_dir} RESULT=$? @@ -527,50 +526,56 @@ scancel --jobname={obsid}-abort-trigger # report status back to SLURM echo "Pipeline exited with status $RESULT" exit $RESULT -""".format( - lofarenv = os.environ.get("LOFARENV", ""), - obsid = otdbId, - parset_dir = "/data/parsets", - repository = parset.dockerRepository(), - image = parset.dockerImage(), - cluster = parset.processingCluster(), - - setStatus_active = setStatus_cmdline("active"), - setStatus_completing = setStatus_cmdline("completing"), - setStatus_finished = setStatus_cmdline("finished"), - setStatus_aborted = setStatus_cmdline("aborted"), - ), - - sbatch_params=sbatch_params - ) - logger.info("Scheduled SLURM job %s", slurm_job_id) - - # Schedule pipelineAborted.sh - logger.info("Scheduling SLURM job for pipelineAborted.sh") - slurm_cancel_job_id = self.slurm.submit("%s-abort-trigger" % self._jobName(otdbId), -""" + """.format( + lofarenv = os.environ.get("LOFARENV", ""), + obsid = otdbId, + parset_dir = "/data/parsets", + repository = parset.dockerRepository(), + image = parset.dockerImage(), + cluster = parset.processingCluster(), + + setStatus_active = setStatus_cmdline("active"), + setStatus_completing = setStatus_cmdline("completing"), + setStatus_finished = setStatus_cmdline("finished"), + setStatus_aborted = setStatus_cmdline("aborted"), + ), + + sbatch_params=sbatch_params + ) + logger.info("Scheduled SLURM job %s", slurm_job_id) + + # Schedule pipelineAborted.sh + logger.info("Scheduling SLURM job for pipelineAborted.sh") + slurm_cancel_job_id = self.slurm.submit("%s-abort-trigger" % self._jobName(otdbId), + """ # notify OTDB {setStatus_aborted} # notify ganglia wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} ABORTED&host_regex=" -""" - .format( - setStatus_aborted = setStatus_cmdline("aborted"), - obsid = otdbId, - ), - - sbatch_params=[ - "--partition=%s" % parset.processingPartition(), - "--cpus-per-task=1", - "--ntasks=1", - "--dependency=afternotok:%s" % slurm_job_id, - "--kill-on-invalid-dep=yes", - "--requeue", - "--output=/data/log/abort-trigger-%s.log" % (otdbId,), - ] - ) - logger.info("Scheduled SLURM job %s", slurm_cancel_job_id) + """ + .format( + setStatus_aborted = setStatus_cmdline("aborted"), + obsid = otdbId, + ), + + sbatch_params=[ + "--partition=%s" % parset.processingPartition(), + "--cpus-per-task=1", + "--ntasks=1", + "--dependency=afternotok:%s" % slurm_job_id, + "--kill-on-invalid-dep=yes", + "--requeue", + "--output=/data/log/abort-trigger-%s.log" % (otdbId,), + ] + ) + logger.info("Scheduled SLURM job %s", slurm_cancel_job_id) + + logger.info("Handed over pipeline %s to SLURM, setting status to QUEUED", otdbId) + self._setStatus(otdbId, "queued") + except Exception as e: + logger.error(str(e)) + self._setStatus(otdbId, "aborted") def _stopPipeline(self, otdbId): # Cancel corresponding SLURM job, but first the abort-trigger -- GitLab From b49674001709f0799a7ba1c97b5d1e86c534389b Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 20 Sep 2016 11:56:40 +0000 Subject: [PATCH 823/933] Task #9607: always set status to completing befor aborted/finished, which solves the aborted trigger race condition --- MAC/Services/src/PipelineControl.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index e8d97a0341a..9b2b134a049 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -497,13 +497,12 @@ runcmd docker-run-slurm.sh --rm --net=host \ -v /data:/data \ {image} \ runPipeline.sh -o {obsid} -c /opt/lofar/share/pipeline/pipeline.cfg.{cluster} -P {parset_dir} - RESULT=$? -if [ $RESULT -eq 0 ]; then - # notify that we're tearing down - runcmd {setStatus_completing} +# notify that we're tearing down +runcmd {setStatus_completing} +if [ $RESULT -eq 0 ]; then # wait for MoM to pick up feedback before we set finished status runcmd sleep 60 @@ -512,17 +511,8 @@ if [ $RESULT -eq 0 ]; then # notify ganglia wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} FINISHED&host_regex=" -else - # notify system that we've aborted - runcmd {setStatus_aborted} - - # notify ganglia - wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&start_time=now&summary=Pipeline {obsid} ABORTED&host_regex=" fi -# remove the abort-trigger job -scancel --jobname={obsid}-abort-trigger - # report status back to SLURM echo "Pipeline exited with status $RESULT" exit $RESULT @@ -537,7 +527,6 @@ exit $RESULT setStatus_active = setStatus_cmdline("active"), setStatus_completing = setStatus_cmdline("completing"), setStatus_finished = setStatus_cmdline("finished"), - setStatus_aborted = setStatus_cmdline("aborted"), ), sbatch_params=sbatch_params -- GitLab From e30f716c76fbf40ad7842347d5269a9f5b51fa9c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 20 Sep 2016 12:32:59 +0000 Subject: [PATCH 824/933] Task #9607: logging. also mention otdbId besides slurm job id --- MAC/Services/src/PipelineControl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAC/Services/src/PipelineControl.py b/MAC/Services/src/PipelineControl.py index 9b2b134a049..d038f4ee475 100755 --- a/MAC/Services/src/PipelineControl.py +++ b/MAC/Services/src/PipelineControl.py @@ -531,7 +531,7 @@ exit $RESULT sbatch_params=sbatch_params ) - logger.info("Scheduled SLURM job %s", slurm_job_id) + logger.info("Scheduled SLURM job %s for otdb_id=%s", slurm_job_id, otdbId) # Schedule pipelineAborted.sh logger.info("Scheduling SLURM job for pipelineAborted.sh") @@ -558,7 +558,7 @@ wget -O - -q "http://ganglia.control.lofar/ganglia/api/events.php?action=add&sta "--output=/data/log/abort-trigger-%s.log" % (otdbId,), ] ) - logger.info("Scheduled SLURM job %s", slurm_cancel_job_id) + logger.info("Scheduled SLURM job %s for abort trigger for otdb_id=%s", slurm_cancel_job_id, otdbId) logger.info("Handed over pipeline %s to SLURM, setting status to QUEUED", otdbId) self._setStatus(otdbId, "queued") -- GitLab From 835e1622c4de74cc68a6af5ad0a67317650d5fbc Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 20 Sep 2016 13:27:41 +0000 Subject: [PATCH 825/933] Task #9607: added mom query to get the dataproducts for a given mom2id --- SAS/MoM/MoMQueryService/momquery | 0 SAS/MoM/MoMQueryService/momqueryrpc.py | 18 ++++++++++++ SAS/MoM/MoMQueryService/momqueryservice | 0 SAS/MoM/MoMQueryService/momqueryservice.py | 34 +++++++++++++++++++++- 4 files changed, 51 insertions(+), 1 deletion(-) mode change 100644 => 100755 SAS/MoM/MoMQueryService/momquery mode change 100644 => 100755 SAS/MoM/MoMQueryService/momqueryservice diff --git a/SAS/MoM/MoMQueryService/momquery b/SAS/MoM/MoMQueryService/momquery old mode 100644 new mode 100755 diff --git a/SAS/MoM/MoMQueryService/momqueryrpc.py b/SAS/MoM/MoMQueryService/momqueryrpc.py index 4e3c31c439b..3f0d88a2edf 100644 --- a/SAS/MoM/MoMQueryService/momqueryrpc.py +++ b/SAS/MoM/MoMQueryService/momqueryrpc.py @@ -2,6 +2,7 @@ import sys import logging +import pprint from optparse import OptionParser from lofar.messaging.RPC import RPC, RPCException, RPCWrapper from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_MOMQUERY_SERVICENAME @@ -62,6 +63,13 @@ class MoMQueryRPC(RPCWrapper): logger.info("getTaskIdsInGroup(%s): %s", mom_group_ids, result) return result + def getDataProducts(self, ids): + logger.debug("getDataProducts(%s)", ids) + result = self.rpc('GetDataProducts', mom_ids=ids) + for mom2id, dps in result.items(): + logger.info('Found %s dataproducts for mom2id %s', len(dps), mom2id) + return result + def main(): # Check the invocation arguments parser = OptionParser('%prog [options]', @@ -75,6 +83,7 @@ def main(): parser.add_option('--predecessors', dest='id_for_predecessors', type='int', help='get the predecessor id\'s for the given mom2id') parser.add_option('--successors', dest='id_for_successors', type='int', help='get the successors id\'s for the given mom2id') parser.add_option('-g', '--group', dest='group_id', type='int', help='get the tasks ids in the given group mom2id') + parser.add_option('-d', '--dataproducts', dest='id_for_dataproducts', type='int', help='get the dataproducts for the given mom2id') (options, args) = parser.parse_args() if len(sys.argv) == 1: @@ -121,5 +130,14 @@ def main(): else: print 'No results' + if options.id_for_dataproducts: + results = rpc.getDataProducts(options.id_for_dataproducts) + if results: + for mom2id, dps in results.items(): + print ' dataproducts for %s' % mom2id + pprint.pprint(dps) + else: + print 'No results' + if __name__ == '__main__': main() diff --git a/SAS/MoM/MoMQueryService/momqueryservice b/SAS/MoM/MoMQueryService/momqueryservice old mode 100644 new mode 100755 diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index 566690a9721..015f64b6f1f 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -257,6 +257,34 @@ class MoMDatabaseWrapper: return result + def getDataProducts(self, mom_ids): + if not mom_ids: + return {} + + ids_str = _toIdsString(mom_ids) + + logger.info("getDataProducts for mom ids: %s" % ids_str) + + query = '''SELECT mo.id as momobject_id, mo.mom2id as mom2id, dp.id, dp.name, dp.exported, dp.status, dp.fileformat + FROM mom2object mo + inner join dataproduct dp on mo.id = dp.mom2objectid + where mo.mom2id in (%s)''' % ids_str + + rows = self._executeQuery(query) + + result = {} + for mom2id in ids_str.split(','): + result[mom2id] = [] + + for row in rows: + mom2id = row['mom2id'] + result[str(mom2id)].append(dict(row)) + + for mom2id, dps in result.items(): + logger.info('Found %s dataproducts for mom2id %s', len(dps), mom2id) + + return result + class ProjectDetailsQueryHandler(MessageHandlerInterface): '''handler class for details query in mom db :param MoMDatabaseWrapper momdb inject database access via wrapper @@ -270,7 +298,8 @@ class ProjectDetailsQueryHandler(MessageHandlerInterface): 'GetProjectDetails': self.getProjectDetails, 'GetPredecessorIds': self.getPredecessorIds, 'GetSuccessorIds': self.getSuccessorIds, - 'GetTaskIdsInGroup': self.getTaskIdsInGroup + 'GetTaskIdsInGroup': self.getTaskIdsInGroup, + 'GetDataProducts': self.getDataProducts } def prepare_loop(self): @@ -291,6 +320,9 @@ class ProjectDetailsQueryHandler(MessageHandlerInterface): def getTaskIdsInGroup(self, mom_group_ids): return self.momdb.getTaskIdsInGroup(mom_group_ids) + def getDataProducts(self, mom_ids): + return self.momdb.getDataProducts(mom_ids) + def createService(busname=DEFAULT_MOMQUERY_BUSNAME, servicename=DEFAULT_MOMQUERY_SERVICENAME, -- GitLab From 8bd597de057a2727e6f6133f86c5f8b9bce09578 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 20 Sep 2016 13:44:21 +0000 Subject: [PATCH 826/933] Task #9607: added ingest_status field to task --- .../ResourceAssignmentEditor/lib/mom.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py index 1534babfdc9..ff19dce9fe2 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py @@ -70,6 +70,38 @@ def updateTaskMomDetails(task, momrpc): else: t['project_name'] = 'OTDB Only' t['project_mom_id'] = -98 + + results = momrpc.getDataProducts(momIds) + + for t in tasklist: + mom_id = str(t['mom_id']) + t['ingest_status'] = None + if mom_id in results: + dps = results[mom_id] + num_ingested = 0 + num_ingest_pending = 0 + num_ingest_running = 0 + num_ingest_failed = 0 + num_ingest_hold = 0 + for dp in dps: + if dp['status'] == 'ingested': + num_ingested += 1 + elif dp['status'] == 'pending': + num_ingest_pending += 1 + elif dp['status'] == 'running': + num_ingest_running += 1 + elif dp['status'] == 'failed': + num_ingest_failed += 1 + elif dp['status'] == 'on_hold': + num_ingest_hold += 1 + + if num_ingested == len(dps): + t['ingest_status'] = 'ingested' + elif num_ingest_pending + num_ingest_running > 0: + t['ingest_status'] = 'ingesting' + elif num_ingest_failed + num_ingest_hold > 0: + t['ingest_status'] = 'failed' + except Exception as e: logger.error(str(e)) -- GitLab From e3e9f86edfc855ae564c70d1159fe1612cd3c487 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 20 Sep 2016 14:33:54 +0000 Subject: [PATCH 827/933] Task #9607: dataproducts have a filetype --- SAS/MoM/MoMQueryService/momqueryservice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index 015f64b6f1f..a00d29835d6 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -268,7 +268,8 @@ class MoMDatabaseWrapper: query = '''SELECT mo.id as momobject_id, mo.mom2id as mom2id, dp.id, dp.name, dp.exported, dp.status, dp.fileformat FROM mom2object mo inner join dataproduct dp on mo.id = dp.mom2objectid - where mo.mom2id in (%s)''' % ids_str + where mo.mom2id in (%s) + and not isnull(dp.fileformat)''' % ids_str rows = self._executeQuery(query) -- GitLab From c02a65a12aefdff4e8cba59b217c32fb5a1099f1 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 20 Sep 2016 14:34:33 +0000 Subject: [PATCH 828/933] Task #9607: check for any dataproducts --- .../ResourceAssignmentEditor/lib/mom.py | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py index ff19dce9fe2..484a3bf25f0 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py @@ -78,29 +78,30 @@ def updateTaskMomDetails(task, momrpc): t['ingest_status'] = None if mom_id in results: dps = results[mom_id] - num_ingested = 0 - num_ingest_pending = 0 - num_ingest_running = 0 - num_ingest_failed = 0 - num_ingest_hold = 0 - for dp in dps: - if dp['status'] == 'ingested': - num_ingested += 1 - elif dp['status'] == 'pending': - num_ingest_pending += 1 - elif dp['status'] == 'running': - num_ingest_running += 1 - elif dp['status'] == 'failed': - num_ingest_failed += 1 - elif dp['status'] == 'on_hold': - num_ingest_hold += 1 - - if num_ingested == len(dps): - t['ingest_status'] = 'ingested' - elif num_ingest_pending + num_ingest_running > 0: - t['ingest_status'] = 'ingesting' - elif num_ingest_failed + num_ingest_hold > 0: - t['ingest_status'] = 'failed' + if dps: + num_ingested = 0 + num_ingest_pending = 0 + num_ingest_running = 0 + num_ingest_failed = 0 + num_ingest_hold = 0 + for dp in dps: + if dp['status'] == 'ingested': + num_ingested += 1 + elif dp['status'] == 'pending': + num_ingest_pending += 1 + elif dp['status'] == 'running': + num_ingest_running += 1 + elif dp['status'] == 'failed': + num_ingest_failed += 1 + elif dp['status'] == 'on_hold': + num_ingest_hold += 1 + + if num_ingested == len(dps): + t['ingest_status'] = 'ingested' + elif num_ingest_pending + num_ingest_running > 0: + t['ingest_status'] = 'ingesting' + elif num_ingest_failed + num_ingest_hold > 0: + t['ingest_status'] = 'failed' except Exception as e: logger.error(str(e)) -- GitLab From f890a00ecc9423d33974748c8bbbfbdb843cdd90 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 20 Sep 2016 14:34:53 +0000 Subject: [PATCH 829/933] Task #9607: executable --- SAS/ResourceAssignment/ResourceAssignmentEditor/bin/raewebservice | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 SAS/ResourceAssignment/ResourceAssignmentEditor/bin/raewebservice diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/bin/raewebservice b/SAS/ResourceAssignment/ResourceAssignmentEditor/bin/raewebservice old mode 100644 new mode 100755 -- GitLab From 8eca41ce8c0ed55d4f3c6905e3ad6c818e29fd8e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 21 Sep 2016 08:02:15 +0000 Subject: [PATCH 830/933] Task #9607: responsiveness. use timeout to gather multiple updates in one --- .../app/controllers/ganttprojectcontroller.js | 26 +++++++++++++++++-- .../controllers/ganttresourcecontroller.js | 24 ++++++++++++++--- .../static/app/controllers/gridcontroller.js | 26 ++++++++++++++++--- .../lib/templates/index.html | 4 +-- 4 files changed, 70 insertions(+), 10 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index 071293a4536..934afd7b818 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -28,6 +28,8 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS $scope.enabled = true; self.taskStatusColors = dataService.taskStatusColors; + self.lastUpdateTimestamp = new Date(0); + self.waitingForDelayedUpdate = false; $scope.options = { mode: 'custom', @@ -106,7 +108,26 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS $scope.$evalAsync(updateGanttData); }; + function updateGanttDataAsync() { + var now = new Date(); + var diff = now.getTime() - self.lastUpdateTimestamp.getTime(); + if(diff > 500) { + self.waitingForDelayedUpdate = false; + $scope.$evalAsync(updateGanttData); + } + else { + if (!self.waitingForDelayedUpdate) { + self.waitingForDelayedUpdate = true; + setTimeout(updateGanttDataAsync, diff); + } + } + }; + function updateGanttData() { + if(!$scope.enabled) { + return; + } + if(!dataService.initialLoadComplete) { return; } @@ -114,7 +135,7 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS var projectsDict = $scope.dataService.momProjectsDict; var numProjecs = $scope.dataService.momProjects.length; - if(numProjecs == 0 || !$scope.enabled) { + if(numProjecs == 0) { $scope.ganttData = []; return; } @@ -301,13 +322,14 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS } $scope.ganttData = ganntRows; + self.lastUpdateTimestamp = new Date(); }; $scope.$watch('dataService.initialLoadComplete', updateGanttDataAsync); $scope.$watch('dataService.selected_task_ids', updateGanttDataAsync, true); $scope.$watch('dataService.viewTimeSpan', updateGanttDataAsync, true); $scope.$watch('dataService.filteredTaskChangeCntr', updateGanttDataAsync); - $scope.$watch('enabled', updateGanttDataAsync); + $scope.$watch('enabled', function() { setTimeout(updateGanttDataAsync, 500); } ); $scope.$watch('dataService.lofarTime', function() { $scope.$evalAsync(function() { if($scope.dataService.lofarTime.getSeconds() % 10 == 0) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js index 1ceadb2f967..9c201da4ac8 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttresourcecontroller.js @@ -26,6 +26,8 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat self.taskStatusColors = dataService.taskStatusColors; self.resourceClaimStatusColors = dataService.resourceClaimStatusColors; + self.lastUpdateTimestamp = new Date(0); + self.waitingForDelayedUpdate = false; $scope.options = { mode: 'custom', @@ -126,10 +128,25 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat }; function updateGanttDataAsync() { - $scope.$evalAsync(updateGanttData); + var now = new Date(); + var diff = now.getTime() - self.lastUpdateTimestamp.getTime(); + if(diff > 500) { + self.waitingForDelayedUpdate = false; + $scope.$evalAsync(updateGanttData); + } + else { + if (!self.waitingForDelayedUpdate) { + self.waitingForDelayedUpdate = true; + setTimeout(updateGanttDataAsync, diff); + } + } }; function updateGanttData() { + if(!$scope.enabled) { + return; + } + if(!dataService.initialLoadComplete) { return; } @@ -153,7 +170,7 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat var resourceClaims = $scope.dataService.resourceClaims; var numResourceClaims = resourceClaims.length; - if(numResourceGroups == 0 || numResources == 0 || !$scope.enabled){ + if(numResourceGroups == 0 || numResources == 0){ $scope.ganttData = []; return; } @@ -463,6 +480,7 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat ganttRows.push(ganttRowsDict[rowId]); $scope.ganttData = ganttRows; + self.lastUpdateTimestamp = new Date(); }; $scope.$watch('dataService.initialLoadComplete', updateGanttDataAsync); @@ -470,7 +488,7 @@ ganttResourceControllerMod.controller('GanttResourceController', ['$scope', 'dat $scope.$watch('dataService.viewTimeSpan', updateGanttDataAsync, true); $scope.$watch('dataService.claimChangeCntr', updateGanttDataAsync); $scope.$watch('dataService.filteredTaskChangeCntr', updateGanttDataAsync); - $scope.$watch('enabled', updateGanttDataAsync); + $scope.$watch('enabled', function() { setTimeout(updateGanttDataAsync, 500); } ); $scope.$watch('dataService.lofarTime', function() { $scope.$evalAsync(function() { if($scope.dataService.lofarTime.getSeconds() % 10 == 0) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 10991e728fe..0c52dcf19a8 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -9,6 +9,10 @@ var gridControllerMod = angular.module('GridControllerMod', ['ui.grid', gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGridConstants', function($scope, dataService, uiGridConstants) { + var self = this; + self.lastUpdateTimestamp = new Date(0); + self.waitingForDelayedUpdate = false; + $scope.dataService = dataService; $scope.columns = [ @@ -240,6 +244,21 @@ $scope.columns = [ columnDef.filter.selectOptions = columnSelectOptions; }; + function populateListAsync() { + var now = new Date(); + var diff = now.getTime() - self.lastUpdateTimestamp.getTime(); + if(diff > 500) { + self.waitingForDelayedUpdate = false; + $scope.$evalAsync(populateList); + } + else { + if (!self.waitingForDelayedUpdate) { + self.waitingForDelayedUpdate = true; + setTimeout(populateListAsync, diff); + } + } + }; + function populateList() { if('tasks' in $scope.dataService && $scope.dataService.tasks.length > 0) { var viewFrom = $scope.dataService.viewTimeSpan.from; @@ -281,6 +300,7 @@ $scope.columns = [ fillProjectsColumFilterSelectOptions() fillGroupsColumFilterSelectOptions(); + self.lastUpdateTimestamp = new Date(); }; function jumpToSelectedTaskRows() { @@ -334,10 +354,10 @@ $scope.columns = [ $scope.$evalAsync(jumpToSelectedTaskRows); }; - $scope.$watch('dataService.taskChangeCntr', function() { $scope.$evalAsync(populateList); }); - $scope.$watch('dataService.claimChangeCntr', function() { $scope.$evalAsync(populateList); }); + $scope.$watch('dataService.taskChangeCntr', function() { populateListAsync(); }); + $scope.$watch('dataService.claimChangeCntr', function() { populateListAsync(); }); $scope.$watch('dataService.viewTimeSpan', function() { - $scope.$evalAsync(populateList); + populateListAsync(); $scope.$evalAsync(jumpToSelectedTaskRows); }, true); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index d09b4578025..af5e45e9fb6 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -111,7 +111,7 @@ <md-content margin-top='10px'> <md-tabs md-dynamic-height md-border-bottom style="height: 100%; width: 100%;"> <div ng-controller="GanttProjectController as ganttProjectCtrl" ng-init="enabled=true"> - <md-tab label="Tasks" md-on-select="enabled=true;" md-on-deselect="enabled=false;"> + <md-tab label="Tasks" md-on-select="enabled=true" md-on-deselect="enabled=false"> <div gantt data=ganttData api=options.api show-side='true' @@ -136,7 +136,7 @@ </div> <div ng-controller="GanttResourceController as ganttResourceCtrl" ng-init="enabled=false"> - <md-tab label="Resources" md-on-select="enabled=true;" md-on-deselect="enabled=false;"> + <md-tab label="Resources" md-on-select="enabled=true" md-on-deselect="enabled=false"> <div gantt data=ganttData api=options.api show-side='true' -- GitLab From c154a14ee8c372e0fd1085c672ef0504b79816c0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 21 Sep 2016 10:18:31 +0000 Subject: [PATCH 831/933] Task #9607: made faster lookup for multiple otdb_ids via cache --- .../DataManagementCommon/path.py | 4 +- .../StorageQueryService/cache.py | 98 +++++++++++++------ .../StorageQueryService/diskusage.py | 8 ++ SAS/DataManagement/StorageQueryService/rpc.py | 19 ++-- .../StorageQueryService/service.py | 1 + .../StorageQueryService/storagequery | 0 .../StorageQueryService/storagequeryservice | 0 7 files changed, 91 insertions(+), 39 deletions(-) mode change 100644 => 100755 SAS/DataManagement/StorageQueryService/storagequery mode change 100644 => 100755 SAS/DataManagement/StorageQueryService/storagequeryservice diff --git a/SAS/DataManagement/DataManagementCommon/path.py b/SAS/DataManagement/DataManagementCommon/path.py index 17d6d9f89f9..489603431b9 100644 --- a/SAS/DataManagement/DataManagementCommon/path.py +++ b/SAS/DataManagement/DataManagementCommon/path.py @@ -63,7 +63,7 @@ class PathResolver: logger.debug("Get path for otdb_id %s" % (otdb_id,)) return self.getPathForTask(otdb_id=otdb_id) - def getPathForTask(self, radb_id=None, mom_id=None, otdb_id=None): + def getPathForTask(self, radb_id=None, mom_id=None, otdb_id=None, include_scratch_paths=True): logger.info("getPathForTask(radb_id=%s, mom_id=%s, otdb_id=%s)", radb_id, mom_id, otdb_id) '''get the path for a task for either the given radb_id, or for the given mom_id, or for the given otdb_id''' result = self._getProjectPathAndDetails(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) @@ -75,7 +75,7 @@ class PathResolver: path_result = {'found': True, 'message': '', 'path': task_data_path} - if task['type'] == 'pipeline': + if include_scratch_paths and task['type'] == 'pipeline': path_result['scratch_paths'] = [] scratch_path = os.path.join(self.scratch_path, 'Observation%s' % task['otdb_id']) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index 2de9f88dffe..c94dd63d959 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -65,7 +65,7 @@ class CacheManager: self._continueUpdateCacheThread = False self._cacheLock = RLock() - self._cache = {} + self._cache = {'path_du_results': {}, 'otdb_id2path': {} } self._readCacheFromDisk() self.disk_usage = DiskUsage(mountpoint=mountpoint, @@ -93,12 +93,12 @@ class CacheManager: with open('.du_cache.py', 'r') as file: with self._cacheLock: self._cache = eval(file.read().strip()) - if not isinstance(self._cache, dict): - self._cache = {} + if not isinstance(self._cache, dict): + self._cache = {'path_du_results': {}, 'otdb_id2path': {} } except Exception as e: logger.error("Error while reading in du cache: %s", e) with self._cacheLock: - self._cache = {} + self._cache = {'path_du_results': {}, 'otdb_id2path': {} } def _writeCacheToDisk(self): @@ -113,23 +113,33 @@ class CacheManager: if not 'path' in du_result: return + otdb_id = du_result.get('otdb_id') + with self._cacheLock: path = du_result['path'] + path_cache = self._cache['path_du_results'] + otdb_id2path_cache = self._cache['otdb_id2path'] if du_result['found']: - if not path in self._cache or self._cache[path]['disk_usage'] != du_result['disk_usage']: + if not path in path_cache or path_cache[path]['disk_usage'] != du_result['disk_usage']: logger.debug('updating cache entry: %s', du_result) - self._cache[path] = du_result + path_cache[path] = du_result self._sendDiskUsageChangedNotification(path, du_result['disk_usage']) - self._cache[path]['cache_timestamp'] = datetime.datetime.utcnow() + path_cache[path]['cache_timestamp'] = datetime.datetime.utcnow() + + if otdb_id != None: + otdb_id2path_cache[otdb_id] = path else: if 'message' not in du_result or 'could not resolve hostname' not in du_result['message'].lower() : - if path in self._cache: + if path in path_cache: logger.info('removing path \'%s\' from cache', path) - del self._cache[path] + del path_cache[path] self._sendDiskUsageChangedNotification(path, 0) + if otdb_id != None and otdb_id in otdb_id2path_cache: + otdb_id2path_cache[otdb_id] = None + self._writeCacheToDisk() if du_result.get('path') == self.disk_usage.path_resolver.projects_path: @@ -137,8 +147,9 @@ class CacheManager: def _invalidateCacheEntryForPath(self, path): with self._cacheLock: - if path in self._cache: - self._cache[path]['cache_timestamp'] = self._cache[path]['cache_timestamp'] - MAX_CACHE_ENTRY_AGE + path_cache = self._cache['path_du_results'] + if path in path_cache: + path_cache[path]['cache_timestamp'] = path_cache[path]['cache_timestamp'] - MAX_CACHE_ENTRY_AGE def _scanProjectsTree(self): try: @@ -148,14 +159,15 @@ class CacheManager: return with self._cacheLock: - if directory not in self._cache: + path_cache = self._cache['path_du_results'] + if directory not in path_cache: logger.info('tree scan: adding \'%s\' with empty disk_usage to cache which will be du\'ed later', directory) empty_du_result = {'found': True, 'disk_usage': None, 'path': directory, 'name': directory.split('/')[-1]} self._updateCache(empty_du_result) - if directory in self._cache: + if directory in path_cache: # make cache entry for directory 'old', so it will be du'ed in _updateOldEntriesInCache immediately - self._cache[directory]['cache_timestamp'] -= MAX_CACHE_ENTRY_AGE + path_cache[directory]['cache_timestamp'] -= MAX_CACHE_ENTRY_AGE if not self._updateCacheThreadRunning: return @@ -183,7 +195,8 @@ class CacheManager: try: now = datetime.datetime.utcnow() with self._cacheLock: - old_entries = {path:cache_entry for path,cache_entry in self._cache.items() if now - cache_entry['cache_timestamp'] > MAX_CACHE_ENTRY_AGE} + path_cache = self._cache['path_du_results'] + old_entries = {path:cache_entry for path,cache_entry in path_cache.items() if now - cache_entry['cache_timestamp'] > MAX_CACHE_ENTRY_AGE} if old_entries: logger.info('%s old cache entries need to be updated', len(old_entries)) @@ -312,28 +325,55 @@ class CacheManager: # trigger update cache thread self._continueUpdateCacheThread = True - def getDiskUsageForOTDBId(self, otdb_id): - return self.getDiskUsageForTask(otdb_id=otdb_id) + def getDiskUsageForOTDBId(self, otdb_id, include_scratch_paths=True): + return self.getDiskUsageForTask(otdb_id=otdb_id, include_scratch_paths=include_scratch_paths) - def getDiskUsageForMoMId(self, mom_id): - return self.getDiskUsageForTask(mom_id=mom_id) + def getDiskUsageForMoMId(self, mom_id, include_scratch_paths=True): + return self.getDiskUsageForTask(mom_id=mom_id, include_scratch_paths=include_scratch_paths) - def getDiskUsageForRADBId(self, radb_id): - return self.getDiskUsageForTask(radb_id=radb_id) + def getDiskUsageForRADBId(self, radb_id, include_scratch_paths=True): + return self.getDiskUsageForTask(radb_id=radb_id, include_scratch_paths=include_scratch_paths) - def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None): + def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None, include_scratch_paths=True): logger.info("cache.getDiskUsageForTask(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) - result = self.disk_usage.path_resolver.getPathForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) - if result['found']: - return self.getDiskUsageForPath(result['path']) - return {'found': False, 'path': result['path']} + if otdb_id != None and not include_scratch_paths: + with self._cacheLock: + path = self._cache['otdb_id2path'].get(otdb_id) + + if path: + logger.info('Using path from cache for otdb_id %s %s', otdb_id, path) + return self.getDiskUsageForPath(path) + + path_result = self.disk_usage.path_resolver.getPathForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, include_scratch_paths=include_scratch_paths) + if path_result['found']: + return self.getDiskUsageForPath(path_result['path']) + + return {'found': False, 'path': path_result['path']} + + def getDiskUsageForTasks(self, radb_ids=None, mom_ids=None, otdb_ids=None, include_scratch_paths=True): + logger.info("cache.getDiskUsageForTask(radb_ids=%s, mom_ids=%s, otdb_ids=%s)" % (radb_ids, mom_ids, otdb_ids)) + result = {'radb_ids': {}, 'mom_ids': {}, 'otdb_ids': {}} + + if radb_ids: + for radb_id in radb_ids: + result['radb_ids'][str(radb_id)] = self.getDiskUsageForTask(radb_id=radb_id, include_scratch_paths=include_scratch_paths) + + if mom_ids: + for mom_id in mom_ids: + result['mom_ids'][str(mom_id)] = self.getDiskUsageForTask(mom_id=mom_id, include_scratch_paths=include_scratch_paths) + + if otdb_ids: + for otdb_id in otdb_ids: + result['otdb_ids'][str(otdb_id)] = self.getDiskUsageForTask(otdb_id=otdb_id, include_scratch_paths=include_scratch_paths) + + return result def getDiskUsageForPath(self, path): logger.info("cache.getDiskUsageForPath(%s)", path) needs_cache_update = False with self._cacheLock: - needs_cache_update |= path not in self._cache + needs_cache_update |= path not in self._cache['path_du_results'] if needs_cache_update: logger.info("cache update needed for %s", path) @@ -341,8 +381,8 @@ class CacheManager: self._updateCache(result) with self._cacheLock: - if path in self._cache: - result = self._cache[path] + if path in self._cache['path_du_results']: + result = self._cache['path_du_results'][path] else: result = { 'found': False, 'path':path, 'message': 'unknown error' } if not self.disk_usage.path_resolver.pathExists(path): diff --git a/SAS/DataManagement/StorageQueryService/diskusage.py b/SAS/DataManagement/StorageQueryService/diskusage.py index 96aa0906ecc..d5a0063f588 100644 --- a/SAS/DataManagement/StorageQueryService/diskusage.py +++ b/SAS/DataManagement/StorageQueryService/diskusage.py @@ -60,6 +60,14 @@ def getDiskUsageForPath(path): else: result = {'found': False, 'path': path } + try: + path_items = path.rstrip('/').split('/') + if len(path_items) >=3 and path_items[-1].startswith('L') and path_items[-1][1:].isdigit() and 'projects' in path_items[-3]: + logger.info('found path for otdb_id %s %s', path_items[-1][1:], path) + result['otdb_id'] = int(path_items[-1][1:]) + except Exception as e: + logger.error('Could not parse otdb_id from path %s %s', path, e) + logger.info('returning: %s' % result) return result diff --git a/SAS/DataManagement/StorageQueryService/rpc.py b/SAS/DataManagement/StorageQueryService/rpc.py index bf8bbd2036a..b50e7917b93 100644 --- a/SAS/DataManagement/StorageQueryService/rpc.py +++ b/SAS/DataManagement/StorageQueryService/rpc.py @@ -27,17 +27,20 @@ class StorageQueryRPC(RPCWrapper): def getPathForOTDBId(self, otdb_id): return self.rpc('GetPathForOTDBId', otdb_id=otdb_id) - def getDiskUsageForOTDBId(self, otdb_id): - return self._convertTimestamps(self.rpc('GetDiskUsageForOTDBId', otdb_id=otdb_id)) + def getDiskUsageForOTDBId(self, otdb_id, include_scratch_paths=True): + return self._convertTimestamps(self.rpc('GetDiskUsageForOTDBId', otdb_id=otdb_id, include_scratch_paths=include_scratch_paths)) - def getDiskUsageForMoMId(self, mom_id): - return self._convertTimestamps(self.rpc('GetDiskUsageForMoMId', mom_id=mom_id)) + def getDiskUsageForMoMId(self, mom_id, include_scratch_paths=True): + return self._convertTimestamps(self.rpc('GetDiskUsageForMoMId', mom_id=mom_id, include_scratch_paths=include_scratch_paths)) - def getDiskUsageForRADBId(self, radb_id): - return self._convertTimestamps(self.rpc('GetDiskUsageForRADBId', radb_id=radb_id)) + def getDiskUsageForRADBId(self, radb_id, include_scratch_paths=True): + return self._convertTimestamps(self.rpc('GetDiskUsageForRADBId', radb_id=radb_id, include_scratch_paths=include_scratch_paths)) - def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None): - return self._convertTimestamps(self.rpc('GetDiskUsageForTask', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id)) + def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None, include_scratch_paths=True): + return self._convertTimestamps(self.rpc('GetDiskUsageForTask', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, include_scratch_paths=include_scratch_paths)) + + def getDiskUsageForTasks(self, radb_ids=None, mom_ids=None, otdb_ids=None, include_scratch_paths=True): + return self._convertTimestamps(self.rpc('GetDiskUsageForTasks', radb_ids=radb_ids, mom_ids=mom_ids, otdb_ids=otdb_ids, include_scratch_paths=include_scratch_paths)) def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): return self._convertTimestamps(self.rpc('GetDiskUsageForTaskAndSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id)) diff --git a/SAS/DataManagement/StorageQueryService/service.py b/SAS/DataManagement/StorageQueryService/service.py index e08c18b2a70..953937cb03f 100644 --- a/SAS/DataManagement/StorageQueryService/service.py +++ b/SAS/DataManagement/StorageQueryService/service.py @@ -47,6 +47,7 @@ class StorageQueryHandler(MessageHandlerInterface): 'GetDiskUsageForMoMId': self.cache.getDiskUsageForMoMId, 'GetDiskUsageForRADBId': self.cache.getDiskUsageForRADBId, 'GetDiskUsageForTask': self.cache.getDiskUsageForTask, + 'GetDiskUsageForTasks': self.cache.getDiskUsageForTasks, 'GetDiskUsageForTaskAndSubDirectories': self.cache.getDiskUsageForTaskAndSubDirectories, 'GetDiskUsageForProjectDirAndSubDirectories': self.cache.getDiskUsageForProjectDirAndSubDirectories, 'GetDiskUsageForProjectsDirAndSubDirectories': self.cache.getDiskUsageForProjectsDirAndSubDirectories} diff --git a/SAS/DataManagement/StorageQueryService/storagequery b/SAS/DataManagement/StorageQueryService/storagequery old mode 100644 new mode 100755 diff --git a/SAS/DataManagement/StorageQueryService/storagequeryservice b/SAS/DataManagement/StorageQueryService/storagequeryservice old mode 100644 new mode 100755 -- GitLab From 9b3909256bdfd1004704706359530a373159925d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 21 Sep 2016 11:11:38 +0000 Subject: [PATCH 832/933] Task #9607: add disk usage to each task --- .gitattributes | 1 + .../lib/CMakeLists.txt | 1 + .../ResourceAssignmentEditor/lib/storage.py | 66 +++++++++++++++++++ .../lib/webservice.py | 8 +++ 4 files changed, 76 insertions(+) create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py diff --git a/.gitattributes b/.gitattributes index ee3b5f8c719..ea2445f31ac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5314,6 +5314,7 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/utils/datetimepick SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/utils/startswith.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/utils/ui-bootstrap-tpls.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/utils/ui-grid-edit-datepicker.js -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/utils.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py -text diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt index 661e51d5558..81785ff06f2 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt @@ -7,6 +7,7 @@ python_install( radbchangeshandler.py fakedata.py mom.py + storage.py DESTINATION lofar/sas/resourceassignment/resourceassignmenteditor) file(GLOB_RECURSE jquery_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/jquery/*) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py new file mode 100644 index 00000000000..d515381e39e --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +# mom.py +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: mom.py 1580 2015-09-30 14:18:57Z loose $ + +""" +TODO: documentation +""" + +import logging + +logger = logging.getLogger(__name__) + +def updateTaskStorageDetails(task, sqrpc): + def applyDefaults(t): + '''apply sane default values for a task''' + t['disk_usage'] = None + t['disk_usage_readable'] = None + + tasklist = task if isinstance(task, list) else [task] + + if len(tasklist) == 0: + return + + for t in tasklist: + applyDefaults(t) + + if not sqrpc: + return + + try: + otdb_ids = [t['otdb_id'] for t in tasklist] + usages = sqrpc.getDiskUsageForTasks(otdb_ids=otdb_ids, include_scratch_paths=False).get('otdb_ids') + + if not usages: + return + + for task in tasklist: + otdb_id = str(task['otdb_id']) + if otdb_id in usages: + usage = usages[otdb_id] + t['disk_usage'] = usage['disk_usage'] + t['disk_usage_readable'] = usage['disk_usage_readable'] + + except Exception as e: + logger.error(str(e)) + diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 3240698ed8b..850d282d37e 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -53,6 +53,7 @@ from lofar.mom.momqueryservice.config import DEFAULT_MOMQUERY_BUSNAME, DEFAULT_M from lofar.mom.momqueryservice.momrpc import MoMRPC from lofar.mom.momqueryservice.config import DEFAULT_MOM_BUSNAME, DEFAULT_MOM_SERVICENAME from lofar.sas.resourceassignment.resourceassignmenteditor.mom import updateTaskMomDetails +from lofar.sas.resourceassignment.resourceassignmenteditor.storage import updateTaskStorageDetails from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME as DEFAULT_CLEANUP_BUSNAME from lofar.sas.datamanagement.cleanup.config import DEFAULT_SERVICENAME as DEFAULT_CLEANUP_SERVICENAME @@ -219,6 +220,7 @@ def getTasksFromUntil(fromTimestamp=None, untilTimestamp=None): # will they come from spec/MoM? # add Task <id> as name for now updateTaskMomDetails(tasks, momqueryrpc) + updateTaskStorageDetails(tasks, sqrpc) return jsonify({'tasks': tasks}) @@ -233,6 +235,7 @@ def getTask(task_id): task['name'] = 'Task %d' % task['id'] updateTaskMomDetails(task, momqueryrpc) + updateTaskStorageDetails(task, sqrpc) return jsonify({'task': task}) except Exception as e: abort(404) @@ -250,6 +253,7 @@ def getTaskByOTDBId(otdb_id): task['name'] = 'Task %d' % task['id'] updateTaskMomDetails(task, momqueryrpc) + updateTaskStorageDetails(task, sqrpc) return jsonify({'task': task}) except Exception as e: abort(404) @@ -267,6 +271,7 @@ def getTaskByMoMId(mom_id): task['name'] = 'Task %d' % task['id'] updateTaskMomDetails(task, momqueryrpc) + updateTaskStorageDetails(task, sqrpc) return jsonify({'task': task}) except Exception as e: abort(404) @@ -281,6 +286,7 @@ def getTasksByMoMGroupId(mom_group_id): tasks = rarpc.getTasks(mom_ids=mom_ids) updateTaskMomDetails(tasks, momqueryrpc) + updateTaskStorageDetails(tasks, sqrpc) return jsonify({'tasks': tasks}) except Exception as e: @@ -561,6 +567,7 @@ def getTasksHtml(): abort(404) updateTaskMomDetails(tasks, momqueryrpc) + updateTaskStorageDetails(tasks, sqrpc) html = '<!DOCTYPE html><html><head><title>Tasks</title><style>table, th, td {border: 1px solid black; border-collapse: collapse; padding: 4px;}</style></head><body><table style="width:100%">\n' @@ -592,6 +599,7 @@ def getTaskHtml(task_id): task['name'] = 'Task %d' % task['id'] updateTaskMomDetails(task, momqueryrpc) + updateTaskStorageDetails(task, sqrpc) html = '<!DOCTYPE html><html><head><title>Tasks</title><style>table, th, td {border: 1px solid black; border-collapse: collapse; padding: 4px;}</style></head><body><table style="">\n' -- GitLab From 8f453c99bf5b9e531678b2660ffc1df913e38f04 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 21 Sep 2016 12:09:40 +0000 Subject: [PATCH 833/933] Task #9607: handle disk usage updates via webservice to client --- .gitattributes | 2 +- .../datamanagementbuslistener.py | 4 +- .../StorageQueryService/cache.py | 9 +-- SAS/DataManagement/StorageQueryService/rpc.py | 2 +- .../lib/CMakeLists.txt | 2 +- ...adbchangeshandler.py => changeshandler.py} | 65 +++++++++++++++++-- .../lib/webservice.py | 20 +++--- 7 files changed, 81 insertions(+), 23 deletions(-) rename SAS/ResourceAssignment/ResourceAssignmentEditor/lib/{radbchangeshandler.py => changeshandler.py} (68%) diff --git a/.gitattributes b/.gitattributes index ea2445f31ac..9e2176e09d5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5149,9 +5149,9 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/config/__init__.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/config/default.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/__init__.py -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/changeshandler.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/fakedata.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py -text -SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/app.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/chartresourceusagecontroller.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js -text diff --git a/SAS/DataManagement/DataManagementCommon/datamanagementbuslistener.py b/SAS/DataManagement/DataManagementCommon/datamanagementbuslistener.py index 02304b53924..c78c5cba3f7 100644 --- a/SAS/DataManagement/DataManagementCommon/datamanagementbuslistener.py +++ b/SAS/DataManagement/DataManagementCommon/datamanagementbuslistener.py @@ -45,7 +45,7 @@ class DataManagementBusListener(AbstractBusListener): elif msg.subject == '%sPathDeleted' % self.subject_prefix: self.onPathDeleted(msg.content.get('path')) elif msg.subject == '%sDiskUsageChanged' % self.subject_prefix: - self.onDiskUsageChanged(msg.content.get('path'), msg.content.get('disk_usage')) + self.onDiskUsageChanged(msg.content.get('path'), msg.content.get('disk_usage'), msg.content.get('otdb_id')) else: logger.error("DataManagementBusListener.handleMessage: unknown subject: %s" %str(msg.subject)) @@ -60,7 +60,7 @@ class DataManagementBusListener(AbstractBusListener): :param path: path of the deleted task''' pass - def onDiskUsageChanged(self, path, disk_usage): + def onDiskUsageChanged(self, path, disk_usage, otdb_id=None): '''onDiskUsageChanged is called upon receiving a DiskUsageChanged message. :param path: path for which the disk_usage changed :param path: the disk_usage of the path''' diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index c94dd63d959..76da6a84642 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -75,12 +75,13 @@ class CacheManager: mom_servicename=mom_servicename, broker=broker) - def _sendDiskUsageChangedNotification(self, path, disk_usage): + def _sendDiskUsageChangedNotification(self, path, disk_usage, otdb_id=None): try: msg = EventMessage(context=self.notification_prefix + 'DiskUsageChanged', content={ 'path': path, 'disk_usage': disk_usage, - 'disk_usage_readable': humanreadablesize(disk_usage) }) + 'disk_usage_readable': humanreadablesize(disk_usage), + 'otdb_id': otdb_id }) logger.info('Sending notification with subject %s to %s: %s', msg.subject, self.event_bus.address, msg.content) self.event_bus.send(msg) except Exception as e: @@ -124,7 +125,7 @@ class CacheManager: if not path in path_cache or path_cache[path]['disk_usage'] != du_result['disk_usage']: logger.debug('updating cache entry: %s', du_result) path_cache[path] = du_result - self._sendDiskUsageChangedNotification(path, du_result['disk_usage']) + self._sendDiskUsageChangedNotification(path, du_result['disk_usage'], otdb_id) path_cache[path]['cache_timestamp'] = datetime.datetime.utcnow() @@ -135,7 +136,7 @@ class CacheManager: if path in path_cache: logger.info('removing path \'%s\' from cache', path) del path_cache[path] - self._sendDiskUsageChangedNotification(path, 0) + self._sendDiskUsageChangedNotification(path, 0, otdb_id) if otdb_id != None and otdb_id in otdb_id2path_cache: otdb_id2path_cache[otdb_id] = None diff --git a/SAS/DataManagement/StorageQueryService/rpc.py b/SAS/DataManagement/StorageQueryService/rpc.py index b50e7917b93..c8f2fc4d3bb 100644 --- a/SAS/DataManagement/StorageQueryService/rpc.py +++ b/SAS/DataManagement/StorageQueryService/rpc.py @@ -106,7 +106,7 @@ def main(): result = rpc.getDiskUsageForTask(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) if result['found']: print 'path %s' % result['path'] - print 'disk_usage %s %s' % (result['disk_usage'], result['disk_usage_readable']) + print 'disk_usage %s %s' % (result['disk_usage'], result.get('disk_usage_readable')) print 'nr_of_files %s' % result['nr_of_files'] else: print result['message'] diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt index 81785ff06f2..d0e059c4f91 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt @@ -4,7 +4,7 @@ python_install( __init__.py webservice.py utils.py - radbchangeshandler.py + changeshandler.py fakedata.py mom.py storage.py diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/changeshandler.py similarity index 68% rename from SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py rename to SAS/ResourceAssignment/ResourceAssignmentEditor/lib/changeshandler.py index 16109932eeb..e1e0433fc96 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/radbchangeshandler.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/changeshandler.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# RADBChangesHandler.py +# ChangesHandler.py # # Copyright (C) 2015 # ASTRON (Netherlands Institute for Radio Astronomy) @@ -27,8 +27,14 @@ RADBChangesHandler listens on the lofar notification message bus and calls (empt Typical usage is to derive your own subclass from RADBChangesHandler and implement the specific on<SomeMessage> methods that you are interested in. """ -from lofar.sas.resourceassignment.database.config import DEFAULT_NOTIFICATION_BUSNAME, DEFAULT_NOTIFICATION_SUBJECTS +from lofar.sas.resourceassignment.database.config import DEFAULT_NOTIFICATION_BUSNAME as RADB_NOTIFICATION_BUSNAME +from lofar.sas.resourceassignment.database.config import DEFAULT_NOTIFICATION_SUBJECTS as RADB_NOTIFICATION_SUBJECTS from lofar.sas.resourceassignment.database.radbbuslistener import RADBBusListener + +from lofar.sas.datamanagement.common.config import DEFAULT_DM_NOTIFICATION_BUSNAME, DEFAULT_DM_NOTIFICATION_SUBJECTS +from lofar.sas.datamanagement.common.datamanagementbuslistener import DataManagementBusListener + +from lofar.common.util import humanreadablesize from lofar.common.util import waitForInterrupt from lofar.sas.resourceassignment.resourceassignmenteditor.mom import updateTaskMomDetails @@ -43,10 +49,12 @@ CHANGE_UPDATE_TYPE = 'update' CHANGE_INSERT_TYPE = 'insert' CHANGE_DELETE_TYPE = 'delete' -class RADBChangesHandler(RADBBusListener): - def __init__(self, busname=DEFAULT_NOTIFICATION_BUSNAME, subjects=DEFAULT_NOTIFICATION_SUBJECTS, broker=None, momqueryrpc=None, radbrpc=None, **kwargs): +class ChangesHandler: + def __init__(self, radb_busname=RADB_NOTIFICATION_BUSNAME, radb_subjects=RADB_NOTIFICATION_SUBJECTS, + dm_busname=DEFAULT_DM_NOTIFICATION_BUSNAME, dm_subjects=DEFAULT_DM_NOTIFICATION_SUBJECTS, + broker=None, momqueryrpc=None, radbrpc=None, sqrpc=None, **kwargs): """ - RADBChangesHandler listens on the lofar notification message bus and keeps track of all the change notifications. + ChangesHandler listens on the lofar notification message bus and keeps track of all the change notifications. :param broker: valid Qpid broker host (default: None, which means localhost) additional parameters in kwargs: options= <dict> Dictionary of options passed to QPID @@ -54,7 +62,19 @@ class RADBChangesHandler(RADBBusListener): numthreads= <int> Number of parallel threads processing messages (default: 1) verbose= <bool> Output extra logging over stdout (default: False) """ - super(RADBChangesHandler, self).__init__(busname=busname, subjects=subjects, broker=broker, **kwargs) + self._radb_listener = RADBBusListener(busname=radb_busname, subjects=radb_subjects, broker=broker, **kwargs) + self._radb_listener.onTaskUpdated = self.onTaskUpdated + self._radb_listener.onTaskInserted = self.onTaskInserted + self._radb_listener.onTaskDeleted = self.onTaskDeleted + self._radb_listener.onResourceClaimUpdated = self.onResourceClaimUpdated + self._radb_listener.onResourceClaimInserted = self.onResourceClaimInserted + self._radb_listener.onResourceClaimDeleted = self.onResourceClaimDeleted + self._radb_listener.onResourceAvailabilityUpdated = self.onResourceAvailabilityUpdated + self._radb_listener.onResourceCapacityUpdated = self.onResourceCapacityUpdated + + self._dm_listener = DataManagementBusListener(busname=dm_busname, subjects=dm_subjects, broker=broker, **kwargs) + self._dm_listener.onDiskUsageChanged = self.onDiskUsageChanged + self._dm_listener.onTaskDeleted = self.onTaskDeletedFromDisk self._changes = [] self._lock = Lock() @@ -63,6 +83,21 @@ class RADBChangesHandler(RADBBusListener): self._momqueryrpc = momqueryrpc self._radbrpc = radbrpc + def __enter__(self): + self.start_listening() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.stop_listening() + + def start_listening(self): + self._radb_listener.start_listening() + self._dm_listener.start_listening() + + def stop_listening(self): + self._radb_listener.stop_listening() + self._dm_listener.stop_listening() + def _handleChange(self, change): '''_handleChange appends a change in the changes list and calls the onChangedCallback. :param change: dictionary with the change''' @@ -129,6 +164,24 @@ class RADBChangesHandler(RADBBusListener): claim_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'resourceCapacity', 'value':updated_capacity} self._handleChange(claim_change) + def _handleDiskUsageChange(self, disk_usage, otdb_id): + if otdb_id != None: + task = self._radbrpc.getTask(otdb_id=otdb_id) + if task: + du_readable = humanreadablesize(disk_usage) + logger.info('disk_usage change: otdb_id %s radb_id %s disk_usage %s %s', otdb_id, task['id'], disk_usage, du_readable) + task['disk_usage'] = disk_usage + task['disk_usage_readable'] = du_readable + + task_change = {'changeType':CHANGE_UPDATE_TYPE, 'objectType':'task', 'value':task} + self._handleChange(task_change) + + def onDiskUsageChanged(self, path, disk_usage, otdb_id): + self._handleDiskUsageChange(disk_usage, otdb_id) + + def onTaskDeletedFromDisk(self, otdb_id, paths): + self._handleDiskUsageChange(0, otdb_id) + def getMostRecentChangeNumber(self): with self._lock: if self._changes: diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 850d282d37e..5cb338f6795 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -42,7 +42,8 @@ from flask.json import jsonify from flask.json import JSONEncoder from lofar.sas.resourceassignment.resourceassignmenteditor.utils import gzipped from lofar.sas.resourceassignment.resourceassignmenteditor.fakedata import * -from lofar.sas.resourceassignment.resourceassignmenteditor.radbchangeshandler import RADBChangesHandler, CHANGE_DELETE_TYPE +from lofar.sas.resourceassignment.resourceassignmenteditor.changeshandler import ChangesHandler, CHANGE_DELETE_TYPE +from lofar.sas.datamanagement.common.config import DEFAULT_DM_NOTIFICATION_BUSNAME, DEFAULT_DM_NOTIFICATION_SUBJECTS from lofar.sas.resourceassignment.database.config import DEFAULT_NOTIFICATION_BUSNAME as DEFAULT_RADB_CHANGES_BUSNAME from lofar.sas.resourceassignment.database.config import DEFAULT_NOTIFICATION_SUBJECTS as DEFAULT_RADB_CHANGES_SUBJECTS from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RARPC @@ -64,7 +65,6 @@ from lofar.sas.otdb.otdbrpc import OTDBRPC from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME from lofar.common import isProductionEnvironment, isTestEnvironment from lofar.common.util import humanreadablesize -#from lofar.sas.resourceassignment.resourceassigner. import updateTaskMomDetails logger = logging.getLogger(__name__) @@ -111,7 +111,7 @@ otdbrpc = None curpc = None sqrpc = None momqueryrpc = None -radbchangeshandler = None +changeshandler = None @app.route('/') @app.route('/index.htm') @@ -539,13 +539,13 @@ def getMoMObjectDetails(mom2id): @app.route('/rest/updates/<int:sinceChangeNumber>') @gzipped def getUpdateEventsSince(sinceChangeNumber): - changesSince = radbchangeshandler.getChangesSince(sinceChangeNumber) + changesSince = changeshandler.getChangesSince(sinceChangeNumber) return jsonify({'changes': changesSince}) @app.route('/rest/mostRecentChangeNumber') @gzipped def getMostRecentChangeNumber(): - mrcn = radbchangeshandler.getMostRecentChangeNumber() + mrcn = changeshandler.getMostRecentChangeNumber() return jsonify({'mostRecentChangeNumber': mrcn}) @app.route('/rest/updates') @@ -737,6 +737,8 @@ def main(): parser.add_option('--cleanup_servicename', dest='cleanup_servicename', type='string', default=DEFAULT_CLEANUP_SERVICENAME, help='Name of the cleanupservice, default: %default') parser.add_option('--storagequery_busname', dest='storagequery_busname', type='string', default=DEFAULT_STORAGEQUERY_BUSNAME, help='Name of the bus exchange on the qpid broker on which the storagequeryservice listens, default: %default') parser.add_option('--storagequery_servicename', dest='storagequery_servicename', type='string', default=DEFAULT_STORAGEQUERY_SERVICENAME, help='Name of the storagequeryservice, default: %default') + parser.add_option('--dm_notification_busname', dest='dm_notification_busname', type='string', default=DEFAULT_DM_NOTIFICATION_BUSNAME, help='Name of the notification bus exchange on the qpid broker on which the data management notifications are published, default: %default') + parser.add_option('--dm_notification_subjects', dest='dm_notification_subjects', type='string', default=DEFAULT_DM_NOTIFICATION_SUBJECTS, help='Subject(s) to listen for on the data management notification bus exchange on the qpid broker, default: %default') parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() @@ -755,10 +757,12 @@ def main(): sqrpc = StorageQueryRPC(busname=options.storagequery_busname, servicename=options.storagequery_servicename, broker=options.broker) global momqueryrpc momqueryrpc = MoMQueryRPC(busname=options.mom_query_busname, servicename=options.mom_query_servicename, timeout=2.5, broker=options.broker) - global radbchangeshandler - radbchangeshandler = RADBChangesHandler(options.radb_notification_busname, subjects=options.radb_notification_subjects, broker=options.broker, momqueryrpc=momqueryrpc, radbrpc=rarpc) + global changeshandler + changeshandler = ChangesHandler(radb_busname=options.radb_notification_busname, radb_subjects=options.radb_notification_subjects, + dm_busname=options.dm_notification_busname, dm_subjects=options.dm_notification_subjects, + broker=options.broker, momqueryrpc=momqueryrpc, radbrpc=rarpc) - with radbchangeshandler, rarpc, otdbrpc, curpc, sqrpc, momrpc, momqueryrpc: + with changeshandler, rarpc, otdbrpc, curpc, sqrpc, momrpc, momqueryrpc: '''Start the webserver''' app.run(debug=options.verbose, threaded=True, host='0.0.0.0', port=options.port) -- GitLab From 9a581e83648c8855992f5308e5ede5316b5e258d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 21 Sep 2016 12:55:27 +0000 Subject: [PATCH 834/933] Task #9607: show disk usage --- .../static/app/controllers/gridcontroller.js | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 0c52dcf19a8..2c3db7f341e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -32,7 +32,7 @@ $scope.columns = [ }, { field: 'starttime', displayName: 'Start', - width: '11%', + width: '8%', type: 'date', enableCellEdit: false, enableCellEditOnFocus: false, @@ -41,7 +41,7 @@ $scope.columns = [ }, { field: 'endtime', displayName: 'End', - width: '11%', + width: '8%', type: 'date', enableCellEdit: false, enableCellEditOnFocus: false, @@ -88,6 +88,17 @@ $scope.columns = [ }, sort: { direction: uiGridConstants.ASC, priority: 2 } }, + { field: 'disk_usage', + displayName: 'Size', + enableCellEdit: false, + cellTemplate:'<span>{{row.entity.disk_usage_readable}}</span>', + width: '6%', + filter: { + type: uiGridConstants.filter.SELECT, + condition: uiGridConstants.filter.GREATER_THAN, + selectOptions: [{ value:1e3, label: '> 1K'}, { value:1e6, label: '> 1M'}, { value:1e9, label: '> 1G'}, { value:1e12, label: '> 1T'} ] + } + }, { field: 'mom_object_group_id', displayName: 'Group', enableCellEdit: false, @@ -289,6 +300,8 @@ $scope.columns = [ mom_object_group_mom2object_id: task.mom_object_group_mom2object_id, cluster: task.cluster, blocked_by_ids: task.blocked_by_ids, + disk_usage: task.disk_usage === null ? undefined : task.disk_usage, + disk_usage_readable: task.disk_usage_readable === null ? undefined : task.disk_usage_readable }; gridTasks.push(gridTask); } @@ -342,7 +355,7 @@ $scope.columns = [ var label = mom_object_group_id + ' ' + mom_object_group_name; var groupSelectOptions = [ { value: mom_object_group_id, label: label} ]; - fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[7]); + fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[8]); group_col.filters[0].term = mom_object_group_id; } @@ -376,7 +389,7 @@ $scope.columns = [ fillTypeColumFilterSelectOptions(); fillProjectsColumFilterSelectOptions(); fillGroupsColumFilterSelectOptions(); - fillColumFilterSelectOptions(['CEP2', 'CEP4'], $scope.columns[11]); + fillColumFilterSelectOptions(['CEP2', 'CEP4'], $scope.columns[12]); }); }); @@ -461,7 +474,7 @@ $scope.columns = [ var groupSelectOptions = groupIds.map(function(gid) { return { value: gid, label: gid + ' ' + groupId2Name[gid]}; }); groupSelectOptions.sort(function(a,b) { return a.value - b.value; }); - fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[7]); + fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[8]); }; $scope.$watch('dataService.selected_task_ids', onSelectedTaskIdsChanged, true);} -- GitLab From 6ff7f4cdce41995396f5801dce7d871c2e65d415 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 21 Sep 2016 14:37:53 +0000 Subject: [PATCH 835/933] Task #9607: sort cache entries oldest to newest, depth==2 paths first --- .../StorageQueryService/cache.py | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index 76da6a84642..c1b1cda8144 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -197,23 +197,53 @@ class CacheManager: now = datetime.datetime.utcnow() with self._cacheLock: path_cache = self._cache['path_du_results'] - old_entries = {path:cache_entry for path,cache_entry in path_cache.items() if now - cache_entry['cache_timestamp'] > MAX_CACHE_ENTRY_AGE} + old_entries = [cache_entry for cache_entry in path_cache.values() if now - cache_entry['cache_timestamp'] > MAX_CACHE_ENTRY_AGE] if old_entries: logger.info('%s old cache entries need to be updated', len(old_entries)) - # sort them oldest to newest - old_entries = sorted(old_entries.items(), key=lambda x: x[1]['cache_timestamp']) + # sort them oldest to newest, depth==2 paths first + def compareFunc(entry1, entry2): + path1 = entry1['path'] + path2 = entry2['path'] + depth1 = len(path1.replace(self.disk_usage.path_resolver.projects_path, '').strip('/').split('/')) + depth2 = len(path2.replace(self.disk_usage.path_resolver.projects_path, '').strip('/').split('/')) + + if depth1 == 2 or depth2 == 2: + if depth1 == 2 and depth2 == 2: + if entry1['cache_timestamp'] < entry2['cache_timestamp']: + return -1 + if entry1['cache_timestamp'] > entry2['cache_timestamp']: + return 1 + return 0 + elif depth1 == 2: + return -1 + elif depth2 == 2: + return 1 + elif depth1 < 2: + return 1 + elif depth2 < 2: + return -1 + + if entry1['cache_timestamp'] < entry2['cache_timestamp']: + return -1 + if entry1['cache_timestamp'] > entry2['cache_timestamp']: + return 1 + return 0 + + old_entries = sorted(old_entries, cmp=compareFunc) cacheUpdateStart = datetime.datetime.utcnow() - for path, cache_entry in old_entries: + for cache_entry in old_entries: try: - logger.info('examining old entry in cache: %s', path) - result = du_getDiskUsageForPath(path) - if result['found']: - logger.info('updating old entry in cache: %s', result) - self._updateCache(result) + path = cache_entry.get('path') + if path: + logger.info('examining old entry in cache: %s', path) + result = du_getDiskUsageForPath(path) + if result['found']: + logger.info('updating old entry in cache: %s', result) + self._updateCache(result) except Exception as e: logger.error(str(e)) -- GitLab From 51c63f527b3450887b225d70ba34730f77789fae Mon Sep 17 00:00:00 2001 From: Ruud Beukema <beukema@astron.nl> Date: Wed, 21 Sep 2016 14:45:08 +0000 Subject: [PATCH 836/933] Task #9607: added info and size columns to grid, and ingest icons. Removed indication of blocked tasks in gantt-chart view. --- .gitattributes | 7 +- .../lib/CMakeLists.txt | 5 +- .../static/app/controllers/datacontroller.js | 16 +- .../static/app/controllers/gridcontroller.js | 160 ++++++++++++------ .../lib/static/css/main.css | 11 +- .../lib/static/icons/blocked.png | Bin 0 -> 775 bytes .../lib/static/icons/ingest_failed.png | Bin 0 -> 1057 bytes .../lib/static/icons/ingest_in_progress.png | Bin 0 -> 720 bytes .../lib/static/icons/ingest_successful.png | Bin 0 -> 1060 bytes .../lib/static/icons/src/iconset.jpg | Bin 0 -> 18093 bytes .../lib/static/icons/src/iconset.png | Bin 0 -> 54615 bytes .../lib/static/icons/warning.png | Bin 692 -> 0 bytes .../lib/templates/index.html | 2 +- 13 files changed, 133 insertions(+), 68 deletions(-) create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/blocked.png create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_failed.png create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_in_progress.png create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_successful.png create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/src/iconset.jpg create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/src/iconset.png delete mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/warning.png diff --git a/.gitattributes b/.gitattributes index 9e2176e09d5..2b0b2d8c52e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5168,7 +5168,12 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-half SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-halflings-regular.ttf -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-halflings-regular.woff -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/fonts/glyphicons-halflings-regular.woff2 -text -SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/warning.png -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/blocked.png -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_failed.png -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_in_progress.png -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_successful.png -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/src/iconset.jpg -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/src/iconset.png -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-animate/angular-animate.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-aria/angular-aria.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-gantt/.bower.json -text diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt index d0e059c4f91..e0d3305c852 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt @@ -42,7 +42,10 @@ set(app_files static/app/controllers/ganttprojectcontroller.js static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js static/css/main.css - static/icons/warning.png + static/icons/blocked.png + static/icons/ingest_in_progress.png + static/icons/ingest_failed.png + static/icons/ingest_successful.png templates/index.html) set(web_files diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 8e28b28b9f5..9011f4b027a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -1049,16 +1049,20 @@ dataControllerMod.controller('DataController', }; $scope.onViewTimeSpanFromChanged = function() { - dataService.autoFollowNow = false; - if(dataService.viewTimeSpan.from >= dataService.viewTimeSpan.to) { - dataService.viewTimeSpan.to = dataService.floorDate(new Date(dataService.viewTimeSpan.from.getTime() + $scope.zoomTimespan.value*60*1000), 1, 5); + if (!isNaN(dataService.viewTimeSpan.from)) { + dataService.autoFollowNow = false; + if(dataService.viewTimeSpan.from >= dataService.viewTimeSpan.to) { + dataService.viewTimeSpan.to = dataService.floorDate(new Date(dataService.viewTimeSpan.from.getTime() + $scope.zoomTimespan.value*60*1000), 1, 5); + } } }; $scope.onViewTimeSpanToChanged = function() { - dataService.autoFollowNow = false; - if(dataService.viewTimeSpan.to <= dataService.viewTimeSpan.from) { - dataService.viewTimeSpan.from = dataService.floorDate(new Date(dataService.viewTimeSpan.to.getTime() - $scope.zoomTimespan.value*60*1000), 1, 5); + if (!isNaN(dataService.viewTimeSpan.to)) { + dataService.autoFollowNow = false; + if(dataService.viewTimeSpan.to <= dataService.viewTimeSpan.from) { + dataService.viewTimeSpan.from = dataService.floorDate(new Date(dataService.viewTimeSpan.to.getTime() - $scope.zoomTimespan.value*60*1000), 1, 5); + } } }; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 2c3db7f341e..be6f7f88474 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -18,13 +18,13 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid $scope.columns = [ { field: 'name', enableCellEdit: false, - width: '12%' + width: '100', }, { field: 'project_name', displayName:'Project', enableCellEdit: false, cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}">{{row.entity[col.field]}}</a>', - width: '12%', + width: '80', filter: { type: uiGridConstants.filter.SELECT, selectOptions: [] @@ -32,7 +32,7 @@ $scope.columns = [ }, { field: 'starttime', displayName: 'Start', - width: '8%', + width: '70', type: 'date', enableCellEdit: false, enableCellEditOnFocus: false, @@ -41,7 +41,7 @@ $scope.columns = [ }, { field: 'endtime', displayName: 'End', - width: '8%', + width: '70', type: 'date', enableCellEdit: false, enableCellEditOnFocus: false, @@ -49,7 +49,7 @@ $scope.columns = [ }, { field: 'duration', displayName: 'Duration', - width: '6%', + width: '70', type: 'number', enableFiltering: false, enableCellEdit: false, @@ -58,7 +58,7 @@ $scope.columns = [ }, { field: 'status', enableCellEdit: true, - width: '6%', + width: '60', filter: { condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, @@ -67,20 +67,41 @@ $scope.columns = [ editableCellTemplate: 'ui-grid/dropdownEditor', editDropdownOptionsArray: [], cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) { - // Scheduled tasks that are blocked tasks use a different make-up style - var css_class = "grid-status-"; - if (row.entity.blocked_by_ids.length > 0) { - css_class += "blocked"; - } - else { - css_class += grid.getCellValue(row,col); - } - return css_class; - } + return "grid-status-" + grid.getCellValue(row,col); + } + }, + { field: 'info', + displayName: 'Info', + enableCellEdit: false, + width: '45', + filter: { + condition: function(searchTerm, cellValue, row, column) { + var do_include = false; + switch(searchTerm) { + case 0: do_include = (row.entity.blocked_by_ids.length > 0); break; + case 1: do_include = (row.entity.ingest_status=="ingesting"); break; + case 2: do_include = (row.entity.ingest_status=="ingested"); break; + case 3: do_include = (row.entity.ingest_status=="failed"); break; + default: break; + }; + return do_include; + }, + type: uiGridConstants.filter.SELECT, + selectOptions: [] + }, + editableCellTemplate: 'ui-grid/dropdownEditor', + editDropdownOptionsArray: [], + headerTooltip: "Additional status information", + cellTemplate: '<div style="text-align: center" class="ui-grid-cell-contents">' + + '<span ng-if="row.entity.blocked_by_ids.length > 0"><img ng-src="static/icons/blocked.png" title="Blocked by {{row.entity.blocked_by_ids.length.toString()}} predecessor(s)" /></span>' + + '<span ng-if="row.entity.ingest_status==\'ingesting\'"><img ng-src="static/icons/ingest_in_progress.png" title="Ingest in progress" /></span>' + + '<span ng-if="row.entity.ingest_status==\'ingested\'"><img ng-src="static/icons/ingest_successful.png" title="Ingest successful" /></span>' + + '<span ng-if="row.entity.ingest_status==\'failed\'"><img ng-src="static/icons/ingest_failed.png" title="Ingest failed" /></span>' + + '</div>' }, { field: 'type', enableCellEdit: false, - width: '6%', + width: '70', filter: { condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, @@ -96,14 +117,14 @@ $scope.columns = [ filter: { type: uiGridConstants.filter.SELECT, condition: uiGridConstants.filter.GREATER_THAN, - selectOptions: [{ value:1e3, label: '> 1K'}, { value:1e6, label: '> 1M'}, { value:1e9, label: '> 1G'}, { value:1e12, label: '> 1T'} ] + selectOptions: [{ value:1e9, label: '> 1G'}, { value:1e10, label: '> 10G'}, { value:1e11, label: '> 100G'}, { value:1e12, label: '> 1T'} ] } }, { field: 'mom_object_group_id', - displayName: 'Group', + displayName: 'Group ID', enableCellEdit: false, - cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity.mom_object_group_id}} {{row.entity.mom_object_group_name}}</a>', - width: '12%', + cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity.mom_object_group_id}}</a>', + width: '80', filter: { condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, @@ -114,23 +135,23 @@ $scope.columns = [ displayName: 'MoM ID', enableCellEdit: false, cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}">{{row.entity[col.field]}}</a>', - width: '6%' + width: '65' }, { field: 'otdb_id', displayName: 'SAS ID', enableCellEdit: false, - width: '6%' + width: '65' }, { field: 'id', displayName: 'RADB ID', enableCellEdit: false, cellTemplate:'<a target="_blank" href="tasks/{{row.entity.id}}.html">{{row.entity[col.field]}}</a>', - width: '6%' + width: '72' }, { field: 'cluster', - displayName: 'cluster', + displayName: 'Cluster', enableCellEdit: false, - width: '6%', + width: '65', filter: { condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, @@ -141,7 +162,7 @@ $scope.columns = [ return "grid-cluster-" + value; }, sort: { direction: uiGridConstants.ASC, priority: 1 } - }]; + }]; $scope.gridOptions = { enableGridMenu: false, enableSorting: true, @@ -238,10 +259,8 @@ $scope.columns = [ function fillColumFilterSelectOptions(options, columnDef) { var columnSelectOptions = []; - if(options) { - for(var i = 0; i < options.length; i++) - { + for(var i = 0; i < options.length; i++) { var option = options[i]; if(option.hasOwnProperty('value') && option.hasOwnProperty('label')) { columnSelectOptions.push({ value: option.value, label: option.label }) @@ -300,6 +319,7 @@ $scope.columns = [ mom_object_group_mom2object_id: task.mom_object_group_mom2object_id, cluster: task.cluster, blocked_by_ids: task.blocked_by_ids, + ingest_status: task.ingest_status === null ? undefined : task.ingest_status, disk_usage: task.disk_usage === null ? undefined : task.disk_usage, disk_usage_readable: task.disk_usage_readable === null ? undefined : task.disk_usage_readable }; @@ -355,8 +375,8 @@ $scope.columns = [ var label = mom_object_group_id + ' ' + mom_object_group_name; var groupSelectOptions = [ { value: mom_object_group_id, label: label} ]; - fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[8]); + fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[9]); group_col.filters[0].term = mom_object_group_id; } } @@ -373,25 +393,20 @@ $scope.columns = [ populateListAsync(); $scope.$evalAsync(jumpToSelectedTaskRows); }, true); - - $scope.$watch('dataService.filteredTaskChangeCntr', function() { - $scope.$evalAsync(function() { + + function fillFilterSelectOptions() { + if(dataService.initialLoadComplete) { fillStatusColumFilterSelectOptions(); + fillInfoColumFilterSelectOptions(); fillTypeColumFilterSelectOptions(); fillProjectsColumFilterSelectOptions(); fillGroupsColumFilterSelectOptions(); - }); - }); + fillColumFilterSelectOptions(['CEP2', 'CEP4'], $scope.columns[13]); + } + }; - $scope.$watch('dataService.initialLoadComplete', function() { - $scope.$evalAsync(function() { - fillStatusColumFilterSelectOptions(); - fillTypeColumFilterSelectOptions(); - fillProjectsColumFilterSelectOptions(); - fillGroupsColumFilterSelectOptions(); - fillColumFilterSelectOptions(['CEP2', 'CEP4'], $scope.columns[12]); - }); - }); + $scope.$watch('dataService.filteredTaskChangeCntr', function() { $scope.$evalAsync(fillFilterSelectOptions()); }); + $scope.$watch('dataService.initialLoadComplete', function() { $scope.$evalAsync(fillFilterSelectOptions()); }); function fillProjectsColumFilterSelectOptions() { var projectNames = []; @@ -448,11 +463,11 @@ $scope.columns = [ task_types = task_types.unique(); task_types.sort(); - fillColumFilterSelectOptions(task_types, $scope.columns[6]); + fillColumFilterSelectOptions(task_types, $scope.columns[7]); }; function fillGroupsColumFilterSelectOptions() { - if($scope.columns[7].filter.term) { + if($scope.columns[9].filter.term) { return; } @@ -471,10 +486,53 @@ $scope.columns = [ } } - var groupSelectOptions = groupIds.map(function(gid) { return { value: gid, label: gid + ' ' + groupId2Name[gid]}; }); - groupSelectOptions.sort(function(a,b) { return a.value - b.value; }); + groupIds.sort(); + + fillColumFilterSelectOptions(groupIds, $scope.columns[9]); + }; + + function fillInfoColumFilterSelectOptions() { + var tasks = $scope.dataService.filteredTasks; - fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[8]); + var info_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'info'; }); + if(info_col && info_col.filters.length && info_col.filters[0].term) { + tasks = $scope.dataService.tasks; + } + + // Generate a list of unique information items + var task_info = []; + var info_bit_flags = 0x00; + for(var task of tasks) { + if((task.blocked_by_ids.length > 0) && !(info_bit_flags & 0x01)) { + task_info.push({ value: 0, label: 'Blocked tasks' }); + info_bit_flags |= 0x01; + } + + if((task.ingest_status === 'ingesting') && !(info_bit_flags & 0x02)) { + task_info.push({ value: 1, label: 'Ingests in progress' }); + info_bit_flags |= 0x02; + } + + if((task.ingest_status === 'ingested') && !(info_bit_flags & 0x04)) { + task_info.push({ value: 2, label: 'Successful ingests' }); + info_bit_flags |= 0x04; + } + + if((task.ingest_status === 'failed') && !(info_bit_flags & 0x08)) { + task_info.push({ value: 3, label: 'Failed ingests' }); + info_bit_flags |= 0x08; + } + }; + + // sort on key values + function keysrt(key,desc) { + return function(a,b){ + return desc ? ~~(a[key] < b[key]) : ~~(a[key] > b[key]); + } + } + + task_info.sort(keysrt('value')); + fillColumFilterSelectOptions(task_info, $scope.columns[6]); }; $scope.$watch('dataService.selected_task_ids', onSelectedTaskIdsChanged, true);} @@ -647,7 +705,7 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); }); if(blocked_selected_cep4_tasks.length > 0) { - var liContent = '<li><a href="#">Select blocking predecessors</a></li>' + var liContent = '<li><a href="#">Select blocking predecessor(s)</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); liElement.on('click', function() { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index 9ef3d56d527..7f5513a22e0 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -200,14 +200,9 @@ div.gantt-task.claim-task-status-error span { color: #ffffff; } -.grid-status-blocked, div.gantt-task.task-status-blocked div { - background-image: url(/static/icons/warning.png); - background-repeat: no-repeat; -} - -.grid-status-blocked { - background-position: right 50%; - padding-right: 18px; +div.gantt-task.task-status-blocked div { +/* background-image: url(/static/icons/blocked.png); + background-repeat: no-repeat; */ } div.gantt-task.task-status-blocked div{ diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/blocked.png b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/blocked.png new file mode 100644 index 0000000000000000000000000000000000000000..764ba9e1aad1438911a9d1482195a47d7f778232 GIT binary patch literal 775 zcmeAS@N?(olHy`uVBq!ia0y~yU=RXf4mJh`hOl#e;S3B6Y)RhkE)4%caKYZ?lNlHo zI14-?iy0WWg+Z8+Vb&Z81_lQ95>H=O_6MAzeEiluUTfqT7?`Gbx;TbdoZdU-WVcAV zMEm?zzgDfD{%YpDz&B@viVZ6aDmoRkUbr5qcX)I$+fR;%Md$>R5{v)5m;)?}<6;E* z^&YTsSs7?p2*)&YF1~Dh*Y<L3YVFsn+bg8`3ytKQ?mhnfucAWlJ^#P{mwR@+kF))A zE_JQCuy*R+r+k|)FOOZ)A7nhu)zqc-kNBdCy{Di3<<|{;e$(&oi)yyXLQ44t58^KG z+WJ1e*N$V>Y`f~*lDh`=siuB+UwqsXn;v@E==+yFzcVJ}`ubLMi*I<#rGD<rr^|*% zCkp2-DiE3X_uEIeVDq{^MN70k_NbgJ`qO9?sa8-lEnu(8Oq*9>EHlr2**Wo-@TWYE z&ZS`=8}$lz_IwWu>a5und_3GN>#og?<%vRbj8b$iiOAV%XSfxwxT6q!He#pu?}LKd zbu?<L&L4fLI%%D=gs*9L#N8K<yBeMqxJ7c+s2Xl^`u*?xY3Wk+eX4q1E&rI*wmw;Q zI3ZN{3#+rCNLizS|HaBv&$C(PuKs=3-r?kt_3Nzar9C<Ke<)nlZaC{~a$A0x_O>-! zB^Gmj;!!t!xyS9_N9%Q8GdR^4tR}brG|9N2bt~iw$HI)q%k5gX_8iw-mVdxCgQ+Hd zLd3~e7mVtkf2dyk=%eWMH)j)s&t7oy(Vc!lGtg*~v$_4l*V(gwJp85QwAO+B#UA&& zRWoF~jKilnD9x_#Fw6fqhdKMBZ9$;JPJ=gp7o9D*k$n4BkHd10L=F~>rwRf~P9^={ z_xjJrwe!;N+msormm98#K9b76SfrJG*NR}huYz?wS2!>625hahc`T6Vv7Y0k^y^Hy z8DFODyT5nWo3wXvDIdR@e62dt)MQcJ+$pqSzT*$|6;hs+a{^LX5~o&_Fel&Me<=IS fhkuvfRk5#T@;EA2)4YL!fq}u()z4*}Q$iB}d8=cz literal 0 HcmV?d00001 diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_failed.png b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_failed.png new file mode 100644 index 0000000000000000000000000000000000000000..287ad48c9f6b973618148f6dcd3d82957c5fbd59 GIT binary patch literal 1057 zcmeAS@N?(olHy`uVBq!ia0y~yU=RXf4mJh`hOl#e;S3B6Y)RhkE)4%caKYZ?lNlHo zI14-?iy0WWg+Z8+Vb&Z81_lQ95>H=O_6MAze0-d-ZD;N=Ffgz4ba4!^I9)o?KSMTD zq<w#N`n^3*mag}`rM7axu?bpIvlLc{texn!vX#YivxZPi#6{Pu8RmMM3MPjA2^P2$ zA@1bOnejw-OSe*nw${XTt1>oeUUaxUWlQDR)7i^r&NMH6e(&+4o34WP76s>?J)Uzv zkfFG^_{{n9m#<%UcXwyM7R69=pZ!2ocTkrC$Bfyt4@aH8a6uq8HujEdxR$7zy1Jx< z#Dt?qT~}Uz{it#t!=&ds86H*6>*z>{RBLW-epET{%(-)3Tch@tgfBUE?3jeS{B+~= za|vsoURdb-=I-w4K`ULVtF5m^Ez($YF++fbks&xZ_{!_ju&}TdAzE*4Y;?{{<}o%l zK2qo-b6g_%#3r8E&poa!Fx4pd@*>c0o{eX8v~;4xr015Cq<3x4fBfsx)-)caCc|g* zbmkOKF$kEc^qKoh*|x`1oLoNC&R3Uv`}pcRKNa7IZ!y6k8X^YA9?Wre>3BS&D0|`~ z{pBa?|MN|G_Vn%U9Xq!6Tl9VOIdplu{=~mu*;j4#*&ei`;A`^w<;&ZXHd=_bDMn>? zMI7@wJwtw<L+&lETj`(QuI737M)A%kg|*`Q7iB+WnzDV-5ykL-F7MXZu6A>CJMlRt zYpay5uCC41+h=D>KfihJW~UJQ|7l!NkzF$UQy7*`)M0$P*k=CLXzd?fnl<;`HCYc_ zdfW1|?|6mYo}QowKN)wP{P4L)x3I}WaC&-%{eehkrIP(W*rKv~gSeK8T#JgUe=Ym1 zwn(f#W6n}ucU`ZIek<}-wp4U|RXG~5P;R-7`?QLL8J8Xkdo9oGx;9J2^Tqr3&kr;* zKZ$FodMUx*yQfJwqO0`^*LJDC6C7JZI*Jl+&ueZu{N8N;vaM>q!3<3b3SCNBTcx5j z&9;{%%rP-PwWEE}@(J6@xR(0*P7LB&Yuf9^EU0+Cc-xfIBK33A>YfE>NwRqT(0BTJ z`!Wy9(q$(koEHWhuxu~po2|BDzF*<n*TOOWEYDjwE^tYSMeRxBPhfrZ+)n-Hvz8YZ zF9!A)vN$@-S|)NWzu@H+_m~}-oxKwx9`<FX6>U&|5VqKIefps`!F}=PEdSpu{Bq<7 z%i_h0*Q{G7#KO2OBJIV@yK78!%!S=E9=x~idw9HDY4UT6J?}lmoZJsP*$Tf}`S_>p z?!a>WdGqE?Yi(uy_Wu6)ZHpf5d#POW;j_|8QM;Olf_nV>{eS+mQ;AXi^~v_!xqogs z4{z*i&$RxRJoo?Mb$2Ix-Cln3m&^VeuclUC+IDsIrcY7p*T~=0+*TFJ@Q-oI&EE>y Un<t%PU|?YIboFyt=akR{023VZ&;S4c literal 0 HcmV?d00001 diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_in_progress.png b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_in_progress.png new file mode 100644 index 0000000000000000000000000000000000000000..c23a0c887ce8c2f9ad8a9218fe61b7a9ee3449dc GIT binary patch literal 720 zcmeAS@N?(olHy`uVBq!ia0y~yU=RXf4mJh`hOl#e;S3B6Y)RhkE)4%caKYZ?lNlHo zI14-?iy0WWg+Z8+Vb&Z81_lQ95>H=O_6MAzd_2Z0_msb5U|@>%ba4!^IK6g?t+$S$ zNZb6BvPUbl>l!0&bj(dCmlizewqf(8>*wYttem<(!J7M`(~bKZ-{xLB=e9*S_DJi@ zxxPh{qGQx{WxQ-W;P9Y!{^w_N8soLD)_nQ>x1zrO{IO$f-QC?C91Ls2xETZ;HCqka z>h||{Y}mc~cX?{+Q-7gXWxGHA`Xwd!@WqRcy?brb($XR#A{<;aPEXpk&hQvRL0#Rs z-@mQ(_4P}3$A*Q6Pi5P&d-vj1TB}wavTWZvZP&WObB4CIXJ5Y5oa@(q^JZk*Va0Qn zaq;mNqdx!m!O_50aL)HZyMRVllg{a&YA?;Co-I+jP8=2QITaUd{U_38DCDGY%p)|E zAtJleP)SKgU;lF6_LZx+-rHV_`efto!lC%bocY9OnddT5*;RYxqNAhNtX&&8O;uiA zJ|-^i(VS!hi3_j4R#a6fo!+D}sl{!v;5o~#MGt=doVj_k@h6+()&1A17O}p!bq##5 z*WE=eI@^fhLe%0ZhpPD}#7qr}$UeELXVt0;QOos&CI)dW6;V2L^eF4?+qdtt`)O{u z*1<8?ul@Gz=w-{6UD}(c;kH<C_Uzd*GBOHE_x*&6dL}-8oc!$hbKz1Yss7^(63H5; zCml@q@K`D$+wj<iEnALM*r>HkP|7rQDUB6QK2m-D&Ye5v6D5=@=LskK9J_YQWvUUE z;u1$`zV`bqQsrw+-<DbL&6xLmzSB9&9z)LUlIP}YwKQxsn;D&Lf7s5(=E>W)yvaVd zemzs&KTjfAD5I>fwzl?@c~Nn(u)Lhyjr;fK&$zK(<*b-Q@{V1*9zA)|GIy@*(fn)n ZmM+Dfv%FaMGB7YOc)I$ztaD0e0sxqHL0<p> literal 0 HcmV?d00001 diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_successful.png b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/ingest_successful.png new file mode 100644 index 0000000000000000000000000000000000000000..e36932e3fdced37b333842bd94b4d0224ec7a9aa GIT binary patch literal 1060 zcmeAS@N?(olHy`uVBq!ia0y~yU=RXf4mJh`hOl#e;S3B6Y)RhkE)4%caKYZ?lNlHo zI14-?iy0WWg+Z8+Vb&Z81_lQ95>H=O_6MAze0=)--JZ$}49punT^vI!PWMjs&yXpV zXq$ik%(=93Vc%_mLKy*GYwERDR=sc)Y+AZ)>DKJ1qK*sATavgLyPLi}P|P~2^wx31 z#;`Sk{F<4}(~<;qM0M6SbxaD-a`KS!F~9xhoyzRHH)sC*`+38TSDyDjAFr;M_g<9Y z^5x5C&YZb$`?j~)Y}agF2c?M&4%xi|O({kvzkD%?%KrBD_Hp~iK0ZDLUtR?M`t|G0 zn>TB|*52Q>E}L=nluZq#vDc%rb7hW~eSUWKT4}7crDf*yY13x8CM6^^EL^B)ZD!VX zZPvkqj=z5YKAB=vQc|*D>$0%bf^CW?Q-m5OP86)UzdS(0z|e5x+TFW<Z&F^GQd2WW zfrF!oK~uF*Mz~Ee$m`NelXgDYM^{z`Z`rmjXl2NRpiO)B^gOmWbM|a4_k<1Cl$QPx z=T7EX8NzkFboQDSfrxBR6`?oxHqV}UJpW_CGsZcU%MYITY_n#oMDm7fLM)67g)+%& zU9)}D)75ixb6Ff43Rr^s|D1@;lMes$`p%1)`3AT4)mpzSxh2NMDsz0{>ebzc+xahF zzU*u^+f~z5ZL*_R=f<{)Q~7tO@8nAr;quK%VtaSD^TyU}aWBnit*IVMgIW?hwu+tq z9N_iPg726FpPbE!vui7vVwYb&9Tc{D3rGKy%ZEh&TlEPnI{BdYqIu<uZgtD@uTGaI z7`EN{Y?+sLHh7x0$LfflX&k;MR>eK8dYZ2HwYKfYz7Ndr<twi5EB<C1x96kQ`Mdvh z#MAdD{^<;?+LO0t>z+7^b8~liT*~}V(%OB{AbS7Nqxbw@oLndVDCncn+g(@XA}eNp z)xFEI?A$IF_U(_E(#_{RIQ-}NpQybD?{=Cw2|wiQ7dxJum^$(CH`8lNH|N~k<Z7|_ zxBsSB*Cs6O5Di_p&iUD4`+@?AU%4}~|M~AYlo~m0<?9JuQOWPV>BdFfIX!WDyd%fO z3;HbyCI3CY+3aYZsyctctqbv*>&^QA1WBffIGQHt?mt!TAOGOY$NrAT5=%oCdw6vU zG!@^rOS^rnuEbYx>Z@M)RbDBYhmNoX2Yc8*dSx6hxc16yH{0{sB2SAfmrPNa?3tx5 zCRtUY|MjM6PRfL%9NN5US5_pQ`MLbuhAk7+`yM}IdSsC&!pqM;`Seo_v2KZMiwROG z|IdkYJYPNI#)3Z_4JZHkC;oiT`FHD4UTHIi2TVS`zI#i5eR(-iqbDOj|GC}%?ybi~ z_Q&t`RJwiWy7j)4htn-rUDf(pHP?VgbNXo+>#`o-*=C&ow3B~+Hj}@*@lXFB&bjqm WTt`A=KQS;cFnGH9xvX<aXaWFfNB5)v literal 0 HcmV?d00001 diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/src/iconset.jpg b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/src/iconset.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9deadcface0a83e3c2825ad039cb31b247c29ab7 GIT binary patch literal 18093 zcmex=<NpH&0WUXCHwH!~28I-d6b6R>w;7xn*jQLtSXtOuS=l(*SlKwaIoQ}ZxOup^ zxVgA^1bDzefQOG?fS->?SXe|vSXf3{T3SXP4nT%5va_@E^6*Ls2uMhY3W`d>C5dJH zKg1x&!BEQhfSFN{fk}{&S&;Gn5e8`n1|}wEMg}Cn#=^?P%np*2Vq|1sW@JQ`WoBVz zVr2V&i-Cukk%38oS%86o@s-=+!^W8pmwY}}x2<vg(Hfbkh&Rmqq7w>plQ;0xgdRM) zweJAaI=)=>7bc=VKcD)3Qta{h^@gucNqzSIXm=!<ciztse<LF!@$86+BGbxVSHGXV z>)Dby?&d-rTYsPOD|o*4-p^HWf$twztB8Lwi	*V(ILaEy7WmX`B4x-R**|KP~FX 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 O%JD8!vg)<}e-i*{HC!bC literal 0 HcmV?d00001 diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/src/iconset.png b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/src/iconset.png new file mode 100644 index 0000000000000000000000000000000000000000..4e700ae9a0feddd82aa55dec0ba9fe0720ebec68 GIT binary patch literal 54615 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+34f={$OC3?*6Xw`(NxMX6$=2XW2!Tx|Y<} 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 literal 0 HcmV?d00001 diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/warning.png b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/icons/warning.png deleted file mode 100644 index e8c259292940ff503711382478f088dcf2224105..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 692 zcmeAS@N?(olHy`uVBq!ia0y~yU=RXf4mJh`hOl#e;S3B6Y)RhkE)4%caKYZ?lNlHo zI14-?iy0W_TZ1s8^5zMG3=9nHC7!;n><>73_za}BzUj+gU|@3eba4!^IK6j@w?{~# zMBDuTMdxN(rmwp<D=p(#iWi5zYxDL`K~X`15|dh-Ty&K<7KQHMy5)YwDk}2AD&O4V zL>A6g(GaIHXU^L*j>gQnXsOE;l)Xad*L3~+|7-N$|F5<G)1REdxPW1=W#T{4(}jCJ zxtv&kj`^Qy@D!0)XZEI@rw>mwDacwoXVp5j^e6wm_P%@ac1iUP^8#^?DO%H1O9CU8 zMN4PfI-mU-cgnfTcJ02h(^HyN?@qt)*m~Pd@$+)J{qeiMPLwWx_R_=h()$w+eq4Uz z{NV3<i%Xp=Hm=`(_U6o<**$&BC1uYGeDbymE?Z@l_T~SZd;LvI(!6_3YpOQ%I2}xl zZ9VqhV~?Dt|LT6{yN!$sK8Gt9Z@gzww?==;hC^Iy6<<uw;Fa?h=#eT}@+KvC@Amqy z7CP*l^L3?XN$*K#<6~0{KJv|DRkGSft>&%?O+FL<zO^eewRCS-7nB$+t@P{E(&CA` zGLAH;1_T-W-`Np%;qIdaEE>^?TyI|9Y^pJhm0-SfT9)&)NcHbB_FvnU%V_LSNPKr$ z=(}8M#RrwsPiEU>Fmk(EMakUScI)rd#JhVBO-w#r@K^2piL2Zfwfc4U=T3KFHs0K= zq52}T@I&j@>({0&*mY@w{{3g#^L%xC7>lJGxRMq&tf~H^<X*L?gCWL&gM(ePcdMB2 zWXn6xcfPlbnDoYJW3a=OR!znS6{!|84g8NOyPYU43~yQC;H4oF=DW$sZ|`aU{qs)V v_esCYpY-!G^ELT5ix`X!JhK10dmn?iXXi3gS0-@=1_lOCS3j3^P6<r_<o7hd diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index af5e45e9fb6..9511fe1e2cc 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -102,7 +102,7 @@ </div> <div class="top-stretch" ui-layout options="{flow: 'column'}"> - <div ng-controller="GridController as gridCtrl" style="margin-right: 4px;" > + <div ng-controller="GridController as gridCtrl" style="margin-right: 4px" > <div id="grid" ui-grid="gridOptions" ui-grid-edit ui-grid-selection ui-grid-cellNav ui-grid-resize-columns ui-grid-auto-resize -- GitLab From f29188c81b5aa69c43dc87635ffe2a6df90c6b1e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 06:40:35 +0000 Subject: [PATCH 837/933] Task #9607: do du on completing event --- SAS/DataManagement/StorageQueryService/cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index c1b1cda8144..fe7626f7a9d 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -48,7 +48,7 @@ class CacheManager: numthreads=2) self.otdb_listener.onObservationAborted = self.onObservationAborted - self.otdb_listener.onObservationFinished = self.onObservationFinished + self.otdb_listener.onObservationCompleting = self.onObservationCompleting self.dm_listener = DataManagementBusListener(busname=dm_notification_busname, subjects=dm_notification_prefix + '*', @@ -322,7 +322,7 @@ class CacheManager: def __exit__(self, exc_type, exc_val, exc_tb): self.close() - def onObservationFinished(self, otdb_id, modificationTime): + def onObservationCompleting(self, otdb_id, modificationTime): self._onDiskActivityForOTDBId(otdb_id) def onObservationAborted(self, otdb_id, modificationTime): -- GitLab From c98c08b53cacd963e26e31d10cbd478e8c0c79e0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 06:41:07 +0000 Subject: [PATCH 838/933] Task #9607: filter for cep4 finalized tasks --- .../ResourceAssignmentEditor/lib/storage.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py index d515381e39e..b82a91c2f4d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py @@ -38,12 +38,15 @@ def updateTaskStorageDetails(task, sqrpc): tasklist = task if isinstance(task, list) else [task] - if len(tasklist) == 0: - return - for t in tasklist: applyDefaults(t) + statuses = set(['finished', 'completing', 'aborted']) + tasklist = [t for t in tasklist if t['cluster'] == 'CEP4' and t['status'] in statuses] + + if len(tasklist) == 0: + return + if not sqrpc: return -- GitLab From fec4bb90635b5cd298ef0410fbd682328a8457e9 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 06:41:34 +0000 Subject: [PATCH 839/933] Task #9607: typo --- .../OTDBtoRATaskStatusPropagator/propagator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index 4e357edcd2e..eb8ce5cce69 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -165,7 +165,7 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): def onObservationCompleting(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'completing') - # otdb adjusts stoptime when aborted, + # otdb adjusts stoptime when completing, self._updateStopTime(treeId, ['observation']) def _updateStopTime(self, treeId, task_types=None): -- GitLab From e23d0749f7d5b54e58e8200558253a9ed7ac4ff1 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 08:02:10 +0000 Subject: [PATCH 840/933] Task #9607: fixed typo --- .../ResourceAssignmentEditor/lib/storage.py | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py index b82a91c2f4d..6a91bdd3684 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py @@ -30,39 +30,43 @@ import logging logger = logging.getLogger(__name__) -def updateTaskStorageDetails(task, sqrpc): - def applyDefaults(t): +def updateTaskStorageDetails(tasks, sqrpc): + def applyDefaults(task): '''apply sane default values for a task''' - t['disk_usage'] = None - t['disk_usage_readable'] = None + task['disk_usage'] = None + task['disk_usage_readable'] = None - tasklist = task if isinstance(task, list) else [task] + try: + _ = (t for t in tasks) + except TypeError: + # make single task instance a list + tasks = [tasks] - for t in tasklist: - applyDefaults(t) + for task in tasks: + applyDefaults(task) statuses = set(['finished', 'completing', 'aborted']) - tasklist = [t for t in tasklist if t['cluster'] == 'CEP4' and t['status'] in statuses] + tasks = [t for t in tasks if t['cluster'] == 'CEP4' and t['status'] in statuses] - if len(tasklist) == 0: + if len(tasks) == 0: return if not sqrpc: return try: - otdb_ids = [t['otdb_id'] for t in tasklist] + otdb_ids = [t['otdb_id'] for t in tasks] usages = sqrpc.getDiskUsageForTasks(otdb_ids=otdb_ids, include_scratch_paths=False).get('otdb_ids') if not usages: return - for task in tasklist: + for task in tasks: otdb_id = str(task['otdb_id']) if otdb_id in usages: usage = usages[otdb_id] - t['disk_usage'] = usage['disk_usage'] - t['disk_usage_readable'] = usage['disk_usage_readable'] + task['disk_usage'] = usage['disk_usage'] + task['disk_usage_readable'] = usage.get('disk_usage_readable') except Exception as e: logger.error(str(e)) -- GitLab From 2f037984583ebfd80e161b8d126d031a17e4001f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 08:02:29 +0000 Subject: [PATCH 841/933] Task #9607: layout polishing --- .../static/app/controllers/gridcontroller.js | 34 +++++++++++-------- .../lib/static/css/main.css | 1 + 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index be6f7f88474..7f9c74e0358 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -18,13 +18,15 @@ gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGrid $scope.columns = [ { field: 'name', enableCellEdit: false, - width: '100', + width: '*', + minWidth: '100', }, { field: 'project_name', displayName:'Project', enableCellEdit: false, - cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}">{{row.entity[col.field]}}</a>', - width: '80', + cellTemplate:'<div style=\'padding-top:5px;\'><a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}">{{row.entity[col.field]}}</a></div>', + width: '*', + minWidth: '80', filter: { type: uiGridConstants.filter.SELECT, selectOptions: [] @@ -32,20 +34,20 @@ $scope.columns = [ }, { field: 'starttime', displayName: 'Start', - width: '70', + width: '120', type: 'date', enableCellEdit: false, enableCellEditOnFocus: false, - cellTemplate:'<div style=\'text-align:left\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>', + cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>', sort: { direction: uiGridConstants.ASC, priority: 3 } }, { field: 'endtime', displayName: 'End', - width: '70', + width: '120', type: 'date', enableCellEdit: false, enableCellEditOnFocus: false, - cellTemplate:'<div style=\'text-align:left\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>' + cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>' }, { field: 'duration', displayName: 'Duration', @@ -54,11 +56,11 @@ $scope.columns = [ enableFiltering: false, enableCellEdit: false, enableCellEditOnFocus: false, - cellTemplate:'<div style=\'text-align:left\'>{{row.entity[col.field] | secondsToHHmmss}}</div>' + cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity[col.field] | secondsToHHmmss}}</div>' }, { field: 'status', enableCellEdit: true, - width: '60', + width: '70', filter: { condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, @@ -101,7 +103,7 @@ $scope.columns = [ }, { field: 'type', enableCellEdit: false, - width: '70', + width: '80', filter: { condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, @@ -111,9 +113,10 @@ $scope.columns = [ }, { field: 'disk_usage', displayName: 'Size', + type: 'number', enableCellEdit: false, - cellTemplate:'<span>{{row.entity.disk_usage_readable}}</span>', - width: '6%', + cellTemplate:'<div style=\'text-align:right\'>{{row.entity.disk_usage_readable}}</div>', + width: '80', filter: { type: uiGridConstants.filter.SELECT, condition: uiGridConstants.filter.GREATER_THAN, @@ -123,7 +126,7 @@ $scope.columns = [ { field: 'mom_object_group_id', displayName: 'Group ID', enableCellEdit: false, - cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity.mom_object_group_id}}</a>', + cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'><a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity.mom_object_group_id}}</a></div>', width: '80', filter: { condition: uiGridConstants.filter.EXACT, @@ -134,18 +137,19 @@ $scope.columns = [ { field: 'mom_id', displayName: 'MoM ID', enableCellEdit: false, - cellTemplate:'<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}">{{row.entity[col.field]}}</a>', + cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'><a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}">{{row.entity[col.field]}}</a></div>', width: '65' }, { field: 'otdb_id', displayName: 'SAS ID', enableCellEdit: false, + cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity.otdb_id}}</div>', width: '65' }, { field: 'id', displayName: 'RADB ID', enableCellEdit: false, - cellTemplate:'<a target="_blank" href="tasks/{{row.entity.id}}.html">{{row.entity[col.field]}}</a>', + cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'><a target="_blank" href="tasks/{{row.entity.id}}.html">{{row.entity[col.field]}}</a></div>', width: '72' }, { field: 'cluster', diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index 7f5513a22e0..94bda4a5d99 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -31,6 +31,7 @@ body { .ui-grid-cell { overflow: visible; + padding: 0px 2px; } .datepicker-wrapper ul { -- GitLab From 02555dbc1180ca46b92d376a5c81de61d3601114 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 08:39:12 +0000 Subject: [PATCH 842/933] Task #9607: enable selection of for example sas id with mouse --- .../lib/static/app/controllers/gridcontroller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 7f9c74e0358..e7dc5794d0c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -3,7 +3,6 @@ var gridControllerMod = angular.module('GridControllerMod', ['ui.grid', 'ui.grid.edit', 'ui.grid.selection', - 'ui.grid.cellNav', 'ui.grid.resizeColumns', 'ui.grid.autoResize']); -- GitLab From 3fdb9fe288bc3449ad23b5f894b01ec50474fb9d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 08:46:07 +0000 Subject: [PATCH 843/933] Task #9607: progress in logging --- SAS/DataManagement/StorageQueryService/cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index fe7626f7a9d..e799c1c48bb 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -235,11 +235,11 @@ class CacheManager: cacheUpdateStart = datetime.datetime.utcnow() - for cache_entry in old_entries: + for i, cache_entry in enumerate(old_entries): try: path = cache_entry.get('path') if path: - logger.info('examining old entry in cache: %s', path) + logger.info('examining old entry %s/%s in cache: %s', i, len(old_entries), path) result = du_getDiskUsageForPath(path) if result['found']: logger.info('updating old entry in cache: %s', result) -- GitLab From a0fdb6b7870c2a373ef7ba3e40c5362f3dba4283 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 10:20:52 +0000 Subject: [PATCH 844/933] Task #9607: added functionality so the panes can have an initial non-evenly-distributed size --- .../js/angular-ui-layout/angular-ui-layout.js | 34 ++++++++++++++++--- .../angular-ui-layout.min.js | 8 +---- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-ui-layout/angular-ui-layout.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-ui-layout/angular-ui-layout.js index 570a6b5939a..9d811b9e5fa 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-ui-layout/angular-ui-layout.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-ui-layout/angular-ui-layout.js @@ -20,19 +20,45 @@ angular.module('ui.layout', []).controller('uiLayoutCtrl', [ var opts = angular.extend({}, $parse(tAttrs.uiLayout)(), $parse(tAttrs.options)()); var isUsingColumnFlow = opts.flow === 'column'; tElement.addClass('stretch').addClass('ui-layout-' + (opts.flow || 'row')); + var child_widths = []; + var total_width = tElement[0].clientWidth; for (_i = 0; _i < _child_len; ++_i) { angular.element(_childens[_i]).addClass('stretch'); + var init_width_attr = _childens[_i].attributes.getNamedItem('ui-layout-init-min-width'); + if(init_width_attr) { + if(init_width_attr.nodeValue.includes('%')) { + var child_width_perc = parseFloat(init_width_attr.nodeValue); + child_widths.push(child_width_perc); + } + else { + var child_width_perc = 100.0*parseFloat(init_width_attr.nodeValue) / total_width; + child_widths.push(child_width_perc); + } + } + else + child_widths.push(undefined); } + if (_child_len > 1) { + var totalDefinedChildWidth = child_widths.filter(function(cw) { return cw != undefined;}).reduce(function(a, b){return a+b;}); + var remainingWithForUndefinedChilds = 100 - totalDefinedChildWidth; + var numUndefinedChilds = child_widths.filter(function(cw) { return cw == undefined;}).length; + var undefinedChildWidth = remainingWithForUndefinedChilds / numUndefinedChilds; + var flowProperty = isUsingColumnFlow ? 'left' : 'top'; var oppositeFlowProperty = isUsingColumnFlow ? 'right' : 'bottom'; - var step = 100 / _child_len; + var prevPerc = 0; for (_i = 0; _i < _child_len; ++_i) { - var area = angular.element(_childens[_i]).css(flowProperty, step * _i + '%').css(oppositeFlowProperty, 100 - step * (_i + 1) + '%'); + var child_width = child_widths[_i]; + if(child_width == undefined) { + child_width = undefinedChildWidth; + } + var area = angular.element(_childens[_i]).css(flowProperty, prevPerc + '%').css(oppositeFlowProperty, 100 - (prevPerc + child_width) + '%'); if (_i < _child_len - 1) { - var bar = angular.element(splitBarElem_htmlTemplate).css(flowProperty, step * (_i + 1) + '%'); + var bar = angular.element(splitBarElem_htmlTemplate).css(flowProperty, prevPerc + child_width + '%'); area.after(bar); } + prevPerc += child_width; } } }, @@ -120,4 +146,4 @@ if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function (id) { clearTimeout(id); }; -} \ No newline at end of file +} diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-ui-layout/angular-ui-layout.min.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-ui-layout/angular-ui-layout.min.js index c6e5df5ccb6..88ac76aa8bf 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-ui-layout/angular-ui-layout.min.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-ui-layout/angular-ui-layout.min.js @@ -1,7 +1 @@ -/** - * angular-ui-layout - This directive allows you to split ! - * @version v0.0.0 - 2014-01-01 - * @link https://github.com/angular-ui/ui-layout - * @license MIT - */ -"use strict";angular.module("ui.layout",[]).controller("uiLayoutCtrl",["$scope","$attrs","$element",function(a,b,c){return{opts:angular.extend({},a.$eval(b.uiLayout),a.$eval(b.options)),element:c}}]).directive("uiLayout",["$parse",function(a){var b='<div class="stretch ui-splitbar"></div>';return{restrict:"AE",compile:function(c,d){var e,f=c.children(),g=f.length,h=angular.extend({},a(d.uiLayout)(),a(d.options)()),i="column"===h.flow;for(c.addClass("stretch").addClass("ui-layout-"+(h.flow||"row")),e=0;g>e;++e)angular.element(f[e]).addClass("stretch");if(g>1){var j=i?"left":"top",k=i?"right":"bottom",l=100/g;for(e=0;g>e;++e){var m=angular.element(f[e]).css(j,l*e+"%").css(k,100-l*(e+1)+"%");if(g-1>e){var n=angular.element(b).css(j,l*(e+1)+"%");m.after(n)}}}},controller:"uiLayoutCtrl"}}]).directive("uiSplitbar",function(){var a=angular.element(document.body.parentElement);return{require:"^uiLayout",restrict:"EAC",link:function(b,c,d,e){function f(){var a=e.element[0].getBoundingClientRect(),b=q.getBoundingClientRect();k.time=+new Date,k.barSize=b[p],k.layoutSize=a[p],k.layoutOrigine=a[n]}function g(){var a=(j-k.layoutOrigine)/k.layoutSize*100;a=Math.min(a,100-k.barSize/k.layoutSize*100),a=Math.max(a,parseInt(q.previousElementSibling.style[n],10)),q.nextElementSibling.nextElementSibling&&(a=Math.min(a,parseInt(q.nextElementSibling.nextElementSibling.style[n],10))),q.style[n]=q.nextElementSibling.style[n]=a+"%",q.previousElementSibling.style[o]=100-a+"%",i=null}function h(a){j=a[m]||a.originalEvent[m],i&&window.cancelAnimationFrame(i),(!k.time||+new Date>k.time+1e3)&&f(),i=window.requestAnimationFrame(g)}var i,j,k={},l="column"===e.opts.flow,m=l?"clientX":"clientY",n=l?"left":"top",o=l?"right":"bottom",p=l?"width":"height",q=c[0];c.on("mousedown touchstart",function(b){return b.preventDefault(),b.stopPropagation(),a.on("mousemove touchmove",h),!1}),a.on("mouseup touchend",function(){a.off("mousemove touchmove")})}}});for(var lastTime=0,vendors=["ms","moz","webkit","o"],x=0;x<vendors.length&&!window.requestAnimationFrame;++x)window.requestAnimationFrame=window[vendors[x]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[vendors[x]+"CancelAnimationFrame"]||window[vendors[x]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(a){var b=(new Date).getTime(),c=Math.max(0,16-(b-lastTime)),d=window.setTimeout(function(){a(b+c)},c);return lastTime=b+c,d}),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(a){clearTimeout(a)}); \ No newline at end of file +"use strict";angular.module("ui.layout",[]).controller("uiLayoutCtrl",["$scope","$attrs","$element",function(b,c,d){return{opts:angular.extend({},b.$eval(c.uiLayout),b.$eval(c.options)),element:d}}]).directive("uiLayout",["$parse",function(a){var b='<div class="stretch ui-splitbar"></div>';return{restrict:"AE",compile:function(d,e){var f,g=d.children(),h=g.length,i=angular.extend({},a(e.uiLayout)(),a(e.options)()),j="column"===i.flow;d.addClass("stretch").addClass("ui-layout-"+(i.flow||"row"));var k=[],l=d[0].clientWidth;for(f=0;f<h;++f){angular.element(g[f]).addClass("stretch");var m=g[f].attributes.getNamedItem("ui-layout-init-min-width");if(m)if(m.nodeValue.includes("%")){var n=parseFloat(m.nodeValue);k.push(n)}else{var n=100*parseFloat(m.nodeValue)/l;k.push(n)}else k.push(void 0)}if(h>1){var o=k.filter(function(a){return void 0!=a}).reduce(function(a,b){return a+b}),p=100-o,q=k.filter(function(a){return void 0==a}).length,r=p/q,s=j?"left":"top",t=j?"right":"bottom",u=0;for(f=0;f<h;++f){var v=k[f];void 0==v&&(v=r);var w=angular.element(g[f]).css(s,u+"%").css(t,100-(u+v)+"%");if(f<h-1){var x=angular.element(b).css(s,u+v+"%");w.after(x)}u+=v}}},controller:"uiLayoutCtrl"}}]).directive("uiSplitbar",function(){var a=angular.element(document.body.parentElement);return{require:"^uiLayout",restrict:"EAC",link:function(b,c,d,e){function o(){var a=e.element[0].getBoundingClientRect(),b=n.getBoundingClientRect();h.time=+new Date,h.barSize=b[m],h.layoutSize=a[m],h.layoutOrigine=a[k]}function p(){var a=(g-h.layoutOrigine)/h.layoutSize*100;a=Math.min(a,100-h.barSize/h.layoutSize*100),a=Math.max(a,parseInt(n.previousElementSibling.style[k],10)),n.nextElementSibling.nextElementSibling&&(a=Math.min(a,parseInt(n.nextElementSibling.nextElementSibling.style[k],10))),n.style[k]=n.nextElementSibling.style[k]=a+"%",n.previousElementSibling.style[l]=100-a+"%",f=null}function q(a){g=a[j]||a.originalEvent[j],f&&window.cancelAnimationFrame(f),(!h.time||+new Date>h.time+1e3)&&o(),f=window.requestAnimationFrame(p)}var f,g,h={},i="column"===e.opts.flow,j=i?"clientX":"clientY",k=i?"left":"top",l=i?"right":"bottom",m=i?"width":"height",n=c[0];c.on("mousedown touchstart",function(b){return b.preventDefault(),b.stopPropagation(),a.on("mousemove touchmove",q),!1}),a.on("mouseup touchend",function(){a.off("mousemove touchmove")})}}});for(var lastTime=0,vendors=["ms","moz","webkit","o"],x=0;x<vendors.length&&!window.requestAnimationFrame;++x)window.requestAnimationFrame=window[vendors[x]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[vendors[x]+"CancelAnimationFrame"]||window[vendors[x]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(a){var b=(new Date).getTime(),c=Math.max(0,16-(b-lastTime)),d=window.setTimeout(function(){a(b+c)},c);return lastTime=b+c,d}),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(a){clearTimeout(a)}); -- GitLab From 9e6477797164779ad02d547fb1380de68b0ab01c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 10:21:35 +0000 Subject: [PATCH 845/933] Task #9607: use min initial width for grid --- .../ResourceAssignmentEditor/lib/templates/index.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index 9511fe1e2cc..bb2fbd340ae 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -28,7 +28,6 @@ <script src="/static/js/angular-ui-grid/ui-grid.js"></script> <script src="/static/js/angular-ui-tree/angular-ui-tree.js"></script> <script src="/static/js/angular-ui-layout/angular-ui-layout.min.js"></script> - <script src="/static/js/angular-ui-layout/angular-ui-layout.min.js"></script> <script src="/static/js/angular-ui-tabs/angular-ui.bootstrap.tabs.min.js"></script> <script src="/static/js/angular-moment/angular-moment.js"></script> <script src="/static/js/angular-animate/angular-animate.min.js"></script> @@ -102,14 +101,14 @@ </div> <div class="top-stretch" ui-layout options="{flow: 'column'}"> - <div ng-controller="GridController as gridCtrl" style="margin-right: 4px" > + <div ng-controller="GridController as gridCtrl" style="margin-right: 4px;" ui-layout-init-min-width="1160px"> <div id="grid" ui-grid="gridOptions" ui-grid-edit ui-grid-selection ui-grid-cellNav ui-grid-resize-columns ui-grid-auto-resize class="grid"></div> </div> - <md-content margin-top='10px'> - <md-tabs md-dynamic-height md-border-bottom style="height: 100%; width: 100%;"> + <md-content margin-top='10px' style="margin-right: 4px;"> + <md-tabs md-dynamic-height md-border-bottom style="height: 100%; width: 100%;" > <div ng-controller="GanttProjectController as ganttProjectCtrl" ng-init="enabled=true"> <md-tab label="Tasks" md-on-select="enabled=true" md-on-deselect="enabled=false"> <div gantt data=ganttData -- GitLab From b1e2124e323c30359ebca3579430c682a528f27e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 10:21:55 +0000 Subject: [PATCH 846/933] Task #9607: no dropdown per task status cell --- .../lib/static/app/controllers/gridcontroller.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index e7dc5794d0c..ef1ec333d9f 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -58,15 +58,13 @@ $scope.columns = [ cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity[col.field] | secondsToHHmmss}}</div>' }, { field: 'status', - enableCellEdit: true, + enableCellEdit: false, width: '70', filter: { condition: uiGridConstants.filter.EXACT, type: uiGridConstants.filter.SELECT, selectOptions: [] }, - editableCellTemplate: 'ui-grid/dropdownEditor', - editDropdownOptionsArray: [], cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) { return "grid-status-" + grid.getCellValue(row,col); } -- GitLab From 752bb0f56ea1385077f5d6aa2c08a2424ef81ffa Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 10:39:21 +0000 Subject: [PATCH 847/933] Task #9607: Selects near each other --- .../static/app/controllers/gridcontroller.js | 38 +++++++++---------- .../angular-gantt-contextmenu-plugin.js | 37 +++++++++--------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index ef1ec333d9f..724621385f6 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -602,6 +602,25 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do dataCtrlScope.loadTasksByMoMGroupIdSelectAndJumpIntoView(task.mom_object_group_id); }); + var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); }); + + if(blocked_selected_cep4_tasks.length > 0) { + var liContent = '<li><a href="#">Select blocking predecessor(s)</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + + var blocking_predecessors = [] + for(var task of blocked_selected_cep4_tasks) { + blocking_predecessors = blocking_predecessors.concat(task.blocked_by_ids); + } + + var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; + dataCtrlScope.loadTasksSelectAndJumpIntoView(blocking_predecessors); + }); + } + if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>'); ulElement.append(liElement); @@ -703,25 +722,6 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } - var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); }); - - if(blocked_selected_cep4_tasks.length > 0) { - var liContent = '<li><a href="#">Select blocking predecessor(s)</a></li>' - var liElement = angular.element(liContent); - ulElement.append(liElement); - liElement.on('click', function() { - closeContextMenu(); - - var blocking_predecessors = [] - for(var task of blocked_selected_cep4_tasks) { - blocking_predecessors = blocking_predecessors.concat(task.blocked_by_ids); - } - - var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; - dataCtrlScope.loadTasksSelectAndJumpIntoView(blocking_predecessors); - }); - } - var closeContextMenu = function(cme) { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index ead27ced7b0..ede6e85c255 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -76,6 +76,24 @@ dataCtrlScope.loadTasksByMoMGroupIdSelectAndJumpIntoView(task.mom_object_group_id); }); + var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); }); + + if(blocked_selected_cep4_tasks.length > 0) { + var liContent = '<li><a href="#">Select blocking predecessors</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + + var blocking_predecessors = [] + for(var task of blocked_selected_cep4_tasks) { + blocking_predecessors = blocking_predecessors.concat(task.blocked_by_ids); + } + + dataCtrlScope.loadTasksSelectAndJumpIntoView(blocking_predecessors); + }); + } + if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>'); ulElement.append(liElement); @@ -177,25 +195,6 @@ }); } - var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); }); - - if(blocked_selected_cep4_tasks.length > 0) { - var liContent = '<li><a href="#">Select blocking predecessors</a></li>' - var liElement = angular.element(liContent); - ulElement.append(liElement); - liElement.on('click', function() { - closeContextMenu(); - - var blocking_predecessors = [] - for(var task of blocked_selected_cep4_tasks) { - blocking_predecessors = blocking_predecessors.concat(task.blocked_by_ids); - } - - dataCtrlScope.loadTasksSelectAndJumpIntoView(blocking_predecessors); - }); - } - - var closeContextMenu = function() { contextmenuElement.remove(); -- GitLab From 227a0070ebcd81f2b2a010c0a4e749bff9c64c9f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 22 Sep 2016 13:00:34 +0000 Subject: [PATCH 848/933] Task #9607: logging, minor fix --- SAS/DataManagement/StorageQueryService/cache.py | 12 ++++++++---- .../ResourceAssignmentEditor/lib/storage.py | 9 ++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index e799c1c48bb..be1626146f3 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -123,7 +123,7 @@ class CacheManager: if du_result['found']: if not path in path_cache or path_cache[path]['disk_usage'] != du_result['disk_usage']: - logger.debug('updating cache entry: %s', du_result) + logger.info('updating cache entry: %s', du_result) path_cache[path] = du_result self._sendDiskUsageChangedNotification(path, du_result['disk_usage'], otdb_id) @@ -161,7 +161,7 @@ class CacheManager: with self._cacheLock: path_cache = self._cache['path_du_results'] - if directory not in path_cache: + if not directory in path_cache: logger.info('tree scan: adding \'%s\' with empty disk_usage to cache which will be du\'ed later', directory) empty_du_result = {'found': True, 'disk_usage': None, 'path': directory, 'name': directory.split('/')[-1]} self._updateCache(empty_du_result) @@ -211,6 +211,10 @@ class CacheManager: if depth1 == 2 or depth2 == 2: if depth1 == 2 and depth2 == 2: + if not entry1['disk_usage']: + return -1 + if not entry2['disk_usage']: + return 1 if entry1['cache_timestamp'] < entry2['cache_timestamp']: return -1 if entry1['cache_timestamp'] > entry2['cache_timestamp']: @@ -242,7 +246,7 @@ class CacheManager: logger.info('examining old entry %s/%s in cache: %s', i, len(old_entries), path) result = du_getDiskUsageForPath(path) if result['found']: - logger.info('updating old entry in cache: %s', result) + logger.debug('trying to update old entry in cache: %s', result) self._updateCache(result) except Exception as e: logger.error(str(e)) @@ -253,7 +257,7 @@ class CacheManager: if datetime.datetime.utcnow() - cacheUpdateStart > datetime.timedelta(minutes=5): # break out of cache update loop if full update takes more than 5min # next loop we'll start with the oldest cache entries again - logger.info('skipping remaining old cache entries updates, they will be updated next time') + logger.info('skipping remaining %s old cache entries updates, they will be updated next time', len(old_entries)-i) break for i in range(60): diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py index 6a91bdd3684..2075f3458ea 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py @@ -31,16 +31,15 @@ import logging logger = logging.getLogger(__name__) def updateTaskStorageDetails(tasks, sqrpc): + if not tasks: + return + def applyDefaults(task): '''apply sane default values for a task''' task['disk_usage'] = None task['disk_usage_readable'] = None - try: - _ = (t for t in tasks) - except TypeError: - # make single task instance a list - tasks = [tasks] + tasks = tasks if isinstance(tasks, list) else [tasks] for task in tasks: applyDefaults(task) -- GitLab From 33673c267e11e677328beed93e8a8c47bfd792f5 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 23 Sep 2016 06:58:21 +0000 Subject: [PATCH 849/933] Task #9607: added force_update flag --- .../StorageQueryService/cache.py | 48 +++++++++---------- SAS/DataManagement/StorageQueryService/rpc.py | 45 ++++++++--------- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index be1626146f3..9e239049540 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -360,16 +360,16 @@ class CacheManager: # trigger update cache thread self._continueUpdateCacheThread = True - def getDiskUsageForOTDBId(self, otdb_id, include_scratch_paths=True): - return self.getDiskUsageForTask(otdb_id=otdb_id, include_scratch_paths=include_scratch_paths) + def getDiskUsageForOTDBId(self, otdb_id, include_scratch_paths=True, force_update=False): + return self.getDiskUsageForTask(otdb_id=otdb_id, include_scratch_paths=include_scratch_paths, force_update=force_update) - def getDiskUsageForMoMId(self, mom_id, include_scratch_paths=True): - return self.getDiskUsageForTask(mom_id=mom_id, include_scratch_paths=include_scratch_paths) + def getDiskUsageForMoMId(self, mom_id, include_scratch_paths=True, force_update=False): + return self.getDiskUsageForTask(mom_id=mom_id, include_scratch_paths=include_scratch_paths, force_update=force_update) - def getDiskUsageForRADBId(self, radb_id, include_scratch_paths=True): - return self.getDiskUsageForTask(radb_id=radb_id, include_scratch_paths=include_scratch_paths) + def getDiskUsageForRADBId(self, radb_id, include_scratch_paths=True, force_update=False): + return self.getDiskUsageForTask(radb_id=radb_id, include_scratch_paths=include_scratch_paths, force_update=force_update) - def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None, include_scratch_paths=True): + def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None, include_scratch_paths=True, force_update=False): logger.info("cache.getDiskUsageForTask(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) if otdb_id != None and not include_scratch_paths: @@ -378,39 +378,39 @@ class CacheManager: if path: logger.info('Using path from cache for otdb_id %s %s', otdb_id, path) - return self.getDiskUsageForPath(path) + return self.getDiskUsageForPath(path, force_update=force_update) path_result = self.disk_usage.path_resolver.getPathForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, include_scratch_paths=include_scratch_paths) if path_result['found']: - return self.getDiskUsageForPath(path_result['path']) + return self.getDiskUsageForPath(path_result['path'], force_update=force_update) return {'found': False, 'path': path_result['path']} - def getDiskUsageForTasks(self, radb_ids=None, mom_ids=None, otdb_ids=None, include_scratch_paths=True): + def getDiskUsageForTasks(self, radb_ids=None, mom_ids=None, otdb_ids=None, include_scratch_paths=True, force_update=False): logger.info("cache.getDiskUsageForTask(radb_ids=%s, mom_ids=%s, otdb_ids=%s)" % (radb_ids, mom_ids, otdb_ids)) result = {'radb_ids': {}, 'mom_ids': {}, 'otdb_ids': {}} if radb_ids: for radb_id in radb_ids: - result['radb_ids'][str(radb_id)] = self.getDiskUsageForTask(radb_id=radb_id, include_scratch_paths=include_scratch_paths) + result['radb_ids'][str(radb_id)] = self.getDiskUsageForTask(radb_id=radb_id, include_scratch_paths=include_scratch_paths, force_update=force_update) if mom_ids: for mom_id in mom_ids: - result['mom_ids'][str(mom_id)] = self.getDiskUsageForTask(mom_id=mom_id, include_scratch_paths=include_scratch_paths) + result['mom_ids'][str(mom_id)] = self.getDiskUsageForTask(mom_id=mom_id, include_scratch_paths=include_scratch_paths, force_update=force_update) if otdb_ids: for otdb_id in otdb_ids: - result['otdb_ids'][str(otdb_id)] = self.getDiskUsageForTask(otdb_id=otdb_id, include_scratch_paths=include_scratch_paths) + result['otdb_ids'][str(otdb_id)] = self.getDiskUsageForTask(otdb_id=otdb_id, include_scratch_paths=include_scratch_paths, force_update=force_update) return result - def getDiskUsageForPath(self, path): + def getDiskUsageForPath(self, path, force_update=False): logger.info("cache.getDiskUsageForPath(%s)", path) needs_cache_update = False with self._cacheLock: needs_cache_update |= path not in self._cache['path_du_results'] - if needs_cache_update: + if needs_cache_update or force_update: logger.info("cache update needed for %s", path) result = du_getDiskUsageForPath(path) self._updateCache(result) @@ -426,9 +426,9 @@ class CacheManager: logger.info('cache.getDiskUsageForPath result: %s' % result) return result - def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): + def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None, force_update=False): logger.info("cache.getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) - task_du_result = self.getDiskUsageForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id) + task_du_result = self.getDiskUsageForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, force_update=force_update) if task_du_result['found']: task_sd_result = self.disk_usage.path_resolver.getSubDirectories(task_du_result['path']) @@ -436,7 +436,7 @@ class CacheManager: subdir_paths = [os.path.join(task_du_result['path'],sd) for sd in task_sd_result['sub_directories']] #TODO: potential for parallelization - subdirs_du_result = { sd: self.getDiskUsageForPath(sd) for sd in subdir_paths } + subdirs_du_result = { sd: self.getDiskUsageForPath(sd, force_update=force_update) for sd in subdir_paths } result = {'found':True, 'task_directory': task_du_result, 'sub_directories': subdirs_du_result } logger.info("result for cache.getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s): %s", radb_id, mom_id, otdb_id, result) @@ -445,25 +445,25 @@ class CacheManager: logger.warn("result for cache.getDiskUsageForTaskAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s): %s", radb_id, mom_id, otdb_id, task_du_result) return task_du_result - def getDiskUsageForProjectDirAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None, project_name=None): + def getDiskUsageForProjectDirAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None, project_name=None, force_update=False): logger.info("cache.getDiskUsageForProjectDirAndSubDirectories(radb_id=%s, mom_id=%s, otdb_id=%s)" % (radb_id, mom_id, otdb_id)) path_result = self.disk_usage.path_resolver.getProjectDirAndSubDirectories(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, project_name=project_name) if path_result['found']: - projectdir_du_result = self.getDiskUsageForPath(path_result['path']) + projectdir_du_result = self.getDiskUsageForPath(path_result['path'], force_update=force_update) subdir_paths = [os.path.join(path_result['path'],sd) for sd in path_result['sub_directories']] #TODO: potential for parallelization - subdirs_du_result = { sd: self.getDiskUsageForPath(sd) for sd in subdir_paths } + subdirs_du_result = { sd: self.getDiskUsageForPath(sd, force_update=force_update) for sd in subdir_paths } result = {'found':True, 'projectdir': projectdir_du_result, 'sub_directories': subdirs_du_result } logger.info('cache.getDiskUsageForProjectDirAndSubDirectories result: %s' % result) return result return path_result - def getDiskUsageForProjectsDirAndSubDirectories(self): + def getDiskUsageForProjectsDirAndSubDirectories(self, force_update=False): logger.info("cache.getDiskUsageForProjectsDirAndSubDirectories") projects_path = self.disk_usage.path_resolver.projects_path - projectsdir_du_result = self.getDiskUsageForPath(projects_path) + projectsdir_du_result = self.getDiskUsageForPath(projects_path, force_update=force_update) result = {'found':True, 'projectdir': projectsdir_du_result } @@ -472,7 +472,7 @@ class CacheManager: subdir_paths = [os.path.join(projects_path,sd) for sd in project_subdirs_result['sub_directories']] #TODO: potential for parallelization - subdirs_du_result = { sd: self.getDiskUsageForPath(sd) for sd in subdir_paths } + subdirs_du_result = { sd: self.getDiskUsageForPath(sd, force_update=force_update) for sd in subdir_paths } result['sub_directories'] = subdirs_du_result logger.info('cache.getDiskUsageForProjectsDirAndSubDirectories result: %s' % result) diff --git a/SAS/DataManagement/StorageQueryService/rpc.py b/SAS/DataManagement/StorageQueryService/rpc.py index c8f2fc4d3bb..600b3d21d91 100644 --- a/SAS/DataManagement/StorageQueryService/rpc.py +++ b/SAS/DataManagement/StorageQueryService/rpc.py @@ -27,29 +27,29 @@ class StorageQueryRPC(RPCWrapper): def getPathForOTDBId(self, otdb_id): return self.rpc('GetPathForOTDBId', otdb_id=otdb_id) - def getDiskUsageForOTDBId(self, otdb_id, include_scratch_paths=True): - return self._convertTimestamps(self.rpc('GetDiskUsageForOTDBId', otdb_id=otdb_id, include_scratch_paths=include_scratch_paths)) + def getDiskUsageForOTDBId(self, otdb_id, include_scratch_paths=True, force_update=False): + return self._convertTimestamps(self.rpc('GetDiskUsageForOTDBId', otdb_id=otdb_id, include_scratch_paths=include_scratch_paths, force_update=force_update)) - def getDiskUsageForMoMId(self, mom_id, include_scratch_paths=True): - return self._convertTimestamps(self.rpc('GetDiskUsageForMoMId', mom_id=mom_id, include_scratch_paths=include_scratch_paths)) + def getDiskUsageForMoMId(self, mom_id, include_scratch_paths=True, force_update=False): + return self._convertTimestamps(self.rpc('GetDiskUsageForMoMId', mom_id=mom_id, include_scratch_paths=include_scratch_paths, force_update=force_update)) - def getDiskUsageForRADBId(self, radb_id, include_scratch_paths=True): - return self._convertTimestamps(self.rpc('GetDiskUsageForRADBId', radb_id=radb_id, include_scratch_paths=include_scratch_paths)) + def getDiskUsageForRADBId(self, radb_id, include_scratch_paths=True, force_update=False): + return self._convertTimestamps(self.rpc('GetDiskUsageForRADBId', radb_id=radb_id, include_scratch_paths=include_scratch_paths, force_update=force_update)) - def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None, include_scratch_paths=True): - return self._convertTimestamps(self.rpc('GetDiskUsageForTask', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, include_scratch_paths=include_scratch_paths)) + def getDiskUsageForTask(self, radb_id=None, mom_id=None, otdb_id=None, include_scratch_paths=True, force_update=False): + return self._convertTimestamps(self.rpc('GetDiskUsageForTask', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, include_scratch_paths=include_scratch_paths, force_update=force_update)) - def getDiskUsageForTasks(self, radb_ids=None, mom_ids=None, otdb_ids=None, include_scratch_paths=True): - return self._convertTimestamps(self.rpc('GetDiskUsageForTasks', radb_ids=radb_ids, mom_ids=mom_ids, otdb_ids=otdb_ids, include_scratch_paths=include_scratch_paths)) + def getDiskUsageForTasks(self, radb_ids=None, mom_ids=None, otdb_ids=None, include_scratch_paths=True, force_update=False): + return self._convertTimestamps(self.rpc('GetDiskUsageForTasks', radb_ids=radb_ids, mom_ids=mom_ids, otdb_ids=otdb_ids, include_scratch_paths=include_scratch_paths, force_update=force_update)) - def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None): - return self._convertTimestamps(self.rpc('GetDiskUsageForTaskAndSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id)) + def getDiskUsageForTaskAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None, force_update=False): + return self._convertTimestamps(self.rpc('GetDiskUsageForTaskAndSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, force_update=force_update)) - def getDiskUsageForProjectDirAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None, project_name=None): - return self._convertTimestamps(self.rpc('GetDiskUsageForProjectDirAndSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, project_name=project_name)) + def getDiskUsageForProjectDirAndSubDirectories(self, radb_id=None, mom_id=None, otdb_id=None, project_name=None, force_update=False): + return self._convertTimestamps(self.rpc('GetDiskUsageForProjectDirAndSubDirectories', radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, project_name=project_name, force_update=force_update)) - def getDiskUsageForProjectsDirAndSubDirectories(self): - return self._convertTimestamps(self.rpc('GetDiskUsageForProjectsDirAndSubDirectories')) + def getDiskUsageForProjectsDirAndSubDirectories(self, force_update=False): + return self._convertTimestamps(self.rpc('GetDiskUsageForProjectsDirAndSubDirectories', force_update=force_update)) def main(): import sys @@ -64,6 +64,7 @@ def main(): parser.add_option('-s', '--subdirs', dest='subdirs', action='store_true', help='get the disk usage of the task and its sub directories for the given otdb_id/mom_id/radb_id') parser.add_option('-p', '--project', dest='project', type='string', default=None, help='get the disk usage of the project path and all its sub directories for the given project name') parser.add_option('-P', '--projects', dest='projects', action='store_true', help='get the disk usage of the projects path and all its projects sub directories') + parser.add_option('-f', '--force_update', dest='force_update', action='store_true', help='force an update of the cache with a new du call') parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') parser.add_option('--busname', dest='busname', type='string', default=DEFAULT_BUSNAME, help='Name of the bus exchange on the qpid broker, default: %s' % DEFAULT_BUSNAME) parser.add_option('--servicename', dest='servicename', type='string', default=DEFAULT_SERVICENAME, help='Name for this service, default: %s' % DEFAULT_SERVICENAME) @@ -79,7 +80,7 @@ def main(): with StorageQueryRPC(busname=options.busname, servicename=options.servicename, broker=options.broker) as rpc: if options.projects: - result = rpc.getDiskUsageForProjectsDirAndSubDirectories() + result = rpc.getDiskUsageForProjectsDirAndSubDirectories(force_update=options.force_update) if result['found']: import pprint pprint.pprint(result) @@ -87,7 +88,7 @@ def main(): print result['message'] exit(1) elif options.project: - result = rpc.getDiskUsageForProjectDirAndSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id, project_name=options.project) + result = rpc.getDiskUsageForProjectDirAndSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id, project_name=options.project, force_update=options.force_update) if result['found']: import pprint pprint.pprint(result) @@ -95,7 +96,7 @@ def main(): print result['message'] exit(1) elif options.subdirs: - result = rpc.getDiskUsageForTaskAndSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + result = rpc.getDiskUsageForTaskAndSubDirectories(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id, force_update=options.force_update) if result['found']: import pprint pprint.pprint(result) @@ -103,11 +104,11 @@ def main(): print result['message'] exit(1) else: - result = rpc.getDiskUsageForTask(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id) + result = rpc.getDiskUsageForTask(otdb_id=options.otdb_id, mom_id=options.mom_id, radb_id=options.radb_id, force_update=options.force_update) if result['found']: print 'path %s' % result['path'] - print 'disk_usage %s %s' % (result['disk_usage'], result.get('disk_usage_readable')) - print 'nr_of_files %s' % result['nr_of_files'] + print 'disk_usage %s %s' % (result.get('disk_usage'), result.get('disk_usage_readable')) + print 'nr_of_files %s' % result.get('nr_of_files') else: print result['message'] exit(1) -- GitLab From 60bf1a9d8e15814fc4b36ee7790b22391a1ba579 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 23 Sep 2016 06:59:07 +0000 Subject: [PATCH 850/933] Task #9607: use get instead of [] --- SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py index 2075f3458ea..77a3a31ac73 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py @@ -64,7 +64,7 @@ def updateTaskStorageDetails(tasks, sqrpc): otdb_id = str(task['otdb_id']) if otdb_id in usages: usage = usages[otdb_id] - task['disk_usage'] = usage['disk_usage'] + task['disk_usage'] = usage.get('disk_usage') task['disk_usage_readable'] = usage.get('disk_usage_readable') except Exception as e: -- GitLab From 3f36c857bf25dd443bed5cea09c0749449bd8c63 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 23 Sep 2016 06:59:49 +0000 Subject: [PATCH 851/933] Task #9607: added nr_of_dataproducts property --- .../ResourceAssignmentEditor/lib/mom.py | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py index 484a3bf25f0..b3f45012727 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py @@ -76,32 +76,35 @@ def updateTaskMomDetails(task, momrpc): for t in tasklist: mom_id = str(t['mom_id']) t['ingest_status'] = None + t['nr_of_dataproducts'] = None if mom_id in results: dps = results[mom_id] - if dps: - num_ingested = 0 - num_ingest_pending = 0 - num_ingest_running = 0 - num_ingest_failed = 0 - num_ingest_hold = 0 - for dp in dps: - if dp['status'] == 'ingested': - num_ingested += 1 - elif dp['status'] == 'pending': - num_ingest_pending += 1 - elif dp['status'] == 'running': - num_ingest_running += 1 - elif dp['status'] == 'failed': - num_ingest_failed += 1 - elif dp['status'] == 'on_hold': - num_ingest_hold += 1 - - if num_ingested == len(dps): - t['ingest_status'] = 'ingested' - elif num_ingest_pending + num_ingest_running > 0: - t['ingest_status'] = 'ingesting' - elif num_ingest_failed + num_ingest_hold > 0: - t['ingest_status'] = 'failed' + if dps != None: + t['nr_of_dataproducts'] = len(dps) + if len(dps) > 0: + num_ingested = 0 + num_ingest_pending = 0 + num_ingest_running = 0 + num_ingest_failed = 0 + num_ingest_hold = 0 + for dp in dps: + if dp['status'] == 'ingested': + num_ingested += 1 + elif dp['status'] == 'pending': + num_ingest_pending += 1 + elif dp['status'] == 'running': + num_ingest_running += 1 + elif dp['status'] == 'failed': + num_ingest_failed += 1 + elif dp['status'] == 'on_hold': + num_ingest_hold += 1 + + if num_ingested == len(dps): + t['ingest_status'] = 'ingested' + elif num_ingest_pending + num_ingest_running > 0: + t['ingest_status'] = 'ingesting' + elif num_ingest_failed + num_ingest_hold > 0: + t['ingest_status'] = 'failed' except Exception as e: logger.error(str(e)) -- GitLab From b5c2340416b1e06caa2b7158cdd1807f93018ca0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 23 Sep 2016 09:23:19 +0000 Subject: [PATCH 852/933] Task #9607: handle pipelines which ran multiple times --- SAS/ResourceAssignment/RAScripts/povero | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/RAScripts/povero b/SAS/ResourceAssignment/RAScripts/povero index de23a38d276..6294620b2ee 100755 --- a/SAS/ResourceAssignment/RAScripts/povero +++ b/SAS/ResourceAssignment/RAScripts/povero @@ -40,24 +40,28 @@ from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SER logger = logging.getLogger(__name__) def getSlurmStats(otdb_id): - cmd = ['ssh', 'lofarsys@head01.cep4.control.lofar', 'sacct', '-o', 'cputimeraw,nnodes', '--name=%s' % otdb_id, '-S', '2016-01-01', '-X', '--parsable2', '-n'] + cmd = ['ssh', 'lofarsys@head01.cep4.control.lofar', 'sacct', '-o', 'jobid,cputimeraw,nnodes', '--name=%s' % otdb_id, '-S', '2016-01-01', '-X', '--parsable2', '-n'] logger.debug(' '.join(cmd)) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() if proc.returncode == 0: try: - parts = out.split('|') - cputimeraw = int(parts[0]) - nnodes = int(parts[1]) + lines = [l.strip() for l in out.split('\n')] + lines.sort() + last_job_line = lines[-1] + parts = last_job_line.split('|') + jobid = int(parts[0]) + cputimeraw = int(parts[1]) + nnodes = int(parts[2]) clusterusage = nnodes / 50.0 - return cputimeraw, nnodes, clusterusage + return jobid, cputimeraw, nnodes, clusterusage except Exception as e: logger.error(e) else: logger.error(err) - return 0, 0, 0 + return 0, 0, 0, 0 def main(): # make sure we run in UTC timezone @@ -89,12 +93,12 @@ def main(): pipelines = [ra.getTask(otdb_id=options.otdb_id)] if options.otdb_id else ra.getTasks(cluster='CEP4', task_type='pipeline', task_status='finished') with open(args[0], 'w') as csv_file: - line = "Project, OBS otdb_id, OBS name, #demix_sources, demix_sources, antennaArray, antennaSet, OBS duration [s], PL otdb_id, PL name, #subbands, type, cluster usage [%], PL duration [s], PL duration_norm [s], P/O" + line = "Project, OBS otdb_id, OBS name, #demix_sources, demix_sources, antennaArray, antennaSet, OBS duration [s], PL slurm_id, PL otdb_id, PL name, #subbands, type, cluster usage [%], PL duration [s], PL duration_norm [s], P/O" csv_file.write(line + "\n") print line for pl in pipelines: - cputimeraw, nnodes, clusterusage = getSlurmStats(pl['otdb_id']) + jobid, cputimeraw, nnodes, clusterusage = getSlurmStats(pl['otdb_id']) if nnodes == 0: continue @@ -126,7 +130,7 @@ def main(): obs_name = pred_obs_parset.getString('ObsSW.Observation.Scheduler.taskName', 'unknown') values = [projectName, pred_obs['otdb_id'], obs_name, len(pl_demix_sources), ';'.join(pl_demix_sources), obs_antennaArray, obs_antennaSet, pred_obs['duration'], - pl['otdb_id'], pl_name, pl_nrofsubbands, pl_sub_type, 100.0*clusterusage, pl['duration'], pl_full_cluster_duration, '%.2f' % (pl_full_cluster_duration/pred_obs['duration'])] + jobid, pl['otdb_id'], pl_name, pl_nrofsubbands, pl_sub_type, 100.0*clusterusage, pl['duration'], pl_full_cluster_duration, '%.2f' % (pl_full_cluster_duration/pred_obs['duration'])] line = ', '.join([str(x) for x in values]) csv_file.write(line + '\n') print line -- GitLab From 0931c3bd89d7557cb31fb69220291a074ea63e7f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 23 Sep 2016 11:37:15 +0000 Subject: [PATCH 853/933] Task #9607: print progress --- SAS/ResourceAssignment/RAScripts/povero | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/RAScripts/povero b/SAS/ResourceAssignment/RAScripts/povero index 6294620b2ee..deff3791e0d 100755 --- a/SAS/ResourceAssignment/RAScripts/povero +++ b/SAS/ResourceAssignment/RAScripts/povero @@ -97,7 +97,7 @@ def main(): csv_file.write(line + "\n") print line - for pl in pipelines: + for i, pl in enumerate(pipelines): jobid, cputimeraw, nnodes, clusterusage = getSlurmStats(pl['otdb_id']) if nnodes == 0: @@ -133,7 +133,7 @@ def main(): jobid, pl['otdb_id'], pl_name, pl_nrofsubbands, pl_sub_type, 100.0*clusterusage, pl['duration'], pl_full_cluster_duration, '%.2f' % (pl_full_cluster_duration/pred_obs['duration'])] line = ', '.join([str(x) for x in values]) csv_file.write(line + '\n') - print line + print '%.1f%%: ' % (100.0*i/len(pipelines)), line print "Done!" -- GitLab From d2f69e8dd15a8bd1697040f9ec2b3af50464dd04 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 23 Sep 2016 11:37:50 +0000 Subject: [PATCH 854/933] Task #9607: added description and sub_type --- SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py index b3f45012727..70c414eeb3a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py @@ -63,6 +63,8 @@ def updateTaskMomDetails(task, momrpc): t['project_name'] = m['project_name'] t['project_mom_id'] = m['project_mom2id'] t['project_mom2object_id'] = m['project_mom2objectid'] + t['description'] = m.get('object_description', '') + t['sub_type'] = m.get('object_type', t['type']).lower() t['mom2object_id'] = m['object_mom2objectid'] t['mom_object_group_id'] = m['object_group_id'] t['mom_object_group_name'] = m.get('object_group_name') -- GitLab From c0bb73ae9047f57279b4b2daa4e80ae7cd34b241 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 23 Sep 2016 11:39:47 +0000 Subject: [PATCH 855/933] Task #9886: added direct link to LTA catalogue for ingested tasks --- .../static/app/controllers/gridcontroller.js | 30 +++++++++++++++---- .../lib/webservice.py | 3 ++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 724621385f6..dfefca1460b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -626,12 +626,32 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - for(var t of selected_tasks) { - if(t) { - var url = dataService.config.inspection_plots_base_url + '/' + t.otdb_id; - $window.open(url, '_blank'); - } + var url = dataService.config.inspection_plots_base_url + '/' + task.otdb_id; + $window.open(url, '_blank'); + }); + } + + if(task.ingest_status == 'ingested' && dataService.config.lta_base_url) { + var liElement = angular.element('<li><a href="#">Open in LTA catalogue</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + //example: http://lofar.target.rug.nl/Lofar?mode=query_result_page_user&product=AveragingPipeline&ObservationId=544965&project=LC6_015 + //map task.sub_type to url product parameter + var product; + switch(task.sub_type) { + case 'averaging_pipeline': product = 'AveragingPipeline'; break; + case 'calibration_pipeline': product = 'CalibrationPipeline'; break; + case 'pulsar_pipeline': product = 'PulsarPipeline'; break; + case 'lofar_imaging_pipeline': product = 'ImagingPipeline'; break; + case 'imaging_pipeline_msss': product = 'ImagingPipeline'; break; + case 'long_baseline_pipeline': product = 'LongBaselinePipeline'; break; + case 'lofar_observation': product = 'Observation'; break; + default: + return; } + var url = dataService.config.lta_base_url + '/Lofar?mode=query_result_page_user&product=' + product + '&ObservationId=' + task.otdb_id + '&project=' + task.project_name; + $window.open(url, '_blank'); }); } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 5cb338f6795..df76efa8cc5 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -125,12 +125,15 @@ def index(): @gzipped def config(): config = {'mom_base_url':'', + 'lta_base_url':'', 'inspection_plots_base_url':'https://proxy.lofar.eu/inspect/HTML/'} if isProductionEnvironment(): config['mom_base_url'] = 'https://lofar.astron.nl/mom3' + config['lta_base_url'] = 'http://lofar.target.rug.nl/' elif isTestEnvironment(): config['mom_base_url'] = 'http://lofartest.control.lofar:8080/mom3' + config['lta_base_url'] = 'http://lofar-test.target.rug.nl/' return jsonify({'config': config}) -- GitLab From 7b0e40e86aa6222e0814b857202404574f35eaf3 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Fri, 23 Sep 2016 12:56:20 +0000 Subject: [PATCH 856/933] Task #9886: added direct link to LTA catalogue for ingested tasks, now works also with multiple tasks even over different projects --- .../static/app/controllers/gridcontroller.js | 53 ++++++++++++++----- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index dfefca1460b..bc41062e8bf 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -631,27 +631,52 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } - if(task.ingest_status == 'ingested' && dataService.config.lta_base_url) { + var ingest_tasks = selected_tasks.filter(function(t) { return t.ingest_status != undefined; }); + + if(ingest_tasks.length > 0 && dataService.config.lta_base_url) { var liElement = angular.element('<li><a href="#">Open in LTA catalogue</a></li>'); ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); //example: http://lofar.target.rug.nl/Lofar?mode=query_result_page_user&product=AveragingPipeline&ObservationId=544965&project=LC6_015 //map task.sub_type to url product parameter - var product; - switch(task.sub_type) { - case 'averaging_pipeline': product = 'AveragingPipeline'; break; - case 'calibration_pipeline': product = 'CalibrationPipeline'; break; - case 'pulsar_pipeline': product = 'PulsarPipeline'; break; - case 'lofar_imaging_pipeline': product = 'ImagingPipeline'; break; - case 'imaging_pipeline_msss': product = 'ImagingPipeline'; break; - case 'long_baseline_pipeline': product = 'LongBaselinePipeline'; break; - case 'lofar_observation': product = 'Observation'; break; - default: - return; + var project2project2product2tasksDict = {}; + + for(var t of ingest_tasks) { + var lta_product; + switch(t.sub_type) { + case 'averaging_pipeline': lta_product = 'AveragingPipeline'; break; + case 'calibration_pipeline': lta_product = 'CalibrationPipeline'; break; + case 'pulsar_pipeline': lta_product = 'PulsarPipeline'; break; + case 'lofar_imaging_pipeline': lta_product = 'ImagingPipeline'; break; + case 'imaging_pipeline_msss': lta_product = 'ImagingPipeline'; break; + case 'long_baseline_pipeline': lta_product = 'LongBaselinePipeline'; break; + case 'lofar_observation': lta_product = 'Observation'; break; + } + if(lta_product) { + if(!project2project2product2tasksDict.hasOwnProperty(t.project_name)) { + project2project2product2tasksDict[t.project_name] = {}; + } + if(!project2project2product2tasksDict[t.project_name].hasOwnProperty(lta_product)) { + project2project2product2tasksDict[t.project_name][lta_product] = []; + } + project2project2product2tasksDict[t.project_name][lta_product].push(t); + } + } + + var window_cntr = 0; + for(var project in project2project2product2tasksDict) { + for(var product in project2project2product2tasksDict[project]) { + var product_tasks = project2project2product2tasksDict[project][product]; + var otdb_ids = product_tasks.map(function(pt) { return pt.otdb_id; }); + var otdb_ids_string = otdb_ids.join(','); + var url = dataService.config.lta_base_url + '/Lofar?mode=query_result_page_user&product=' + product + '&ObservationId=' + otdb_ids_string + '&project=' + project; + setTimeout(function(url_arg) { + $window.open(url_arg, '_blank'); + }, window_cntr*250, url); + window_cntr += 1; + } } - var url = dataService.config.lta_base_url + '/Lofar?mode=query_result_page_user&product=' + product + '&ObservationId=' + task.otdb_id + '&project=' + task.project_name; - $window.open(url, '_blank'); }); } -- GitLab From 1ec159c1ccedbdca5307122e676ac05d5550ee29 Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Mon, 26 Sep 2016 11:29:53 +0000 Subject: [PATCH 857/933] Task #6517: New License file for CCU002 --- .gitattributes | 3 + .../471_3031_2_Astron_Gen_II_3_314.log | 31 +++++++++++ .../PVSS/License/Astron_Station_1_shield.txt | 55 +++++++++---------- .../data/PVSS/License/CCU002_option.txt | 29 ++++++++++ .../data/PVSS/License/shield.CCU002.txt | 29 ++++++++++ 5 files changed, 119 insertions(+), 28 deletions(-) create mode 100644 MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log create mode 100644 MAC/Deployment/data/PVSS/License/CCU002_option.txt create mode 100644 MAC/Deployment/data/PVSS/License/shield.CCU002.txt diff --git a/.gitattributes b/.gitattributes index 2313004a7f5..2908022eaed 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3482,6 +3482,8 @@ MAC/Deployment/data/OTDB/genArrayTable.py -text MAC/Deployment/data/OTDB/genArrayTest.py -text MAC/Deployment/data/PVSS/License/471_3031_1_Astron_Gen_I_3_314.log -text MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_2_311.log -text +MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log -text +MAC/Deployment/data/PVSS/License/CCU002_option.txt -text MAC/Deployment/data/PVSS/License/CCU099_option.txt -text MAC/Deployment/data/PVSS/License/CS001C_option_lcu037.txt -text MAC/Deployment/data/PVSS/License/CS002_option.txt -text @@ -3552,6 +3554,7 @@ MAC/Deployment/data/PVSS/License/RS509C_option.txt -text MAC/Deployment/data/PVSS/License/SE607C_option.txt -text MAC/Deployment/data/PVSS/License/Station.rtu -text MAC/Deployment/data/PVSS/License/UK608C_option.txt -text +MAC/Deployment/data/PVSS/License/shield.CCU002.txt -text MAC/Deployment/data/PVSS/License/shield.CCU099.txt -text MAC/Deployment/data/PVSS/License/shield.CS001C.txt -text MAC/Deployment/data/PVSS/License/shield.CS001C_lcu037.txt -text diff --git a/MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log b/MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log new file mode 100644 index 00000000000..f1a4e42cd34 --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log @@ -0,0 +1,31 @@ + +--------------------------------------------------- +[license] +code = "ccu002.control.lofar 60371410767" +version = 31400002 +sn = "471_3031_2_Astron_Gen_II_3_314/1" +date = 2016.09.26;13:29:09,000 +comment = "CentOS7 CepCU system CCU002" +expire = 0000.00.00;00:00:00,000 +redundancy = 0 +ui = 2 +para = 1 +dde = 5 +event = 1 +ios = 4000 +ssi = 0 +api = 80 +excelreport = 5 +http = 0 +infoserver = 1000 +comcenter = 5 +maintenance = 1 +scheduler = 1 +recipe = 1 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + diff --git a/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt b/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt index f0a7c1ba441..2e623aca67b 100644 --- a/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt +++ b/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt @@ -1,30 +1,29 @@ [license] -#hw = 12831493085 -code = "dongleHost 10388280866" -version = 31400002 -sn = "471_3031_2_Astron_Gen_II_3_314" -expire = 0000.00.00;00:00:00,000 -redundancy = 0 -ui = 2 -para = 1 -dde = 5 -event = 1 -api = 80 -excelreport = 5 -http = 0 -infoserver = 1000 -ios = 4000 -comcenter = 5 -maintenance = 1 -scheduler = 1 -ssi = 0 -recipe = 1 -distributed = 255 -uifix = 0 -parafix = 0 -pararemote = 0 -ctrlext = 1 -update = 0 -licenseMax = 100 -licenseLeft = 100 +code = "dongleHost 10312063756" +version = 31400002 +sn = "471_3031_2_Astron_Gen_II_3_314" +expire = 0000.00.00;00:00:00,000 +redundancy = 0 +ui = 2 +para = 1 +dde = 5 +event = 1 +ios = 4000 +ssi = 0 +api = 80 +excelreport = 5 +http = 0 +infoserver = 1000 +comcenter = 5 +maintenance = 1 +scheduler = 1 +recipe = 1 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 +licenseMax = 100 +licenseLeft = 99 diff --git a/MAC/Deployment/data/PVSS/License/CCU002_option.txt b/MAC/Deployment/data/PVSS/License/CCU002_option.txt new file mode 100644 index 00000000000..a69ed03666d --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/CCU002_option.txt @@ -0,0 +1,29 @@ +[license] +code = "ccu002.control.lofar 14006546712" +version = 31400002 +comment = "CentOS7 CepCU system CCU002" +sn = "471_3031_2_Astron_Gen_II_1_38" +expire = 0000.00.00;00:00:00,000 +redundancy = 0 +ui = 2 +para = 1 +dde = 5 +event = 1 +api = 80 +excelreport = 5 +http = 0 +infoserver = 1000 +ios = 4000 +comcenter = 5 +maintenance = 1 +scheduler = 1 +ssi = 0 +recipe = 1 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + + diff --git a/MAC/Deployment/data/PVSS/License/shield.CCU002.txt b/MAC/Deployment/data/PVSS/License/shield.CCU002.txt new file mode 100644 index 00000000000..fd5563bd7f2 --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/shield.CCU002.txt @@ -0,0 +1,29 @@ +[license] +code = "ccu002.control.lofar 60371410767" +version = 31400002 +sn = "471_3031_2_Astron_Gen_II_3_314/1" +date = 2016.09.26;13:29:09,000 +comment = "CentOS7 CepCU system CCU002" +expire = 0000.00.00;00:00:00,000 +redundancy = 0 +ui = 2 +para = 1 +dde = 5 +event = 1 +ios = 4000 +ssi = 0 +api = 80 +excelreport = 5 +http = 0 +infoserver = 1000 +comcenter = 5 +maintenance = 1 +scheduler = 1 +recipe = 1 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + -- GitLab From 679ea82e036b3b07fc9f657872fbe051acb55531 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 26 Sep 2016 13:04:53 +0000 Subject: [PATCH 858/933] Task #9607: added starttimes. minor fixes. --- SAS/ResourceAssignment/RAScripts/povero | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/SAS/ResourceAssignment/RAScripts/povero b/SAS/ResourceAssignment/RAScripts/povero index deff3791e0d..8c9df128f62 100755 --- a/SAS/ResourceAssignment/RAScripts/povero +++ b/SAS/ResourceAssignment/RAScripts/povero @@ -36,6 +36,7 @@ from lofar.sas.resourceassignment.resourceassignmentservice.config import DEFAUL from lofar.sas.otdb.otdbrpc import OTDBRPC from lofar.sas.otdb.config import DEFAULT_OTDB_SERVICE_BUSNAME, DEFAULT_OTDB_SERVICENAME +from lofar.messaging import setQpidLogLevel logger = logging.getLogger(__name__) @@ -47,8 +48,8 @@ def getSlurmStats(otdb_id): if proc.returncode == 0: try: - lines = [l.strip() for l in out.split('\n')] - lines.sort() + logger.debug(out.strip()) + lines = [l.strip() for l in out.strip().split('\n')] last_job_line = lines[-1] parts = last_job_line.split('|') jobid = int(parts[0]) @@ -77,6 +78,7 @@ def main(): parser.add_option('--otdb_busname', dest='otdb_busname', type='string', default=DEFAULT_OTDB_SERVICE_BUSNAME, help='Name of the bus exchange on the qpid broker on which the otdbservice listens, default: %default') parser.add_option('--otdb_servicename', dest='otdb_servicename', type='string', default=DEFAULT_OTDB_SERVICENAME, help='Name of the otdbservice, default: %default') parser.add_option('-o', '--otdb_id', dest='otdb_id', type='int', default=None, help='compute P/O for the given pipeline otdb_id') + parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') (options, args) = parser.parse_args() if len(args) != 1: @@ -84,7 +86,8 @@ def main(): exit(1) logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', - level=logging.INFO) + level=logging.DEBUG if options.verbose else logging.INFO) + setQpidLogLevel(logging.INFO) ra = RARPC(busname=options.radb_busname, servicename=options.radb_servicename, broker=options.broker) otdb = OTDBRPC(busname=options.otdb_busname, servicename=options.otdb_servicename, broker=options.broker) @@ -93,7 +96,7 @@ def main(): pipelines = [ra.getTask(otdb_id=options.otdb_id)] if options.otdb_id else ra.getTasks(cluster='CEP4', task_type='pipeline', task_status='finished') with open(args[0], 'w') as csv_file: - line = "Project, OBS otdb_id, OBS name, #demix_sources, demix_sources, antennaArray, antennaSet, OBS duration [s], PL slurm_id, PL otdb_id, PL name, #subbands, type, cluster usage [%], PL duration [s], PL duration_norm [s], P/O" + line = "Project, OBS otdb_id, OBS name, #demix_sources, demix_sources, antennaArray, antennaSet, OBS starttime (UTC), OBS duration [s], PL slurm_id, PL otdb_id, PL name, #subbands, type, cluster usage [%], PL starttime (UTC), PL duration [s], PL duration_norm [s], P/O" csv_file.write(line + "\n") print line @@ -129,8 +132,8 @@ def main(): projectName = pred_obs_parset.getString('ObsSW.Observation.Campaign.name', 'unknown') obs_name = pred_obs_parset.getString('ObsSW.Observation.Scheduler.taskName', 'unknown') - values = [projectName, pred_obs['otdb_id'], obs_name, len(pl_demix_sources), ';'.join(pl_demix_sources), obs_antennaArray, obs_antennaSet, pred_obs['duration'], - jobid, pl['otdb_id'], pl_name, pl_nrofsubbands, pl_sub_type, 100.0*clusterusage, pl['duration'], pl_full_cluster_duration, '%.2f' % (pl_full_cluster_duration/pred_obs['duration'])] + values = [projectName, pred_obs['otdb_id'], obs_name, len(pl_demix_sources), ';'.join(pl_demix_sources), obs_antennaArray, obs_antennaSet, pred_obs['starttime'], pred_obs['duration'], + jobid, pl['otdb_id'], pl_name, pl_nrofsubbands, pl_sub_type, 100.0*clusterusage, pl['starttime'], pl['duration'], pl_full_cluster_duration, '%.2f' % (pl_full_cluster_duration/pred_obs['duration'])] line = ', '.join([str(x) for x in values]) csv_file.write(line + '\n') print '%.1f%%: ' % (100.0*i/len(pipelines)), line -- GitLab From 9e7ca6525ad14e6b9557efd9340f1bd2d62cced3 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 26 Sep 2016 13:05:37 +0000 Subject: [PATCH 859/933] Task #9607: get mom2id from all tasks within a parent group --- SAS/MoM/MoMQueryService/momqueryrpc.py | 15 ++++++ SAS/MoM/MoMQueryService/momqueryservice.py | 63 +++++++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/SAS/MoM/MoMQueryService/momqueryrpc.py b/SAS/MoM/MoMQueryService/momqueryrpc.py index 3f0d88a2edf..9d228e18f1d 100644 --- a/SAS/MoM/MoMQueryService/momqueryrpc.py +++ b/SAS/MoM/MoMQueryService/momqueryrpc.py @@ -63,6 +63,12 @@ class MoMQueryRPC(RPCWrapper): logger.info("getTaskIdsInGroup(%s): %s", mom_group_ids, result) return result + def getTaskIdsInParentGroup(self, mom_parent_group_ids): + logger.debug("getTaskIdsInParentGroup(%s)", mom_parent_group_ids) + result = self.rpc('GetTaskIdsInParentGroup', mom_parent_group_ids=mom_parent_group_ids) + logger.info("getTaskIdsInParentGroup(%s): %s", mom_parent_group_ids, result) + return result + def getDataProducts(self, ids): logger.debug("getDataProducts(%s)", ids) result = self.rpc('GetDataProducts', mom_ids=ids) @@ -83,6 +89,7 @@ def main(): parser.add_option('--predecessors', dest='id_for_predecessors', type='int', help='get the predecessor id\'s for the given mom2id') parser.add_option('--successors', dest='id_for_successors', type='int', help='get the successors id\'s for the given mom2id') parser.add_option('-g', '--group', dest='group_id', type='int', help='get the tasks ids in the given group mom2id') + parser.add_option('--parent_group', dest='parent_group_id', type='int', help='get the tasks ids in the given parent group mom2id') parser.add_option('-d', '--dataproducts', dest='id_for_dataproducts', type='int', help='get the dataproducts for the given mom2id') (options, args) = parser.parse_args() @@ -130,6 +137,14 @@ def main(): else: print 'No results' + if options.parent_group_id: + task_ids = rpc.getTaskIdsInParentGroup(options.parent_group_id) + if task_ids: + for k, v in task_ids.items(): + print ' %s: %s' % (k, v) + else: + print 'No results' + if options.id_for_dataproducts: results = rpc.getDataProducts(options.id_for_dataproducts) if results: diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index a00d29835d6..ac0c25e85c8 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -119,13 +119,15 @@ class MoMDatabaseWrapper: # TODO: make a view for this query in momdb! query = '''SELECT project.mom2id as project_mom2id, project.id as project_mom2objectid, project.name as project_name, project.description as project_description, - object.mom2id as object_mom2id, object.id as object_mom2objectid, object.name as object_name, object.description as object_description, object.mom2objecttype as object_type, object.group_id as object_group_id, grp.id as object_group_mom2objectid, grp.name as object_group_name, - status.code as object_status + object.mom2id as object_mom2id, object.id as object_mom2objectid, object.name as object_name, object.description as object_description, object.mom2objecttype as object_type, status.code as object_status, + object.group_id as object_group_id, grp.id as object_group_mom2objectid, grp.name as object_group_name, grp.description as object_group_description, + parent_grp.id as parent_group_mom2objectid, parent_grp.mom2id as parent_group_mom2id, parent_grp.name as parent_group_name, parent_grp.description as parent_group_description FROM mom2object as object left join mom2object as project on project.id = object.ownerprojectid left join mom2object as grp on grp.mom2id = object.group_id left join mom2objectstatus as mostatus on object.currentstatusid = mostatus.id inner join status on mostatus.statusid = status.id + left join mom2object as parent_grp on parent_grp.id = grp.parentid where object.mom2id in (%s) order by project_mom2id ''' % (ids_str,) @@ -257,6 +259,59 @@ class MoMDatabaseWrapper: return result + def getGroupsInParentGroup(self, mom_parent_group_ids): + if not mom_parent_group_ids: + return {} + + ids_str = _toIdsString(mom_parent_group_ids) + + logger.debug("getGroupsInParentGroup for mom parent group ids: %s" % ids_str) + + query = '''SELECT parent.id as parent_mom2object_id, parent.mom2id as parent_mom2id, + grp.mom2id as group_mom2id, grp.id as group_mom2object_id, grp.name as group_name, grp.description as group_description + from mom2object parent + inner join mom2object grp on parent.id = grp.parentid + where parent.mom2id in (%s) + and grp.group_id = grp.mom2id''' % ids_str + + rows = self._executeQuery(query) + + result = {} + for parent_group_id in ids_str.split(','): + result[parent_group_id] = [] + + for row in rows: + parent_group_id = row['parent_mom2id'] + result[str(parent_group_id)].append(row) + + logger.info("getGroupsInParentGroup result: %s", result) + + return result + + def getTaskIdsInParentGroup(self, mom_parent_group_ids): + if not mom_parent_group_ids: + return {} + + ids_str = _toIdsString(mom_parent_group_ids) + + logger.debug("getTaskIdsInParentGroup for mom parent group ids: %s" % ids_str) + + groups_result = self.getGroupsInParentGroup(ids_str) + + result = {} + for parent_mom2id, groups in groups_result.items(): + task_mom2ids_for_parent = set() + group_ids = [x['group_mom2id'] for x in groups] + group_tasks_id_result = self.getTaskIdsInGroup(group_ids) + for group_id, task_mom2ids in group_tasks_id_result.items(): + task_mom2ids_for_parent |= set(task_mom2ids) + + result[parent_mom2id] = list(task_mom2ids_for_parent) + + logger.info('getTaskIdsInParentGroup: %s', result) + + return result + def getDataProducts(self, mom_ids): if not mom_ids: return {} @@ -300,6 +355,7 @@ class ProjectDetailsQueryHandler(MessageHandlerInterface): 'GetPredecessorIds': self.getPredecessorIds, 'GetSuccessorIds': self.getSuccessorIds, 'GetTaskIdsInGroup': self.getTaskIdsInGroup, + 'GetTaskIdsInParentGroup': self.getTaskIdsInParentGroup, 'GetDataProducts': self.getDataProducts } @@ -321,6 +377,9 @@ class ProjectDetailsQueryHandler(MessageHandlerInterface): def getTaskIdsInGroup(self, mom_group_ids): return self.momdb.getTaskIdsInGroup(mom_group_ids) + def getTaskIdsInParentGroup(self, mom_parent_group_ids): + return self.momdb.getTaskIdsInParentGroup(mom_parent_group_ids) + def getDataProducts(self, mom_ids): return self.momdb.getDataProducts(mom_ids) -- GitLab From a1deb3348e08b07aa9939f2eeaa0adb6cb0ccc9f Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 26 Sep 2016 13:07:00 +0000 Subject: [PATCH 860/933] Task #9607: Select tasks by parent group --- .../ResourceAssignmentEditor/lib/mom.py | 1 + .../static/app/controllers/datacontroller.js | 56 +++++++++++++++++++ .../static/app/controllers/gridcontroller.js | 43 ++++++++++++-- .../angular-gantt-contextmenu-plugin.js | 7 +++ .../lib/webservice.py | 18 +++++- 5 files changed, 117 insertions(+), 8 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py index 70c414eeb3a..328c24e9904 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py @@ -69,6 +69,7 @@ def updateTaskMomDetails(task, momrpc): t['mom_object_group_id'] = m['object_group_id'] t['mom_object_group_name'] = m.get('object_group_name') t['mom_object_group_mom2object_id'] = m.get('object_group_mom2objectid') + t['mom_object_parent_group_id'] = m['parent_group_mom2id'] else: t['project_name'] = 'OTDB Only' t['project_mom_id'] = -98 diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 9011f4b027a..51441b87330 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -439,6 +439,41 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, return defer.promise; }; + self.getTasksByMoMParentGroupId = function(mom_object_parent_group_id) { + var defer = $q.defer(); + var url = '/rest/tasks/mom/parentgroup/' + mom_object_parent_group_id; + + $http.get(url).success(function(result) { + //convert datetime strings to Date objects + for(var i in result.tasks) { + var task = result.tasks[i]; + task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); + task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); + } + + var newTaskDict = self.toIdBasedDict(result.tasks); + var newTaskIds = Object.keys(newTaskDict); + + for(var i = newTaskIds.length-1; i >= 0; i--) { + var task_id = newTaskIds[i]; + if(!self.taskDict.hasOwnProperty(task_id)) { + var task = newTaskDict[task_id]; + self.tasks.push(task); + self.taskDict[task_id] = task; + } + } + + self.taskChangeCntr++; + self.computeMinMaxTaskTimes(); + + defer.resolve(result.tasks); + }).error(function(result) { + defer.resolve(undefined); + }); + + return defer.promise; + }; + self.copyTask = function(task) { $http.put('/rest/tasks/' + task.id + '/copy').error(function(result) { console.log("Error. Could not copy task. " + result); @@ -950,6 +985,27 @@ dataControllerMod.controller('DataController', return defer.promise; }; + $scope.loadTasksByMoMParentGroupIdSelectAndJumpIntoView = function(mom_parent_group_id) { + var defer = $q.defer(); + $scope.dataService.getTasksByMoMParentGroupId(mom_parent_group_id).then(function(tasks) { + if(tasks) { + var task_ids = tasks.map(function(t) { return t.id; }); + + $scope.dataService.setSelectedTaskIds(task_ids); + + if(tasks.length > 1) { + $scope.dataService.selected_project_id = tasks[0].project_mom_id; + } + + $scope.jumpToSelectedTasks(); + defer.resolve(tasks); + } else { + defer.resolve(undefined); + } + }); + return defer.promise; + }; + $scope.selectCurrentTask = function() { var currentTasks = dataService.tasks.filter(function(t) { return t.starttime <= dataService.viewTimeSpan.to && t.endime >= dataService.viewTimeSpan.from; }); if(currentTasks.lenght > 0) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index bc41062e8bf..5c19bb062b4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -223,14 +223,20 @@ $scope.columns = [ if(mom_col && mom_col.filters.length && mom_col.filters[0].hasOwnProperty('term')) { var mom_id = mom_col.filters[0].term; $scope.$parent.$parent.loadTaskByMoMIdSelectAndJumpIntoView(mom_id).then(function(task) { - if(task) { - mom_col.filters[0].term = null; - } else { + mom_col.filters[0].term = null; + if(task == undefined) { //getting the task by mom_id did not find a task //maybe the entered id was a mom group_id? //let's try to loadTasksByMoMGroupIdSelectAndJumpIntoView $scope.$parent.$parent.loadTasksByMoMGroupIdSelectAndJumpIntoView(mom_id).then(function(tasks) { - mom_col.filters[0].term = null; + if(tasks == undefined || tasks.length == 0) { + //getting the tasks by mom group id did not find any tasks + //maybe the entered id was a mom parent group_id? + //let's try to loadTasksByMoMParentGroupIdSelectAndJumpIntoView + $scope.$parent.$parent.loadTasksByMoMParentGroupIdSelectAndJumpIntoView(mom_id).then(function(tasks) { + //pass + }); + } }); } }); @@ -318,6 +324,7 @@ $scope.columns = [ mom_object_group_id: task.mom_object_group_id, mom_object_group_name: task.mom_object_group_name, mom_object_group_mom2object_id: task.mom_object_group_mom2object_id, + mom_object_parent_group_id: task.mom_object_parent_group_id, cluster: task.cluster, blocked_by_ids: task.blocked_by_ids, ingest_status: task.ingest_status === null ? undefined : task.ingest_status, @@ -536,8 +543,24 @@ $scope.columns = [ fillColumFilterSelectOptions(task_info, $scope.columns[6]); }; - $scope.$watch('dataService.selected_task_ids', onSelectedTaskIdsChanged, true);} -]); + $scope.$watch('dataService.selected_task_ids', onSelectedTaskIdsChanged, true); + $scope.$watch('dataService.selected_project_id', function() { + fillProjectsColumFilterSelectOptions(); + + var project_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'project_name'; }); + if(project_col && project_col.filters.length) { + if(dataService.selected_project_id != undefined) { + var projectName = dataService.momProjectsDict[dataService.selected_project_id].name; + if(projectName != undefined) { + var project_names = project_col.filter.selectOptions.map(function(so) { return so.value;}); + if(project_names.includes(projectName)) { + project_col.filters[0].term = projectName; + } + } + } + } + }); +}]); gridControllerMod.directive('contextMenu', ['$document', '$window', function($document, $window) { return { @@ -602,6 +625,14 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do dataCtrlScope.loadTasksByMoMGroupIdSelectAndJumpIntoView(task.mom_object_group_id); }); + var liElement = angular.element('<li><a href="#">Select parent group</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; + dataCtrlScope.loadTasksByMoMParentGroupIdSelectAndJumpIntoView(task.mom_object_parent_group_id); + }); + var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); }); if(blocked_selected_cep4_tasks.length > 0) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index ede6e85c255..746cd6707e5 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -76,6 +76,13 @@ dataCtrlScope.loadTasksByMoMGroupIdSelectAndJumpIntoView(task.mom_object_group_id); }); + var liElement = angular.element('<li><a href="#">Select parent group</a></li>'); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + dataCtrlScope.loadTasksByMoMParentGroupIdSelectAndJumpIntoView(task.mom_object_parent_group_id); + }); + var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); }); if(blocked_selected_cep4_tasks.length > 0) { diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index df76efa8cc5..146ef8a2a79 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -295,6 +295,20 @@ def getTasksByMoMGroupId(mom_group_id): except Exception as e: abort(404) +@app.route('/rest/tasks/mom/parentgroup/<int:mom_parent_group_id>', methods=['GET']) +@gzipped +def getTasksByMoMParentGroupId(mom_parent_group_id): + try: + mom_ids = momqueryrpc.getTaskIdsInParentGroup(mom_parent_group_id)[str(mom_parent_group_id)] + tasks = rarpc.getTasks(mom_ids=mom_ids) + + updateTaskMomDetails(tasks, momqueryrpc) + updateTaskStorageDetails(tasks, sqrpc) + + return jsonify({'tasks': tasks}) + except Exception as e: + abort(404) + @app.route('/rest/tasks/<int:task_id>', methods=['PUT']) def putTask(task_id): if 'Content-Type' in request.headers and \ @@ -751,7 +765,7 @@ def main(): global rarpc rarpc = RARPC(busname=options.radb_busname, servicename=options.radb_servicename, broker=options.broker) global momrpc - momrpc = MoMRPC(busname=options.mom_busname, servicename=options.mom_servicename, timeout=2.5, broker=options.broker) + momrpc = MoMRPC(busname=options.mom_busname, servicename=options.mom_servicename, timeout=10, broker=options.broker) global otdbrpc otdbrpc = OTDBRPC(busname=options.otdb_busname, servicename=options.otdb_servicename, broker=options.broker) global curpc @@ -759,7 +773,7 @@ def main(): global sqrpc sqrpc = StorageQueryRPC(busname=options.storagequery_busname, servicename=options.storagequery_servicename, broker=options.broker) global momqueryrpc - momqueryrpc = MoMQueryRPC(busname=options.mom_query_busname, servicename=options.mom_query_servicename, timeout=2.5, broker=options.broker) + momqueryrpc = MoMQueryRPC(busname=options.mom_query_busname, servicename=options.mom_query_servicename, timeout=10, broker=options.broker) global changeshandler changeshandler = ChangesHandler(radb_busname=options.radb_notification_busname, radb_subjects=options.radb_notification_subjects, dm_busname=options.dm_notification_busname, dm_subjects=options.dm_notification_subjects, -- GitLab From 6697cc9e447aa465fe83290b97889406d17693d2 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 26 Sep 2016 18:08:00 +0000 Subject: [PATCH 861/933] Task #9607: logging --- SAS/MoM/MoMQueryService/momqueryrpc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SAS/MoM/MoMQueryService/momqueryrpc.py b/SAS/MoM/MoMQueryService/momqueryrpc.py index 9d228e18f1d..1ad59e4c361 100644 --- a/SAS/MoM/MoMQueryService/momqueryrpc.py +++ b/SAS/MoM/MoMQueryService/momqueryrpc.py @@ -72,8 +72,7 @@ class MoMQueryRPC(RPCWrapper): def getDataProducts(self, ids): logger.debug("getDataProducts(%s)", ids) result = self.rpc('GetDataProducts', mom_ids=ids) - for mom2id, dps in result.items(): - logger.info('Found %s dataproducts for mom2id %s', len(dps), mom2id) + logger.info('Found # dataproducts per mom2id: %s', ', '.join('%s:%s' % (id, len(dps)) for id, dps in result.items())) return result def main(): -- GitLab From b28aa23c0dcac29dca5936722c6e3ec2505a2570 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 26 Sep 2016 18:08:34 +0000 Subject: [PATCH 862/933] Task #9607: bug fix: selected incorrect timespan for selected tasks --- .../lib/static/app/controllers/datacontroller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 51441b87330..c716386903a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -1041,8 +1041,8 @@ dataControllerMod.controller('DataController', var focusTime = new Date(minStarttime.getTime() + 0.5*selectedTasksDurationInmsec); dataService.viewTimeSpan = { - from: dataService.floorDate(new Date(focusTime.getTime() - 0.25*viewSpanInMinutes*60*1000), 1, 5), - to: dataService.floorDate(new Date(focusTime.getTime() + 0.75*viewSpanInMinutes*60*1000), 1, 5) + from: dataService.floorDate(new Date(focusTime.getTime() - 0.5*viewSpanInMinutes*60*1000), 1, 5), + to: dataService.floorDate(new Date(focusTime.getTime() + 0.5*viewSpanInMinutes*60*1000), 1, 5) }; dataService.autoFollowNow = false; }; -- GitLab From 5780fb9df16131632eabeb00a6bf68841ef794b8 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 26 Sep 2016 18:16:59 +0000 Subject: [PATCH 863/933] Task #9607: bug fix: deselect tasks out of scope --- .../lib/static/app/controllers/datacontroller.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index c716386903a..bdf9c6d5245 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -61,7 +61,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } self.isTaskIdSelected = function(task_id) { - return self.selected_task_ids.indexOf(task_id) != -1; + return self.selected_task_ids.includes(task_id); } self.toggleTaskSelection = function(task_id) { @@ -235,8 +235,14 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, var visibleTasks = []; for(var i = 0; i < numTasks; i++) { var task = self.tasks[i]; - if(task.endtime >= from && task.starttime <= until) + if(task.endtime >= from && task.starttime <= until) { visibleTasks.push(task); + } + else { + if(self.isTaskIdSelected(task.id)) { + self.removeSelectedTaskId(task.id); + } + } } self.tasks = visibleTasks; -- GitLab From 3cc23a0348ed716b1a15fe7929ddefc3e9bdb6ff Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Mon, 26 Sep 2016 18:33:08 +0000 Subject: [PATCH 864/933] Task #9607: multi threaded support --- SAS/MoM/MoMQueryService/momqueryservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index ac0c25e85c8..e950237a9b3 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -402,7 +402,7 @@ def createService(busname=DEFAULT_MOMQUERY_BUSNAME, return Service(servicename, handler, busname=busname, - numthreads=1, + numthreads=8, use_service_methods=True, verbose=False, broker=broker, -- GitLab From 212068a9ff7264905a1f2e34e4fed0f17e92b2a7 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 27 Sep 2016 08:13:14 +0000 Subject: [PATCH 865/933] Task #9607: bug fix: added scratch paths --- SAS/DataManagement/StorageQueryService/cache.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/SAS/DataManagement/StorageQueryService/cache.py b/SAS/DataManagement/StorageQueryService/cache.py index 9e239049540..6416ce67631 100644 --- a/SAS/DataManagement/StorageQueryService/cache.py +++ b/SAS/DataManagement/StorageQueryService/cache.py @@ -382,7 +382,16 @@ class CacheManager: path_result = self.disk_usage.path_resolver.getPathForTask(radb_id=radb_id, mom_id=mom_id, otdb_id=otdb_id, include_scratch_paths=include_scratch_paths) if path_result['found']: - return self.getDiskUsageForPath(path_result['path'], force_update=force_update) + path_du_result = self.getDiskUsageForPath(path_result['path'], force_update=force_update) + + if 'scratch_paths' in path_result: + path_du_result['scratch_paths'] = {} + + for scratch_path in path_result['scratch_paths']: + scratch_path_du_result = self.getDiskUsageForPath(scratch_path, force_update=force_update) + path_du_result['scratch_paths'][scratch_path] = scratch_path_du_result + + return path_du_result return {'found': False, 'path': path_result['path']} -- GitLab From 7ea47f7ec8f8da556dc3a1760f339379733fce5c Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 27 Sep 2016 08:14:37 +0000 Subject: [PATCH 866/933] Task #9607: adapted custom timespan string --- .../lib/static/app/controllers/datacontroller.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index bdf9c6d5245..647af631dac 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -1105,7 +1105,13 @@ dataControllerMod.controller('DataController', } else { var customZoomTimespan = $scope.zoomTimespans.find(function(zts) { return zts.name.startsWith('Custom'); }); customZoomTimespan.value = viewTimeSpanInMinutes; - customZoomTimespan.name = 'Custom (' + viewTimeSpanInMinutes + ' min)'; + if(viewTimeSpanInMinutes < 1440) { + customZoomTimespan.name = 'Custom (' + viewTimeSpanInMinutes + ' min)'; + } else { + var viewTimeSpanInDays = Math.floor(viewTimeSpanInMinutes / 1440); + var viewTimeSpanReaminingMinutes = viewTimeSpanInMinutes - viewTimeSpanInDays * 1440; + customZoomTimespan.name = 'Custom (' + viewTimeSpanInDays + ' days ' + viewTimeSpanReaminingMinutes + ' min)'; + } $scope.zoomTimespan = customZoomTimespan; } }; -- GitLab From 1070e8b1c7b9f5b4b0b0ace8b5c8c4014d72a5ab Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 27 Sep 2016 08:15:36 +0000 Subject: [PATCH 867/933] Task #9607: show scratch amounts in cleanup. show multiple tasks disk usage --- .../app/controllers/cleanupcontroller.js | 63 +++++++++++++++---- .../static/app/controllers/gridcontroller.js | 31 +++++---- .../angular-gantt-contextmenu-plugin.js | 20 +++--- 3 files changed, 83 insertions(+), 31 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js index 13de0463b74..79abb6f8ebb 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -18,11 +18,6 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m return defer.promise; }; - self.deleteSelectedTasksDataWithConfirmation = function() { - var tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); - self.deleteTasksDataWithConfirmation(tasks); - } - self.deleteTasksDataWithConfirmation = function(tasks) { suc_tasks_promises = []; for(var task of tasks) { @@ -96,7 +91,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m <label ng-if="has_im" style="margin-left:24px">IM: {{amount_im}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_im"></label>\ <label ng-if="has_img" style="margin-left:24px">Images: {{amount_img}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_img"></label>\ <label ng-if="has_pulp" style="margin-left:24px">Pulp: {{amount_pulp}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_pulp"></label>\ - <label ng-if="has_scratch" style="margin-left:24px">scratch<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_scratch"></label>\ + <label ng-if="has_scratch" style="margin-left:24px">scratch: {{amount_scratch}}<input style="margin-left:8px" type="checkbox" ng-model="$parent.delete_scratch"></label>\ </div>\ <div class="modal-footer">\ <button class="btn btn-primary" type="button" ng-click="ok()">OK</button>\ @@ -117,6 +112,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m $scope.amount_im = 0; $scope.amount_img = 0; $scope.amount_pulp = 0; + $scope.amount_scratch = 0; for(var du_result of du_results) { var path = du_result.task_directory.path; @@ -126,7 +122,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m var has_im = du_result.sub_directories.hasOwnProperty(path + '/im'); var has_img = du_result.sub_directories.hasOwnProperty(path + '/img'); var has_pulp = du_result.sub_directories.hasOwnProperty(path + '/pulp'); - var has_scratch = du_result.sub_directories.hasOwnProperty('scratch_paths'); + var has_scratch = du_result.task_directory.hasOwnProperty('scratch_paths'); $scope.has_is |= has_is; $scope.has_cs |= has_cs; @@ -142,6 +138,14 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m $scope.amount_im += has_im ? du_result.sub_directories[path + '/im'].disk_usage : 0; $scope.amount_img += has_img ? du_result.sub_directories[path + '/img'].disk_usage : 0; $scope.amount_pulp += has_pulp ? du_result.sub_directories[path + '/pulp'].disk_usage : 0; + if(has_scratch) { + for(var scratch_path in du_result.task_directory.scratch_paths) { + var scratch_path_du = du_result.task_directory.scratch_paths[scratch_path]; + if(scratch_path_du.found) { + $scope.amount_scratch += scratch_path_du.disk_usage; + } + } + } } $scope.amount_is = dataService.humanreadablesize($scope.amount_is); @@ -150,6 +154,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m $scope.amount_im = dataService.humanreadablesize($scope.amount_im); $scope.amount_img = dataService.humanreadablesize($scope.amount_img); $scope.amount_pulp = dataService.humanreadablesize($scope.amount_pulp); + $scope.amount_scratch = dataService.humanreadablesize($scope.amount_scratch); $scope.delete_is = true; $scope.delete_cs = true; @@ -202,6 +207,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m }; const OBJECT_TYPE_TASK = 'task'; + const OBJECT_TYPE_TASKS = 'tasks'; const OBJECT_TYPE_PROJECT = 'project'; const OBJECT_TYPE_PROJECTS = 'projects'; $scope.watchedObjectType = OBJECT_TYPE_TASK; @@ -210,6 +216,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m $scope.onPieClicked = function(event) { switch($scope.watchedObjectType) { + case OBJECT_TYPE_TASKS: case OBJECT_TYPE_PROJECT: loadTaskDiskUsage(this.otdb_id); break; @@ -224,6 +231,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m case OBJECT_TYPE_TASK: loadProjectDiskUsage($scope.diskUsageChartSeries[0].project_name); break; + case OBJECT_TYPE_TASKS: case OBJECT_TYPE_PROJECT: loadAllProjectsDiskUsage(); break; @@ -347,7 +355,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m if(result.found) { $scope.watchedObjectType = OBJECT_TYPE_TASK; $scope.diskUsageChartConfig.title.text = result.task_directory.name + ' ' + result.task_directory.disk_usage_readable; - $scope.diskUsageChartSeries[0].name = result.task_directory.name + ' ' + result.task_directory.disk_usage_readable; + $scope.diskUsageChartSeries[0].name = $scope.diskUsageChartConfig.title.text; var path_parts = result.task_directory.path.split('/'); $scope.diskUsageChartSeries[0].project_name = path_parts[path_parts.length-2]; $scope.diskUsageChartSeries[0].data.splice(0, $scope.diskUsageChartSeries[0].data.length); @@ -371,12 +379,41 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m }); }; + var loadTasksDiskUsage = function(tasks) { + var du_promises = tasks.map(function(t) { return dataService.getTaskDiskUsageByOTDBId(t.otdb_id); }); + $q.all(du_promises).then(function(du_results) { + var found_task_dus = du_results.filter(function(r) { return r.found;}).map(function(r) { return r.task_directory; }); + + if(found_task_dus.length > 0) { + $scope.watchedObjectType = OBJECT_TYPE_TASKS; + + $scope.leastRecentCacheTimestamp = new Date(Math.min.apply(null, found_task_dus.map(function(tdu) { return dataService.convertDatestringToLocalUTCDate(tdu.cache_timestamp); }))); + + var total_usage = found_task_dus.map(function(tdu) { return tdu.disk_usage; }).reduce(function(a, b) { return a + b;}); + var total_usage_readable = dataService.humanreadablesize(total_usage); + + $scope.diskUsageChartConfig.title.text = 'Total size: ' + total_usage_readable; + $scope.diskUsageChartSeries[0].data.splice(0, $scope.diskUsageChartSeries[0].data.length); + $scope.diskUsageChartSeries[0].name = $scope.diskUsageChartConfig.title.text; + + for(var task_du of found_task_dus) { + $scope.diskUsageChartSeries[0].data.push({name:task_du.name + ' ' + task_du.disk_usage_readable, + y:task_du.disk_usage || 0, + otdb_id: task_du.otdb_id }); + } + }else { + $scope.ok(); + $scope.$evalAsync(function() { alert("Could not find disk usage for task " + otdb_id); }); + } + }); + }; + var loadProjectDiskUsage = function(project_name) { dataService.getProjectDiskUsage(project_name).then(function(result) { if(result.found) { $scope.watchedObjectType = OBJECT_TYPE_PROJECT; $scope.diskUsageChartConfig.title.text = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable; - $scope.diskUsageChartSeries[0].name = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable; + $scope.diskUsageChartSeries[0].name = $scope.diskUsageChartConfig.title.text; $scope.diskUsageChartSeries[0].data.splice(0, $scope.diskUsageChartSeries[0].data.length); $scope.leastRecentCacheTimestamp = result.projectdir.cache_timestamp; @@ -405,7 +442,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m if(result.found) { $scope.watchedObjectType = OBJECT_TYPE_PROJECTS; $scope.diskUsageChartConfig.title.text = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable; - $scope.diskUsageChartSeries[0].name = result.projectdir.name + ' ' + result.projectdir.disk_usage_readable; + $scope.diskUsageChartSeries[0].name = $scope.diskUsageChartConfig.title.text; $scope.diskUsageChartSeries[0].data.splice(0, $scope.diskUsageChartSeries[0].data.length); $scope.leastRecentCacheTimestamp = result.projectdir.cache_timestamp; @@ -430,7 +467,11 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m }; if(task) { - loadTaskDiskUsage(task.otdb_id); + if(task.constructor === Array) { + loadTasksDiskUsage(task); + } else { + loadTaskDiskUsage(task.otdb_id); + } } else { loadAllProjectsDiskUsage(); } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 5c19bb062b4..b632a2dc85d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -214,8 +214,10 @@ $scope.columns = [ var otdb_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'otdb_id'; }); if(otdb_col && otdb_col.filters.length && otdb_col.filters[0].hasOwnProperty('term')) { var otdb_id = otdb_col.filters[0].term; - $scope.$parent.$parent.loadTaskByOTDBIdSelectAndJumpIntoView(otdb_id).then(function() { - otdb_col.filters[0].term = null; + $scope.$parent.$parent.loadTaskByOTDBIdSelectAndJumpIntoView(otdb_id).then(function(task) { + if(task) { + otdb_col.filters[0].term = null; + } }); } else { var mom_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_id'; }); @@ -223,8 +225,9 @@ $scope.columns = [ if(mom_col && mom_col.filters.length && mom_col.filters[0].hasOwnProperty('term')) { var mom_id = mom_col.filters[0].term; $scope.$parent.$parent.loadTaskByMoMIdSelectAndJumpIntoView(mom_id).then(function(task) { - mom_col.filters[0].term = null; - if(task == undefined) { + if(task) { + mom_col.filters[0].term = null; + } else { //getting the task by mom_id did not find a task //maybe the entered id was a mom group_id? //let's try to loadTasksByMoMGroupIdSelectAndJumpIntoView @@ -711,25 +714,29 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } - var liContent = selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' + var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); + + var liContent = completed_selected_cep4_tasks.length > 0 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); - if(selected_cep4_tasks.length == selected_tasks.length) { + if(completed_selected_cep4_tasks.length > 0) { liElement.on('click', function() { closeContextMenu(); - cleanupCtrl.showTaskDiskUsage(task); + if(selected_cep4_tasks.length == 1) { + cleanupCtrl.showTaskDiskUsage(task); + } else { + cleanupCtrl.showTaskDiskUsage(completed_selected_cep4_tasks); + } }); } - var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); - - var liContent = completed_selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>' + var liContent = completed_selected_cep4_tasks.length > 0 ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); - if(completed_selected_cep4_tasks.length == selected_tasks.length) { + if(completed_selected_cep4_tasks.length > 0) { liElement.on('click', function() { closeContextMenu(); - cleanupCtrl.deleteSelectedTasksDataWithConfirmation(); + cleanupCtrl.deleteTasksDataWithConfirmation(completed_selected_cep4_tasks); }); } diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index 746cd6707e5..06bfc0308fe 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -115,25 +115,29 @@ }); } - var liContent = selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' + var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); + + var liContent = completed_selected_cep4_tasks.length > 0 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); - if(selected_cep4_tasks.length == selected_tasks.length) { + if(completed_selected_cep4_tasks.length > 0) { liElement.on('click', function() { closeContextMenu(); - cleanupCtrl.showTaskDiskUsage(task); + if(selected_cep4_tasks.length == 1) { + cleanupCtrl.showTaskDiskUsage(task); + } else { + cleanupCtrl.showTaskDiskUsage(completed_selected_cep4_tasks); + } }); } - var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); - - var liContent = completed_selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>' + var liContent = completed_selected_cep4_tasks.length > 0 ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); - if(completed_selected_cep4_tasks.length == selected_tasks.length) { + if(completed_selected_cep4_tasks.length > 0) { liElement.on('click', function() { closeContextMenu(); - cleanupCtrl.deleteSelectedTasksDataWithConfirmation(); + cleanupCtrl.deleteTasksDataWithConfirmation(completed_selected_cep4_tasks); }); } -- GitLab From de21b9dfeee5549c15f7275c04e8611a661855ee Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 27 Sep 2016 10:56:57 +0000 Subject: [PATCH 868/933] Task #9607: scisup would like to be involved in chosing these kind of defaults, and what to do after the claim expires, we now choose a default period of a year, and do nothing if the claim expires --- SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py index 65a00d60478..a2fd3f2e7dd 100755 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/assignment.py @@ -406,9 +406,12 @@ class ResourceAssigner(): 'status':'claimed', 'claim_size':needed_claim_value} - #FIXME: find proper way to extend storage time with a month + # FIXME: find proper way to extend storage time with a year + # 2016-09-27 scisup would like to be involved in chosing these kind of defaults + # and what to do after the claim expires + # we now choose a default period of a year, and do nothing if the claim expires if 'storage' in db_cep4_resources_for_type[0]['name']: - claim['endtime'] += timedelta(days=31) + claim['endtime'] += timedelta(days=365) # if the needed_claim_for_resource_type dict contains more kvp's, # then the subdicts contains groups of properties for the claim -- GitLab From 4dfd41befba518c878bee31fbe2295f1c9985445 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 27 Sep 2016 10:57:49 +0000 Subject: [PATCH 869/933] Task #9607: open multiple inspection plots in one click --- .../static/app/controllers/gridcontroller.js | 20 +++++++++++++------ .../angular-gantt-contextmenu-plugin.js | 20 +++++++++++-------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index b632a2dc85d..6c84a343c1f 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -655,13 +655,23 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } - if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { + var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); + var completed_selected_cep4_observations = completed_selected_cep4_tasks.filter(function(t) { return t.type == 'observation'; }); + + if(completed_selected_cep4_observations.length > 0 && dataService.config.inspection_plots_base_url) { var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>'); ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - var url = dataService.config.inspection_plots_base_url + '/' + task.otdb_id; - $window.open(url, '_blank'); + + var window_cntr = 0; + for(var obs of completed_selected_cep4_observations) { + var url = dataService.config.inspection_plots_base_url + '/' + obs.otdb_id; + setTimeout(function(url_arg) { + $window.open(url_arg, '_blank'); + }, window_cntr*750, url); + window_cntr += 1; + } }); } @@ -707,15 +717,13 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do var url = dataService.config.lta_base_url + '/Lofar?mode=query_result_page_user&product=' + product + '&ObservationId=' + otdb_ids_string + '&project=' + project; setTimeout(function(url_arg) { $window.open(url_arg, '_blank'); - }, window_cntr*250, url); + }, window_cntr*750, url); window_cntr += 1; } } }); } - var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); - var liContent = completed_selected_cep4_tasks.length > 0 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index 06bfc0308fe..bdbd8603707 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -101,22 +101,26 @@ }); } - if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { + var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); + var completed_selected_cep4_observations = completed_selected_cep4_tasks.filter(function(t) { return t.type == 'observation'; }); + + if(completed_selected_cep4_observations.length > 0 && dataService.config.inspection_plots_base_url) { var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>'); ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - for(var t of selected_tasks) { - if(t) { - var url = dataService.config.inspection_plots_base_url + '/' + t.otdb_id; - $window.open(url, '_blank'); - } + + var window_cntr = 0; + for(var obs of completed_selected_cep4_observations) { + var url = dataService.config.inspection_plots_base_url + '/' + obs.otdb_id; + setTimeout(function(url_arg) { + $window.open(url_arg, '_blank'); + }, window_cntr*750, url); + window_cntr += 1; } }); } - var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); - var liContent = completed_selected_cep4_tasks.length > 0 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); -- GitLab From 93324190ba537c315c749813d5116042e4d4d149 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 27 Sep 2016 13:46:09 +0000 Subject: [PATCH 870/933] Task #9607: fixed typo --- SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py index 2e16e1b6831..fa025466cbc 100644 --- a/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py +++ b/SAS/ResourceAssignment/ResourceAssigner/lib/schedulechecker.py @@ -152,7 +152,7 @@ class ScheduleChecker(): # moving pipelines might take a while # so this task might have changed status to active # in that case we don't want to move it - uptodate_task = radbrpc.getTask(task['id']) + uptodate_task = self._radbrpc.getTask(task['id']) if uptodate_task['status'] in ['scheduled', 'queued']: movePipelineAfterItsPredecessors(uptodate_task, self._radbrpc, min_start_timestamp) except Exception as e: -- GitLab From 161944f0ac4fa58376162c1637d371f5721f0fa7 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Tue, 27 Sep 2016 13:47:02 +0000 Subject: [PATCH 871/933] Task #9607: no seperate alert box per deleted task anymore. For now, log to console. Should become a log window in the scheduler UI --- .../lib/static/app/controllers/cleanupcontroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js index 79abb6f8ebb..6774c949f11 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -71,7 +71,7 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m $http.delete('/rest/tasks/' + task.id + '/cleanup', {data: params}).error(function(result) { console.log("Error. Could cleanup data for task " + task.id + ", " + result); }).success(function(result) { - alert(result.message); + console.log(result.message); }); }; -- GitLab From a113624e57c2a99eed8bec4a2622c41b4f508b80 Mon Sep 17 00:00:00 2001 From: Ruud Beukema <beukema@astron.nl> Date: Tue, 27 Sep 2016 14:14:31 +0000 Subject: [PATCH 872/933] Task #9886: - Added mouse left-click behaviour to Info-column items similar to already existing context-menu options: 'select blocking predecessors' and 'open in LTA catalog' - Task/project description now available as Mom ID tooltip as well. - Improved vertical alignment of items shown in disk usage column - Some refactoring --- .../static/app/controllers/gridcontroller.js | 237 ++++++++++-------- 1 file changed, 127 insertions(+), 110 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 6c84a343c1f..535e2036875 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -6,15 +6,63 @@ var gridControllerMod = angular.module('GridControllerMod', ['ui.grid', 'ui.grid.resizeColumns', 'ui.grid.autoResize']); -gridControllerMod.controller('GridController', ['$scope', 'dataService', 'uiGridConstants', function($scope, dataService, uiGridConstants) { +gridControllerMod.controller('GridController', ['$scope', '$window', 'dataService', 'uiGridConstants', function($scope, $window, dataService, uiGridConstants) { var self = this; self.lastUpdateTimestamp = new Date(0); self.waitingForDelayedUpdate = false; $scope.dataService = dataService; + + $scope.selectBlockingPredecessors = function(in_blocking_predecessors) { + $scope.$parent.$parent.loadTasksSelectAndJumpIntoView(in_blocking_predecessors); + }; + + $scope.openLtaLocation = function(in_ingest_tasks) { + var ingest_tasks = Array.isArray(in_ingest_tasks) ? in_ingest_tasks : [in_ingest_tasks]; + + //example: http://lofar.target.rug.nl/Lofar?mode=query_result_page_user&product=AveragingPipeline&ObservationId=544965&project=LC6_015 + //map task.sub_type to url product parameter + var project2project2product2tasksDict = {}; + + for(var t of ingest_tasks) { + var lta_product; + switch(t.sub_type) { + case 'averaging_pipeline': lta_product = 'AveragingPipeline'; break; + case 'calibration_pipeline': lta_product = 'CalibrationPipeline'; break; + case 'pulsar_pipeline': lta_product = 'PulsarPipeline'; break; + case 'lofar_imaging_pipeline': lta_product = 'ImagingPipeline'; break; + case 'imaging_pipeline_msss': lta_product = 'ImagingPipeline'; break; + case 'long_baseline_pipeline': lta_product = 'LongBaselinePipeline'; break; + case 'lofar_observation': lta_product = 'Observation'; break; + } + if(lta_product && (t.ingest_status != undefined) ) { + if(!project2project2product2tasksDict.hasOwnProperty(t.project_name)) { + project2project2product2tasksDict[t.project_name] = {}; + } + if(!project2project2product2tasksDict[t.project_name].hasOwnProperty(lta_product)) { + project2project2product2tasksDict[t.project_name][lta_product] = []; + } + project2project2product2tasksDict[t.project_name][lta_product].push(t); + } + } -$scope.columns = [ + var window_cntr = 0; + for(var project in project2project2product2tasksDict) { + for(var product in project2project2product2tasksDict[project]) { + var product_tasks = project2project2product2tasksDict[project][product]; + var otdb_ids = product_tasks.map(function(pt) { return pt.otdb_id; }); + var otdb_ids_string = otdb_ids.join(','); + var url = dataService.config.lta_base_url + '/Lofar?mode=query_result_page_user&product=' + product + '&ObservationId=' + otdb_ids_string + '&project=' + project; + setTimeout(function(url_arg) { + $window.open(url_arg, '_blank'); + }, window_cntr*250, url); + window_cntr += 1; + } + } + } + + $scope.columns = [ { field: 'name', enableCellEdit: false, width: '*', @@ -67,7 +115,11 @@ $scope.columns = [ }, cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) { return "grid-status-" + grid.getCellValue(row,col); - } + } + // Supposedly [1], select-box items can be formatted using: + // headerCellFilter: 'statusFormatter' + // + // [1] http://stackoverflow.com/questions/37286945/ui-grid-setting-template-for-filter-options }, { field: 'info', displayName: 'Info', @@ -92,10 +144,10 @@ $scope.columns = [ editDropdownOptionsArray: [], headerTooltip: "Additional status information", cellTemplate: '<div style="text-align: center" class="ui-grid-cell-contents">' + - '<span ng-if="row.entity.blocked_by_ids.length > 0"><img ng-src="static/icons/blocked.png" title="Blocked by {{row.entity.blocked_by_ids.length.toString()}} predecessor(s)" /></span>' + - '<span ng-if="row.entity.ingest_status==\'ingesting\'"><img ng-src="static/icons/ingest_in_progress.png" title="Ingest in progress" /></span>' + - '<span ng-if="row.entity.ingest_status==\'ingested\'"><img ng-src="static/icons/ingest_successful.png" title="Ingest successful" /></span>' + - '<span ng-if="row.entity.ingest_status==\'failed\'"><img ng-src="static/icons/ingest_failed.png" title="Ingest failed" /></span>' + + '<span ng-if="row.entity.blocked_by_ids.length > 0"><img ng-click="row.grid.appScope.selectBlockingPredecessors(row.entity.blocked_by_ids)" ng-src="static/icons/blocked.png" title="Blocked by {{row.entity.blocked_by_ids.length.toString()}} predecessor(s) - Click to select" /></span>' + + '<span ng-if="row.entity.ingest_status==\'ingesting\'"><img ng-click="row.grid.appScope.openLtaLocation(row.entity)" ng-src="static/icons/ingest_in_progress.png" title="Ingest in progress - Click to open LTA catalog" /></span>' + + '<span ng-if="row.entity.ingest_status==\'ingested\'"><img ng-click="row.grid.appScope.openLtaLocation(row.entity)" ng-src="static/icons/ingest_successful.png" title="Ingest successful - Click to open LTA catalog" /></span>' + + '<span ng-if="row.entity.ingest_status==\'failed\'"><img ng-click="row.grid.appScope.openLtaLocation(row.entity)" ng-src="static/icons/ingest_failed.png" title="Ingest failed - Click to open LTA catalog" /></span>' + '</div>' }, { field: 'type', @@ -112,7 +164,7 @@ $scope.columns = [ displayName: 'Size', type: 'number', enableCellEdit: false, - cellTemplate:'<div style=\'text-align:right\'>{{row.entity.disk_usage_readable}}</div>', + cellTemplate:'<div style=\'text-align:right; padding-top: 5px;\'>{{row.entity.disk_usage_readable}}</div>', width: '80', filter: { type: uiGridConstants.filter.SELECT, @@ -134,7 +186,14 @@ $scope.columns = [ { field: 'mom_id', displayName: 'MoM ID', enableCellEdit: false, - cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'><a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}">{{row.entity[col.field]}}</a></div>', + cellTemplate:'<div style=\'text-align: center; padding-top:5px;\'>' + + '<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}"' + + 'title="' + + 'Project description:\n' + '{{row.grid.appScope.dataService.momProjectsDict[row.entity.project_mom_id].description}}\n\n' + + 'Task description:\n' + '{{row.entity.description}}\n\n' + + 'Group ID:\n' + '{{row.entity.mom_object_group_id}}\n\n' + + 'Parent group ID:\n' + '{{row.entity.mom_object_parent_group_id}}' + + '">{{row.entity[col.field]}} </a></div>', width: '65' }, { field: 'otdb_id', @@ -214,10 +273,8 @@ $scope.columns = [ var otdb_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'otdb_id'; }); if(otdb_col && otdb_col.filters.length && otdb_col.filters[0].hasOwnProperty('term')) { var otdb_id = otdb_col.filters[0].term; - $scope.$parent.$parent.loadTaskByOTDBIdSelectAndJumpIntoView(otdb_id).then(function(task) { - if(task) { - otdb_col.filters[0].term = null; - } + $scope.$parent.$parent.loadTaskByOTDBIdSelectAndJumpIntoView(otdb_id).then(function() { + otdb_col.filters[0].term = null; }); } else { var mom_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_id'; }); @@ -225,9 +282,8 @@ $scope.columns = [ if(mom_col && mom_col.filters.length && mom_col.filters[0].hasOwnProperty('term')) { var mom_id = mom_col.filters[0].term; $scope.$parent.$parent.loadTaskByMoMIdSelectAndJumpIntoView(mom_id).then(function(task) { - if(task) { - mom_col.filters[0].term = null; - } else { + mom_col.filters[0].term = null; + if(task == undefined) { //getting the task by mom_id did not find a task //maybe the entered id was a mom group_id? //let's try to loadTasksByMoMGroupIdSelectAndJumpIntoView @@ -310,29 +366,42 @@ $scope.columns = [ for(var task of $scope.dataService.tasks) { if(task.endtime >= viewFrom && task.starttime <= viewTo) { $scope.dataService.filteredTasks.push(task); + + function convertNullToUndefined(in_value) { + return in_value === null ? undefined : in_value; + }; var gridTask = { - id: task.id, - name: task.name, - project_name: task.project_name, - mom_id: task.mom_id, - otdb_id: task.otdb_id, - starttime: task.starttime, - endtime: task.endtime, - duration: task.duration, - status: task.status, - type: task.type, - project_mom2object_id: task.project_mom2object_id, - mom2object_id: task.mom2object_id, - mom_object_group_id: task.mom_object_group_id, - mom_object_group_name: task.mom_object_group_name, - mom_object_group_mom2object_id: task.mom_object_group_mom2object_id, - mom_object_parent_group_id: task.mom_object_parent_group_id, - cluster: task.cluster, - blocked_by_ids: task.blocked_by_ids, - ingest_status: task.ingest_status === null ? undefined : task.ingest_status, - disk_usage: task.disk_usage === null ? undefined : task.disk_usage, - disk_usage_readable: task.disk_usage_readable === null ? undefined : task.disk_usage_readable + blocked_by_ids: convertNullToUndefined(task.blocked_by_ids), + cluster: convertNullToUndefined(task.cluster), + description: convertNullToUndefined(task.description), + disk_usage: convertNullToUndefined(task.disk_usage), + disk_usage_readable: convertNullToUndefined(task.disk_usage_readable), + duration: convertNullToUndefined(task.duration), + endtime: convertNullToUndefined(task.endtime), + id: convertNullToUndefined(task.id), + ingest_status: convertNullToUndefined(task.ingest_status), + mom2object_id: convertNullToUndefined(task.mom2object_id), + mom_id: convertNullToUndefined(task.mom_id), + mom_object_group_id: convertNullToUndefined(task.mom_object_group_id), + mom_object_group_mom2object_id: convertNullToUndefined(task.mom_object_group_mom2object_id), + mom_object_group_name: convertNullToUndefined(task.mom_object_group_name), + mom_object_parent_group_id: convertNullToUndefined(task.mom_object_parent_group_id), + name: convertNullToUndefined(task.name), + nr_of_dataproducts: convertNullToUndefined(task.nr_of_dataproducts), + otdb_id: convertNullToUndefined(task.otdb_id), + predecessor_ids: convertNullToUndefined(task.predecessor_ids), + project_mom2object_id: convertNullToUndefined(task.project_mom2object_id), + project_mom_id: convertNullToUndefined(task.project_mom_id), + project_name: convertNullToUndefined(task.project_name), + specification_id: convertNullToUndefined(task.specification_id), + starttime: convertNullToUndefined(task.starttime), + status: convertNullToUndefined(task.status), + status_id: convertNullToUndefined(task.status_id), + sub_type: convertNullToUndefined(task.sub_type), + successor_ids: convertNullToUndefined(task.successor_ids), + type: convertNullToUndefined(task.type), + type_id: convertNullToUndefined(task.type_id) }; gridTasks.push(gridTask); } @@ -456,8 +525,8 @@ $scope.columns = [ //get unique statuses from tasks var task_statuses = tasks.map(function(t) { return t.status; }); task_statuses = task_statuses.unique(); - task_statuses.sort(); + fillColumFilterSelectOptions(task_statuses, $scope.columns[5]); }; @@ -472,8 +541,8 @@ $scope.columns = [ //get unique types from tasks var task_types = tasks.map(function(t) { return t.type; }); task_types = task_types.unique(); - task_types.sort(); + fillColumFilterSelectOptions(task_types, $scope.columns[7]); }; @@ -570,12 +639,14 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do restrict: 'A', scope: { }, - link: function($scope, $element, $attrs) { + link: function($scope, $element, $attrs) { function handleContextMenuEvent(event) { - //pragmatic 'hard-coded' way of getting the dataService and the rowEntity via scope tree. + //pragmatic 'hard-coded' way of getting the dataService and the rowEntity via scope tree. var dataService = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.dataService; var cleanupCtrl = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.cleanupCtrl; - var rowEntity = $scope.$parent.$parent.$parent.row.entity; + var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; + var row = $scope.$parent.$parent.$parent.row; + var rowEntity = row.entity; if(!dataService || !rowEntity) return true; @@ -624,7 +695,6 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; dataCtrlScope.loadTasksByMoMGroupIdSelectAndJumpIntoView(task.mom_object_group_id); }); @@ -632,7 +702,6 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; dataCtrlScope.loadTasksByMoMParentGroupIdSelectAndJumpIntoView(task.mom_object_parent_group_id); }); @@ -649,29 +718,17 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do for(var task of blocked_selected_cep4_tasks) { blocking_predecessors = blocking_predecessors.concat(task.blocked_by_ids); } - - var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; - dataCtrlScope.loadTasksSelectAndJumpIntoView(blocking_predecessors); + row.grid.appScope.selectBlockingPredecessors(blocking_predecessors); }); } - var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); - var completed_selected_cep4_observations = completed_selected_cep4_tasks.filter(function(t) { return t.type == 'observation'; }); - - if(completed_selected_cep4_observations.length > 0 && dataService.config.inspection_plots_base_url) { + if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>'); ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - - var window_cntr = 0; - for(var obs of completed_selected_cep4_observations) { - var url = dataService.config.inspection_plots_base_url + '/' + obs.otdb_id; - setTimeout(function(url_arg) { - $window.open(url_arg, '_blank'); - }, window_cntr*750, url); - window_cntr += 1; - } + var url = dataService.config.inspection_plots_base_url + '/' + task.otdb_id; + $window.open(url, '_blank'); }); } @@ -681,70 +738,30 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do var liElement = angular.element('<li><a href="#">Open in LTA catalogue</a></li>'); ulElement.append(liElement); liElement.on('click', function() { - closeContextMenu(); - //example: http://lofar.target.rug.nl/Lofar?mode=query_result_page_user&product=AveragingPipeline&ObservationId=544965&project=LC6_015 - //map task.sub_type to url product parameter - var project2project2product2tasksDict = {}; - - for(var t of ingest_tasks) { - var lta_product; - switch(t.sub_type) { - case 'averaging_pipeline': lta_product = 'AveragingPipeline'; break; - case 'calibration_pipeline': lta_product = 'CalibrationPipeline'; break; - case 'pulsar_pipeline': lta_product = 'PulsarPipeline'; break; - case 'lofar_imaging_pipeline': lta_product = 'ImagingPipeline'; break; - case 'imaging_pipeline_msss': lta_product = 'ImagingPipeline'; break; - case 'long_baseline_pipeline': lta_product = 'LongBaselinePipeline'; break; - case 'lofar_observation': lta_product = 'Observation'; break; - } - if(lta_product) { - if(!project2project2product2tasksDict.hasOwnProperty(t.project_name)) { - project2project2product2tasksDict[t.project_name] = {}; - } - if(!project2project2product2tasksDict[t.project_name].hasOwnProperty(lta_product)) { - project2project2product2tasksDict[t.project_name][lta_product] = []; - } - project2project2product2tasksDict[t.project_name][lta_product].push(t); - } - } - - var window_cntr = 0; - for(var project in project2project2product2tasksDict) { - for(var product in project2project2product2tasksDict[project]) { - var product_tasks = project2project2product2tasksDict[project][product]; - var otdb_ids = product_tasks.map(function(pt) { return pt.otdb_id; }); - var otdb_ids_string = otdb_ids.join(','); - var url = dataService.config.lta_base_url + '/Lofar?mode=query_result_page_user&product=' + product + '&ObservationId=' + otdb_ids_string + '&project=' + project; - setTimeout(function(url_arg) { - $window.open(url_arg, '_blank'); - }, window_cntr*750, url); - window_cntr += 1; - } - } + closeContextMenu(); + row.appScope.openLtaLocation(ingest_tasks); }); } - var liContent = completed_selected_cep4_tasks.length > 0 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' + var liContent = selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); - if(completed_selected_cep4_tasks.length > 0) { + if(selected_cep4_tasks.length == selected_tasks.length) { liElement.on('click', function() { closeContextMenu(); - if(selected_cep4_tasks.length == 1) { - cleanupCtrl.showTaskDiskUsage(task); - } else { - cleanupCtrl.showTaskDiskUsage(completed_selected_cep4_tasks); - } + cleanupCtrl.showTaskDiskUsage(task); }); } - var liContent = completed_selected_cep4_tasks.length > 0 ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>' + var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); + + var liContent = completed_selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); - if(completed_selected_cep4_tasks.length > 0) { + if(completed_selected_cep4_tasks.length == selected_tasks.length) { liElement.on('click', function() { closeContextMenu(); - cleanupCtrl.deleteTasksDataWithConfirmation(completed_selected_cep4_tasks); + cleanupCtrl.deleteSelectedTasksDataWithConfirmation(); }); } -- GitLab From 05c12da33c030cda9aa55ac02b5c016084f49987 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 06:53:10 +0000 Subject: [PATCH 873/933] Task #9607: handle obsolete status --- SAS/OTDB_Services/OTDBBusListener.py | 5 +++++ .../OTDBtoRATaskStatusPropagator/propagator.py | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/SAS/OTDB_Services/OTDBBusListener.py b/SAS/OTDB_Services/OTDBBusListener.py index d699cf878a5..65b740a38e4 100644 --- a/SAS/OTDB_Services/OTDBBusListener.py +++ b/SAS/OTDB_Services/OTDBBusListener.py @@ -93,6 +93,8 @@ class OTDBBusListener(AbstractBusListener): self.onObservationFinished(treeId, modificationTime) elif msg.content['state'] == 'aborted': self.onObservationAborted(treeId, modificationTime) + elif msg.content['state'] == 'obsolete': + self.onObservationObsolete(treeId, modificationTime) def onObservationDescribed(self, treeId, modificationTime): pass @@ -130,4 +132,7 @@ class OTDBBusListener(AbstractBusListener): def onObservationAborted(self, treeId, modificationTime): pass + def onObservationObsolete(self, treeId, modificationTime): + pass + __all__ = ["OTDBBusListener"] diff --git a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py index eb8ce5cce69..3b14e604617 100644 --- a/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py +++ b/SAS/ResourceAssignment/OTDBtoRATaskStatusPropagator/propagator.py @@ -47,8 +47,8 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): def _update_radb_task_status(self, otdb_id, task_status): try: - if task_status in ['approved', 'prescheduled']: - radb_task = self.radb.getTask(otdb_id=treeId) + if task_status in ['approved', 'prescheduled', 'obsolete']: + radb_task = self.radb.getTask(otdb_id=otdb_id) if (radb_task and radb_task['cluster'] == 'CEP4' and radb_task['status'] in ['queued', 'active', 'completing']): @@ -105,6 +105,9 @@ class OTDBtoRATaskStatusPropagator(OTDBBusListener): def onObservationConflict(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'conflict') + def onObservationObsolete(self, treeId, modificationTime): + self._update_radb_task_status(treeId, 'obsolete') + def onObservationPrescheduled(self, treeId, modificationTime): self._update_radb_task_status(treeId, 'prescheduled') -- GitLab From 9283bd4913fc75f93371a3c199d334738a56a586 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 06:54:12 +0000 Subject: [PATCH 874/933] Task #9607: added ng-sanitize --- .gitattributes | 1 + .../ResourceAssignmentEditor/lib/CMakeLists.txt | 2 ++ .../js/angular-sanitize/angular-sanitize.min.js | 16 ++++++++++++++++ .../lib/templates/index.html | 1 + 4 files changed, 20 insertions(+) create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-sanitize/angular-sanitize.min.js diff --git a/.gitattributes b/.gitattributes index 2b0b2d8c52e..c845f0cb81c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5280,6 +5280,7 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-resource/a SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-resource/angular-resource.min.js.map -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-route/angular-route.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-route/angular-route.min.js.map -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-sanitize/angular-sanitize.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-touch/angular-touch.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-touch/angular-touch.min.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-touch/angular-touch.min.js.map -text diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt index e0d3305c852..a2cf8287a7f 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt @@ -23,6 +23,7 @@ file(GLOB_RECURSE angular_gantt_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} stati file(GLOB_RECURSE angular_animate_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-animate/*) file(GLOB_RECURSE angular_aria_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-aria/*) file(GLOB_RECURSE angular_material_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-material/*) +file(GLOB_RECURSE angular_sanitize_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-sanitize/*) file(GLOB_RECURSE angular_moment_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/angular-moment/*) file(GLOB_RECURSE jsplumb_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/jsplumb/*) file(GLOB_RECURSE moment_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} static/js/moment/*) @@ -62,6 +63,7 @@ set(web_files ${angular_animate_files} ${angular_aria_files} ${angular_material_files} + ${angular_sanitize_files} ${angular_moment_files} ${moment_files} ${jsplumb_files} diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-sanitize/angular-sanitize.min.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-sanitize/angular-sanitize.min.js new file mode 100644 index 00000000000..48c46be8c14 --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/angular-sanitize/angular-sanitize.min.js @@ -0,0 +1,16 @@ +/* + AngularJS v1.5.9-build.5041+sha.ddb4ef1 + (c) 2010-2016 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(s,g){'use strict';function H(g){var l=[];t(l,A).chars(g);return l.join("")}var B=g.$$minErr("$sanitize"),C,l,D,E,q,A,F,t;g.module("ngSanitize",[]).provider("$sanitize",function(){function k(a,e){var b={},c=a.split(","),h;for(h=0;h<c.length;h++)b[e?q(c[h]):c[h]]=!0;return b}function I(a){for(var e={},b=0,c=a.length;b<c;b++){var h=a[b];e[h.name]=h.value}return e}function G(a){return a.replace(/&/g,"&").replace(J,function(a){var b=a.charCodeAt(0);a=a.charCodeAt(1);return"&#"+(1024*(b-55296)+ +(a-56320)+65536)+";"}).replace(K,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"<").replace(/>/g,">")}function x(a){for(;a;){if(a.nodeType===s.Node.ELEMENT_NODE)for(var e=a.attributes,b=0,c=e.length;b<c;b++){var h=e[b],d=h.name.toLowerCase();if("xmlns:ns1"===d||0===d.lastIndexOf("ns1:",0))a.removeAttributeNode(h),b--,c--}(e=a.firstChild)&&x(e);a=a.nextSibling}}var u=!1;this.$get=["$$sanitizeUri",function(a){u&&l(v,w);return function(e){var b=[];F(e,t(b,function(b,h){return!/^unsafe:/.test(a(b, +h))}));return b.join("")}}];this.enableSvg=function(a){return E(a)?(u=a,this):u};C=g.bind;l=g.extend;D=g.forEach;E=g.isDefined;q=g.lowercase;A=g.noop;F=function(a,e){null===a||void 0===a?a="":"string"!==typeof a&&(a=""+a);f.innerHTML=a;var b=5;do{if(0===b)throw B("uinput");b--;s.document.documentMode&&x(f);a=f.innerHTML;f.innerHTML=a}while(a!==f.innerHTML);for(b=f.firstChild;b;){switch(b.nodeType){case 1:e.start(b.nodeName.toLowerCase(),I(b.attributes));break;case 3:e.chars(b.textContent)}var c;if(!(c= +b.firstChild)&&(1===b.nodeType&&e.end(b.nodeName.toLowerCase()),c=b.nextSibling,!c))for(;null==c;){b=b.parentNode;if(b===f)break;c=b.nextSibling;1===b.nodeType&&e.end(b.nodeName.toLowerCase())}b=c}for(;b=f.firstChild;)f.removeChild(b)};t=function(a,e){var b=!1,c=C(a,a.push);return{start:function(a,d){a=q(a);!b&&z[a]&&(b=a);b||!0!==v[a]||(c("<"),c(a),D(d,function(b,d){var f=q(d),g="img"===a&&"src"===f||"background"===f;!0!==m[f]||!0===n[f]&&!e(b,g)||(c(" "),c(d),c('="'),c(G(b)),c('"'))}),c(">"))}, +end:function(a){a=q(a);b||!0!==v[a]||!0===y[a]||(c("</"),c(a),c(">"));a==b&&(b=!1)},chars:function(a){b||c(G(a))}}};var J=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,K=/([^#-~ |!])/g,y=k("area,br,col,hr,img,wbr"),d=k("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),r=k("rp,rt"),p=l({},r,d),d=l({},d,k("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul")),r=l({},r,k("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")), +w=k("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,stop,svg,switch,text,title,tspan"),z=k("script,style"),v=l({},y,d,r,p),n=k("background,cite,href,longdesc,src,xlink:href"),p=k("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,valign,value,vspace,width"), +r=k("accent-height,accumulate,additive,alphabetic,arabic-form,ascent,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan", +!0),m=l({},n,r,p),f;(function(a){if(a.document&&a.document.implementation)a=a.document.implementation.createHTMLDocument("inert");else throw B("noinert");var e=(a.documentElement||a.getDocumentElement()).getElementsByTagName("body");1===e.length?f=e[0]:(e=a.createElement("html"),f=a.createElement("body"),e.appendChild(f),a.appendChild(e))})(s)});g.module("ngSanitize").filter("linky",["$sanitize",function(k){var l=/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i, +q=/^mailto:/i,x=g.$$minErr("linky"),u=g.isDefined,s=g.isFunction,t=g.isObject,y=g.isString;return function(d,g,p){function w(a){a&&m.push(H(a))}function z(a,b){var c,d=v(a);m.push("<a ");for(c in d)m.push(c+'="'+d[c]+'" ');!u(g)||"target"in d||m.push('target="',g,'" ');m.push('href="',a.replace(/"/g,"""),'">');w(b);m.push("</a>")}if(null==d||""===d)return d;if(!y(d))throw x("notstring",d);for(var v=s(p)?p:t(p)?function(){return p}:function(){return{}},n=d,m=[],f,a;d=n.match(l);)f=d[0],d[2]|| +d[4]||(f=(d[3]?"http://":"mailto:")+f),a=d.index,w(n.substr(0,a)),z(f,d[0].replace(q,"")),n=n.substring(a+d[0].length);w(n);return k(m.join(""))}}])})(window,window.angular); +//# sourceMappingURL=angular-sanitize.min.js.map diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index bb2fbd340ae..4a2109ce11a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -30,6 +30,7 @@ <script src="/static/js/angular-ui-layout/angular-ui-layout.min.js"></script> <script src="/static/js/angular-ui-tabs/angular-ui.bootstrap.tabs.min.js"></script> <script src="/static/js/angular-moment/angular-moment.js"></script> + <script src="/static/js/angular-sanitize/angular-sanitize.min.js"></script> <script src="/static/js/angular-animate/angular-animate.min.js"></script> <script src="/static/js/angular-aria/angular-aria.min.js"></script> <script src="/static/js/angular-material/angular-material.min.js"></script> -- GitLab From 5ab641d8b7cae4853910c1fd175c07bf128492e7 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 06:54:36 +0000 Subject: [PATCH 875/933] Task #9607: dialog font size --- .../ResourceAssignmentEditor/lib/static/css/main.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index 94bda4a5d99..ffe7ca08a41 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -82,6 +82,10 @@ table.uib-timepicker td.uib-time { width: 960px } +.md-dialog-content { + font-size: 14px; +} + .gantt-task-content { /* margin-top: 2.2px; */ padding-top: 2.2px; -- GitLab From 41266a5513529abc487bdc6de2cf86f245723adf Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 06:55:34 +0000 Subject: [PATCH 876/933] Task #9607: added force_reload option --- .../lib/static/app/controllers/datacontroller.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 647af631dac..49ca74ab928 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -354,7 +354,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }) }; - var _getTaskBy = function(id_name, id) { + var _getTaskBy = function(id_name, id, force_reload) { var defer = $q.defer(); if(typeof(id) === 'string') { @@ -363,7 +363,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, var foundTask = id_name == 'id' ? self.taskDict[id] : self.tasks.find(function(t) { return t[id_name] == id; }); - if(foundTask) { + if(foundTask && !force_reload) { defer.resolve(foundTask); } else { var url; @@ -398,16 +398,16 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, return defer.promise; }; - self.getTask= function(id) { - return _getTaskBy('id', id); + self.getTask= function(id, force_reload) { + return _getTaskBy('id', id, force_reload); }; - self.getTaskByOTDBId = function(otdb_id) { - return _getTaskBy('otdb_id', otdb_id); + self.getTaskByOTDBId = function(otdb_id, force_reload) { + return _getTaskBy('otdb_id', otdb_id, force_reload); }; - self.getTaskByMoMId = function(mom_id) { - return _getTaskBy('mom_id', mom_id); + self.getTaskByMoMId = function(mom_id, force_reload) { + return _getTaskBy('mom_id', mom_id, force_reload); }; self.getTasksByMoMGroupId = function(mom_object_group_id) { -- GitLab From 7b3904680076094f62e8492c68c08f775baff3c6 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 06:58:13 +0000 Subject: [PATCH 877/933] Task #9607: added override for deleting data of tasks with unfinished successors. These are set to obselete if you proceed with the delete action --- .../app/controllers/cleanupcontroller.js | 70 ++++++++++++++++--- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js index 6774c949f11..2dd1b99d89c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/cleanupcontroller.js @@ -1,6 +1,6 @@ // $Id: controller.js 32761 2015-11-02 11:50:21Z schaap $ -var cleanupControllerMod = angular.module('CleanupControllerMod', ['ui.bootstrap', 'ngMaterial']); +var cleanupControllerMod = angular.module('CleanupControllerMod', ['ui.bootstrap', 'ngMaterial', 'ngSanitize']); cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$mdDialog', '$http', '$q', 'dataService', function($scope, $uibModal, $mdDialog, $http, $q, dataService) { var self = this; @@ -29,16 +29,68 @@ cleanupControllerMod.controller('CleanupController', ['$scope', '$uibModal', '$m } $q.all(suc_tasks_promises).then(function(suc_tasks) { - var unfinished_suc_tasks = suc_tasks.filter(function(t) { return t && t.status != 'finished' }); + var unfinished_suc_tasks = suc_tasks.filter(function(t) { return t && !(t.status == 'finished' || t.status == 'obsolete') }); if(unfinished_suc_tasks.length > 0) { - var unfinished_ids = unfinished_suc_tasks.map(function(t) { return t.otdb_id; }); - $mdDialog.show($mdDialog.alert() - .parent(angular.element(document.querySelector('#popupContainer'))) - .title('Error') - .textContent("Cannot delete data for " + unfinished_ids + " because there are unfinished successors") - .ariaLabel('Error') - .ok('Ok')); + var unfinished_ids = unfinished_suc_tasks.map(function(t) { return t.id; }); + var unfinished_otdb_ids = unfinished_suc_tasks.map(function(t) { return t.otdb_id; }); + + var undeletable_predecessors = tasks.filter(function(t) { + for (var suc_id of t.successor_ids) { + if(unfinished_ids.includes(suc_id)) + return true; + } + return false; + }); + var undeletable_pred_otdb_ids = undeletable_predecessors.map(function(t) { return t.otdb_id; }); + + $mdDialog.show($mdDialog.confirm() + .parent(angular.element(document.querySelector('#popupContainer'))) + .title('Warning: Delete data which is needed by succesors?') + .htmlContent("Cannot delete data for " + undeletable_pred_otdb_ids + " because there are unfinished successors: " + unfinished_otdb_ids + "<br>Do you want to set the unfinished succesors to obsolete and proceed with the deletion of all data?") + .ariaLabel('Error') + .ok('Yes') + .cancel('No')).then(function() { + + waiting_dialog = $mdDialog.show($mdDialog.alert() + .parent(angular.element(document.querySelector('#popupContainer'))) + .title('Waiting...') + .textContent("Waiting for the unfinished succesors to become obsolete...") + .ariaLabel('Waiting') + .ok('Ok')); + + for(var unfinished_suc_task of unfinished_suc_tasks) { + var newTask = { id: unfinished_suc_task.id, status: 'obsolete' }; + dataService.putTask(newTask); + } + + var waitForTasksToBecomeObsolete = function(tasks_to_be_obsolete) { + // polling for all tasks_to_be_obsolete + // if they are all obsolete, then the returned promise resolves + // else, we poll again, and again, until they are all obsolete + var defer = $q.defer(); + var load_promises = tasks_to_be_obsolete.map(function(t) { return dataService.getTask(t.id, true); }); + + $q.all(load_promises).then(function(loaded_tasks) { + var loaded_obsolete_tasks = loaded_tasks.filter(function(t) { return t['status'] == 'obsolete';}); + if(loaded_obsolete_tasks.length == loaded_tasks.length) { + defer.resolve(); + } else { + waitForTasksToBecomeObsolete(tasks_to_be_obsolete).then(function() { + defer.resolve(); + }); + } + }); + return defer.promise; + }; + + waitForTasksToBecomeObsolete(unfinished_suc_tasks).then( + function() { + $mdDialog.cancel(waiting_dialog); + self.deleteTasksDataWithConfirmation(tasks); + }); + }, function() { + }); return; } -- GitLab From 3caf2f1cbd7ed03ac9193020a45151c687a86192 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 07:06:05 +0000 Subject: [PATCH 878/933] Task #9607: moved convertNullToUndefined into datacontroller. Only apply to certain fields. Do not copy task in grid anymore. --- .../static/app/controllers/datacontroller.js | 7 ++++ .../static/app/controllers/gridcontroller.js | 40 +------------------ 2 files changed, 9 insertions(+), 38 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 49ca74ab928..32e58d35e4a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -172,6 +172,10 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, //local client time offset to utc in milliseconds self.utcOffset = moment().utcOffset()*60000; + self.convertNullToUndefined = function(in_value) { + return in_value === null ? undefined : in_value; + }; + self.toIdBasedDict = function(list) { var dict = {} if(list) { @@ -291,6 +295,9 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, var task = result.tasks[i]; task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); + task.ingest_status = self.convertNullToUndefined(task.ingest_status); + task.disk_usage = self.convertNullToUndefined(task.disk_usage); + task.disk_usage_readable = self.convertNullToUndefined(task.disk_usage_readable); } var initialTaskLoad = self.tasks.length == 0; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 535e2036875..adef33325d4 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -366,44 +366,8 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic for(var task of $scope.dataService.tasks) { if(task.endtime >= viewFrom && task.starttime <= viewTo) { $scope.dataService.filteredTasks.push(task); - - function convertNullToUndefined(in_value) { - return in_value === null ? undefined : in_value; - }; - - var gridTask = { - blocked_by_ids: convertNullToUndefined(task.blocked_by_ids), - cluster: convertNullToUndefined(task.cluster), - description: convertNullToUndefined(task.description), - disk_usage: convertNullToUndefined(task.disk_usage), - disk_usage_readable: convertNullToUndefined(task.disk_usage_readable), - duration: convertNullToUndefined(task.duration), - endtime: convertNullToUndefined(task.endtime), - id: convertNullToUndefined(task.id), - ingest_status: convertNullToUndefined(task.ingest_status), - mom2object_id: convertNullToUndefined(task.mom2object_id), - mom_id: convertNullToUndefined(task.mom_id), - mom_object_group_id: convertNullToUndefined(task.mom_object_group_id), - mom_object_group_mom2object_id: convertNullToUndefined(task.mom_object_group_mom2object_id), - mom_object_group_name: convertNullToUndefined(task.mom_object_group_name), - mom_object_parent_group_id: convertNullToUndefined(task.mom_object_parent_group_id), - name: convertNullToUndefined(task.name), - nr_of_dataproducts: convertNullToUndefined(task.nr_of_dataproducts), - otdb_id: convertNullToUndefined(task.otdb_id), - predecessor_ids: convertNullToUndefined(task.predecessor_ids), - project_mom2object_id: convertNullToUndefined(task.project_mom2object_id), - project_mom_id: convertNullToUndefined(task.project_mom_id), - project_name: convertNullToUndefined(task.project_name), - specification_id: convertNullToUndefined(task.specification_id), - starttime: convertNullToUndefined(task.starttime), - status: convertNullToUndefined(task.status), - status_id: convertNullToUndefined(task.status_id), - sub_type: convertNullToUndefined(task.sub_type), - successor_ids: convertNullToUndefined(task.successor_ids), - type: convertNullToUndefined(task.type), - type_id: convertNullToUndefined(task.type_id) - }; - gridTasks.push(gridTask); + + gridTasks.push(task); } } -- GitLab From d52ca395e8eb0de30fa3cf21f15c5beb82dde0a0 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 07:08:01 +0000 Subject: [PATCH 879/933] Task #9607: apply convertNullToUndefined for all loaded tasks --- .../lib/static/app/controllers/datacontroller.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 32e58d35e4a..78ca930a725 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -386,6 +386,9 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, if(task) { task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); + task.ingest_status = self.convertNullToUndefined(task.ingest_status); + task.disk_usage = self.convertNullToUndefined(task.disk_usage); + task.disk_usage_readable = self.convertNullToUndefined(task.disk_usage_readable); if(!self.taskDict.hasOwnProperty(task.id)) { self.tasks.push(task); @@ -427,6 +430,9 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, var task = result.tasks[i]; task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); + task.ingest_status = self.convertNullToUndefined(task.ingest_status); + task.disk_usage = self.convertNullToUndefined(task.disk_usage); + task.disk_usage_readable = self.convertNullToUndefined(task.disk_usage_readable); } var newTaskDict = self.toIdBasedDict(result.tasks); @@ -462,6 +468,9 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, var task = result.tasks[i]; task.starttime = self.convertDatestringToLocalUTCDate(task.starttime); task.endtime = self.convertDatestringToLocalUTCDate(task.endtime); + task.ingest_status = self.convertNullToUndefined(task.ingest_status); + task.disk_usage = self.convertNullToUndefined(task.disk_usage); + task.disk_usage_readable = self.convertNullToUndefined(task.disk_usage_readable); } var newTaskDict = self.toIdBasedDict(result.tasks); @@ -827,6 +836,9 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, if(!task) { changedTask.starttime = self.convertDatestringToLocalUTCDate(changedTask.starttime); changedTask.endtime = self.convertDatestringToLocalUTCDate(changedTask.endtime); + changedTask.ingest_status = self.convertNullToUndefined(changedTask.ingest_status); + changedTask.disk_usage = self.convertNullToUndefined(changedTask.disk_usage); + changedTask.disk_usage_readable = self.convertNullToUndefined(changedTask.disk_usage_readable); self.tasks.push(changedTask); self.taskDict[changedTask.id] = changedTask; } -- GitLab From 5fe8d28eab51b3fc05efe51d491e5a005473b4d4 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 08:27:38 +0000 Subject: [PATCH 880/933] Task #9607: repaired lost changes between r35441 and r35447 --- .../static/app/controllers/gridcontroller.js | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index adef33325d4..d8cb98e166e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -686,13 +686,23 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } - if(task.type == 'observation' && dataService.config.inspection_plots_base_url) { + var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); + var completed_selected_cep4_observations = completed_selected_cep4_tasks.filter(function(t) { return t.type == 'observation'; }); + + if(completed_selected_cep4_observations.length > 0 && dataService.config.inspection_plots_base_url) { var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>'); ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - var url = dataService.config.inspection_plots_base_url + '/' + task.otdb_id; - $window.open(url, '_blank'); + + var window_cntr = 0; + for(var obs of completed_selected_cep4_observations) { + var url = dataService.config.inspection_plots_base_url + '/' + obs.otdb_id; + setTimeout(function(url_arg) { + $window.open(url_arg, '_blank'); + }, window_cntr*750, url); + window_cntr += 1; + } }); } @@ -707,25 +717,27 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } - var liContent = selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' + var liContent = completed_selected_cep4_tasks.length > 0 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); - if(selected_cep4_tasks.length == selected_tasks.length) { + if(completed_selected_cep4_tasks.length > 0) { liElement.on('click', function() { closeContextMenu(); - cleanupCtrl.showTaskDiskUsage(task); + if(selected_cep4_tasks.length == 1) { + cleanupCtrl.showTaskDiskUsage(task); + } else { + cleanupCtrl.showTaskDiskUsage(completed_selected_cep4_tasks); + } }); } - var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; }); - - var liContent = completed_selected_cep4_tasks.length == selected_tasks.length ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>' + var liContent = completed_selected_cep4_tasks.length > 0 ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); - if(completed_selected_cep4_tasks.length == selected_tasks.length) { + if(completed_selected_cep4_tasks.length > 0) { liElement.on('click', function() { closeContextMenu(); - cleanupCtrl.deleteSelectedTasksDataWithConfirmation(); + cleanupCtrl.deleteTasksDataWithConfirmation(completed_selected_cep4_tasks); }); } -- GitLab From 525e0aa40cc5ec358a7c5b98be4d9f20a8a1fde2 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 08:39:03 +0000 Subject: [PATCH 881/933] Task #9607: tooltips refinements --- .../lib/static/app/controllers/gridcontroller.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index d8cb98e166e..f187513035e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -65,13 +65,18 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic $scope.columns = [ { field: 'name', enableCellEdit: false, + cellTooltip: function(row, col) { return row.entity.description; }, width: '*', minWidth: '100', }, { field: 'project_name', displayName:'Project', enableCellEdit: false, - cellTemplate:'<div style=\'padding-top:5px;\'><a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}">{{row.entity[col.field]}}</a></div>', + cellTemplate:'<div style=\'padding-top:5px;\'>' + + '<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}"' + + ' title="{{row.grid.appScope.dataService.momProjectsDict[row.entity.project_mom_id].description}}"' + + '>{{row.entity[col.field]}}' + + '</a></div>', width: '*', minWidth: '80', filter: { @@ -189,10 +194,10 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic cellTemplate:'<div style=\'text-align: center; padding-top:5px;\'>' + '<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}"' + 'title="' + - 'Project description:\n' + '{{row.grid.appScope.dataService.momProjectsDict[row.entity.project_mom_id].description}}\n\n' + - 'Task description:\n' + '{{row.entity.description}}\n\n' + - 'Group ID:\n' + '{{row.entity.mom_object_group_id}}\n\n' + - 'Parent group ID:\n' + '{{row.entity.mom_object_parent_group_id}}' + + 'Project description: ' + '{{row.grid.appScope.dataService.momProjectsDict[row.entity.project_mom_id].description}}\n' + + 'Task description: ' + '{{row.entity.description}}\n' + + 'Group ID: ' + '{{row.entity.mom_object_group_id}}\n' + + 'Parent group ID: ' + '{{row.entity.mom_object_parent_group_id}}' + '">{{row.entity[col.field]}} </a></div>', width: '65' }, -- GitLab From ef57e5ef73bca4c620b33b2b16929613df95cdbc Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 08:42:19 +0000 Subject: [PATCH 882/933] Task #9607: initial load complete check with .all --- .../static/app/controllers/datacontroller.js | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 78ca930a725..7b7d29a00e1 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -783,22 +783,15 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.lastUpdateChangeNumber = result.mostRecentChangeNumber; } - var nrOfItemsToLoad = 7; - var nrOfItemsLoaded = 0; - var checkInitialLoadCompleteness = function() { - nrOfItemsLoaded += 1; - if(nrOfItemsLoaded >= nrOfItemsToLoad) { - self.initialLoadComplete = true; - } - }; - - self.getConfig().then(checkInitialLoadCompleteness); - self.getMoMProjects().then(checkInitialLoadCompleteness); - self.getTaskTypes().then(checkInitialLoadCompleteness); - self.getTaskStatusTypes().then(checkInitialLoadCompleteness); - self.getResourceGroups().then(checkInitialLoadCompleteness); - self.getResources().then(checkInitialLoadCompleteness); - self.getResourceGroupMemberships().then(checkInitialLoadCompleteness); + $q.all([self.getConfig(), + self.getMoMProjects(), + self.getTaskTypes(), + self.getTaskStatusTypes(), + self.getResourceGroups(), + self.getResources(), + self.getResourceGroupMemberships()]).then(function() { + self.initialLoadComplete = true; + }); self.getTasksAndClaimsForViewSpan(); -- GitLab From 05bf3b55a87c94b3af030ae01331d471bfc48863 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 08:55:14 +0000 Subject: [PATCH 883/933] Task #9607: populate list after initial loca complete --- .../lib/static/app/controllers/gridcontroller.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index f187513035e..118a07f56df 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -348,7 +348,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic function populateListAsync() { var now = new Date(); var diff = now.getTime() - self.lastUpdateTimestamp.getTime(); - if(diff > 500) { + if(diff > 750) { self.waitingForDelayedUpdate = false; $scope.$evalAsync(populateList); } @@ -455,7 +455,9 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic }; $scope.$watch('dataService.filteredTaskChangeCntr', function() { $scope.$evalAsync(fillFilterSelectOptions()); }); - $scope.$watch('dataService.initialLoadComplete', function() { $scope.$evalAsync(fillFilterSelectOptions()); }); + $scope.$watch('dataService.initialLoadComplete', function() { + populateListAsync(); + $scope.$evalAsync(fillFilterSelectOptions()); }); function fillProjectsColumFilterSelectOptions() { var projectNames = []; -- GitLab From a02bf8bc6c189192f0c3592b15e2131ffae46d46 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 09:02:35 +0000 Subject: [PATCH 884/933] Task #9607: added mom_object_parent_group_name --- SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py index 328c24e9904..909490e31b7 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/mom.py @@ -70,6 +70,7 @@ def updateTaskMomDetails(task, momrpc): t['mom_object_group_name'] = m.get('object_group_name') t['mom_object_group_mom2object_id'] = m.get('object_group_mom2objectid') t['mom_object_parent_group_id'] = m['parent_group_mom2id'] + t['mom_object_parent_group_name'] = m['parent_group_name'] else: t['project_name'] = 'OTDB Only' t['project_mom_id'] = -98 -- GitLab From 0f6ade240dcada6117e99b753e86bbe896ea4955 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 09:02:47 +0000 Subject: [PATCH 885/933] Task #9607: tooltips --- .../lib/static/app/controllers/gridcontroller.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 118a07f56df..860af2da27a 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -180,7 +180,13 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic { field: 'mom_object_group_id', displayName: 'Group ID', enableCellEdit: false, - cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'><a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}">{{row.entity.mom_object_group_id}}</a></div>', + cellTemplate:'<div style=\'text-align: center; padding-top:5px;\'>' + + '<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}"' + + 'title="' + + 'Group name: ' + '{{row.entity.mom_object_group_name}}\n' + + 'Parent group name: ' + '{{row.entity.mom_object_parent_group_name}}\n' + + 'Parent group ID: ' + '{{row.entity.mom_object_parent_group_id}}' + + '">{{row.entity.mom_object_group_id}}</a></div>', width: '80', filter: { condition: uiGridConstants.filter.EXACT, @@ -196,7 +202,9 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic 'title="' + 'Project description: ' + '{{row.grid.appScope.dataService.momProjectsDict[row.entity.project_mom_id].description}}\n' + 'Task description: ' + '{{row.entity.description}}\n' + + 'Group name: ' + '{{row.entity.mom_object_group_name}}\n' + 'Group ID: ' + '{{row.entity.mom_object_group_id}}\n' + + 'Parent group name: ' + '{{row.entity.mom_object_parent_group_name}}\n' + 'Parent group ID: ' + '{{row.entity.mom_object_parent_group_id}}' + '">{{row.entity[col.field]}} </a></div>', width: '65' -- GitLab From 9e0712ef0a20d2fe1992a5f279d2c8990d9bff6d Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 09:31:30 +0000 Subject: [PATCH 886/933] Task #9607: fix in jumpToSelectedTasks --- .../static/app/controllers/datacontroller.js | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 7b7d29a00e1..0f9d94b3d0b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -945,19 +945,19 @@ dataControllerMod.controller('DataController', }; $scope.jumpToNow(); - + $scope.loadTasksSelectAndJumpIntoView = function(task_ids) { var list_of_promises = task_ids.map(function(t_id) { return $scope.dataService.getTask(t_id); }); var defer = $q.defer(); $q.all(list_of_promises).then(function(in_tasks) { - var loaded_tasks = in_tasks.filter(function(t) { return t != undefined; }); + var loaded_tasks = in_tasks.filter(function(t) { return t != undefined; }); var loaded_tasks_ids = loaded_tasks.map(function(t) { return t.id; }); $scope.dataService.setSelectedTaskIds(loaded_tasks_ids); $scope.jumpToSelectedTasks(); defer.resolve(loaded_tasks); }); return defer.promise; - }; + }; $scope.loadTaskByOTDBIdSelectAndJumpIntoView = function(otdb_id) { var defer = $q.defer(); @@ -1037,30 +1037,22 @@ dataControllerMod.controller('DataController', var tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; }); - if(tasks.lenght == 0) + if(tasks.length == 0) return; var minStarttime = new Date(Math.min.apply(null, tasks.map(function(t) { return t.starttime; }))); var maxEndtime = new Date(Math.max.apply(null, tasks.map(function(t) { return t.endtime; }))); - var selectedTasksDurationInmsec = maxEndtime.getTime() - minStarttime.getTime(); - var selectedTasksDurationInMinutes = selectedTasksDurationInmsec/60000; - var viewSpanInMinutes = selectedTasksDurationInMinutes; - - var fittingSpans = $scope.zoomTimespans.filter(function(w) { return w.value >= selectedTasksDurationInMinutes; }); - if(fittingSpans.length > 0) { - $scope.zoomTimespan = fittingSpans[0]; - //select one span larger if possible - if(fittingSpans.length > 1) - $scope.zoomTimespan = fittingSpans[1]; - viewSpanInMinutes = $scope.zoomTimespan.value; + if(maxEndtime <= minStarttime) { + //swap + var tmp = new Date(maxEndtime.getTime()); + maxEndtime = new Date(minStarttime.getTime()); + minStarttime = new Date(tmp.getTime()); } - var focusTime = new Date(minStarttime.getTime() + 0.5*selectedTasksDurationInmsec); - dataService.viewTimeSpan = { - from: dataService.floorDate(new Date(focusTime.getTime() - 0.5*viewSpanInMinutes*60*1000), 1, 5), - to: dataService.floorDate(new Date(focusTime.getTime() + 0.5*viewSpanInMinutes*60*1000), 1, 5) + from: dataService.floorDate(minStarttime, 1, 5), + to: dataService.ceilDate(maxEndtime, 1, 5) }; dataService.autoFollowNow = false; }; -- GitLab From ee050bba36ba708ae6450fe8b1e1d6f136318f81 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 11:23:26 +0000 Subject: [PATCH 887/933] Task #9607: added obsolete status color --- .../ResourceAssignmentEditor/lib/static/css/main.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index ffe7ca08a41..0326516672c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -205,6 +205,12 @@ div.gantt-task.claim-task-status-error span { color: #ffffff; } +.grid-status-obsolete, +div.gantt-task.task-status-obsolete div, +div.gantt-task.claim-task-status-obsolete span { + background-color: #bcb39c !important; +} + div.gantt-task.task-status-blocked div { /* background-image: url(/static/icons/blocked.png); background-repeat: no-repeat; */ -- GitLab From 8770695d83b1033696a012103b32b4d6f7c19729 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Wed, 28 Sep 2016 11:23:45 +0000 Subject: [PATCH 888/933] Task #9607: obsolete tasks should not block deletion --- SAS/DataManagement/CleanupService/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index 9853a50e22e..8179d9d8665 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -95,7 +95,7 @@ class CleanupHandler(MessageHandlerInterface): task = radbrpc.getTask(otdb_id=otdb_id) if task: suc_tasks = radbrpc.getTasks(task_ids=task['successor_ids']) - unfinished_scu_tasks = [t for t in suc_tasks if t['status'] != 'finished'] + unfinished_scu_tasks = [t for t in suc_tasks if !(t['status'] == 'finished' or t['status'] == 'obsolete')] if unfinished_scu_tasks: message = "Task otdb_id=%s has unfinished successor tasks (otdb_ids: %s)" % (task['otdb_id'], [t['otdb_id'] for t in unfinished_scu_tasks]) logger.error(message) -- GitLab From 67f564551cedaaca52fdda72d5433748856f7f7a Mon Sep 17 00:00:00 2001 From: Adriaan Renting <renting@astron.nl> Date: Wed, 28 Sep 2016 12:05:44 +0000 Subject: [PATCH 889/933] Task #9906: minor fix in string parsing to make it CEP4/CentOS7 compatible. Removed one period --- LTA/LTAIngest/ingestpipeline.py | 2 +- LTA/LTAIngest/ingestpipeline_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LTA/LTAIngest/ingestpipeline.py b/LTA/LTAIngest/ingestpipeline.py index 32ae589a94f..3e6e46f572b 100755 --- a/LTA/LTAIngest/ingestpipeline.py +++ b/LTA/LTAIngest/ingestpipeline.py @@ -215,7 +215,7 @@ class IngestPipeline(): self.logger.debug('Shell command for %s exited with code %s' % (self.JobId, p.returncode)) self.logger.debug('STD ERR of TransferFile command for %s:\n%s' % (self.JobId, logs[1])) self.logger.debug(log) - if (not 'No such file or directory.' in logs[1]) and (not 'does not exist' in logs[0]): + if (not 'No such file or directory' in logs[1]) and (not 'does not exist' in logs[0]): if not self.ParseLTAcpLog(log): self.logger.error("Parsing ltacp result failed for %s" % self.JobId) raise Exception('File transfer failed of %s' % self.JobId) diff --git a/LTA/LTAIngest/ingestpipeline_test.py b/LTA/LTAIngest/ingestpipeline_test.py index 7b04ab91942..3bf5b75415c 100755 --- a/LTA/LTAIngest/ingestpipeline_test.py +++ b/LTA/LTAIngest/ingestpipeline_test.py @@ -205,7 +205,7 @@ class IngestPipeline(): ## self.logger.debug('Shell command for %s exited with code %s' % (self.JobId, p.returncode)) self.logger.debug('STD ERR of TransferFile command for %s:\n%s' % (self.JobId, logs[1])) self.logger.debug(log) - if (not 'No such file or directory.' in logs[1]) and (not 'does not exist' in logs[0]): + if (not 'No such file or directory' in logs[1]) and (not 'does not exist' in logs[0]): if not self.ParseLTAcpLog(log): self.logger.error("Parsing ltacp result failed for %s" % self.JobId) raise Exception('File transfer failed of %s' % self.JobId) -- GitLab From 58ddf893036b82b36bd2e81afb9fbe5fd1c31040 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 04:30:04 +0000 Subject: [PATCH 890/933] Task #9607: renamed rest url momprojects to projects --- .../lib/static/app/controllers/datacontroller.js | 6 +++--- .../ResourceAssignmentEditor/lib/webservice.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 0f9d94b3d0b..01c76523771 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -692,7 +692,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.getMoMProjects = function() { var defer = $q.defer(); - $http.get('/rest/momprojects').success(function(result) { + $http.get('/rest/projects').success(function(result) { //convert datetime strings to Date objects var dict = {}; var list = []; @@ -726,7 +726,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.getProjectDiskUsage = function(project_name) { var defer = $q.defer(); - $http.get('/rest/momprojects/' + project_name + '/diskusage').success(function(result) { + $http.get('/rest/projects/' + project_name + '/diskusage').success(function(result) { defer.resolve(result); }).error(function(result) { defer.resolve({found:false}); @@ -737,7 +737,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.getProjectsDiskUsage = function() { var defer = $q.defer(); - $http.get('/rest/momprojects/diskusage').success(function(result) { + $http.get('/rest/projects/diskusage').success(function(result) { defer.resolve(result); }).error(function(result) { defer.resolve({found:false}); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 146ef8a2a79..3b72fd88922 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -478,7 +478,7 @@ def resourceclaimpropertytypes(): result = sorted(result, key=lambda q: q['id']) return jsonify({'resourceclaimpropertytypes': result}) -@app.route('/rest/momprojects') +@app.route('/rest/projects') @gzipped def getMoMProjects(): projects = [] @@ -494,7 +494,7 @@ def getMoMProjects(): projects.append({'name':'OTDB Only', 'mom_id':-98, 'description': 'Container project for tasks which exists only in OTDB'}) return jsonify({'momprojects': projects}) -@app.route('/rest/momprojects/<int:project_mom2id>') +@app.route('/rest/projects/<int:project_mom2id>') @gzipped def getMoMProject(project_mom2id): try: @@ -508,7 +508,7 @@ def getMoMProject(project_mom2id): logger.error(e) abort(404, str(e)) -@app.route('/rest/momprojects/<int:project_mom2id>/diskusage') +@app.route('/rest/projects/<int:project_mom2id>/diskusage') @gzipped def getMoMProjectDiskUsageById(project_mom2id): try: @@ -522,7 +522,7 @@ def getMoMProjectDiskUsageById(project_mom2id): logger.error(e) abort(404, str(e)) -@app.route('/rest/momprojects/<string:project_name>/diskusage') +@app.route('/rest/projects/<string:project_name>/diskusage') @gzipped def getMoMProjectDiskUsageByName(project_name): try: @@ -532,7 +532,7 @@ def getMoMProjectDiskUsageByName(project_name): logger.error(e) abort(404, str(e)) -@app.route('/rest/momprojects/diskusage') +@app.route('/rest/projects/diskusage') @gzipped def getMoMProjectsDiskUsage(): try: -- GitLab From 6962b1181721a91d8e3d1118404db6abb3a88f69 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 07:23:26 +0000 Subject: [PATCH 891/933] Task #9886: made queries and rest urls to get tasks by project --- SAS/MoM/MoMQueryService/momqueryrpc.py | 13 +++++ SAS/MoM/MoMQueryService/momqueryservice.py | 52 ++++++++++++++++++- .../lib/webservice.py | 45 ++++++++++++---- 3 files changed, 98 insertions(+), 12 deletions(-) diff --git a/SAS/MoM/MoMQueryService/momqueryrpc.py b/SAS/MoM/MoMQueryService/momqueryrpc.py index 1ad59e4c361..99a51e57268 100644 --- a/SAS/MoM/MoMQueryService/momqueryrpc.py +++ b/SAS/MoM/MoMQueryService/momqueryrpc.py @@ -45,6 +45,19 @@ class MoMQueryRPC(RPCWrapper): logger.info("Received %s projects" % (len(projects))) return projects + def getProject(self, project_mom2id): + '''get projects by mo2_id''' + logger.info("getProject(%s)", project_mom2id) + project = self.rpc('GetProject', project_mom2id=project_mom2id) + return project + + def getProjectTaskIds(self, project_mom2id): + '''get all task mom2id's for the given project + :rtype dict with all projects''' + logger.info("getProjectTaskIds") + task_ids = self.rpc('GetProjectTaskIds', project_mom2id=project_mom2id) + return task_ids + def getPredecessorIds(self, ids): logger.debug("getSuccessorIds(%s)", ids) result = self.rpc('GetPredecessorIds', mom_ids=ids) diff --git a/SAS/MoM/MoMQueryService/momqueryservice.py b/SAS/MoM/MoMQueryService/momqueryservice.py index e950237a9b3..2b57e1744fa 100755 --- a/SAS/MoM/MoMQueryService/momqueryservice.py +++ b/SAS/MoM/MoMQueryService/momqueryservice.py @@ -167,6 +167,48 @@ class MoMDatabaseWrapper: return result + def getProject(self, project_mom2id): + ''' get project for the given project_mom2id with columns (project_mom2id, project_name, + project_description, status_name, status_id, last_user_id, + last_user_name, statustime) + ''' + ids_str = _toIdsString(mom_ids) + + # TODO: make a view for this query in momdb! + query = '''SELECT project.mom2id as mom2id, project.name as name, project.description as description, + statustype.code as status_name, statustype.id as status_id, + status.userid as last_user_id, status.name as last_user_name, status.statustime as statustime + FROM mom2object as project + left join mom2objectstatus as status on project.currentstatusid = status.id + left join status as statustype on status.statusid=statustype.id + where project.mom2objecttype='PROJECT' and project.mom2id = %s + order by mom2id; + ''' % (ids_str) + result = self._executeQuery(query) + + return result + + def getProjectTaskIds(self, project_mom2id): + if not project_mom2id: + return {} + + ids_str = _toIdsString(project_mom2id) + + logger.info("getProjectTaskIds for project_mom2id: %s" % ids_str) + + query = '''SELECT tasks.mom2id FROM mom2object tasks + inner join mom2object project on project.id = tasks.ownerprojectid + where project.mom2id = %s and + (tasks.mom2objecttype = 'LOFAR_OBSERVATION' or tasks.mom2objecttype like \'%%PIPELINE%%\');''' % ids_str + + rows = self._executeQuery(query) + + result = { 'project_mom2id': project_mom2id, 'task_mom2ids': [r['mom2id'] for r in rows]} + + logger.info('task ids for project: %s', result) + + return result + def getPredecessorIds(self, mom_ids): if not mom_ids: return {} @@ -351,12 +393,14 @@ class ProjectDetailsQueryHandler(MessageHandlerInterface): self.service2MethodMap = { 'GetProjects': self.getProjects, + 'GetProject': self.getProject, 'GetProjectDetails': self.getProjectDetails, 'GetPredecessorIds': self.getPredecessorIds, 'GetSuccessorIds': self.getSuccessorIds, 'GetTaskIdsInGroup': self.getTaskIdsInGroup, 'GetTaskIdsInParentGroup': self.getTaskIdsInParentGroup, - 'GetDataProducts': self.getDataProducts + 'GetDataProducts': self.getDataProducts, + 'GetProjectTaskIds': self.getProjectTaskIds } def prepare_loop(self): @@ -368,6 +412,12 @@ class ProjectDetailsQueryHandler(MessageHandlerInterface): def getProjects(self): return self.momdb.getProjects() + def getProjectTaskIds(self, project_mom2id): + return self.momdb.getProjectTaskIds(project_mom2id) + + def getProject(self, project_mom2id): + return self.momdb.getProject(project_mom2id) + def getPredecessorIds(self, mom_ids): return self.momdb.getPredecessorIds(mom_ids) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index 3b72fd88922..cc2ecfe214b 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -219,9 +219,6 @@ def getTasksFromUntil(fromTimestamp=None, untilTimestamp=None): tasks = rarpc.getTasks(fromTimestamp, untilTimestamp) - # there are no task names in the database yet. - # will they come from spec/MoM? - # add Task <id> as name for now updateTaskMomDetails(tasks, momqueryrpc) updateTaskStorageDetails(tasks, sqrpc) @@ -480,7 +477,7 @@ def resourceclaimpropertytypes(): @app.route('/rest/projects') @gzipped -def getMoMProjects(): +def getProjects(): projects = [] try: projects = momqueryrpc.getProjects() @@ -496,7 +493,7 @@ def getMoMProjects(): @app.route('/rest/projects/<int:project_mom2id>') @gzipped -def getMoMProject(project_mom2id): +def getProject(project_mom2id): try: projects = momqueryrpc.getProjects() project = next(x for x in projects if x['mom2id'] == project_mom2id) @@ -508,13 +505,39 @@ def getMoMProject(project_mom2id): logger.error(e) abort(404, str(e)) +@app.route('/rest/projects/<int:project_mom2id>/tasks') +@gzipped +def getProjectTasks(project_mom2id): + return getProjectTasksFromUntil(project_mom2id, None, None) + +@app.route('/rest/projects/<int:project_mom2id>/tasks/<string:fromTimestamp>/<string:untilTimestamp>') +@gzipped +def getProjectTasksFromUntil(project_mom2id, fromTimestamp=None, untilTimestamp=None): + try: + if fromTimestamp and isinstance(fromTimestamp, basestring): + fromTimestamp = asDatetime(fromTimestamp) + + if untilTimestamp and isinstance(untilTimestamp, basestring): + untilTimestamp = asDatetime(untilTimestamp) + + task_mom2ids = momqueryrpc.getProjectTaskIds(project_mom2id)['task_mom2ids'] + + tasks = rarpc.getTasks(mom_ids=task_mom2ids, lower_bound=fromTimestamp, upper_bound=untilTimestamp) + + updateTaskMomDetails(tasks, momqueryrpc) + #updateTaskStorageDetails(tasks, sqrpc) + + return jsonify({'tasks': tasks}) + except Exception as e: + logger.error(e) + abort(404, str(e)) + @app.route('/rest/projects/<int:project_mom2id>/diskusage') @gzipped -def getMoMProjectDiskUsageById(project_mom2id): +def getProjectDiskUsageById(project_mom2id): try: - projects = momqueryrpc.getProjects() - project_name = next(x['name'] for x in projects if x['mom2id'] == project_mom2id) - return getMoMProjectDiskUsageByName(project_name) + project = momqueryrpc.getProject(project_mom2id=project_mom2id) + return getProjectDiskUsageByName(project['name']) except StopIteration as e: logger.error(e) abort(404, "No project with mom2id %s" % project_mom2id) @@ -524,7 +547,7 @@ def getMoMProjectDiskUsageById(project_mom2id): @app.route('/rest/projects/<string:project_name>/diskusage') @gzipped -def getMoMProjectDiskUsageByName(project_name): +def getProjectDiskUsageByName(project_name): try: result = sqrpc.getDiskUsageForProjectDirAndSubDirectories(project_name=project_name) return jsonify(result) @@ -534,7 +557,7 @@ def getMoMProjectDiskUsageByName(project_name): @app.route('/rest/projects/diskusage') @gzipped -def getMoMProjectsDiskUsage(): +def getProjectsDiskUsage(): try: result = sqrpc.getDiskUsageForProjectsDirAndSubDirectories() return jsonify(result) -- GitLab From 4adbed4975b468e70841b58acdef4f3463a92dd1 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Thu, 29 Sep 2016 07:39:44 +0000 Subject: [PATCH 892/933] Task #8693: Dragnet variants file: use cuda-8.0 --- CMake/variants/variants.dragnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMake/variants/variants.dragnet b/CMake/variants/variants.dragnet index de81cc7cd9a..76573452c5a 100644 --- a/CMake/variants/variants.dragnet +++ b/CMake/variants/variants.dragnet @@ -13,7 +13,7 @@ option(USE_CUDA "Use CUDA" ON) # Specify versioned paths, such that ABI incompat updates of these don't break already installed LOFAR binaries. Matters when we have to roll-back. set(LOG4CPLUS_ROOT_DIR /opt/log4cplus-1.1.2) # RHEL/CentOS 7 has log4cxx in the repo, but LOFAR log4cxx is dodgy, so install log4cplus from CentOS 6 rpm pkgs. set(BLITZ_ROOT_DIR /opt/blitz-0.10) -set(CUDA_TOOLKIT_ROOT_DIR /opt/cuda-7.5) # libcuda.so on CentOS 7 w/ CUDA driver from ElRepo resides under /usr/lib64/nvidia/ +set(CUDA_TOOLKIT_ROOT_DIR /opt/cuda-8.0) # libcuda.so on CentOS 7 w/ CUDA driver from ElRepo resides under /usr/lib64/nvidia/ set(CASACORE_ROOT_DIR /opt/casacore-2.1.0) set(CASAREST_ROOT_DIR /opt/casarest-1.4.1) set(CASA_ROOT_DIR /opt/casasynthesis) # for awimager2; pkg has no releases; it's a chunk of CASA, so var name is misleading, since it'll fail on the real CASA root dir -- GitLab From ac976ce53d8354c1463741033285007dd39800ea Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Thu, 29 Sep 2016 08:10:41 +0000 Subject: [PATCH 893/933] Task #8693: Dragnet variants file: revert to cuda-7.5, since 8.0 needs an even newer driver than the installer claims and we had installed --- CMake/variants/variants.dragnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMake/variants/variants.dragnet b/CMake/variants/variants.dragnet index 76573452c5a..de81cc7cd9a 100644 --- a/CMake/variants/variants.dragnet +++ b/CMake/variants/variants.dragnet @@ -13,7 +13,7 @@ option(USE_CUDA "Use CUDA" ON) # Specify versioned paths, such that ABI incompat updates of these don't break already installed LOFAR binaries. Matters when we have to roll-back. set(LOG4CPLUS_ROOT_DIR /opt/log4cplus-1.1.2) # RHEL/CentOS 7 has log4cxx in the repo, but LOFAR log4cxx is dodgy, so install log4cplus from CentOS 6 rpm pkgs. set(BLITZ_ROOT_DIR /opt/blitz-0.10) -set(CUDA_TOOLKIT_ROOT_DIR /opt/cuda-8.0) # libcuda.so on CentOS 7 w/ CUDA driver from ElRepo resides under /usr/lib64/nvidia/ +set(CUDA_TOOLKIT_ROOT_DIR /opt/cuda-7.5) # libcuda.so on CentOS 7 w/ CUDA driver from ElRepo resides under /usr/lib64/nvidia/ set(CASACORE_ROOT_DIR /opt/casacore-2.1.0) set(CASAREST_ROOT_DIR /opt/casarest-1.4.1) set(CASA_ROOT_DIR /opt/casasynthesis) # for awimager2; pkg has no releases; it's a chunk of CASA, so var name is misleading, since it'll fail on the real CASA root dir -- GitLab From d7de2fa301c41e465cbebac54c0657fa5409bfbe Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 10:10:26 +0000 Subject: [PATCH 894/933] Task #9607: typo fix --- .../lib/static/app/controllers/gridcontroller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 860af2da27a..4198858bba2 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -236,6 +236,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic }, sort: { direction: uiGridConstants.ASC, priority: 1 } }]; + $scope.gridOptions = { enableGridMenu: false, enableSorting: true, @@ -728,7 +729,7 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - row.appScope.openLtaLocation(ingest_tasks); + row.grid.appScope.openLtaLocation(ingest_tasks); }); } -- GitLab From ea3783e5801a294d3c48585a0b93007ed90b1ecb Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 10:15:31 +0000 Subject: [PATCH 895/933] Task #9886: added getTasksTimeWindow method --- .../ResourceAssignmentDatabase/radb.py | 46 +++++++++++++++++++ .../ResourceAssignmentService/rpc.py | 6 +++ .../ResourceAssignmentService/service.py | 7 +++ 3 files changed, 59 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py index 3d1c0ea38c7..14a18dea7ed 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py @@ -165,6 +165,52 @@ class RADatabase: raise KeyError('No such status: %s. Valid values are: %s' % (status_name, ', '.join(self.getResourceClaimStatusNames()))) + def getTasksTimeWindow(self, task_ids=None, mom_ids=None, otdb_ids=None): + if len([x for x in [task_ids, mom_ids, otdb_ids] if x != None]) > 1: + raise KeyError("Provide either task_ids or mom_ids or otdb_ids, not multiple kinds.") + + query = '''SELECT min(starttime) as min_starttime, max(endtime) as max_endtime from resource_allocation.task_view''' + + conditions = [] + qargs = [] + + if task_ids is not None: + if isinstance(task_ids, int): # just a single id + conditions.append('id = %s') + qargs.append(task_ids) + elif len(task_ids) > 0: #assume a list/enumerable of id's + conditions.append('id in %s') + qargs.append(tuple(task_ids)) + elif len(task_ids) == 0: #assume a list/enumerable of id's, length 0 + return [] + + if mom_ids is not None: + if isinstance(mom_ids, int): # just a single id + conditions.append('mom_id = %s') + qargs.append(mom_ids) + elif len(mom_ids) > 0: #assume a list/enumerable of id's + conditions.append('mom_id in %s') + qargs.append(tuple(mom_ids)) + elif len(mom_ids) == 0: #assume a list/enumerable of id's, length 0 + return [] + + if otdb_ids is not None: + if isinstance(otdb_ids, int): # just a single id + conditions.append('otdb_id = %s') + qargs.append(otdb_ids) + elif len(otdb_ids) > 0: #assume a list/enumerable of id's + conditions.append('otdb_id in %s') + qargs.append(tuple(otdb_ids)) + elif len(otdb_ids) == 0: #assume a list/enumerable of id's, length 0 + return [] + + if conditions: + query += ' WHERE ' + ' AND '.join(conditions) + + result = list(self._executeQuery(query, qargs, fetch=_FETCH_ALL)) + + return result + def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None, cluster=None): if len([x for x in [task_ids, mom_ids, otdb_ids] if x != None]) > 1: raise KeyError("Provide either task_ids or mom_ids or otdb_ids, not multiple kinds.") diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py index fd258223c4a..e7720e0acfa 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py @@ -179,6 +179,12 @@ class RARPC(RPCWrapper): otdb_id=otdb_id, status=status) + def getTasksTimeWindow(self, task_ids=None, mom_ids=None, otdb_ids=None): + result = self.rpc('GetTasksTimeWindow', task_ids=task_ids, mom_ids=mom_ids, otdb_ids=otdb_ids) + result['min_starttime'] = result['min_starttime'].datetime() + result['max_endtime'] = result['max_endtime'].datetime() + return result + def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None, cluster=None): '''getTasks let's you query tasks from the radb with many optional filters. :param lower_bound: datetime specifies the lower_bound of a time window above which to select tasks diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/service.py b/SAS/ResourceAssignment/ResourceAssignmentService/service.py index 10afe86da26..6c9138a8a11 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentService/service.py +++ b/SAS/ResourceAssignment/ResourceAssignmentService/service.py @@ -56,6 +56,7 @@ class RADBHandler(MessageHandlerInterface): 'GetResourceTypes': self._getResourceTypes, 'GetResources': self._getResources, 'UpdateResourceAvailability': self._updateResourceAvailability, + 'GetTasksTimeWindow': self._getTasksTimeWindow, 'GetTasks': self._getTasks, 'GetTask': self._getTask, 'InsertTask': self._insertTask, @@ -211,6 +212,12 @@ class RADBHandler(MessageHandlerInterface): available_capacity=kwargs.get('available_capacity'), total_capacity=kwargs.get('total_capacity')) + def _getTasksTimeWindow(self, **kwargs): + logger.info('GetTasksTimeWindow: %s' % dict({k:v for k,v in kwargs.items() if v != None})) + return self.radb.getTasksTimeWindow(task_ids=kwargs.get('task_ids'), + mom_ids=kwargs.get('mom_ids'), + otdb_ids=kwargs.get('otdb_ids')) + def _getTasks(self, **kwargs): logger.info('GetTasks: %s' % dict({k:v for k,v in kwargs.items() if v != None})) return self.radb.getTasks(lower_bound=kwargs.get('lower_bound').datetime() if kwargs.get('lower_bound') else None, -- GitLab From 77ecbd6e22375c00b969c6a30e1c0ecaef366fcf Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 11:56:35 +0000 Subject: [PATCH 896/933] Task #9886: added projects view --- .gitattributes | 1 + .../lib/CMakeLists.txt | 3 +- .../static/app/controllers/datacontroller.js | 97 ++++++++++++-- .../static/app/controllers/gridcontroller.js | 24 ++-- .../lib/templates/index.html | 2 +- .../lib/templates/projects.html | 120 ++++++++++++++++++ .../lib/webservice.py | 24 +++- 7 files changed, 249 insertions(+), 22 deletions(-) create mode 100644 SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html diff --git a/.gitattributes b/.gitattributes index c845f0cb81c..786f4056a41 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5322,6 +5322,7 @@ SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/utils/ui-bootstrap SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/js/utils/ui-grid-edit-datepicker.js -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/storage.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html -text +SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/utils.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py -text SAS/ResourceAssignment/ResourceAssignmentEditor/test/CMakeLists.txt -text diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt index a2cf8287a7f..2d2a6b7986c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/CMakeLists.txt @@ -47,7 +47,8 @@ set(app_files static/icons/ingest_in_progress.png static/icons/ingest_failed.png static/icons/ingest_successful.png - templates/index.html) + templates/index.html + templates/projects.html) set(web_files ${jquery_files} diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 01c76523771..735be7ea7fc 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -2,6 +2,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, $q){ var self = this; + self.projectMode = false; self.tasks = []; self.resources = []; self.resourceGroups = []; @@ -37,6 +38,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.selected_project_id; self.selected_resourceClaim_id; + self.selected_project = { name: 'Please select project', value: undefined }; + self.initialLoadComplete = false; self.taskChangeCntr = 0; self.filteredTaskChangeCntr = 0; @@ -212,7 +215,8 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, timestamp += 3600000; } else { - var chuckUpperLimit = Math.min(upperTS, timestamp + 24*3600000); + var chunkFactor = self.projectMode ? 7 : 1; + var chuckUpperLimit = Math.min(upperTS, timestamp + chunkFactor*24*3600000); for (var chunkTimestamp = timestamp; chunkTimestamp < chuckUpperLimit; chunkTimestamp += 3600000) { if(self.loadedHours.hasOwnProperty(chunkTimestamp)) break; @@ -281,6 +285,15 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.getTasks = function(from, until) { var defer = $q.defer(); var url = '/rest/tasks'; + if(self.projectMode) { + if(self.selected_project_id === undefined){ + defer.resolve([]); + return defer; + } + + url = '/rest/projects/' + self.selected_project_id + '/tasks'; + } + if(from) { url += '/' + self.convertLocalUTCDateToISOString(from); @@ -307,10 +320,13 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, for(var i = newTaskIds.length-1; i >= 0; i--) { var task_id = newTaskIds[i]; + if(!self.taskDict.hasOwnProperty(task_id)) { var task = newTaskDict[task_id]; - self.tasks.push(task); - self.taskDict[task_id] = task; + if(!self.projectMode || self.selected_project_id == task.project_mom_id) { + self.tasks.push(task); + self.taskDict[task_id] = task; + } } } @@ -562,6 +578,10 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.getResourceUsages = function() { var defer = $q.defer(); + if(self.projectMode) { + defer.resolve([]); + return defer; + } $http.get('/rest/resourceusages').success(function(result) { //convert datetime strings to Date objects for(var i in result.resourceusages) { @@ -586,6 +606,10 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.getResourceClaims = function(from, until) { var defer = $q.defer(); + if(self.projectMode) { + defer.resolve([]); + return defer; + } var url = '/rest/resourceclaims'; if(from) { url += '/' + self.convertLocalUTCDateToISOString(from); @@ -724,6 +748,17 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, }); }; + self.getProjectTasksTimeWindow = function(project_mom_id) { + var defer = $q.defer(); + $http.get('/rest/projects/' + project_mom_id + '/taskstimewindow').success(function(result) { + defer.resolve(result); + }).error(function(result) { + defer.resolve(undefined); + }); + + return defer.promise; + }; + self.getProjectDiskUsage = function(project_name) { var defer = $q.defer(); $http.get('/rest/projects/' + project_name + '/diskusage').success(function(result) { @@ -783,13 +818,18 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.lastUpdateChangeNumber = result.mostRecentChangeNumber; } - $q.all([self.getConfig(), - self.getMoMProjects(), - self.getTaskTypes(), - self.getTaskStatusTypes(), - self.getResourceGroups(), - self.getResources(), - self.getResourceGroupMemberships()]).then(function() { + var load_promisses = [self.getConfig(), + self.getMoMProjects(), + self.getTaskTypes(), + self.getTaskStatusTypes()]; + + if(!self.projectMode) { + load_promisses = load_promisses.concat([self.getResourceGroups(), + self.getResources(), + self.getResourceGroupMemberships()]); + } + + $q.all(load_promisses).then(function() { self.initialLoadComplete = true; }); @@ -826,7 +866,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } } else if(change.changeType == 'insert') { var task = self.taskDict[changedTask.id]; - if(!task) { + if(!task && (!self.projectMode || self.selected_project_id == task.project_mom_id)) { changedTask.starttime = self.convertDatestringToLocalUTCDate(changedTask.starttime); changedTask.endtime = self.convertDatestringToLocalUTCDate(changedTask.endtime); changedTask.ingest_status = self.convertNullToUndefined(changedTask.ingest_status); @@ -849,6 +889,9 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.computeMinMaxTaskTimes(); } else if(change.objectType == 'resourceClaim') { + if(self.projectMode) + continue; //skip claims in projectMode + anyResourceClaims = true; var changedClaim = change.value; if(change.changeType == 'update') { @@ -877,6 +920,9 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.claimChangeCntr++; self.computeMinMaxResourceClaimTimes(); } else if(change.objectType == 'resourceCapacity') { + if(self.projectMode) + continue; //skip capacities in projectMode + if(change.changeType == 'update') { var changedCapacity = change.value; var resource = self.resourceDict[changedCapacity.resource_id]; @@ -886,6 +932,9 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } } } else if(change.objectType == 'resourceAvailability') { + if(self.projectMode) + continue; //skip resourceAvailability in projectMode + if(change.changeType == 'update') { var changedAvailability = change.value; var resource = self.resourceDict[changedAvailability.resource_id]; @@ -1159,6 +1208,32 @@ dataControllerMod.controller('DataController', } }); + $scope.$watch('dataService.selected_project_id', function() { + if(dataService.projectMode) { + $scope.$evalAsync(function() { + dataService.autoFollowNow = false; + dataService.viewTimeSpan.from = dataService.lofarTime; + dataService.viewTimeSpan.to = dataService.lofarTime; + dataService.tasks.splice(0, dataService.tasks.length); + dataService.tasksDict = dataService.toIdBasedDict(dataService.tasks); + dataService.taskChangeCntr++; + dataService.getProjectTasksTimeWindow(dataService.selected_project_id).then(function(window) { + if(window.min_starttime && window.max_endtime) { + dataService.viewTimeSpan.from = dataService.convertDatestringToLocalUTCDate(window.min_starttime); + dataService.viewTimeSpan.to = dataService.convertDatestringToLocalUTCDate(window.max_endtime); + dataService.getTasksAndClaimsForViewSpan(); + } + }); + }); + } + }); + + $scope.$watch('dataService.selected_project', function() { + $scope.$evalAsync(function() { + dataService.selected_project_id = dataService.selected_project.value; + }); + }); + dataService.initialLoad(); //clock ticking every second diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 4198858bba2..6c1dfb2b04e 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -237,6 +237,10 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic sort: { direction: uiGridConstants.ASC, priority: 1 } }]; + if($scope.dataService.projectMode) { + $scope.columns.splice(1, 1); + } + $scope.gridOptions = { enableGridMenu: false, enableSorting: true, @@ -338,6 +342,9 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic }; function fillColumFilterSelectOptions(options, columnDef) { + if (columnDef == undefined) + return; + var columnSelectOptions = []; if(options) { for(var i = 0; i < options.length; i++) { @@ -434,7 +441,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic var groupSelectOptions = [ { value: mom_object_group_id, label: label} ]; - fillColumFilterSelectOptions(groupSelectOptions, $scope.columns[9]); + fillColumFilterSelectOptions(groupSelectOptions, $scope.columns.find(function(c) {return c.field == 'mom_object_group_id'; })); group_col.filters[0].term = mom_object_group_id; } } @@ -459,7 +466,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic fillTypeColumFilterSelectOptions(); fillProjectsColumFilterSelectOptions(); fillGroupsColumFilterSelectOptions(); - fillColumFilterSelectOptions(['CEP2', 'CEP4'], $scope.columns[13]); + fillColumFilterSelectOptions(['CEP2', 'CEP4'], $scope.columns.find(function(c) {return c.field == 'cluster'; })); } }; @@ -491,7 +498,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic } } projectNames.sort(); - fillColumFilterSelectOptions(projectNames, $scope.columns[1]); + fillColumFilterSelectOptions(projectNames, $scope.columns.find(function(c) {return c.field == 'project_name'; })); }; function fillStatusColumFilterSelectOptions() { @@ -507,7 +514,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic task_statuses = task_statuses.unique(); task_statuses.sort(); - fillColumFilterSelectOptions(task_statuses, $scope.columns[5]); + fillColumFilterSelectOptions(task_statuses, $scope.columns.find(function(c) {return c.field == 'status'; })); }; function fillTypeColumFilterSelectOptions() { @@ -523,11 +530,12 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic task_types = task_types.unique(); task_types.sort(); - fillColumFilterSelectOptions(task_types, $scope.columns[7]); + fillColumFilterSelectOptions(task_types, $scope.columns.find(function(c) {return c.field == 'type'; })); }; function fillGroupsColumFilterSelectOptions() { - if($scope.columns[9].filter.term) { + var group_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_object_group_id'; }); + if(!group_col || group_col.filter.term) { return; } @@ -548,7 +556,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic groupIds.sort(); - fillColumFilterSelectOptions(groupIds, $scope.columns[9]); + fillColumFilterSelectOptions(groupIds, $scope.columns.find(function(c) {return c.field == 'mom_object_group_id'; })); }; function fillInfoColumFilterSelectOptions() { @@ -592,7 +600,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic } task_info.sort(keysrt('value')); - fillColumFilterSelectOptions(task_info, $scope.columns[6]); + fillColumFilterSelectOptions(task_info, $scope.columns.find(function(c) {return c.field == 'info'; })); }; $scope.$watch('dataService.selected_task_ids', onSelectedTaskIdsChanged, true); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index 4a2109ce11a..6090be4503d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -53,7 +53,7 @@ <div ng-controller="DataController as dataCtrl" class="container-fluid"> <div ng-controller="CleanupController as cleanupCtrl" class="container-fluid"> <div class="row"> - <div class="col-md-2"> + <div class="col-md-1"> <label>Time (UTC):</label> <p> <strong style="font-size:16px">{{dataService.lofarTime | date }}</strong> diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html new file mode 100644 index 00000000000..6fe5bccb97a --- /dev/null +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html @@ -0,0 +1,120 @@ +<!doctype html> +<!-- $Id: index.html 35449 2016-09-28 06:54:12Z schaap $ --> +<html lang='en' ng-app="raeApp"> + <head> + <meta http-equiv='Content-Type' content='text/html; charset=utf-8'/> + <title>{{title}}</title> + <link rel='shortcut icon' href='{{ url_for('static', filename='favicon.ico') }}'> + <link href="/static/js/angular-gantt/angular-gantt.min.css" rel="stylesheet" type="text/css"> + <link href="/static/js/angular-gantt/angular-gantt-plugins.min.css" rel="stylesheet" type="text/css"> + <link href="/static/css/bootstrap.min.css" rel="stylesheet" type="text/css"> + <link href="/static/js/angular-ui-grid/ui-grid.min.css" rel="stylesheet" type="text/css"> + <link href="/static/js/angular-ui-layout/angular-ui-layout.css" rel="stylesheet" type="text/css"> + <link href="/static/js/angular-material/angular-material.min.css" rel="stylesheet" type="text/css"> + <link href="/static/js/utils/datetimepicker.css" rel="stylesheet" type="text/css"> + <link href="/static/css/main.css" rel="stylesheet" type="text/css"> + <script src="/static/js/utils/startswith.js"></script> + <script src="/static/js/moment/moment.js"></script> + <script src="/static/js/jquery/jquery.min.js"></script> + <script src="/static/js/utils/bootstrap.min.js"></script> + <script type="text/javascript" src="/static/js/highcharts/highcharts.js"></script> + <script type="text/javascript" src="/static/js/highcharts/exporting.js"></script> + <script src="/static/js/angular/angular.min.js"></script> + <script src="/static/js/utils/ui-bootstrap-tpls.min.js"></script> + <script src="/static/js/angular-route/angular-route.min.js"></script> + <script src="/static/js/angular-touch/angular-touch.js"></script> + <script src="/static/js/angular-resource/angular-resource.min.js"></script> +<!-- <script src="/static/js/utils/ui-grid-edit-datepicker.js"></script>--> + <script src="/static/js/angular-ui-grid/ui-grid.js"></script> + <script src="/static/js/angular-ui-tree/angular-ui-tree.js"></script> + <script src="/static/js/angular-ui-layout/angular-ui-layout.min.js"></script> + <script src="/static/js/angular-ui-tabs/angular-ui.bootstrap.tabs.min.js"></script> + <script src="/static/js/angular-moment/angular-moment.js"></script> + <script src="/static/js/angular-sanitize/angular-sanitize.min.js"></script> + <script src="/static/js/angular-animate/angular-animate.min.js"></script> + <script src="/static/js/angular-aria/angular-aria.min.js"></script> + <script src="/static/js/angular-material/angular-material.min.js"></script> + <script src="/static/js/utils/datetimepicker.js"></script> + <script src="/static/js/jsplumb/jsplumb-2.0.7-min.js"></script> + <script src="/static/js/angular-gantt/angular-gantt.js"></script> + <script src="/static/js/angular-gantt/angular-gantt-plugins.js"></script> + <script type="text/javascript" src="/static/js/highcharts/highcharts-ng.js"></script> + <script src="/static/app/app.js"></script> + <script src="/static/app/controllers/datacontroller.js"></script> + <script src="/static/app/controllers/cleanupcontroller.js"></script> + <script src="/static/app/controllers/gridcontroller.js"></script> + <script src="/static/app/controllers/ganttresourcecontroller.js"></script> + <script src="/static/app/controllers/ganttprojectcontroller.js"></script> + <script src="/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js"></script> + <script src="/static/app/controllers/chartresourceusagecontroller.js"></script> + </head> + <body style="overflow:hidden;"> + {% raw %} + <div ng-controller="DataController as dataCtrl" class="container-fluid" ng-init="dataService.projectMode=true"> + <div ng-controller="CleanupController as cleanupCtrl" class="container-fluid"> + <div> + <div style="float:left; min-width:250px;"> + <label>Project:</label> + <p style="font-size:14px;" > + <select ng-model="dataService.selected_project" ng-options="{ name: item.name, value: item.mom_id } as item.name for item in dataService.momProjects"></select> + </p> + </div> + <div style="float:left; width:220px;"> + <label>Time (UTC):</label> + <p> + <strong style="font-size:16px">{{dataService.lofarTime | date }}</strong> + </p> + </div> + <div style="float:left; width:280px; padding-right:12px; "> + <label>From:</label> + <p class="input-group"> + <input type="text" class="form-control" style="float:left; min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.from" ng-change="$parent.onViewTimeSpanFromChanged()" is-open="viewFromDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> + <span class="input-group-btn"> + <button type="button" class="btn btn-default" ng-click="openViewFromDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> + </span> + <uib-timepicker ng-model="$parent.dataService.viewTimeSpan.from" ng-change="$parent.onViewTimeSpanFromChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> + </p> + </div> + <div style="float:left; width:280px; padding-right:12px; "> + <label>To:</label> + <p class="input-group"> + <input type="text" class="form-control" style="float:left; min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.to" ng-change="$parent.onViewTimeSpanToChanged()" is-open="viewToDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> + <span class="input-group-btn"> + <button type="button" class="btn btn-default" ng-click="openViewToDatePopup()"><i class="glyphicon glyphicon-calendar"></i></button> + </span> + <uib-timepicker ng-model="$parent.dataService.viewTimeSpan.to" ng-change="$parent.onViewTimeSpanToChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> + </p> + </div> + <div style="float:left; min-width:90px;"> + <label>Scroll:</label> + <p class="input-group"> + <button title="Scroll back in time" type="button" class="btn btn-default" ng-click="scrollBack()"><i class="glyphicon glyphicon-step-backward"></i></button> + <button title="Scroll forward in time" type="button" class="btn btn-default" ng-click="scrollForward()"><i class="glyphicon glyphicon-step-forward"></i></button> + </p> + </div> + <div style="float:left; min-width:180px;"> + <label>Zoom:</label> + <p class="input-group"> + <select class="form-control" ng-model="$parent.zoomTimespan" ng-options="option.name for option in $parent.zoomTimespans track by option.value" ng-change="$parent.onZoomTimespanChanged()"></select> + </p> + </div> + <div style="float:left; min-width:50px;"> + <label></label> + <p class="input-group"> + <button title="Show disk usage by project" type="button" class="btn btn-default" ng-click="cleanupCtrl.showAllProjectsDiskUsage()"><i class="glyphicon glyphicon-floppy-disk"></i></button> + </p> + </div> + </div> + + <div class="top-stretch" ui-layout options="{flow: 'column'}"> + <div ng-controller="GridController as gridCtrl" style="margin-right: 4px;" ui-layout-init-min-width="1160px"> + <div id="grid" + ui-grid="gridOptions" + ui-grid-edit ui-grid-selection ui-grid-cellNav ui-grid-resize-columns ui-grid-auto-resize + class="grid"></div> + </div> + </div> + </div> + {% endraw %} + </body> +</html> diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py index cc2ecfe214b..2998ab6df1c 100755 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/webservice.py @@ -121,6 +121,13 @@ def index(): '''Serves the ResourceAssignmentEditor's index page''' return render_template('index.html', title='Scheduler') +@app.route('/projects') +@app.route('/projects.htm') +@app.route('/projects.html') +@gzipped +def projects(): + return render_template('projects.html', title='Projects') + @app.route('/rest/config') @gzipped def config(): @@ -525,13 +532,28 @@ def getProjectTasksFromUntil(project_mom2id, fromTimestamp=None, untilTimestamp= tasks = rarpc.getTasks(mom_ids=task_mom2ids, lower_bound=fromTimestamp, upper_bound=untilTimestamp) updateTaskMomDetails(tasks, momqueryrpc) - #updateTaskStorageDetails(tasks, sqrpc) + updateTaskStorageDetails(tasks, sqrpc) return jsonify({'tasks': tasks}) except Exception as e: logger.error(e) abort(404, str(e)) +@app.route('/rest/projects/<int:project_mom2id>/taskstimewindow') +@gzipped +def getProjectTasksTimeWindow(project_mom2id): + try: + return jsonify({'min_starttime': datetime(2016, 01, 01), 'max_endtime': datetime(2016, 12, 01)}) + + task_mom2ids = momqueryrpc.getProjectTaskIds(project_mom2id)['task_mom2ids'] + + timewindow = rarpc.getTasksTimeWindow(mom_ids=task_mom2ids) + + return jsonify(timewindow) + except Exception as e: + logger.error(e) + abort(404, str(e)) + @app.route('/rest/projects/<int:project_mom2id>/diskusage') @gzipped def getProjectDiskUsageById(project_mom2id): -- GitLab From 9ea33323dc4fd000690f72ed09c0e858737c2081 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 12:07:17 +0000 Subject: [PATCH 897/933] Task #9607: typo fix --- SAS/DataManagement/CleanupService/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py index 8179d9d8665..d02babcfa5f 100644 --- a/SAS/DataManagement/CleanupService/service.py +++ b/SAS/DataManagement/CleanupService/service.py @@ -95,7 +95,7 @@ class CleanupHandler(MessageHandlerInterface): task = radbrpc.getTask(otdb_id=otdb_id) if task: suc_tasks = radbrpc.getTasks(task_ids=task['successor_ids']) - unfinished_scu_tasks = [t for t in suc_tasks if !(t['status'] == 'finished' or t['status'] == 'obsolete')] + unfinished_scu_tasks = [t for t in suc_tasks if not (t['status'] == 'finished' or t['status'] == 'obsolete')] if unfinished_scu_tasks: message = "Task otdb_id=%s has unfinished successor tasks (otdb_ids: %s)" % (task['otdb_id'], [t['otdb_id'] for t in unfinished_scu_tasks]) logger.error(message) -- GitLab From 21e7d94bc2802373756ec7e2dbe5820f298e7303 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 12:27:19 +0000 Subject: [PATCH 898/933] Task #9886: layout --- .../lib/templates/projects.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html index 6fe5bccb97a..bb0d6ca683f 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html @@ -65,7 +65,7 @@ <strong style="font-size:16px">{{dataService.lofarTime | date }}</strong> </p> </div> - <div style="float:left; width:280px; padding-right:12px; "> + <div style="float:left; width:300px; padding-right:16px; "> <label>From:</label> <p class="input-group"> <input type="text" class="form-control" style="float:left; min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.from" ng-change="$parent.onViewTimeSpanFromChanged()" is-open="viewFromDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> @@ -75,7 +75,7 @@ <uib-timepicker ng-model="$parent.dataService.viewTimeSpan.from" ng-change="$parent.onViewTimeSpanFromChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> </p> </div> - <div style="float:left; width:280px; padding-right:12px; "> + <div style="float:left; width:300px; padding-right:16px; "> <label>To:</label> <p class="input-group"> <input type="text" class="form-control" style="float:left; min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.to" ng-change="$parent.onViewTimeSpanToChanged()" is-open="viewToDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> @@ -85,21 +85,21 @@ <uib-timepicker ng-model="$parent.dataService.viewTimeSpan.to" ng-change="$parent.onViewTimeSpanToChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> </p> </div> - <div style="float:left; min-width:90px;"> + <div style="float:left; min-width:90px; padding-right:16px; "> <label>Scroll:</label> <p class="input-group"> <button title="Scroll back in time" type="button" class="btn btn-default" ng-click="scrollBack()"><i class="glyphicon glyphicon-step-backward"></i></button> <button title="Scroll forward in time" type="button" class="btn btn-default" ng-click="scrollForward()"><i class="glyphicon glyphicon-step-forward"></i></button> </p> </div> - <div style="float:left; min-width:180px;"> + <div style="float:left; min-width:180px; padding-right:16px; "> <label>Zoom:</label> <p class="input-group"> <select class="form-control" ng-model="$parent.zoomTimespan" ng-options="option.name for option in $parent.zoomTimespans track by option.value" ng-change="$parent.onZoomTimespanChanged()"></select> </p> </div> <div style="float:left; min-width:50px;"> - <label></label> + <label>Disk usage:</label> <p class="input-group"> <button title="Show disk usage by project" type="button" class="btn btn-default" ng-click="cleanupCtrl.showAllProjectsDiskUsage()"><i class="glyphicon glyphicon-floppy-disk"></i></button> </p> -- GitLab From 168636be601b37773b0795826182b8da81511c81 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 12:27:56 +0000 Subject: [PATCH 899/933] Task #9886: load resources and usages so we can show cep 4 storage capacity --- .../static/app/controllers/datacontroller.js | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 735be7ea7fc..1b9287c926d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -578,10 +578,6 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.getResourceUsages = function() { var defer = $q.defer(); - if(self.projectMode) { - defer.resolve([]); - return defer; - } $http.get('/rest/resourceusages').success(function(result) { //convert datetime strings to Date objects for(var i in result.resourceusages) { @@ -821,13 +817,10 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, var load_promisses = [self.getConfig(), self.getMoMProjects(), self.getTaskTypes(), - self.getTaskStatusTypes()]; - - if(!self.projectMode) { - load_promisses = load_promisses.concat([self.getResourceGroups(), - self.getResources(), - self.getResourceGroupMemberships()]); - } + self.getTaskStatusTypes(), + self.getResourceGroups(), + self.getResources(), + self.getResourceGroupMemberships()]; $q.all(load_promisses).then(function() { self.initialLoadComplete = true; @@ -920,9 +913,6 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, self.claimChangeCntr++; self.computeMinMaxResourceClaimTimes(); } else if(change.objectType == 'resourceCapacity') { - if(self.projectMode) - continue; //skip capacities in projectMode - if(change.changeType == 'update') { var changedCapacity = change.value; var resource = self.resourceDict[changedCapacity.resource_id]; @@ -932,9 +922,6 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, } } } else if(change.objectType == 'resourceAvailability') { - if(self.projectMode) - continue; //skip resourceAvailability in projectMode - if(change.changeType == 'update') { var changedAvailability = change.value; var resource = self.resourceDict[changedAvailability.resource_id]; -- GitLab From 5fd8a313d82c901746f21dcd4ad9402c95e0df97 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 12:38:19 +0000 Subject: [PATCH 900/933] Task #9886: layout --- .../ResourceAssignmentEditor/lib/templates/projects.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html index bb0d6ca683f..70fd1f62512 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html @@ -53,10 +53,10 @@ <div ng-controller="DataController as dataCtrl" class="container-fluid" ng-init="dataService.projectMode=true"> <div ng-controller="CleanupController as cleanupCtrl" class="container-fluid"> <div> - <div style="float:left; min-width:250px;"> + <div style="float:left; min-width:250px; padding-right:16px;"> <label>Project:</label> <p style="font-size:14px;" > - <select ng-model="dataService.selected_project" ng-options="{ name: item.name, value: item.mom_id } as item.name for item in dataService.momProjects"></select> + <select class="form-control ng-pristine" ng-model="dataService.selected_project" ng-options="{ name: item.name, value: item.mom_id } as item.name for item in dataService.momProjects"></select> </p> </div> <div style="float:left; width:220px;"> -- GitLab From ddef6dadc12ea8f8498c0e85bd0de7d1e78d0e8e Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 13:08:26 +0000 Subject: [PATCH 901/933] Task #9886: added button to load full project --- .../lib/static/app/controllers/datacontroller.js | 9 +++++++++ .../ResourceAssignmentEditor/lib/templates/projects.html | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 1b9287c926d..d799e1bee2f 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -1174,6 +1174,15 @@ dataControllerMod.controller('DataController', } }; + $scope.getFullTimeWindowForSelectedProject = function() { + dataService.getProjectTasksTimeWindow(dataService.selected_project_id).then(function(window) { + if(window.min_starttime && window.max_endtime) { + dataService.viewTimeSpan.from = dataService.convertDatestringToLocalUTCDate(window.min_starttime); + dataService.viewTimeSpan.to = dataService.convertDatestringToLocalUTCDate(window.max_endtime); + } + }); + }; + $scope.$watch('dataService.viewTimeSpan', function() { $scope.selectZoomTimespan(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html index 70fd1f62512..264cf31e92b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/projects.html @@ -92,10 +92,11 @@ <button title="Scroll forward in time" type="button" class="btn btn-default" ng-click="scrollForward()"><i class="glyphicon glyphicon-step-forward"></i></button> </p> </div> - <div style="float:left; min-width:180px; padding-right:16px; "> + <div style="float:left; min-width:230px; padding-right:16px; "> <label>Zoom:</label> <p class="input-group"> - <select class="form-control" ng-model="$parent.zoomTimespan" ng-options="option.name for option in $parent.zoomTimespans track by option.value" ng-change="$parent.onZoomTimespanChanged()"></select> + <select style="float:left; width:180px" class="form-control" ng-model="$parent.zoomTimespan" ng-options="option.name for option in $parent.zoomTimespans track by option.value" ng-change="$parent.onZoomTimespanChanged()"></select> + <button style="float:left; " title="Select full time window for this project" type="button" class="btn btn-default" ng-click="getFullTimeWindowForSelectedProject()"><i class="glyphicon glyphicon-resize-horizontal"></i></button> </p> </div> <div style="float:left; min-width:50px;"> -- GitLab From 20892c6c3cef2fd659e26b654f955de14452cb47 Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Thu, 29 Sep 2016 14:35:53 +0000 Subject: [PATCH 902/933] Task #9924: Adding license file for CS302N --- .gitattributes | 2 ++ .../471_3031_2_Astron_Gen_II_3_314.log | 31 +++++++++++++++++++ .../PVSS/License/Astron_Station_1_shield.txt | 4 +-- .../data/PVSS/License/CS302N_option.txt | 29 +++++++++++++++++ .../data/PVSS/License/shield.CS302N.txt | 29 +++++++++++++++++ 5 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 MAC/Deployment/data/PVSS/License/CS302N_option.txt create mode 100644 MAC/Deployment/data/PVSS/License/shield.CS302N.txt diff --git a/.gitattributes b/.gitattributes index 2908022eaed..169f6471c3c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3510,6 +3510,7 @@ MAC/Deployment/data/PVSS/License/CS103C_option.txt -text MAC/Deployment/data/PVSS/License/CS201C_option.txt -text MAC/Deployment/data/PVSS/License/CS301C_option.txt -text MAC/Deployment/data/PVSS/License/CS302C_option.txt -text +MAC/Deployment/data/PVSS/License/CS302N_option.txt -text MAC/Deployment/data/PVSS/License/CS401C_option.lcu044.txt -text MAC/Deployment/data/PVSS/License/CS401C_option.txt -text MAC/Deployment/data/PVSS/License/CS501C_option.txt -text @@ -3582,6 +3583,7 @@ MAC/Deployment/data/PVSS/License/shield.CS103C.txt -text MAC/Deployment/data/PVSS/License/shield.CS201C.txt -text MAC/Deployment/data/PVSS/License/shield.CS301C.txt -text MAC/Deployment/data/PVSS/License/shield.CS302C.txt -text +MAC/Deployment/data/PVSS/License/shield.CS302N.txt -text MAC/Deployment/data/PVSS/License/shield.CS401C.txt -text MAC/Deployment/data/PVSS/License/shield.CS401C_lcu044.txt -text MAC/Deployment/data/PVSS/License/shield.CS501C.txt -text diff --git a/MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log b/MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log index f1a4e42cd34..14097beb645 100644 --- a/MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log +++ b/MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log @@ -29,3 +29,34 @@ pararemote = 0 ctrlext = 1 update = 0 + +--------------------------------------------------- +[license] +code = "CS302N.control.lofar 20356783426" +version = 31400002 +sn = "471_3031_2_Astron_Gen_II_3_314/2" +date = 2016.09.29;16:32:35,000 +comment = "Core Station CS302N" +expire = 0000.00.00;00:00:00,000 +redundancy = 0 +ui = 2 +para = 1 +dde = 5 +event = 1 +ios = 4000 +ssi = 0 +api = 80 +excelreport = 5 +http = 0 +infoserver = 1000 +comcenter = 5 +maintenance = 1 +scheduler = 1 +recipe = 1 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + diff --git a/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt b/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt index 2e623aca67b..04a483dd569 100644 --- a/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt +++ b/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt @@ -1,5 +1,5 @@ [license] -code = "dongleHost 10312063756" +code = "dongleHost 30201154033" version = 31400002 sn = "471_3031_2_Astron_Gen_II_3_314" expire = 0000.00.00;00:00:00,000 @@ -25,5 +25,5 @@ pararemote = 0 ctrlext = 1 update = 0 licenseMax = 100 -licenseLeft = 99 +licenseLeft = 98 diff --git a/MAC/Deployment/data/PVSS/License/CS302N_option.txt b/MAC/Deployment/data/PVSS/License/CS302N_option.txt new file mode 100644 index 00000000000..1da874f8837 --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/CS302N_option.txt @@ -0,0 +1,29 @@ +[license] +code = "CS302N.control.lofar 32748977553" +version = 31400002 +comment = "Core Station CS302N" +sn = "471_3031_2_Astron_Gen_II_1_38" +expire = 0000.00.00;00:00:00,000 +redundancy = 0 +ui = 2 +para = 1 +dde = 5 +event = 1 +api = 80 +excelreport = 5 +http = 0 +infoserver = 1000 +ios = 4000 +comcenter = 5 +maintenance = 1 +scheduler = 1 +ssi = 0 +recipe = 1 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + + diff --git a/MAC/Deployment/data/PVSS/License/shield.CS302N.txt b/MAC/Deployment/data/PVSS/License/shield.CS302N.txt new file mode 100644 index 00000000000..dea6224460a --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/shield.CS302N.txt @@ -0,0 +1,29 @@ +[license] +code = "CS302N.control.lofar 20356783426" +version = 31400002 +sn = "471_3031_2_Astron_Gen_II_3_314/2" +date = 2016.09.29;16:32:35,000 +comment = "Core Station CS302N" +expire = 0000.00.00;00:00:00,000 +redundancy = 0 +ui = 2 +para = 1 +dde = 5 +event = 1 +ios = 4000 +ssi = 0 +api = 80 +excelreport = 5 +http = 0 +infoserver = 1000 +comcenter = 5 +maintenance = 1 +scheduler = 1 +recipe = 1 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + -- GitLab From 3f1765c3ab88e2f282b567fda718f918e37ce485 Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 14:44:45 +0000 Subject: [PATCH 903/933] Task #9886: do copy task to grid, otherwise css does not pick up changes in the task properties --- .../static/app/controllers/gridcontroller.js | 85 +++++++++++++------ 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 6c1dfb2b04e..11a1fcfc09b 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -13,19 +13,19 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic self.waitingForDelayedUpdate = false; $scope.dataService = dataService; - + $scope.selectBlockingPredecessors = function(in_blocking_predecessors) { $scope.$parent.$parent.loadTasksSelectAndJumpIntoView(in_blocking_predecessors); }; - + $scope.openLtaLocation = function(in_ingest_tasks) { var ingest_tasks = Array.isArray(in_ingest_tasks) ? in_ingest_tasks : [in_ingest_tasks]; - + //example: http://lofar.target.rug.nl/Lofar?mode=query_result_page_user&product=AveragingPipeline&ObservationId=544965&project=LC6_015 //map task.sub_type to url product parameter var project2project2product2tasksDict = {}; - for(var t of ingest_tasks) { + for(var t of ingest_tasks) { var lta_product; switch(t.sub_type) { case 'averaging_pipeline': lta_product = 'AveragingPipeline'; break; @@ -125,7 +125,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic // headerCellFilter: 'statusFormatter' // // [1] http://stackoverflow.com/questions/37286945/ui-grid-setting-template-for-filter-options - }, + }, { field: 'info', displayName: 'Info', enableCellEdit: false, @@ -235,7 +235,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic return "grid-cluster-" + value; }, sort: { direction: uiGridConstants.ASC, priority: 1 } - }]; + }]; if($scope.dataService.projectMode) { $scope.columns.splice(1, 1); @@ -388,6 +388,41 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic if(task.endtime >= viewFrom && task.starttime <= viewTo) { $scope.dataService.filteredTasks.push(task); + var gridTask = { + blocked_by_ids: task.blocked_by_ids, + cluster: task.cluster, + description: task.description, + disk_usage: task.disk_usage, + disk_usage_readable: task.disk_usage_readable, + duration: task.duration, + endtime: task.endtime, + id: task.id, + ingest_status: task.ingest_status, + mom2object_id: task.mom2object_id, + mom_id: task.mom_id, + mom_object_group_id: task.mom_object_group_id, + mom_object_group_mom2object_id: task.mom_object_group_mom2object_id, + mom_object_group_name: task.mom_object_group_name, + mom_object_parent_group_id: task.mom_object_parent_group_id, + name: task.name, + nr_of_dataproducts: task.nr_of_dataproducts, + otdb_id: task.otdb_id, + predecessor_ids: task.predecessor_ids, + project_mom2object_id: task.project_mom2object_id, + project_mom_id: task.project_mom_id, + project_name: task.project_name, + specification_id: task.specification_id, + starttime: task.starttime, + status: task.status, + status_id: task.status_id, + sub_type: task.sub_type, + successor_ids: task.successor_ids, + type: task.type, + type_id: task.type_id + }; + + + gridTasks.push(task); } } @@ -458,9 +493,9 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic populateListAsync(); $scope.$evalAsync(jumpToSelectedTaskRows); }, true); - + function fillFilterSelectOptions() { - if(dataService.initialLoadComplete) { + if(dataService.initialLoadComplete) { fillStatusColumFilterSelectOptions(); fillInfoColumFilterSelectOptions(); fillTypeColumFilterSelectOptions(); @@ -513,7 +548,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic var task_statuses = tasks.map(function(t) { return t.status; }); task_statuses = task_statuses.unique(); task_statuses.sort(); - + fillColumFilterSelectOptions(task_statuses, $scope.columns.find(function(c) {return c.field == 'status'; })); }; @@ -529,7 +564,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic var task_types = tasks.map(function(t) { return t.type; }); task_types = task_types.unique(); task_types.sort(); - + fillColumFilterSelectOptions(task_types, $scope.columns.find(function(c) {return c.field == 'type'; })); }; @@ -558,47 +593,47 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic fillColumFilterSelectOptions(groupIds, $scope.columns.find(function(c) {return c.field == 'mom_object_group_id'; })); }; - + function fillInfoColumFilterSelectOptions() { var tasks = $scope.dataService.filteredTasks; var info_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'info'; }); if(info_col && info_col.filters.length && info_col.filters[0].term) { tasks = $scope.dataService.tasks; - } + } // Generate a list of unique information items var task_info = []; var info_bit_flags = 0x00; - for(var task of tasks) { - if((task.blocked_by_ids.length > 0) && !(info_bit_flags & 0x01)) { + for(var task of tasks) { + if((task.blocked_by_ids.length > 0) && !(info_bit_flags & 0x01)) { task_info.push({ value: 0, label: 'Blocked tasks' }); info_bit_flags |= 0x01; } - + if((task.ingest_status === 'ingesting') && !(info_bit_flags & 0x02)) { task_info.push({ value: 1, label: 'Ingests in progress' }); info_bit_flags |= 0x02; } - + if((task.ingest_status === 'ingested') && !(info_bit_flags & 0x04)) { task_info.push({ value: 2, label: 'Successful ingests' }); info_bit_flags |= 0x04; } - + if((task.ingest_status === 'failed') && !(info_bit_flags & 0x08)) { - task_info.push({ value: 3, label: 'Failed ingests' }); + task_info.push({ value: 3, label: 'Failed ingests' }); info_bit_flags |= 0x08; } }; - + // sort on key values function keysrt(key,desc) { return function(a,b){ return desc ? ~~(a[key] < b[key]) : ~~(a[key] > b[key]); } } - + task_info.sort(keysrt('value')); fillColumFilterSelectOptions(task_info, $scope.columns.find(function(c) {return c.field == 'info'; })); }; @@ -627,12 +662,12 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do restrict: 'A', scope: { }, - link: function($scope, $element, $attrs) { + link: function($scope, $element, $attrs) { function handleContextMenuEvent(event) { - //pragmatic 'hard-coded' way of getting the dataService and the rowEntity via scope tree. + //pragmatic 'hard-coded' way of getting the dataService and the rowEntity via scope tree. var dataService = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.dataService; var cleanupCtrl = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.cleanupCtrl; - var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; + var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; var row = $scope.$parent.$parent.$parent.row; var rowEntity = row.entity; @@ -736,7 +771,7 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do var liElement = angular.element('<li><a href="#">Open in LTA catalogue</a></li>'); ulElement.append(liElement); liElement.on('click', function() { - closeContextMenu(); + closeContextMenu(); row.grid.appScope.openLtaLocation(ingest_tasks); }); } @@ -829,7 +864,7 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do } }); } - + var closeContextMenu = function(cme) { contextmenuElement.remove(); -- GitLab From 957161c1db75f4c2b8a829151eaa470d1bd02baf Mon Sep 17 00:00:00 2001 From: Jorrit Schaap <schaap@astron.nl> Date: Thu, 29 Sep 2016 14:51:32 +0000 Subject: [PATCH 904/933] Task #9886: layout, and added link to projects page --- .../lib/templates/index.html | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html index 6090be4503d..9e5bec8c592 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/templates/index.html @@ -52,14 +52,14 @@ {% raw %} <div ng-controller="DataController as dataCtrl" class="container-fluid"> <div ng-controller="CleanupController as cleanupCtrl" class="container-fluid"> - <div class="row"> - <div class="col-md-1"> + <div> + <div style="float:left; width:220px;"> <label>Time (UTC):</label> <p> <strong style="font-size:16px">{{dataService.lofarTime | date }}</strong> </p> </div> - <div class="col-md-3"> + <div style="float:left; width:300px; padding-right:16px; "> <label>From:</label> <p class="input-group"> <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.from" ng-change="$parent.onViewTimeSpanFromChanged()" is-open="viewFromDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> @@ -69,7 +69,7 @@ <uib-timepicker ng-model="$parent.dataService.viewTimeSpan.from" ng-change="$parent.onViewTimeSpanFromChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> </p> </div> - <div class="col-md-3"> + <div style="float:left; width:300px; padding-right:16px; "> <label>To:</label> <p class="input-group"> <input type="text" class="form-control" style="min-width:100px" uib-datepicker-popup="yyyy-MM-dd" ng-model="$parent.dataService.viewTimeSpan.to" ng-change="$parent.onViewTimeSpanToChanged()" is-open="viewToDatePopupOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" close-on-date-selection="false"/> @@ -79,7 +79,7 @@ <uib-timepicker ng-model="$parent.dataService.viewTimeSpan.to" ng-change="$parent.onViewTimeSpanToChanged()" hour-step="1" minute-step="5" show-meridian="false" show-spinners="false"></uib-timepicker> </p> </div> - <div class="col-md-1"> + <div style="float:left; min-width:90px; padding-right:16px; "> <label>Scroll:</label> <p class="input-group"> <label title="Automatically scroll 'From' and 'To' to watch live events" style="padding-right: 4px; vertical-align: top;">Live <input type="checkbox" ng-model="$parent.dataService.autoFollowNow"></label> @@ -87,18 +87,21 @@ <button title="Scroll forward in time" type="button" class="btn btn-default" ng-click="scrollForward()"><i class="glyphicon glyphicon-step-forward"></i></button> </p> </div> - <div class="col-md-2"> + <div style="float:left; min-width:190px; padding-right:16px; "> <label>Zoom:</label> <p class="input-group"> <select class="form-control" ng-model="$parent.zoomTimespan" ng-options="option.name for option in $parent.zoomTimespans track by option.value" ng-change="$parent.onZoomTimespanChanged()"></select> </p> </div> - <div class="col-md-1"> - <label></label> + <div style="float:left; min-width:50px; padding-right:16px; "> + <label>Disk usage:</label> <p class="input-group"> <button title="Show disk usage by project" type="button" class="btn btn-default" ng-click="cleanupCtrl.showAllProjectsDiskUsage()"><i class="glyphicon glyphicon-floppy-disk"></i></button> </p> </div> + <div style="float:left; min-width:50px; padding-top:30px; font-size:14px;"> + <a href="/projects" target="_blank">Projects</a> + </div> </div> <div class="top-stretch" ui-layout options="{flow: 'column'}"> -- GitLab From 1e5f4c9df3fcea1a488bcb9cdd1985a6966f132a Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Fri, 30 Sep 2016 11:44:59 +0000 Subject: [PATCH 905/933] TAsk #9929: Add files fro new Navigator system smu002 --- .gitattributes | 2 ++ MAC/Navigator2/config/config.smu002 | 12 ++++++++++++ MAC/Navigator2/config/progs.navigator_3.14 | 6 ++++++ 3 files changed, 20 insertions(+) create mode 100644 MAC/Navigator2/config/config.smu002 create mode 100644 MAC/Navigator2/config/progs.navigator_3.14 diff --git a/.gitattributes b/.gitattributes index 169f6471c3c..2415b8797c3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3866,11 +3866,13 @@ MAC/Navigator2/config/config.level.station -text MAC/Navigator2/config/config.maincu -text MAC/Navigator2/config/config.navigator.3.14 -text MAC/Navigator2/config/config.sas099 -text +MAC/Navigator2/config/config.smu002 -text MAC/Navigator2/config/config.standalone.station -text MAC/Navigator2/config/progs.ccu -text MAC/Navigator2/config/progs.dist.station -text MAC/Navigator2/config/progs.maincu -text MAC/Navigator2/config/progs.navigator -text +MAC/Navigator2/config/progs.navigator_3.14 -text MAC/Navigator2/config/progs.standalone.station -text MAC/Navigator2/data/PVSS[!!-~]performance[!!-~]results.xls -text MAC/Navigator2/dplist/maincu_system.dpl -text diff --git a/MAC/Navigator2/config/config.smu002 b/MAC/Navigator2/config/config.smu002 new file mode 100644 index 00000000000..082ed8db951 --- /dev/null +++ b/MAC/Navigator2/config/config.smu002 @@ -0,0 +1,12 @@ +[general] +pvss_path = "/opt/WinCC_OA/3.14" +proj_path = "/opt/pvss/pvssproj/Navigator" +proj_version = "3.14" +langs = "en_US.iso88591" +data = "mcu001.control.lofar" +event = "mcu001.control.lofar" +userName = "root" +password = "" +mxproxy = "none" + +pmonPort = 4999 diff --git a/MAC/Navigator2/config/progs.navigator_3.14 b/MAC/Navigator2/config/progs.navigator_3.14 new file mode 100644 index 00000000000..a14d7216309 --- /dev/null +++ b/MAC/Navigator2/config/progs.navigator_3.14 @@ -0,0 +1,6 @@ +version 1 + +auth "" "" +#Manager | Start | SecKill | Restart# | ResetMin | Options +WCCILpmon | manual | 30 | 3 | 1 | +WCCOAui | once | 30 | 3 | 1 | -p vision/login.pnl -- GitLab From 669a323d90e584ca7499ff19b9925717e209b477 Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Mon, 3 Oct 2016 07:50:15 +0000 Subject: [PATCH 906/933] Task #9655: changes for rollout 3.14, missing points and changes to put setup stuff in datapoints.dpl files also incase it is datapoint dependant --- MAC/Deployment/data/PVSS/bin/create_db_files | 45 +++- MAC/Deployment/data/PVSS/data/CEPbase.dpdef | 5 + MAC/Deployment/data/PVSS/data/MCUbase.dpdef | 4 + MAC/Deployment/data/PVSS/data/PowerUnit.dpdef | 203 +++++++++--------- .../data/PVSS/data/Stationbase.dpdef | 3 + 5 files changed, 152 insertions(+), 108 deletions(-) diff --git a/MAC/Deployment/data/PVSS/bin/create_db_files b/MAC/Deployment/data/PVSS/bin/create_db_files index 777a9923f00..7ae7ae9e623 100755 --- a/MAC/Deployment/data/PVSS/bin/create_db_files +++ b/MAC/Deployment/data/PVSS/bin/create_db_files @@ -93,9 +93,9 @@ concatfile() # create_ring_station_list() { - # Only create info for existing stations, which have a height != 0 in StationInfo.dat + # Only create info for existing stations, which have a height != 0 in script.dat # stationname ID stationType ... - cleanlist ${STATIONINFOFILE} | awk '{ + cleanlist ${FILE} | awk '{ if ($3 == "C" && substr($6,1,1) != "0") { print "Core_"$1 } @@ -1094,10 +1094,10 @@ create_dpt_file() } # -# add_values inputfile dbtype +# add_dpt_values inputfile dbtype # -add_values() +add_dpt_values() { # setup master point and archives # NOTE: THIS IS ADDED TO THE DataPointTYPES file! @@ -1123,6 +1123,36 @@ add_values() } +# +# add_dp_values inputfile dbtype +# + +add_dp_values() +{ + # setup special points added in dpdef files preceeded by a ? + # NOTE: THIS IS ADDED TO THE DataPoints file! + prevdpt="abc" + cleanlist $1 | sort | while read dpt prefix dbtype leaf dp + do + if [ "${dbtype}" != "$2" ]; then + continue + fi + + if [ "${prevdpt}" == "${dpt}" ]; then + continue + fi + prevdpt=${dpt} + + # Create MasterDataPoint for LOFAR tree elements + + if [ -f ${dpt}.dpdef ]; then + cleancomments ${dpt}.dpdef|grep "^\?" |cut -d! -f2- >>${DESTDIR}/${DP_FILE} + fi + done + echo "added values to ${DESTDIR}/${DP_FILE}" + +} + # # create_dp_file inputfile dbtype # @@ -1471,7 +1501,7 @@ if [ "$DBTYPE" == "S" -a "$DBTYPENAME" == "LOCALHOST" ]; then cd $DESTDIR echo "Making files for station $STNNAME in $DESTDIR" elif [ "$DBTYPE" == "M" ]; then - if [ "$HOSTNAME" == "MCU001" -o "$HOSTNAME" == "MCU099" ]; then + if [ "$HOSTNAME" == "MCU001" -o "$HOSTNAME" == "MCU002" -o "$HOSTNAME" == "MCU099" ]; then configdir="/opt/lofar/etc/" dpdefdir="/opt/pvss/dpdef/" DESTDIR=$dpdefdir @@ -1484,7 +1514,7 @@ elif [ "$DBTYPE" == "M" ]; then dpdefdir="../data/" fi elif [ "$DBTYPE" == "C" ]; then - if [ "$HOSTNAME" == "CCU001" -o "$HOSTNAME" == "CCU099" ]; then + if [ "$HOSTNAME" == "CCU001" -o "$HOSTNAME" == "CCU002" -o "$HOSTNAME" == "CCU099" ]; then configdir="/opt/lofar/etc/" dpdefdir="/opt/pvss/dpdef/" DESTDIR=$dpdefdir @@ -1554,7 +1584,8 @@ fi # create the desired files create_dpt_file ${INPUTFILE} ${DBTYPE} create_dp_file ${INPUTFILE} ${DBTYPE} -add_values ${INPUTFILE} ${DBTYPE} +add_dpt_values ${INPUTFILE} ${DBTYPE} +add_dp_values ${INPUTFILE} ${DBTYPE} cat ${TMP_FILE} >>${DESTDIR}/${DPT_FILE} rm -f ${TMP_FILE} diff --git a/MAC/Deployment/data/PVSS/data/CEPbase.dpdef b/MAC/Deployment/data/PVSS/data/CEPbase.dpdef index 5d6f8df5477..5a9c3051644 100644 --- a/MAC/Deployment/data/PVSS/data/CEPbase.dpdef +++ b/MAC/Deployment/data/PVSS/data/CEPbase.dpdef @@ -2,3 +2,8 @@ # If CEP need some extra DPT DP DPE defines or powerconfig settings # they can be defined here +#Fill some defaults +# DpValue +ElementName TypeName _original.._value +scriptInfo.transferMPs.runDone ScriptInfo 0 +scriptInfo.setSumAlerts.runDone ScriptInfo 0 diff --git a/MAC/Deployment/data/PVSS/data/MCUbase.dpdef b/MAC/Deployment/data/PVSS/data/MCUbase.dpdef index b7eb95f5b40..bc996f787eb 100644 --- a/MAC/Deployment/data/PVSS/data/MCUbase.dpdef +++ b/MAC/Deployment/data/PVSS/data/MCUbase.dpdef @@ -138,6 +138,7 @@ NavPanelConfig.NavPanelConfig 1# CobaltOutputProc_Processes 9# FeedbackService_Processes 9# MessageRouter_Processes 9# + PowerUnit_Hardware 9# TypeName NavigatorUserSaves.NavigatorUserSaves 1# @@ -152,6 +153,7 @@ GCFWatchDog.GCFWatchDog 1# online 7# lastUpTime 10# lastDownTime 10# + involved 9# name 9# @@ -179,6 +181,7 @@ rootSaves NavigatorUserSaves # DpValue ElementName TypeName _original.._value scriptInfo.transferMPs.runDone ScriptInfo 0 +scriptInfo.setSumAlerts.runDone ScriptInfo 0 __navigator.alarmSettings.emails Navigator "observer@astron.nl" root.LOFAR_Processes NavPanelConfig "Processes/MainCU_Processes.pnl" root.LOFAR_Observations NavPanelConfig "Observations/Observations.pnl" @@ -236,6 +239,7 @@ root.CobaltGPUProc_Processes NavPanelConfig "Processes/ObservationGPUProcEmbedde root.CobaltOutputProc_Processes NavPanelConfig "Processes/ObservationOutputProcs.pnl" root.FeedbackService_Processes NavPanelConfig "Processes/FeedbackService.pnl" root.MessageRouter_Processes NavPanelConfig "Processes/MessageRouter.pnl" +root.PowerUnit_Hardware NavPanelConfig "Hardware/Station_PowerUnits.pnl" rootSaves.Queries.Query NavigatorUserSaves "SELECT '_original.._value' FROM 'LOFAR_PIC*.status.state' REMOTE ALL WHERE '_original.._value' >= 20 AND '_original.._value' < 30", "SELECT '_original.._value' FROM 'LOFAR_PIC*.status.state' REMOTE ALL WHERE '_original.._value' >= 30 AND '_original.._value' < 40", "SELECT '_original.._value' FROM 'LOFAR_PIC*.status.state' REMOTE ALL WHERE '_original.._value' >= 40 AND '_original.._value' < 50", "SELECT '_original.._value' FROM 'LOFAR_PIC*.status.state' REMOTE ALL WHERE '_original.._value' >= 50 AND '_original.._value' < 60" rootSaves.Queries.Short NavigatorUserSaves "All hardware in Maintenance", "All hardware in Test", "All hardware in Suspicious", "All hardware in Alarm" diff --git a/MAC/Deployment/data/PVSS/data/PowerUnit.dpdef b/MAC/Deployment/data/PVSS/data/PowerUnit.dpdef index e50ca72a355..c579fb14081 100644 --- a/MAC/Deployment/data/PVSS/data/PowerUnit.dpdef +++ b/MAC/Deployment/data/PVSS/data/PowerUnit.dpdef @@ -15,114 +15,115 @@ clearAlarmHistory int # Next points are needed for dataparameterization Do not alter them unless you know what you are doing. tabs are essential in these # + # Datapoint/DpId -!DpName TypeName -!LOFAR_PIC_POWEC0 PowerUnit -!LOFAR_PIC_POWEC1 PowerUnit -!_2_SNMPAgent_1 _SNMPAgent -!_2_SNMPAgent_2 _SNMPAgent +?DpName TypeName +?LOFAR_PIC_POWEC0 PowerUnit +?LOFAR_PIC_POWEC1 PowerUnit +?_2_SNMPAgent_1 _SNMPAgent +?_2_SNMPAgent_2 _SNMPAgent # Aliases/Comments -!AliasId AliasName CommentName -!_2_SNMPAgent_1. "" lt:1 LANG:1 "POWEC0@@" -!_2_SNMPAgent_2. "" lt:1 LANG:1 "POWEC1@@" +?AliasId AliasName CommentName +?_2_SNMPAgent_1. "" lt:1 LANG:1 "POWEC0@@" +?_2_SNMPAgent_2. "" lt:1 LANG:1 "POWEC1@@" # DpValue -!ElementName TypeName _original.._value -!LOFAR_PIC_POWEC0.status.leaf PowerUnit 1 -!LOFAR_PIC_POWEC1.status.leaf PowerUnit 1 -!_2_SNMPAgent_1.Access.ReadCommunity _SNMPAgent "public" -!_2_SNMPAgent_1.Access.WriteCommunity _SNMPAgent "public" -!_2_SNMPAgent_1.Access.Timeout _SNMPAgent 100 -!_2_SNMPAgent_1.Access.Retries _SNMPAgent 5 -!_2_SNMPAgent_1.Access.Protocol _SNMPAgent 1 -!_2_SNMPAgent_1.Access.Port _SNMPAgent 161 -!_2_SNMPAgent_1.Status.Timeout _SNMPAgent 1 -!_2_SNMPAgent_1.Redundancy.ReduAgent _SNMPAgent 0 -!_2_SNMPAgent_1.Redundancy.FallBack _SNMPAgent 0 -!_2_SNMPAgent_2.Access.ReadCommunity _SNMPAgent "public" -!_2_SNMPAgent_2.Access.WriteCommunity _SNMPAgent "public" -!_2_SNMPAgent_2.Access.Timeout _SNMPAgent 100 -!_2_SNMPAgent_2.Access.Retries _SNMPAgent 5 -!_2_SNMPAgent_2.Access.Protocol _SNMPAgent 1 -!_2_SNMPAgent_2.Access.Port _SNMPAgent 161 -!_2_SNMPAgent_2.Status.Timeout _SNMPAgent 1 -!_2_SNMPAgent_2.Redundancy.ReduAgent _SNMPAgent 0 -!_2_SNMPAgent_2.Redundancy.FallBack _SNMPAgent 0 +?ElementName TypeName _original.._value +?LOFAR_PIC_POWEC0.status.leaf PowerUnit 1 +?LOFAR_PIC_POWEC1.status.leaf PowerUnit 1 +?_2_SNMPAgent_1.Access.ReadCommunity _SNMPAgent "public" +?_2_SNMPAgent_1.Access.WriteCommunity _SNMPAgent "public" +?_2_SNMPAgent_1.Access.Timeout _SNMPAgent 100 +?_2_SNMPAgent_1.Access.Retries _SNMPAgent 5 +?_2_SNMPAgent_1.Access.Protocol _SNMPAgent 1 +?_2_SNMPAgent_1.Access.Port _SNMPAgent 161 +?_2_SNMPAgent_1.Status.Timeout _SNMPAgent 1 +?_2_SNMPAgent_1.Redundancy.ReduAgent _SNMPAgent 0 +?_2_SNMPAgent_1.Redundancy.FallBack _SNMPAgent 0 +?_2_SNMPAgent_2.Access.ReadCommunity _SNMPAgent "public" +?_2_SNMPAgent_2.Access.WriteCommunity _SNMPAgent "public" +?_2_SNMPAgent_2.Access.Timeout _SNMPAgent 100 +?_2_SNMPAgent_2.Access.Retries _SNMPAgent 5 +?_2_SNMPAgent_2.Access.Protocol _SNMPAgent 1 +?_2_SNMPAgent_2.Access.Port _SNMPAgent 161 +?_2_SNMPAgent_2.Status.Timeout _SNMPAgent 1 +?_2_SNMPAgent_2.Redundancy.ReduAgent _SNMPAgent 0 +?_2_SNMPAgent_2.Redundancy.FallBack _SNMPAgent 0 -!# DistributionInfo -!ElementName TypeName _distrib.._type _distrib.._driver -!LOFAR_PIC_POWEC0.nrOfModules PowerUnit 56 \2 -!LOFAR_PIC_POWEC0.voltage PowerUnit 56 \2 -!LOFAR_PIC_POWEC0.current PowerUnit 56 \2 -!LOFAR_PIC_POWEC0.temperature PowerUnit 56 \2 -!LOFAR_PIC_POWEC0.OK PowerUnit 56 \2 -!LOFAR_PIC_POWEC0.nrOfAlarms PowerUnit 56 \2 -!LOFAR_PIC_POWEC0.clearAlarmHistory PowerUnit 56 \2 -!LOFAR_PIC_POWEC0.alarmType PowerUnit 56 \2 -!LOFAR_PIC_POWEC0.alarmReason PowerUnit 56 \2 -!LOFAR_PIC_POWEC0.alarmTime PowerUnit 56 \2 -!LOFAR_PIC_POWEC0.alarmText PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.voltage PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.current PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.temperature PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.OK PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.nrOfAlarms PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.clearAlarmHistory PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.alarmType PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.alarmReason PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.alarmTime PowerUnit 56 \2 -!LOFAR_PIC_POWEC1.alarmText PowerUnit 56 \2 +?# DistributionInfo +?ElementName TypeName _distrib.._type _distrib.._driver +?LOFAR_PIC_POWEC0.nrOfModules PowerUnit 56 \2 +?LOFAR_PIC_POWEC0.voltage PowerUnit 56 \2 +?LOFAR_PIC_POWEC0.current PowerUnit 56 \2 +?LOFAR_PIC_POWEC0.temperature PowerUnit 56 \2 +?LOFAR_PIC_POWEC0.OK PowerUnit 56 \2 +?LOFAR_PIC_POWEC0.nrOfAlarms PowerUnit 56 \2 +?LOFAR_PIC_POWEC0.clearAlarmHistory PowerUnit 56 \2 +?LOFAR_PIC_POWEC0.alarmType PowerUnit 56 \2 +?LOFAR_PIC_POWEC0.alarmReason PowerUnit 56 \2 +?LOFAR_PIC_POWEC0.alarmTime PowerUnit 56 \2 +?LOFAR_PIC_POWEC0.alarmText PowerUnit 56 \2 +?LOFAR_PIC_POWEC1.nrOfModules PowerUnit 56 \2 +?LOFAR_PIC_POWEC1.voltage PowerUnit 56 \2 +?LOFAR_PIC_POWEC1.current PowerUnit 56 \2 +?LOFAR_PIC_POWEC1.temperature PowerUnit 56 \2 +?LOFAR_PIC_POWEC1.OK PowerUnit 56 \2 +?LOFAR_PIC_POWEC1.nrOfAlarms PowerUnit 56 \2 +?LOFAR_PIC_POWEC1.clearAlarmHistory PowerUnit 56 \2 +?LOFAR_PIC_POWEC1.alarmType PowerUnit 56 \2 +?LOFAR_PIC_POWEC1.alarmReason PowerUnit 56 \2 +?LOFAR_PIC_POWEC1.alarmTime PowerUnit 56 \2 +?LOFAR_PIC_POWEC1.alarmText PowerUnit 56 \2 -!# DpSmoothMain -!ElementName TypeName _smooth.._type _smooth.._std_type -!LOFAR_PIC_POWEC0.nrOfModules PowerUnit 48 4 -!LOFAR_PIC_POWEC0.voltage PowerUnit 48 4 -!LOFAR_PIC_POWEC0.current PowerUnit 48 4 -!LOFAR_PIC_POWEC0.temperature PowerUnit 48 4 -!LOFAR_PIC_POWEC0.OK PowerUnit 48 4 -!LOFAR_PIC_POWEC0.nrOfAlarms PowerUnit 48 4 -!LOFAR_PIC_POWEC0.clearAlarmHistory PowerUnit 48 4 -!LOFAR_PIC_POWEC0.alarmType PowerUnit 48 4 -!LOFAR_PIC_POWEC0.alarmReason PowerUnit 48 4 -!LOFAR_PIC_POWEC0.alarmTime PowerUnit 48 4 -!LOFAR_PIC_POWEC0.alarmText PowerUnit 48 4 -!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 48 4 -!LOFAR_PIC_POWEC1.voltage PowerUnit 48 4 -!LOFAR_PIC_POWEC1.current PowerUnit 48 4 -!LOFAR_PIC_POWEC1.temperature PowerUnit 48 4 -!LOFAR_PIC_POWEC1.OK PowerUnit 48 4 -!LOFAR_PIC_POWEC1.nrOfAlarms PowerUnit 48 4 -!LOFAR_PIC_POWEC1.clearAlarmHistory PowerUnit 48 4 -!LOFAR_PIC_POWEC1.alarmType PowerUnit 48 4 -!LOFAR_PIC_POWEC1.alarmReason PowerUnit 48 4 -!LOFAR_PIC_POWEC1.alarmTime PowerUnit 48 4 -!LOFAR_PIC_POWEC1.alarmText PowerUnit 48 4 +?# DpSmoothMain +?ElementName TypeName _smooth.._type _smooth.._std_type +?LOFAR_PIC_POWEC0.nrOfModules PowerUnit 48 4 +?LOFAR_PIC_POWEC0.voltage PowerUnit 48 4 +?LOFAR_PIC_POWEC0.current PowerUnit 48 4 +?LOFAR_PIC_POWEC0.temperature PowerUnit 48 4 +?LOFAR_PIC_POWEC0.OK PowerUnit 48 4 +?LOFAR_PIC_POWEC0.nrOfAlarms PowerUnit 48 4 +?LOFAR_PIC_POWEC0.clearAlarmHistory PowerUnit 48 4 +?LOFAR_PIC_POWEC0.alarmType PowerUnit 48 4 +?LOFAR_PIC_POWEC0.alarmReason PowerUnit 48 4 +?LOFAR_PIC_POWEC0.alarmTime PowerUnit 48 4 +?LOFAR_PIC_POWEC0.alarmText PowerUnit 48 4 +?LOFAR_PIC_POWEC1.nrOfModules PowerUnit 48 4 +?LOFAR_PIC_POWEC1.voltage PowerUnit 48 4 +?LOFAR_PIC_POWEC1.current PowerUnit 48 4 +?LOFAR_PIC_POWEC1.temperature PowerUnit 48 4 +?LOFAR_PIC_POWEC1.OK PowerUnit 48 4 +?LOFAR_PIC_POWEC1.nrOfAlarms PowerUnit 48 4 +?LOFAR_PIC_POWEC1.clearAlarmHistory PowerUnit 48 4 +?LOFAR_PIC_POWEC1.alarmType PowerUnit 48 4 +?LOFAR_PIC_POWEC1.alarmReason PowerUnit 48 4 +?LOFAR_PIC_POWEC1.alarmTime PowerUnit 48 4 +?LOFAR_PIC_POWEC1.alarmText PowerUnit 48 4 -!# PeriphAddrMain -!ElementName TypeName _address.._type _address.._reference _address.._poll_group _address.._offset _address.._subindex _address.._direction _address.._internal _address.._lowlevel _address.._active _address.._start _address.._interval _address.._reply _address.._datatype _address.._drv_ident -!LOFAR_PIC_POWEC0.nrOfModules PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.1.0" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC0.voltage PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.3B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC0.current PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.4B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC0.temperature PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.6B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC0.nrOfAlarms PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.1.0" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC0.clearAlarmHistory PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.3.0" "_SNMP" 0 0 \5 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC0.alarmReason PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.3B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC0.alarmText PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.5B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" -!LOFAR_PIC_POWEC0.alarmType PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.2B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC0.alarmTime PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.4B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" -!LOFAR_PIC_POWEC0.OK PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.2B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.nrOfModules PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.1.0" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.voltage PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.3B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.current PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.4B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.temperature PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.6B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.OK PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.2B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.nrOfAlarms PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.1.0" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.clearAlarmHistory PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.3.0" "_SNMP" 0 0 \5 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.alarmReason PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.3B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.alarmText PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.5B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" -!LOFAR_PIC_POWEC1.alarmType PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.2B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" -!LOFAR_PIC_POWEC1.alarmTime PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.4B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" +?# PeriphAddrMain +?ElementName TypeName _address.._type _address.._reference _address.._poll_group _address.._offset _address.._subindex _address.._direction _address.._internal _address.._lowlevel _address.._active _address.._start _address.._interval _address.._reply _address.._datatype _address.._drv_ident +?LOFAR_PIC_POWEC0.nrOfModules PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.1.0" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC0.voltage PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.3B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC0.current PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.4B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC0.temperature PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.6B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC0.nrOfAlarms PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.1.0" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC0.clearAlarmHistory PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.3.0" "_SNMP" 0 0 \5 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC0.alarmReason PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.3B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC0.alarmText PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.5B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" +?LOFAR_PIC_POWEC0.alarmType PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.2B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC0.alarmTime PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.4B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" +?LOFAR_PIC_POWEC0.OK PowerUnit 16 "1_1.3.6.1.4.1.5961.1.3.2.1.2B" "_SNMP" 0 0 \4 0 0 1 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC1.nrOfModules PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.1.0" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC1.voltage PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.3B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC1.current PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.4B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC1.temperature PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.6B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC1.OK PowerUnit 16 "2_1.3.6.1.4.1.5961.1.3.2.1.2B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC1.nrOfAlarms PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.1.0" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC1.clearAlarmHistory PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.3.0" "_SNMP" 0 0 \5 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC1.alarmReason PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.3B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC1.alarmText PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.5B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" +?LOFAR_PIC_POWEC1.alarmType PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.2B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 661 "SNMP" +?LOFAR_PIC_POWEC1.alarmTime PowerUnit 16 "1_1.3.6.1.4.1.5961.1.12.2.1.4B" "_SNMP" 0 0 \4 0 0 0 02.01.1970 00:00:00.000 01.01.1970 00:00:00.000 01.01.1970 00:00:00.000 666 "SNMP" diff --git a/MAC/Deployment/data/PVSS/data/Stationbase.dpdef b/MAC/Deployment/data/PVSS/data/Stationbase.dpdef index e5356910199..f757a162534 100644 --- a/MAC/Deployment/data/PVSS/data/Stationbase.dpdef +++ b/MAC/Deployment/data/PVSS/data/Stationbase.dpdef @@ -125,6 +125,7 @@ NavPanelConfig.NavPanelConfig 1# UniBoard_Hardware 9# LOFAR_Pipelines 9# Observation_Pipelines 9# + PowerUnit_Hardware 9# TypeName NavigatorUserSaves.NavigatorUserSaves 1# @@ -164,6 +165,7 @@ rootSaves NavigatorUserSaves # DpValue ElementName TypeName _original.._value scriptInfo.transferMPs.runDone ScriptInfo 0 +scriptInfo.setSumAlerts.runDone ScriptInfo 0 __navigator.alarmSettings.emails Navigator "observer@astron.nl" standalone.StnLOFAR_Hardware NavPanelConfig "Hardware/Station.pnl" standalone.StnPIC_Hardware NavPanelConfig "Hardware/Station_Cabinet.pnl" @@ -207,5 +209,6 @@ standalone.UriBoard_Hardware NavPanelConfig "Hardware/Station_UriBoard.pnl" standalone.UniBoard_Hardware NavPanelConfig "Hardware/Station_UniBoard.pnl" standalone.StnLOFAR_Pipelines NavPanelConfig "Observations/Pipelines.pnl" standalone.Observation_Pipelines NavPanelConfig "Observations/Pipeline_overview.pnl" +standalone.PowerUnit_Hardware NavPanelConfig "Hardware/Station_PowerUnits.pnl" rootSaves.Queries.Query NavigatorUserSaves "SELECT '_original.._value' FROM 'LOFAR_PIC*.status.state' REMOTE ALL WHERE '_original.._value' >= 20 AND '_original.._value' < 30", "SELECT '_original.._value' FROM 'LOFAR_PIC*.status.state' REMOTE ALL WHERE '_original.._value' >= 30 AND '_original.._value' < 40", "SELECT '_original.._value' FROM 'LOFAR_PIC*.status.state' REMOTE ALL WHERE '_original.._value' >= 40 AND '_original.._value' < 50", "SELECT '_original.._value' FROM 'LOFAR_PIC*.status.state' REMOTE ALL WHERE '_original.._value' >= 50 AND '_original.._value' < 60" rootSaves.Queries.Short NavigatorUserSaves "All hardware in Maintenance", "All hardware in Test", "All hardware in Suspicious", "All hardware in Alarm" -- GitLab From dad88bc51f433f0cc5b7906218ad56e8be055579 Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Mon, 3 Oct 2016 09:50:46 +0000 Subject: [PATCH 907/933] Task #9655: changes for rollout 3.14,claimmanager, python control has been removed --- MAC/Navigator2/scripts/claim.ctl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/MAC/Navigator2/scripts/claim.ctl b/MAC/Navigator2/scripts/claim.ctl index b25728b4251..54702e94a79 100644 --- a/MAC/Navigator2/scripts/claim.ctl +++ b/MAC/Navigator2/scripts/claim.ctl @@ -979,11 +979,6 @@ void checkAndCreateDPs() { dpCreate("LOFAR_ObsSW_TempObs"+pre+"_OnlineControl","OnlineControl"); changed = true; } - //PythonControl - if (!dpExists("LOFAR_ObsSW_TempObs"+pre+"_PythonControl")) { - dpCreate("LOFAR_ObsSW_TempObs"+pre+"_PythonControl","PythonControl"); - changed = true; - } //CobaltGPUProc for (int k=1; k < 10; k++) { for (int l=0; l < 2; l++) { -- GitLab From 37c1cf5270e140515c62b019394c72483884ec92 Mon Sep 17 00:00:00 2001 From: Ruud Beukema <beukema@astron.nl> Date: Tue, 4 Oct 2016 11:41:24 +0000 Subject: [PATCH 908/933] Task #9886: URLs are now sanitized (unwanted '/'-multiples are replaced by single '/'), which solves the problem regarding the display of inspection plots. --- .../lib/static/app/controllers/gridcontroller.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 11a1fcfc09b..0c0053601ab 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -13,6 +13,11 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic self.waitingForDelayedUpdate = false; $scope.dataService = dataService; + + $scope.sanitize_url = function(in_url) { + var split_url = in_url.split("://"); + return split_url[0] + "://" + split_url[1].replace(/\/+/g, "/"); + } $scope.selectBlockingPredecessors = function(in_blocking_predecessors) { $scope.$parent.$parent.loadTasksSelectAndJumpIntoView(in_blocking_predecessors); @@ -54,6 +59,7 @@ gridControllerMod.controller('GridController', ['$scope', '$window', 'dataServic var otdb_ids = product_tasks.map(function(pt) { return pt.otdb_id; }); var otdb_ids_string = otdb_ids.join(','); var url = dataService.config.lta_base_url + '/Lofar?mode=query_result_page_user&product=' + product + '&ObservationId=' + otdb_ids_string + '&project=' + project; + url = $scope.sanitize_url(url); setTimeout(function(url_arg) { $window.open(url_arg, '_blank'); }, window_cntr*250, url); @@ -757,6 +763,7 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do var window_cntr = 0; for(var obs of completed_selected_cep4_observations) { var url = dataService.config.inspection_plots_base_url + '/' + obs.otdb_id; + url = row.grid.appScope.sanitize_url(url); setTimeout(function(url_arg) { $window.open(url_arg, '_blank'); }, window_cntr*750, url); -- GitLab From 4546f432414a563c2d3f80af3eb814b7dbbfb9f6 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Tue, 4 Oct 2016 12:12:01 +0000 Subject: [PATCH 909/933] Task #9858: reintegrate task branch, DPPP can now use Dysco if it is installed --- .gitattributes | 1 + CEP/DP3/DPPP/include/DPPP/CMakeLists.txt | 1 + CEP/DP3/DPPP/include/DPPP/MSUpdater.h | 4 + CEP/DP3/DPPP/include/DPPP/MSWriter.h | 5 +- CEP/DP3/DPPP/include/DPPP/StManParsetKeys.h | 61 ++++++++++++++ CEP/DP3/DPPP/src/MSUpdater.cc | 91 ++++++++++++++++---- CEP/DP3/DPPP/src/MSWriter.cc | 93 +++++++++++++++++---- 7 files changed, 221 insertions(+), 35 deletions(-) create mode 100644 CEP/DP3/DPPP/include/DPPP/StManParsetKeys.h diff --git a/.gitattributes b/.gitattributes index 2415b8797c3..7659a1ac42e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -849,6 +849,7 @@ CEP/DP3/DPPP/include/DPPP/ScaleData.h -text CEP/DP3/DPPP/include/DPPP/Simulate.h -text CEP/DP3/DPPP/include/DPPP/Simulator.h -text CEP/DP3/DPPP/include/DPPP/SourceDBUtil.h -text +CEP/DP3/DPPP/include/DPPP/StManParsetKeys.h -text CEP/DP3/DPPP/include/DPPP/StefCal.h -text CEP/DP3/DPPP/include/DPPP/Stokes.h -text CEP/DP3/DPPP/include/DPPP/SubtractMixed.h -text diff --git a/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt b/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt index 07b6c83e8f2..498b197ff27 100644 --- a/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt +++ b/CEP/DP3/DPPP/include/DPPP/CMakeLists.txt @@ -17,6 +17,7 @@ set(inst_HEADERS ApplyBeam.h ApplyBeam.tcc Predict.h GainCal.h StefCal.h phasefitter.h + StManParsetKeys.h ) # Create symbolic link to include directory. diff --git a/CEP/DP3/DPPP/include/DPPP/MSUpdater.h b/CEP/DP3/DPPP/include/DPPP/MSUpdater.h index ae2a6c66d14..1ce92bdc62e 100644 --- a/CEP/DP3/DPPP/include/DPPP/MSUpdater.h +++ b/CEP/DP3/DPPP/include/DPPP/MSUpdater.h @@ -28,6 +28,8 @@ // @brief DPPP step writing to an MS #include <DPPP/DPStep.h> +#include <DPPP/StManParsetKeys.h> + #include <Common/LofarTypes.h> #include <tables/Tables/ColumnDesc.h> #include <tables/Tables/RefRows.h> @@ -122,6 +124,8 @@ namespace LOFAR { bool itsWeightColAdded; //# has weight column been added? bool itsWriteHistory; //# Should history be written? NSTimer itsTimer; + uint itsTileSize; + StManParsetKeys itsStManKeys; }; } //# end namespace diff --git a/CEP/DP3/DPPP/include/DPPP/MSWriter.h b/CEP/DP3/DPPP/include/DPPP/MSWriter.h index 2ef2f60274c..bd98edff54e 100644 --- a/CEP/DP3/DPPP/include/DPPP/MSWriter.h +++ b/CEP/DP3/DPPP/include/DPPP/MSWriter.h @@ -29,6 +29,8 @@ #include <DPPP/DPStep.h> #include <DPPP/MSReader.h> +#include <DPPP/StManParsetKeys.h> + #include <tables/Tables/Table.h> #include <tables/Tables/ColumnDesc.h> #include <tables/Tables/ScalarColumn.h> @@ -93,7 +95,7 @@ namespace LOFAR { // Create an array column description and add to table with given // stoage manager (if given). void makeArrayColumn (casa::ColumnDesc desc, const casa::IPosition& shape, - casa::TiledColumnStMan* tsm, casa::Table& table); + casa::DataManager* dm, casa::Table& table, bool makeDirectColumn = false); // Create the MS by cloning all subtables from the input MS. // All output columns in the main table are using normal storage managers. @@ -189,6 +191,7 @@ namespace LOFAR { std::string itsVdsDir; //# directory where to put VDS file std::string itsClusterDesc; //# name of clusterdesc file NSTimer itsTimer; + StManParsetKeys itsStManKeys; }; } //# end namespace diff --git a/CEP/DP3/DPPP/include/DPPP/StManParsetKeys.h b/CEP/DP3/DPPP/include/DPPP/StManParsetKeys.h new file mode 100644 index 00000000000..f513e128b07 --- /dev/null +++ b/CEP/DP3/DPPP/include/DPPP/StManParsetKeys.h @@ -0,0 +1,61 @@ +#ifndef DPPP_STMANPARSETKEYS_H +#define DPPP_STMANPARSETKEYS_H + +#include <Common/ParameterSet.h> + +#include <string> + +#include <casa/Containers/Record.h> + +namespace LOFAR { + namespace DPPP { + struct StManParsetKeys + { + casa::String stManName; + uint dyscoDataBitRate; //# Bits per data float, or 0 if data column is not compressed + uint dyscoWeightBitRate; //# Bits per weight float, or 0 if weight column is not compressed + std::string dyscoDistribution; //# Distribution assumed for compression; e.g. "Uniform" or "TruncatedGaussian" + double dyscoDistTruncation; //# For truncated distributions, the truncation point (e.g. 3 for 3 sigma in TruncGaus). + std::string dyscoNormalization; //# Kind of normalization; "AF", "RF" or "Row". + + void Set(const ParameterSet& parset, const std::string& prefix) + { + stManName = toLower(parset.getString(prefix+"storagemanager", string())); + if(stManName == "dysco") { + dyscoDataBitRate = parset.getInt( + prefix+"storagemanager.databitrate", 10); + dyscoWeightBitRate = parset.getInt( + prefix+"storagemanager.weightbitrate", 12); + dyscoDistribution = parset.getString( + prefix+"storagemanager.distribution", + "TruncatedGaussian"); + dyscoDistTruncation = parset.getDouble( + prefix+"storagemanager.disttruncation", 2.5); + dyscoNormalization = parset.getString( + prefix+"storagemanager.normalization", "AF"); + } + } + + casa::Record GetDyscoSpec() const + { + casa::Record dyscoSpec; + dyscoSpec.define ("distribution", dyscoDistribution); + dyscoSpec.define ("normalization", dyscoNormalization); + dyscoSpec.define ("distributionTruncation", dyscoDistTruncation); + // DPPP uses bitrate of 0 to disable compression of the data/weight column. + // However, Dysco does not allow the data or weight bitrates to be set to 0, + // so we set the values to something different. The values are not actually used. + uint dataBitRate = dyscoDataBitRate; + if(dataBitRate == 0) + dataBitRate = 16; + dyscoSpec.define ("dataBitCount", dataBitRate); + uint weightBitRate = dyscoWeightBitRate; + if(weightBitRate == 0) + weightBitRate = 16; + dyscoSpec.define ("weightBitCount", weightBitRate); + return dyscoSpec; + } + }; + } +} +#endif diff --git a/CEP/DP3/DPPP/src/MSUpdater.cc b/CEP/DP3/DPPP/src/MSUpdater.cc index e74f07ac4ce..b8d72496af1 100644 --- a/CEP/DP3/DPPP/src/MSUpdater.cc +++ b/CEP/DP3/DPPP/src/MSUpdater.cc @@ -32,10 +32,12 @@ #include <tables/Tables/ScalarColumn.h> #include <tables/Tables/ArrColDesc.h> #include <tables/Tables/ColumnDesc.h> +#include <tables/Tables/StandardStMan.h> #include <casa/Containers/Record.h> #include <casa/Utilities/LinearSearch.h> #include <ms/MeasurementSets/MeasurementSet.h> #include <iostream> +#include <limits> using namespace casa; @@ -60,6 +62,8 @@ namespace LOFAR { itsDataColName = parset.getString (prefix+"datacolumn", ""); itsWeightColName = parset.getString (prefix+"weightcolumn",""); itsNrTimesFlush = parset.getUint (prefix+"flush", 0); + itsTileSize = parset.getUint (prefix+"tilesize", 1024); + itsStManKeys.Set(parset, prefix); } MSUpdater::~MSUpdater() @@ -76,24 +80,51 @@ namespace LOFAR { return false; } - TableDesc td; - td.addColumn (cd, colName); - - // Use the same data manager as the DATA column. - // Get the data manager info and find the DATA column in it. - Record dminfo = itsMS.dataManagerInfo(); - Record colinfo; - for (uInt i=0; i<dminfo.nfields(); ++i) { - const Record& subrec = dminfo.subRecord(i); - if (linearSearch1 (Vector<String>(subrec.asArrayString("COLUMNS")), - "DATA") >= 0) { - colinfo = subrec; - break; + if(itsStManKeys.stManName == "dysco" && itsStManKeys.dyscoDataBitRate != 0) + { + casa::Record dyscoSpec = itsStManKeys.GetDyscoSpec(); + DataManagerCtor dyscoConstructor = DataManager::getCtor("DyscoStMan"); + CountedPtr<DataManager> dyscoStMan(dyscoConstructor(colName + "_dm", dyscoSpec)); + ColumnDesc directColumnDesc(cd); + directColumnDesc.setOptions(casacore::ColumnDesc::Direct | casacore::ColumnDesc::FixedShape); + TableDesc td; + td.addColumn (directColumnDesc, colName); + itsMS.addColumn (td, *dyscoStMan); + } + else { + // When no specific storage manager is requested, use the same + // as for the DATA column. + // Get the data manager info and find the DATA column in it. + Record dminfo = itsMS.dataManagerInfo(); + Record colinfo; + for (uInt i=0; i<dminfo.nfields(); ++i) { + const Record& subrec = dminfo.subRecord(i); + if (linearSearch1 (Vector<String>(subrec.asArrayString("COLUMNS")), + "DATA") >= 0) { + colinfo = subrec; + break; + } + } + ASSERT(colinfo.nfields()>0); + // When the storage manager is compressed, do not implicitly (re)compress it. Use TiledStMan instead. + std::string dmType = colinfo.asString("TYPE"); + TableDesc td; + td.addColumn (cd, colName); + if(dmType == "DyscoStMan") + { + IPosition tileShape(3, info().ncorr(), info().nchan(), 1); + tileShape[2] = itsTileSize * 1024 / (8 * tileShape[0] * tileShape[1]); + if (tileShape[2] < 1) { + tileShape[2] = 1; + } + TiledColumnStMan tsm(colName + "_dm", tileShape); + itsMS.addColumn (td, tsm); + } + else { + colinfo.define ("NAME", colName + "_dm"); + itsMS.addColumn (td, colinfo); } } - ASSERT(colinfo.nfields()>0); - colinfo.define ("NAME", colName + "_dm"); - itsMS.addColumn (td, colinfo); return true; } @@ -104,7 +135,22 @@ namespace LOFAR { putFlags (buf.getRowNrs(), buf.getFlags()); } if (itsWriteData) { - putData (buf.getRowNrs(), buf.getData()); + // If compressing, flagged values need to be set to NaN to decrease the dynamic range + if(itsStManKeys.stManName == "dysco") + { + casa::Cube<casa::Complex> dataCopy = buf.getData().copy(); + casa::Cube<casa::Complex>::iterator dataIter = dataCopy.begin(); + for(casa::Cube<bool>::const_iterator flagIter = buf.getFlags().begin(); flagIter != buf.getFlags().end(); ++flagIter) + { + if(*flagIter) + *dataIter = casa::Complex(std::numeric_limits<float>::quiet_NaN(), std::numeric_limits<float>::quiet_NaN()); + ++dataIter; + } + putData (buf.getRowNrs(), dataCopy); + } + else { + putData (buf.getRowNrs(), buf.getData()); + } } if (itsWriteWeights) { if (!buf.getWeights().empty()) { @@ -215,6 +261,17 @@ namespace LOFAR { if (itsWriteWeights) os << " weights"; os << std::endl; } + if(itsStManKeys.stManName == "dysco") { + os + << " Compressed: yes\n" + << " Data bitrate: " << itsStManKeys.dyscoDataBitRate << '\n' + << " Weight bitrate: " << itsStManKeys.dyscoWeightBitRate << '\n' + << " Dysco mode: " << itsStManKeys.dyscoNormalization << ' ' + << itsStManKeys.dyscoDistribution << '(' << itsStManKeys.dyscoDistTruncation << ")\n"; + } + else { + os << " Compressed: no\n"; + } os << std::endl; os << " flush: " << itsNrTimesFlush << std::endl; } diff --git a/CEP/DP3/DPPP/src/MSWriter.cc b/CEP/DP3/DPPP/src/MSWriter.cc index a4cf6ee2fb1..bb708f6d473 100644 --- a/CEP/DP3/DPPP/src/MSWriter.cc +++ b/CEP/DP3/DPPP/src/MSWriter.cc @@ -47,6 +47,7 @@ #include <casa/Containers/Record.h> #include <casa/OS/Path.h> #include <iostream> +#include <limits> using namespace casa; @@ -78,6 +79,8 @@ namespace LOFAR { " can be used as output when writing a new MS"); ASSERTSTR (itsWeightColName == "WEIGHT_SPECTRUM", "Currently only the " "WEIGHT_SPECTRUM column can be used as output when writing a new MS"); + + itsStManKeys.Set(parset, prefix); } MSWriter::~MSWriter() @@ -181,6 +184,17 @@ namespace LOFAR { os << " time interval: " << itsInterval << std::endl; os << " DATA column: " << itsDataColName << std::endl; os << " WEIGHT column: " << itsWeightColName << std::endl; + if(itsStManKeys.stManName == "dysco") { + os + << " Compressed: yes\n" + << " Data bitrate: " << itsStManKeys.dyscoDataBitRate << '\n' + << " Weight bitrate: " << itsStManKeys.dyscoWeightBitRate << '\n' + << " Dysco mode: " << itsStManKeys.dyscoNormalization << ' ' + << itsStManKeys.dyscoDistribution << '(' << itsStManKeys.dyscoDistTruncation << ")\n"; + } + else { + os << " Compressed: no\n"; + } } void MSWriter::showTimings (std::ostream& os, double duration) const @@ -191,19 +205,24 @@ namespace LOFAR { } void MSWriter::makeArrayColumn (ColumnDesc desc, const IPosition& ipos, - TiledColumnStMan* tsm, Table& table) + DataManager* dm, Table& table, bool makeDirectColumn) { desc.setOptions(0); desc.setShape(ipos); - desc.setOptions(ColumnDesc::FixedShape); + if (makeDirectColumn) { + desc.setOptions(ColumnDesc::Direct | ColumnDesc::FixedShape); + } + else { + desc.setOptions(ColumnDesc::FixedShape); + } if (table.tableDesc().isColumn(desc.name())) { table.removeColumn(desc.name()); } - // Use tiled storage manager if given. - if (tsm == 0) { + // Use storage manager if given. + if (dm == 0) { table.addColumn (desc); } else { - table.addColumn (desc, *tsm); + table.addColumn (desc, *dm); } } @@ -300,6 +319,7 @@ namespace LOFAR { // Setup table creation. Exception is thrown if it exists already. Table::TableOption opt = itsOverwrite ? Table::New : Table::NewNoReplace; SetupNewTable newtab(outName, newdesc, opt); + // First bind all columns to SSM. // For all columns defined in dminfo the bindings will be overwritten. // In this way variable columns like ANTENNA1/2 are bound to SSM. @@ -308,22 +328,35 @@ namespace LOFAR { StandardStMan ssm("SSMVar", 32768); newtab.bindAll (ssm); } + // Bind all columns according to dminfo. newtab.bindCreate (dminfo); + DataManagerCtor dyscoConstructor = 0; + Record dyscoSpec; + if(itsStManKeys.stManName == "dysco") { + dyscoSpec = itsStManKeys.GetDyscoSpec(); + dyscoConstructor = DataManager::getCtor("DyscoStMan"); + } itsMS = Table(newtab); - { + + if (itsStManKeys.stManName == "dysco" && itsStManKeys.dyscoDataBitRate != 0) { + // Add DATA column using Dysco stman. + CountedPtr<DataManager> dyscoStMan(dyscoConstructor("DyscoData", dyscoSpec)); + makeArrayColumn (tdesc["DATA"], dataShape, dyscoStMan.get(), itsMS, true); + } + else { // Add DATA column using tsm. TiledColumnStMan tsm("TiledData", tileShape); makeArrayColumn (tdesc["DATA"], dataShape, &tsm, itsMS); } - { - // Add FLAG column using tsm. - // Use larger tile shape because flags are stored as bits. - IPosition tileShapeF(tileShape); - tileShapeF[2] *= 8; - TiledColumnStMan tsmf("TiledFlag", tileShapeF); - makeArrayColumn(tdesc["FLAG"], dataShape, &tsmf, itsMS); - } + + // Add FLAG column using tsm. + // Use larger tile shape because flags are stored as bits. + IPosition tileShapeF(tileShape); + tileShapeF[2] *= 8; + TiledColumnStMan tsmf("TiledFlag", tileShapeF); + makeArrayColumn(tdesc["FLAG"], dataShape, &tsmf, itsMS); + if (itsWriteFullResFlags) { // Add LOFAR_FULL_RES_FLAG column using tsm. // The input can already be averaged and averaging can be done in @@ -337,7 +370,17 @@ namespace LOFAR { dataShapeF, ColumnDesc::FixedShape); makeArrayColumn(padesc, dataShapeF, &tsmf, itsMS); } - { + if (itsStManKeys.stManName == "dysco" && itsStManKeys.dyscoWeightBitRate != 0) { + // Add WEIGHT_SPECTRUM column using Dysco stman. + CountedPtr<DataManager> dyscoStMan(dyscoConstructor( + "DyscoWeightSpectrum", dyscoSpec) + ); + ArrayColumnDesc<float> wsdesc("WEIGHT_SPECTRUM", "weight per corr/chan", + dataShape, + ColumnDesc::FixedShape | ColumnDesc::Direct); + makeArrayColumn (wsdesc, dataShape, dyscoStMan.get(), itsMS, true); + } + else { // Add WEIGHT_SPECTRUM column using tsm. TiledColumnStMan tsmw("TiledWeightSpectrum", tileShape); ArrayColumnDesc<float> wsdesc("WEIGHT_SPECTRUM", "weight per corr/chan", @@ -355,7 +398,7 @@ namespace LOFAR { TiledColumnStMan tsmc("CorrectedData", tileShape); makeArrayColumn(tdesc["CORRECTED_DATA"], dataShape, &tsmc, itsMS); - + IPosition iwShape(1, dataShape[1]); IPosition iwShapeTile(2, tileShape[1], tileShape[2]); TiledColumnStMan tsmw("TiledImagingWeight", iwShapeTile); @@ -513,7 +556,23 @@ namespace LOFAR { return; } - dataCol.putColumn (buf.getData()); + // If compressing, flagged values need to be set to NaN to decrease the dynamic range + if(itsStManKeys.stManName == "dysco") + { + casa::Cube<casa::Complex> dataCopy = buf.getData().copy(); + casa::Cube<casa::Complex>::iterator dataIter = dataCopy.begin(); + for(casa::Cube<bool>::const_iterator flagIter = buf.getFlags().begin(); flagIter != buf.getFlags().end(); ++flagIter) + { + if(*flagIter) + *dataIter = casa::Complex(std::numeric_limits<float>::quiet_NaN(), std::numeric_limits<float>::quiet_NaN()); + ++dataIter; + } + dataCol.putColumn (dataCopy); + } + else { + dataCol.putColumn (buf.getData()); + } + flagCol.putColumn (buf.getFlags()); // A row is flagged if no flags in the row are False. Vector<Bool> rowFlags (partialNFalse(buf.getFlags(), IPosition(2,0,1)) == 0u); -- GitLab From adacc10ca6d56bc10d0e7c908c4188ceb93766d4 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 5 Oct 2016 12:55:25 +0000 Subject: [PATCH 910/933] Task #8475: Print interface to pulp --- CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py index ddf51a5aa7c..7fb03e3d794 100755 --- a/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/pulsar_pipeline.py @@ -18,6 +18,7 @@ import sys import pulp from string import join +import pprint from lofarpipe.support.control import control from lofarpipe.support.data_map import DataMap, validate_data_maps from lofarpipe.support.lofarexceptions import PipelineException @@ -172,6 +173,7 @@ class pulsar_pipeline(control): # Run the pulsar pipeline self.logger.debug("Starting pulp with: " + join(sys.argv)) + self.logger.debug("Calling pulp.pulp(self) with self = %s", pprint.pformat(vars(self))) p = pulp.pulp(self) # TODO: MUCK self to capture the API # NOTE: PULP returns 0 on SUCCESS!! -- GitLab From 5c0a6d6ba916033b58d571030a5f88fa62b88a3c Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 5 Oct 2016 14:48:58 +0000 Subject: [PATCH 911/933] Task #9948: Added IPS_Commissioning to use dynspec inspection tools --- .../RAtoOTDBTaskSpecificationPropagator/lib/translator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py index 3b409aca9e8..e8411ba0f5a 100755 --- a/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py +++ b/SAS/ResourceAssignment/RAtoOTDBTaskSpecificationPropagator/lib/translator.py @@ -234,7 +234,7 @@ class RAtoOTDBTranslator(): parset[PREFIX+'ObservationControl.OnlineControl.inspectionProgram'] = 'inspection-plots-observation.sh' #special case for dynspec projects for Richard Fallows - if project_name in ['LC6_001']: + if project_name in ['LC6_001','IPS_Commissioning']: parset[PREFIX+'ObservationControl.OnlineControl.inspectionProgram'] = '/data/home/lofarsys/dynspec/scripts/inspection-dynspec-observation.sh' return parset -- GitLab From 9d91eadfdf984bc25e976d250c39476fce971bc6 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Mon, 10 Oct 2016 08:19:26 +0000 Subject: [PATCH 912/933] Task #8745: Backported lofar-pulp to 2.17 release branch --- .gitattributes | 3 ++ Docker/CMakeLists.txt | 2 + Docker/lofar-pulp/Dockerfile.tmpl | 62 +++++++++++++++++++++++++++++++ Docker/lofar-pulp/bashrc | 11 ++++++ Docker/lofar-pulp/sudoers | 1 + 5 files changed, 79 insertions(+) create mode 100644 Docker/lofar-pulp/Dockerfile.tmpl create mode 100644 Docker/lofar-pulp/bashrc create mode 100644 Docker/lofar-pulp/sudoers diff --git a/.gitattributes b/.gitattributes index 786f4056a41..b343cf1fb6d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2369,6 +2369,9 @@ Docker/lofar-base/bashrc.d/50-lofar -text Docker/lofar-base/chuser.sh -text Docker/lofar-outputproc/Dockerfile.tmpl -text Docker/lofar-pipeline/Dockerfile.tmpl -text +Docker/lofar-pulp/Dockerfile.tmpl -text +Docker/lofar-pulp/bashrc -text +Docker/lofar-pulp/sudoers -text Docker/lofar-tbbwriter/Dockerfile -text Docker/lofar-tbbwriter/bashrc -text Docker/lofar-tbbwriter/chuser.sh -text diff --git a/Docker/CMakeLists.txt b/Docker/CMakeLists.txt index 2aab64bc7d2..a8965c57355 100644 --- a/Docker/CMakeLists.txt +++ b/Docker/CMakeLists.txt @@ -25,6 +25,7 @@ lofar_add_bin_program(versiondocker versiondocker.cc) set(DOCKER_TEMPLATE_DIRS lofar-base lofar-pipeline + lofar-pulp lofar-outputproc) # Note: "docker-template" only works as long as the sources are still around, @@ -60,6 +61,7 @@ install(DIRECTORY dynspec lofar-base lofar-pipeline + lofar-pulp lofar-outputproc lofar-tbbwriter DESTINATION share/docker diff --git a/Docker/lofar-pulp/Dockerfile.tmpl b/Docker/lofar-pulp/Dockerfile.tmpl new file mode 100644 index 00000000000..f214fa8d765 --- /dev/null +++ b/Docker/lofar-pulp/Dockerfile.tmpl @@ -0,0 +1,62 @@ +# +# base +# +FROM pulp:latest + +COPY ["sudoers", "/etc/"] + +# Run-time dependencies +RUN sudo apt-get update && sudo apt-get install -y python-xmlrunner liblog4cplus-1.0-4 libxml2 libxml++2.6-2 openssh-client gettext-base rsync python-matplotlib +# +# ******************* +# QPID client +# ******************* +# + +ENV BOOST_VERSION=1.54 + +# Run-time dependencies +# QPID daemon legacy store would require: libaio1 libdb5.1++ +RUN sudo apt-get update && sudo apt-get install -y sasl2-bin libuuid1 libnss3 libnspr4 xqilla + +# Install +# QPID daemon legacy store would require: libaio-dev libdb5.1++-dev +RUN sudo apt-get update && sudo apt-get install -y ruby ruby-dev libsasl2-dev uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man libsslcommon2-dev libxqilla-dev && \ + mkdir /opt/qpid && \ + svn --non-interactive -q co https://svn.astron.nl/LOFAR/trunk/LCS/MessageBus/qpid/ /opt/qpid; \ + bash -c "HOME=/tmp /opt/qpid/local/sbin/build_qpid" && \ + bash -c "strip /opt/qpid/{bin,lib}/* || true" && \ + bash -c "rm -rf /tmp/sources" && \ + sudo apt-get purge -y ruby ruby-dev libsasl2-dev uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man libsslcommon2-dev libxqilla-dev && \ + sudo apt-get autoremove -y + +# +# ******************* +# LOFAR +# ******************* +# + +# Tell image build information +ENV LOFAR_BRANCH=${LOFAR_BRANCH_NAME} \ + LOFAR_TAG=${LOFAR_TAG} \ + LOFAR_REVISION=${LOFAR_REVISION} \ + LOFAR_BUILDVARIANT=gnu_optarch + +# Install +#some are already installed, but we need boost, and RUN sudo apt-get update && sudo apt-get install -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev python-dev libxml2-dev pkg-config libunittest++-dev libxml++2.6-dev binutils-dev && \ +RUN sudo apt-get update && sudo apt-get install -y liblog4cplus-dev libhdf5-dev libblitz0-dev libunittest++-dev libxml++2.6-dev binutils-dev && \ + mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ + cd ${INSTALLDIR}/lofar && \ + svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ + svn --non-interactive -q up src/CMake && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && cmake -DBUILD_PACKAGES="Pipeline MessageBus OTDB_Services" -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=${INSTALLDIR}/lofar/ -DCASACORE_ROOT_DIR=${INSTALLDIR}/casacore/ -DQPID_ROOT_DIR=/opt/qpid/ -DUSE_OPENMP=True ${INSTALLDIR}/lofar/src/ && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make -j ${J} && \ + cd ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && make install && \ + bash -c "mkdir -p ${INSTALLDIR}/lofar/var/{log,run}" && \ + bash -c "chmod a+rwx ${INSTALLDIR}/lofar/var/{log,run}" && \ + bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ + bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ + sudo apt-get purge -y liblog4cplus-dev libhdf5-dev libblitz0-dev libunittest++-dev libxml++2.6-dev binutils-dev && \ + sudo apt-get autoremove -y + +COPY ["bashrc", "/opt/"] diff --git a/Docker/lofar-pulp/bashrc b/Docker/lofar-pulp/bashrc new file mode 100644 index 00000000000..7c9e948ce7e --- /dev/null +++ b/Docker/lofar-pulp/bashrc @@ -0,0 +1,11 @@ +#!/bin/bash + +# lofar +[ -r ${INSTALLDIR}/lofar/lofarinit.sh ] && source ${INSTALLDIR}/lofar/lofarinit.sh +export PATH PYTHONPATH LD_LIBRARY_PATH LOFARROOT + +# qpid +source ${INSTALLDIR}/qpid/.profile + +# lofarsoft +source ${LOFARSOFT}/devel_common/scripts/init.sh diff --git a/Docker/lofar-pulp/sudoers b/Docker/lofar-pulp/sudoers new file mode 100644 index 00000000000..24c18523f4b --- /dev/null +++ b/Docker/lofar-pulp/sudoers @@ -0,0 +1 @@ +ALL ALL=(ALL) NOPASSWD:ALL -- GitLab From f1131b8036b1b0300c43621dadaeb134d7824ea1 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Mon, 10 Oct 2016 16:05:50 +0000 Subject: [PATCH 913/933] Task #9970: improve logging output of genericpipeline (by Stephen Bourke) --- CEP/Pipeline/recipes/sip/bin/genericpipeline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/CEP/Pipeline/recipes/sip/bin/genericpipeline.py b/CEP/Pipeline/recipes/sip/bin/genericpipeline.py index 72a207f0a09..feca430e942 100755 --- a/CEP/Pipeline/recipes/sip/bin/genericpipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/genericpipeline.py @@ -188,6 +188,7 @@ class GenericPipeline(control): # plugins are not used at the moment and might better be replaced with master recipes while step_name_list: stepname = step_name_list.pop(0) + self.logger.info("Beginning step %s" % (stepname,)) step = step_control_dict[stepname] #step_parset = step_parset_obj[stepname] inputdict = {} -- GitLab From 708511f1a6041aa79e3532c69f4e703a7dc08260 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 11 Oct 2016 18:25:41 +0000 Subject: [PATCH 914/933] Task #9048: Add ipython for pybdsm interactive shell --- Docker/lofar-pipeline/Dockerfile.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 643b4cbeab4..dea0bd27163 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -6,7 +6,7 @@ FROM lofar-base:${LOFAR_TAG} ENV AOFLAGGER_VERSION=2.8.0 # Run-time dependencies -RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib slurm-client && \ +RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib ipython slurm-client && \ apt-get -y install python-pip python-dev && \ pip install pyfits pywcs python-monetdb && \ apt-get -y purge python-pip python-dev && \ -- GitLab From c9971eb94b3d14534f72d044294b2983d535c690 Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Wed, 12 Oct 2016 13:25:31 +0000 Subject: [PATCH 915/933] Task #9976: Creating shield file for ccu199 --- .gitattributes | 2 ++ .../471_3031_2_Astron_Gen_II_3_314.log | 31 +++++++++++++++++++ .../PVSS/License/Astron_Station_1_shield.txt | 4 +-- .../data/PVSS/License/CCU199_option.txt | 29 +++++++++++++++++ .../data/PVSS/License/shield.CCU199.txt | 29 +++++++++++++++++ 5 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 MAC/Deployment/data/PVSS/License/CCU199_option.txt create mode 100644 MAC/Deployment/data/PVSS/License/shield.CCU199.txt diff --git a/.gitattributes b/.gitattributes index 7659a1ac42e..7766db2d6a6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3486,6 +3486,7 @@ MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_2_311.log -text MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log -text MAC/Deployment/data/PVSS/License/CCU002_option.txt -text MAC/Deployment/data/PVSS/License/CCU099_option.txt -text +MAC/Deployment/data/PVSS/License/CCU199_option.txt -text MAC/Deployment/data/PVSS/License/CS001C_option_lcu037.txt -text MAC/Deployment/data/PVSS/License/CS002_option.txt -text MAC/Deployment/data/PVSS/License/CS003C_option.txt -text @@ -3558,6 +3559,7 @@ MAC/Deployment/data/PVSS/License/Station.rtu -text MAC/Deployment/data/PVSS/License/UK608C_option.txt -text MAC/Deployment/data/PVSS/License/shield.CCU002.txt -text MAC/Deployment/data/PVSS/License/shield.CCU099.txt -text +MAC/Deployment/data/PVSS/License/shield.CCU199.txt -text MAC/Deployment/data/PVSS/License/shield.CS001C.txt -text MAC/Deployment/data/PVSS/License/shield.CS001C_lcu037.txt -text MAC/Deployment/data/PVSS/License/shield.CS002C.txt -text diff --git a/MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log b/MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log index 14097beb645..e887b5fa962 100644 --- a/MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log +++ b/MAC/Deployment/data/PVSS/License/471_3031_2_Astron_Gen_II_3_314.log @@ -60,3 +60,34 @@ pararemote = 0 ctrlext = 1 update = 0 + +--------------------------------------------------- +[license] +code = "ccu199.control.lofar 20392303325" +version = 31400002 +sn = "471_3031_2_Astron_Gen_II_3_314/3" +date = 2016.10.12;15:24:28,000 +comment = "CentOS7 CepCU Test system CCU199" +expire = 0000.00.00;00:00:00,000 +redundancy = 0 +ui = 2 +para = 1 +dde = 5 +event = 1 +ios = 4000 +ssi = 0 +api = 80 +excelreport = 5 +http = 0 +infoserver = 1000 +comcenter = 5 +maintenance = 1 +scheduler = 1 +recipe = 1 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + diff --git a/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt b/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt index 04a483dd569..58f1503ff17 100644 --- a/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt +++ b/MAC/Deployment/data/PVSS/License/Astron_Station_1_shield.txt @@ -1,5 +1,5 @@ [license] -code = "dongleHost 30201154033" +code = "dongleHost 90090244310" version = 31400002 sn = "471_3031_2_Astron_Gen_II_3_314" expire = 0000.00.00;00:00:00,000 @@ -25,5 +25,5 @@ pararemote = 0 ctrlext = 1 update = 0 licenseMax = 100 -licenseLeft = 98 +licenseLeft = 97 diff --git a/MAC/Deployment/data/PVSS/License/CCU199_option.txt b/MAC/Deployment/data/PVSS/License/CCU199_option.txt new file mode 100644 index 00000000000..8e37f78b5bf --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/CCU199_option.txt @@ -0,0 +1,29 @@ +[license] +code = "ccu199.control.lofar 31504983450" +version = 31400002 +comment = "CentOS7 CepCU Test system CCU199" +sn = "471_3031_2_Astron_Gen_II_1_38" +expire = 0000.00.00;00:00:00,000 +redundancy = 0 +ui = 2 +para = 1 +dde = 5 +event = 1 +api = 80 +excelreport = 5 +http = 0 +infoserver = 1000 +ios = 4000 +comcenter = 5 +maintenance = 1 +scheduler = 1 +ssi = 0 +recipe = 1 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + + diff --git a/MAC/Deployment/data/PVSS/License/shield.CCU199.txt b/MAC/Deployment/data/PVSS/License/shield.CCU199.txt new file mode 100644 index 00000000000..7548ca1957f --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/shield.CCU199.txt @@ -0,0 +1,29 @@ +[license] +code = "ccu199.control.lofar 20392303325" +version = 31400002 +sn = "471_3031_2_Astron_Gen_II_3_314/3" +date = 2016.10.12;15:24:28,000 +comment = "CentOS7 CepCU Test system CCU199" +expire = 0000.00.00;00:00:00,000 +redundancy = 0 +ui = 2 +para = 1 +dde = 5 +event = 1 +ios = 4000 +ssi = 0 +api = 80 +excelreport = 5 +http = 0 +infoserver = 1000 +comcenter = 5 +maintenance = 1 +scheduler = 1 +recipe = 1 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + -- GitLab From 789885a73c3e24d3307921b64c876e1237a13370 Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Wed, 12 Oct 2016 13:46:00 +0000 Subject: [PATCH 916/933] Task #9976: Creating license file for mcu199 --- .gitattributes | 2 ++ .../License/471_3031_1_Astron_Gen_I_3_314.log | 30 +++++++++++++++++++ .../PVSS/License/Astron_Central_1_shield.txt | 4 +-- .../data/PVSS/License/MCU199_option.txt | 27 +++++++++++++++++ .../data/PVSS/License/shield.MCU199.txt | 28 +++++++++++++++++ 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 MAC/Deployment/data/PVSS/License/MCU199_option.txt create mode 100644 MAC/Deployment/data/PVSS/License/shield.MCU199.txt diff --git a/.gitattributes b/.gitattributes index 7766db2d6a6..c9ad99c5098 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3533,6 +3533,7 @@ MAC/Deployment/data/PVSS/License/License[!!-~]administration.xlsx -text MAC/Deployment/data/PVSS/License/MCU001_option.old.txt -text MAC/Deployment/data/PVSS/License/MCU002_option.txt -text MAC/Deployment/data/PVSS/License/MCU099_option.txt -text +MAC/Deployment/data/PVSS/License/MCU199_option.txt -text MAC/Deployment/data/PVSS/License/PL610C_option.txt -text MAC/Deployment/data/PVSS/License/PL611C_option.txt -text MAC/Deployment/data/PVSS/License/PL612C_option.txt -text @@ -3600,6 +3601,7 @@ MAC/Deployment/data/PVSS/License/shield.FR606C.txt -text MAC/Deployment/data/PVSS/License/shield.FR606C_lcu101.txt -text MAC/Deployment/data/PVSS/License/shield.MCU002.txt -text MAC/Deployment/data/PVSS/License/shield.MCU099.txt -text +MAC/Deployment/data/PVSS/License/shield.MCU199.txt -text MAC/Deployment/data/PVSS/License/shield.PL610C.txt -text MAC/Deployment/data/PVSS/License/shield.PL611C.txt -text MAC/Deployment/data/PVSS/License/shield.PL612C.txt -text diff --git a/MAC/Deployment/data/PVSS/License/471_3031_1_Astron_Gen_I_3_314.log b/MAC/Deployment/data/PVSS/License/471_3031_1_Astron_Gen_I_3_314.log index 78659da440f..4fee4495106 100644 --- a/MAC/Deployment/data/PVSS/License/471_3031_1_Astron_Gen_I_3_314.log +++ b/MAC/Deployment/data/PVSS/License/471_3031_1_Astron_Gen_I_3_314.log @@ -28,3 +28,33 @@ pararemote = 0 ctrlext = 1 update = 0 + +--------------------------------------------------- +[license] +code = "mcu199.control.lofar 00033501895" +version = 31400002 +sn = "471_3031_1_Astron_Gen_I_3_314/2" +date = 2016.10.12;15:45:33,000 +comment = "CentOS7 MainCU system MCU002" +expire = 0000.00.00;00:00:00,000 +redundancy = 1 +ui = 15 +para = 4 +dde = 5 +event = 1 +ios = 100000 +ssi = 0 +api = 80 +excelreport = 5 +http = 15 +infoserver = 5000 +comcenter = 5 +maintenance = 0 +scheduler = 0 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + diff --git a/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt b/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt index 850425c82d7..f928182de56 100644 --- a/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt +++ b/MAC/Deployment/data/PVSS/License/Astron_Central_1_shield.txt @@ -1,5 +1,5 @@ [license] -code = "dongleHost 10387235999" +code = "dongleHost 40222181284" version = 31400002 sn = "471_3031_1_Astron_Gen_I_3_314" expire = 0000.00.00;00:00:00,000 @@ -24,5 +24,5 @@ pararemote = 0 ctrlext = 1 update = 0 licenseMax = 8 -licenseLeft = 7 +licenseLeft = 6 diff --git a/MAC/Deployment/data/PVSS/License/MCU199_option.txt b/MAC/Deployment/data/PVSS/License/MCU199_option.txt new file mode 100644 index 00000000000..f67bb06f771 --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/MCU199_option.txt @@ -0,0 +1,27 @@ +[license] +code = "mcu199.control.lofar 31976234155" +version = 31400002 +comment = "CentOS7 MainCU system MCU002" +sn = "471_3031_1_Astron_Gen_I_1_314" +expire = 0000.00.00;00:00:00,000 +redundancy = 1 +ui = 15 +para = 4 +dde = 5 +event = 1 +api = 80 +excelreport = 5 +http = 15 +infoserver = 5000 +ios = 100000 +comcenter = 5 +maintenance = 0 +scheduler = 0 +ssi = 0 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + diff --git a/MAC/Deployment/data/PVSS/License/shield.MCU199.txt b/MAC/Deployment/data/PVSS/License/shield.MCU199.txt new file mode 100644 index 00000000000..db420dbedc3 --- /dev/null +++ b/MAC/Deployment/data/PVSS/License/shield.MCU199.txt @@ -0,0 +1,28 @@ +[license] +code = "mcu199.control.lofar 00033501895" +version = 31400002 +sn = "471_3031_1_Astron_Gen_I_3_314/2" +date = 2016.10.12;15:45:33,000 +comment = "CentOS7 MainCU system MCU002" +expire = 0000.00.00;00:00:00,000 +redundancy = 1 +ui = 15 +para = 4 +dde = 5 +event = 1 +ios = 100000 +ssi = 0 +api = 80 +excelreport = 5 +http = 15 +infoserver = 5000 +comcenter = 5 +maintenance = 0 +scheduler = 0 +distributed = 255 +uifix = 0 +parafix = 0 +pararemote = 0 +ctrlext = 1 +update = 0 + -- GitLab From 4aab7e2018b24110c39dc5c07f64bd762f24bd00 Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Fri, 14 Oct 2016 09:09:37 +0000 Subject: [PATCH 917/933] Task #8993: Fixed compile error in bbs-shared-estimator.cc when using c++-11 --- CEP/Calibration/BBSControl/src/bbs-shared-estimator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/Calibration/BBSControl/src/bbs-shared-estimator.cc b/CEP/Calibration/BBSControl/src/bbs-shared-estimator.cc index 5fd06d9b049..f0153f75fd3 100644 --- a/CEP/Calibration/BBSControl/src/bbs-shared-estimator.cc +++ b/CEP/Calibration/BBSControl/src/bbs-shared-estimator.cc @@ -175,7 +175,7 @@ int run(const ParameterSet &options, const OptionParser::ArgumentList&) wait = false; } - pair<CommandId, shared_ptr<const Command> > command = session.getCommand(); + pair<CommandId, LOFAR::shared_ptr<Command> > command = session.getCommand(); if(command.second) { LOG_DEBUG_STR("Executing a " << command.second->type() << " command:" -- GitLab From e7546e96d067375160b6bf13d3e5a63dc87b8da9 Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Fri, 14 Oct 2016 12:03:58 +0000 Subject: [PATCH 918/933] Task #6553: do same in TRUNK navigator --- .../panels/objects/Hardware/RSPBoard_AP.pnl | 70 +++++------ MAC/Navigator2/scripts/setSumAlerts.ctl | 93 ++++++++------ MAC/Navigator2/scripts/transferMPs.ctl | 117 ++++++++++++++++-- 3 files changed, 193 insertions(+), 87 deletions(-) diff --git a/MAC/Navigator2/panels/objects/Hardware/RSPBoard_AP.pnl b/MAC/Navigator2/panels/objects/Hardware/RSPBoard_AP.pnl index 6b133fc2263..8835eb5411a 100644 --- a/MAC/Navigator2/panels/objects/Hardware/RSPBoard_AP.pnl +++ b/MAC/Navigator2/panels/objects/Hardware/RSPBoard_AP.pnl @@ -1,4 +1,4 @@ -V 10 +V 11 1 LANG:1 10 Subrack_AP PANEL,-1 -1 184 193 N "_3DFace" 1 @@ -49,9 +49,9 @@ E "#uses \"navPanel.ctl\" string baseDP=\"\"; -FPGASyncDetails(string dp1, int sampleCount, - string dp2, int syncCount, - string dp3, int errorCount) +FPGASyncDetails(string dp1, uint sampleCount, + string dp2, uint syncCount, + string dp3, uint errorCount) { setValue(\"output_errorCount\" , \"text\", errorCount); setValue(\"output_sampleCount\", \"text\", sampleCount); @@ -106,12 +106,11 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 1 1 0 1 E U 0 E 27 33 45 50 +E E 0 1 1 0 1 E U 0 E 27 33 45 49 0 2 0 "0s" 0 0 0 192 0 0 27 33 1 1 -LANG:1 84 -*-Arial-*-r-normal-*-13-*-100-100-*-*-iso8859-1|-13,0,0,0,404,0,0,0,0,0,0,0,0,Arial -0 "" -1 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +0 1 LANG:1 2 AP 2 6 "txt_version" @@ -126,12 +125,11 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 1 1 0 1 E U 0 E 121 166 158 181 +E E 0 1 1 0 1 E U 0 E 121 166 158 180 0 2 0 "0s" 0 0 0 194 0 0 158 166 1 1 -LANG:1 84 -*-Arial-*-r-normal-*-11-*-100-100-*-*-iso8859-1|-11,0,0,0,404,0,0,0,0,0,0,0,0,Arial -0 "" -1 +LANG:1 26 Arial,-1,11,5,40,0,0,0,0,0 +0 1 LANG:1 8 ver: x.x 2 8 "label_sampleCount" @@ -149,9 +147,8 @@ LANG:1 0 E E 0 1 1 0 1 E U 0 E 22 97 72 116 0 2 0 "0s" 0 0 0 66 0 0 22 97 1 1 -LANG:1 84 -*-Arial-*-r-normal-*-12-*-100-100-*-*-iso8859-1|-12,0,0,0,404,0,0,0,0,0,0,0,0,Arial -0 "" -1 +LANG:1 26 Arial,-1,12,5,40,0,0,0,0,0 +0 1 LANG:1 7 Sample: 2 9 "label_syncCount" @@ -169,9 +166,8 @@ LANG:1 0 E E 0 1 1 0 1 E U 0 E 39 120 72 136 0 2 0 "0s" 0 0 0 66 0 0 39 120 1 1 -LANG:1 84 -*-Arial-*-r-normal-*-12-*-100-100-*-*-iso8859-1|-12,0,0,0,404,0,0,0,0,0,0,0,0,Arial -0 "" -1 +LANG:1 26 Arial,-1,12,5,40,0,0,0,0,0 +0 1 LANG:1 5 Sync: 2 7 "label_errorCount" @@ -189,9 +185,8 @@ LANG:1 0 E E 0 1 1 0 1 E U 0 E 39 143 72 159 0 2 0 "0s" 0 0 0 66 0 0 39 143 1 1 -LANG:1 84 -*-Arial-*-r-normal-*-12-*-100-100-*-*-iso8859-1|-12,0,0,0,404,0,0,0,0,0,0,0,0,Arial -0 "" -1 +LANG:1 26 Arial,-1,12,5,40,0,0,0,0,0 +0 1 LANG:1 6 Error: 2 18 "txt_temperature" @@ -206,12 +201,11 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 1 1 0 1 E U 0 E 126 32 133 49 +E E 0 1 1 0 1 E U 0 E 126 32 133 48 0 2 0 "3.0f" 4 0 0 194 0 0 133 32 1 1 -LANG:1 84 -*-Arial-*-r-normal-*-13-*-100-100-*-*-iso8859-1|-13,0,0,0,404,0,0,0,0,0,0,0,0,Arial -0 "" -1 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +0 1 LANG:1 1 x 2 4 "label_temperature" @@ -226,12 +220,11 @@ LANG:1 0 1 "dashclr"N "_Transparent" -E E 0 1 1 0 1 E U 0 E 134 32 148 49 +E E 0 1 1 0 1 E U 0 E 134 32 148 48 0 2 0 "0s" 0 0 0 194 0 0 148 32 1 1 -LANG:1 84 -*-Arial-*-r-normal-*-13-*-100-100-*-*-iso8859-1|-13,0,0,0,404,0,0,0,0,0,0,0,0,Arial -0 "" -1 +LANG:1 26 Arial,-1,13,5,40,0,0,0,0,0 +0 1 LANG:1 2 �C 1 19 0 "" 1 0 @@ -248,9 +241,8 @@ LANG:1 0 0 1 -LANG:1 84 -*-Arial-*-r-normal-*-11-*-100-100-*-*-iso8859-1|-11,0,0,0,404,0,0,0,0,0,0,0,0,Arial -0 "" - 78 139 152 164 +LANG:1 26 Arial,-1,11,5,40,0,0,0,0,0 +0 78 139 152 164 3 "0s" 0 0 0 2 0 -1 E E E 14 13 "output_sampleCount" @@ -265,9 +257,8 @@ LANG:1 0 0 1 -LANG:1 84 -*-Arial-*-r-normal-*-11-*-100-100-*-*-iso8859-1|-11,0,0,0,404,0,0,0,0,0,0,0,0,Arial -0 "" - 78 90 152 115 +LANG:1 26 Arial,-1,11,5,40,0,0,0,0,0 +0 78 90 152 115 3 "0s" 0 0 0 2 0 -1 E E E 14 14 "output_syncCount" @@ -282,9 +273,8 @@ LANG:1 0 0 1 -LANG:1 84 -*-Arial-*-r-normal-*-11-*-100-100-*-*-iso8859-1|-11,0,0,0,404,0,0,0,0,0,0,0,0,Arial -0 "" - 78 114 152 139 +LANG:1 26 Arial,-1,11,5,40,0,0,0,0,0 +0 78 114 152 139 3 "0s" 0 0 0 2 0 -1 E E E 0 LAYER, 1 @@ -315,7 +305,7 @@ LAYER, 7 1 LANG:1 6 Layer8 0 -3 0 "selfState" +3 0 "selfState" -1 "objects\\lofar_self_state.pnl" 25 171 T 17 1 0 1 -2 -15 0 -0 \ No newline at end of file +0 diff --git a/MAC/Navigator2/scripts/setSumAlerts.ctl b/MAC/Navigator2/scripts/setSumAlerts.ctl index 71fbb9d3439..844e90f4441 100644 --- a/MAC/Navigator2/scripts/setSumAlerts.ctl +++ b/MAC/Navigator2/scripts/setSumAlerts.ctl @@ -45,10 +45,12 @@ private void setSumAlerts( string strDPE, bool bRunDone ) // Get all DPE's with a 'childSumAlert' dsParentDPEs = dpNames( "*.**.childSumAlert" ); + dynSort(dsParentDPEs); + // Go through sumalerts for( x=1; x<=dynlen(dsParentDPEs); x++ ) { - bool bRetVal1, bRetVal2; + bool bRetVal1, bRetVal2, bLeaf; int iRetVal, iAlertHdlType; string strDPE; dyn_string dsChilds, dsChildDPEs; @@ -57,8 +59,8 @@ private void setSumAlerts( string strDPE, bool bRunDone ) strDPE = dpSubStr( dsParentDPEs[x], DPSUB_DP_EL ); strDPE = RemoveLastDpeParts(strDPE); - // Skip master datapoints - if( patternMatch( "*_mp*", strDPE ) ) + // Skip master datapoints and leaf=True points + if( patternMatch( "*_mp*", strDPE )) { continue; } @@ -68,58 +70,74 @@ private void setSumAlerts( string strDPE, bool bRunDone ) continue; } + dpGet(strDPE+".status.leaf",bLeaf); + if (bLeaf) + { + continue; + } + + + if (bDebug) + { + DebugTN("Working on: " + strDPE); + } + // Get childs of this DPE dsChilds = GetChilds( strDPE ); + // For each child: get DPE's to add to sumalerts and append to list for( y=1; y<=dynlen(dsChilds); y++ ) { dynAppend( dsChildDPEs, GetChildDPEs( dsChilds[y] ) ); } + // Check if this DPE has an alert_hdl of type sumalert dpGet( dsParentDPEs[x] + ":_alert_hdl.._type", iAlertHdlType ); - if( iAlertHdlType == DPCONFIG_SUM_ALERT ) + if( iAlertHdlType == DPCONFIG_SUM_ALERT) { - // First deactivate the alert - dpDeactivateAlert( dsParentDPEs[x], bRetVal1 ); - if( !bRetVal1 ) - { - DebugTN( "setSumAlerts(): FAILED TO dpDeactivateAlert FOR DPE '" + dsParentDPEs[x] + "'!!" ); - } - - // No change the sumalert dplist - iRetVal = dpSet( dsParentDPEs[x] + ":_alert_hdl.._dp_list", dsChildDPEs, - dsParentDPEs[x] + ":_alert_hdl.._dp_pattern", "" ); - - dyn_errClass derrLastError = getLastError(); - if( dynlen(derrLastError) > 0 ) - { - DebugTN( "setSumAlerts(): FAILED TO dpSet FOR DPE, getLastError():", derrLastError ); - } - else if( iRetVal != 0 ) - { - DebugTN( "setSumAlerts(): FAILED TO dpSet FOR DPE, iRetVal = " + iRetVal ); - } - - // Activate alert again - dpActivateAlert( dsParentDPEs[x], bRetVal2 ); - if( !bRetVal2 ) - { - DebugTN( "setSumAlerts(): FAILED TO dpActivateAlert FOR DPE '" + dsParentDPEs[x] + "'!!" ); - } - - // Show if we're succesfull - if( bRetVal1 && bRetVal2 && ( iRetVal == 0 ) ) + if (dynlen(dsChildDPEs) > 0) { - if( bDebug ) + // First deactivate the alert + dpDeactivateAlert( dsParentDPEs[x], bRetVal1 ); + if( !bRetVal1 ) + { + DebugTN( "setSumAlerts(): FAILED TO dpDeactivateAlert FOR DPE '" + dsParentDPEs[x] + "'!!" ); + } + + // No change the sumalert dplist + iRetVal = dpSet( dsParentDPEs[x] + ":_alert_hdl.._dp_list", dsChildDPEs, + dsParentDPEs[x] + ":_alert_hdl.._dp_pattern", "" ); + + dyn_errClass derrLastError = getLastError(); + if( dynlen(derrLastError) > 0 ) { - DebugTN( "setSumAlerts(): SumAlerts for DPE '" + dsParentDPEs[x] + "' succesfully set to " + dynlen(dsChildDPEs) + " child DPEs" ); + DebugTN( "setSumAlerts(): FAILED TO dpSet FOR DPE, getLastError():", derrLastError ); + } + else if( iRetVal != 0 ) + { + DebugTN( "setSumAlerts(): FAILED TO dpSet FOR DPE, iRetVal = " + iRetVal ); + } + + // Activate alert again + dpActivateAlert( dsParentDPEs[x], bRetVal2 ); + if( !bRetVal2 ) + { + DebugTN( "setSumAlerts(): FAILED TO dpActivateAlert FOR DPE '" + dsParentDPEs[x] + "'!!" ); + } + + // Show if we're succesfull + if( bRetVal1 && bRetVal2 && ( iRetVal == 0 ) ) + { + if( bDebug ) + { + DebugTN( "setSumAlerts(): SumAlerts for DPE '" + dsParentDPEs[x] + "' succesfully set to " + dynlen(dsChildDPEs) + " child DPEs" ); + } } } } - else { DebugTN( "setSumAlerts(): DPE '" + dsParentDPEs[x] + "' DOESN'T HAVE AN ALERT-HANDLE OR NOT OF TYPE SUM-ALERT!!" ); @@ -131,6 +149,7 @@ private void setSumAlerts( string strDPE, bool bRunDone ) } dpSet( "scriptInfo.setSumAlerts.runDone", true ); + DebugTN("setSumAlerts.ctl:main|set of sumAlerts done"); } diff --git a/MAC/Navigator2/scripts/transferMPs.ctl b/MAC/Navigator2/scripts/transferMPs.ctl index 632cbc3f43a..da806d60e5a 100644 --- a/MAC/Navigator2/scripts/transferMPs.ctl +++ b/MAC/Navigator2/scripts/transferMPs.ctl @@ -12,7 +12,7 @@ void main() if (dpExists("scriptInfo.transferMPs.debug")) { dpConnect("debugCB",true,"scriptInfo.transferMPs.debug"); } else { - DebugTN("transferMPs.ctl:main|scriptInfo.transferMPs.debugpoint not found in Database"); + DebugTN("transferMPs.ctl:main|scriptInfo.transferMPs.debugpoint not found in bbase"); } if (dpExists("scriptInfo.transferMPs.runDone")) { @@ -35,10 +35,6 @@ void main() } else { DebugTN("transferMPs.ctl:main|set leafpoints to value in mp failed"); } - - // if on a station, determine the ip adres to construct the ip adres of the POEWEC(S) write them in then database - // and set the manager active - } private void debugCB(string dp1, bool debug) { @@ -53,10 +49,11 @@ private void startTransferMP(string dp1, bool done ) { string sDestinationDPE; if (done) return; + + startTransferMP_Embedded(); - DebugTN("transferMPs.ctl:main|start transfer of MPconfigs to all DP's"); + DebugTN("transferMPs.ctl:main|start transfer of MPconfigs to all DP's"); - string query="SELECT '_original.._value' FROM '_mp_*'"; dyn_dyn_anytype tab; dyn_string dps; @@ -71,12 +68,12 @@ private void startTransferMP(string dp1, bool done ) { } } } - + for (k=1;k <= dynlen(dps); k++) { string dpstr = dps[k]; dyn_string dsDpes = dpNames( dpstr + ".**"), dsDps = dpNames("*",dpTypeName(dpstr)); - + // no datapoints found if ( dynlen(dsDps) > 1 ) { @@ -85,6 +82,7 @@ private void startTransferMP(string dp1, bool done ) { ll = l * (l1 - 1); // !!! dynlen(dsDpes) * dynlen(dsDps) verwenden mit der 2-Sek-Verz�gerung + for ( i = 1; i <= l; i++ ) { if ( strpos(dsDpes[i],".") < 1 ) dsDpes[i] += "."; dsConfigs = dpNames( dsDpes[i] + ":*" ); @@ -101,13 +99,16 @@ private void startTransferMP(string dp1, bool done ) { sDestinationDPE = dsDpes[i]; strreplace( sDestinationDPE, dpSubStr(dsDpes[i], DPSUB_DP), dpSubStr(dsDps[j], DPSUB_DP)); daCheckDPE(sDestinationDPE); - dpCopyConfig(dsDpes[i], sDestinationDPE, dsConfigs, iError); } } } } dpSet("scriptInfo.transferMPs.runDone",true); + + // fire setSumAlerts now that the database is complete + dpSet("scriptInfo.setSumAlerts.runDone",false); + DebugTN("MPTransfer Done."); } @@ -213,3 +214,99 @@ bool skipDP(string dp) { if (strpos(dp,"MODE_CMD") > -1) return true; return false; } + + + + + +void startTransferMP_Embedded() +{ + int x, y; + dyn_dyn_anytype ddaData; + dyn_dyn_string ddsRefs; + dyn_string dsDPs; + dyn_string dsDPTs; + + DebugTN("========== transferMPs.ctl:main|start transfer of embedded MPconfigs to all master DP's"); + + string strQuery = "SELECT '_original.._value' FROM '_mp_*'"; + dpQuery( strQuery,ddaData ); + + for( x=2; x<=dynlen(ddaData); x++) + { + string strDP = dpSubStr( ddaData[x][1], DPSUB_DP ); + string strDPT = dpTypeName(strDP); + + if( skipDP( strDP ) ) + { + continue; + } + + if (dynContains(dsDPTs, strDPT)) + { + continue; + } + + dynAppend(dsDPTs,strDPT); + + + // Get all DP-Types that has reference to this DP-type + ddsRefs = dpGetRefsToDpType( strDPT ); + + for( y=1; y<=dynlen(ddsRefs); y++ ) + { + if( dynlen(ddsRefs[y]) != 2 ) + continue; + + string strDPTsource = strDPT; + string strDPTtarget = ddsRefs[y][1]; + string strDPEtarget = ddsRefs[y][2]; + + CopyEmbbeddedMasterDP( strDPTsource, strDPTtarget, strDPEtarget ); + } + } + + DebugTN("========== transfer of embedded MPconfigs to all master DP's done"); + +} + + + +// Copies the master-datapoint configs of the given datapoint type 'strDPTsource' to the master-datapoint of the given type 'strDPTtarget' +void CopyEmbbeddedMasterDP( string strDPTsource, string strDPTtarget, string strDPTtargetElement ) +{ + int x, iError; + string strDPsource, strDPtarget, strDPEsource, strDPEtarget; + dyn_string dsDPEsSource; + dyn_string dsConfigs = makeDynString( "_distrib", "_address", "_archive", "_alert_hdl", + "_cmd_conv", "_msg_conv", + "_default", "_dp_fct", "_pv_range", + "_smooth", "_u_range", "_auth", + "_alert_class", "_original" ); + + + strDPsource = "_mp_" + strDPTsource; + strDPtarget = "_mp_" + strDPTtarget; + + strDPsource = dpSubStr( strDPsource, DPSUB_DP ); + strDPtarget = dpSubStr( strDPtarget, DPSUB_DP ); + + if( !dpExists( strDPsource ) ) + return; + + if( !dpExists( strDPtarget ) ) + return; + + dsDPEsSource = dpNames( strDPsource + ".**" ); + + if (bDebug) DebugN( "CopyEmbbeddedMasterDP(): Copy config from " + dynlen(dsDPEsSource) + " DPE's of '" + strDPsource + "' to '" + strDPtarget + "." + strDPTtargetElement +"'" ); + for( x=1; x<=dynlen(dsDPEsSource); x++ ) + { + strDPEsource = dpSubStr( dsDPEsSource[x], DPSUB_DP_EL ); + + strDPEtarget = strDPtarget + "." + strDPTtargetElement + strltrim( strDPEsource, strDPsource ); + + if (bDebug)DebugN( "Copy " + strDPEsource + " -> " + strDPEtarget ); + dpCopyConfig( strDPEsource, strDPEtarget, dsConfigs, iError); + } +} -- GitLab From be27d7207e2881825ec842495e2176abac5f7aa1 Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Fri, 14 Oct 2016 13:22:51 +0000 Subject: [PATCH 919/933] Task #8993: Fixed compilation errors related to assigning real/imaginary values. Changed standard compiler options from c++11 to gnu++11 (it provides -fext-numeric-literals implicitly, which used to be the default for all pre-C++11 dialects). --- CMake/variants/GNUCXX11.cmake | 2 +- RTCP/Cobalt/GPUProc/test/cuda/tBandPassCorrection.cc | 8 ++++---- RTCP/Cobalt/GPUProc/test/cuda/tDelayAndBandPass.cc | 8 ++++---- RTCP/Cobalt/GPUProc/test/cuda/tIntToFloat.cc | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CMake/variants/GNUCXX11.cmake b/CMake/variants/GNUCXX11.cmake index 66f112039ee..643198e9b76 100644 --- a/CMake/variants/GNUCXX11.cmake +++ b/CMake/variants/GNUCXX11.cmake @@ -21,7 +21,7 @@ set(GNUCXX11_C_FLAGS_DEBUG "-g") set(GNUCXX11_C_FLAGS_OPT "-g -O2") set(GNUCXX11_C_FLAGS_OPT3 "-g -O3") set(GNUCXX11_C_FLAGS_OPTARCH "-g -O3 -march=native") -set(GNUCXX11_CXX_FLAGS "-std=c++11 -fext-numeric-literals -W -Wall -Woverloaded-virtual -Wno-unknown-pragmas") +set(GNUCXX11_CXX_FLAGS "-std=gnu++11 -W -Wall -Woverloaded-virtual -Wno-unknown-pragmas") set(GNUCXX11_CXX_FLAGS_DEBUG "-g") set(GNUCXX11_CXX_FLAGS_OPT "-g -O2") set(GNUCXX11_CXX_FLAGS_OPT3 "-g -O3") diff --git a/RTCP/Cobalt/GPUProc/test/cuda/tBandPassCorrection.cc b/RTCP/Cobalt/GPUProc/test/cuda/tBandPassCorrection.cc index ab505a98f92..866a4e9cd16 100644 --- a/RTCP/Cobalt/GPUProc/test/cuda/tBandPassCorrection.cc +++ b/RTCP/Cobalt/GPUProc/test/cuda/tBandPassCorrection.cc @@ -166,8 +166,8 @@ vector<fcomplex> runTest(const CompileDefinitions& compileDefs) // set inputs for (size_t i = 0; i < inputData->num_elements(); i++) { - inputData->origin()[i].real() = 1.0f; - inputData->origin()[i].imag() = 1.0f; + inputData->origin()[i].real(1.0f); + inputData->origin()[i].imag(1.0f); } for (size_t i = 1; i < bandPassFactors.num_elements(); i++) { @@ -176,8 +176,8 @@ vector<fcomplex> runTest(const CompileDefinitions& compileDefs) // set output for proper verification later for (size_t i = 0; i < outputData->num_elements(); i++) { - outputData->origin()[i].real() = 42.0f; - outputData->origin()[i].imag() = 42.0f; + outputData->origin()[i].real(42.0f); + outputData->origin()[i].imag(42.0f); } gpu::Function kfunc(initKernel(ctx, compileDefs)); diff --git a/RTCP/Cobalt/GPUProc/test/cuda/tDelayAndBandPass.cc b/RTCP/Cobalt/GPUProc/test/cuda/tDelayAndBandPass.cc index 53bcee0c064..d41fd47db07 100644 --- a/RTCP/Cobalt/GPUProc/test/cuda/tDelayAndBandPass.cc +++ b/RTCP/Cobalt/GPUProc/test/cuda/tDelayAndBandPass.cc @@ -244,8 +244,8 @@ vector<fcomplex> runTest(const CompileDefinitions& compileDefs, // set inputs for (size_t i = 0; i < inputData->num_elements(); i++) { - inputData->origin()[i].real() = 1.0f; - inputData->origin()[i].imag() = 1.0f; + inputData->origin()[i].real(1.0f); + inputData->origin()[i].imag(1.0f); } for (size_t i = 0; i < delaysAtBegin.num_elements(); i++) { delaysAtBegin.origin()[i] = delayBegin; @@ -262,8 +262,8 @@ vector<fcomplex> runTest(const CompileDefinitions& compileDefs, // set output for proper verification later for (size_t i = 0; i < outputData->num_elements(); i++) { - outputData->origin()[i].real() = 42.0f; - outputData->origin()[i].imag() = 42.0f; + outputData->origin()[i].real(42.0f); + outputData->origin()[i].imag(42.0f); } gpu::Function kfunc(initKernel(ctx, compileDefs)); diff --git a/RTCP/Cobalt/GPUProc/test/cuda/tIntToFloat.cc b/RTCP/Cobalt/GPUProc/test/cuda/tIntToFloat.cc index baef5de12a7..fee1b377c05 100644 --- a/RTCP/Cobalt/GPUProc/test/cuda/tIntToFloat.cc +++ b/RTCP/Cobalt/GPUProc/test/cuda/tIntToFloat.cc @@ -126,8 +126,8 @@ vector<complex<float> > runTest(int defaultVal) // set input for (size_t i = 0; i < input.num_elements(); i++) { - input.origin()[i].real() = defaultVal; - input.origin()[i].imag() = defaultVal; + input.origin()[i].real(defaultVal); + input.origin()[i].imag(defaultVal); } // set output for proper verification later @@ -157,13 +157,13 @@ vector<complex<float> > runTest(int defaultVal) template <typename T> void setSample(T& sample, int& val) { - sample.real() = val; + sample.real(val); if (val == numeric_limits<typename T::value_type>::max()) val = numeric_limits<typename T::value_type>::min(); // wrap back to minimum else val += 1; - sample.imag() = val; + sample.imag(val); if (val == numeric_limits<typename T::value_type>::max()) val = numeric_limits<typename T::value_type>::min(); // wrap back to minimum else -- GitLab From ae9f0cea9b6c2828fc32c149a2576ad8e105cbf6 Mon Sep 17 00:00:00 2001 From: Marcel Loose <loose@astron.nl> Date: Fri, 14 Oct 2016 13:35:10 +0000 Subject: [PATCH 920/933] Task #8993: Accidentally reverted conflicting changes to Docker directory when merging the trunk (commit r35701), whilst the trunk version should have been adopted. --- Docker/lofar-base/Dockerfile.tmpl | 20 +++++++------------- Docker/lofar-outputproc/Dockerfile.tmpl | 10 +++++----- Docker/lofar-pipeline/Dockerfile.tmpl | 9 ++++----- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/Docker/lofar-base/Dockerfile.tmpl b/Docker/lofar-base/Dockerfile.tmpl index 77228e242bc..a5679fe25f5 100644 --- a/Docker/lofar-base/Dockerfile.tmpl +++ b/Docker/lofar-base/Dockerfile.tmpl @@ -6,7 +6,6 @@ FROM ubuntu:16.04 # # common-environment # -ENV USER=lofar ENV INSTALLDIR=/opt # @@ -23,11 +22,6 @@ ENV CASACORE_VERSION=latest \ PYTHON_CASACORE_VERSION=2.1.2 \ BOOST_VERSION=1.58 -# -# set-uid -# -ENV UID=${BUILD_UID} - # # set-build-options # @@ -43,8 +37,8 @@ RUN apt-get update && \ apt-get install -y python-pip && \ pip install numpy && \ apt-get purge -y python-pip && \ - apt-get autoremove -y && \ - apt-get install -y nano + apt-get autoremove -y --purge && \ + apt-get install -y nano sudo # # open security holes (allow smooth user switching, allow sudo) @@ -76,7 +70,7 @@ RUN apt-get update && apt-get install -y wget git cmake g++ gfortran flex bison bash -c "strip ${INSTALLDIR}/casacore/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casacore/{build,src}" && \ apt-get purge -y wget git cmake g++ gfortran flex bison libopenblas-dev libfftw3-dev libhdf5-dev libboost-python-dev libcfitsio-dev wcslib-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge # # ******************* @@ -93,7 +87,7 @@ RUN apt-get update && apt-get install -y git cmake g++ gfortran libboost-system- bash -c "strip ${INSTALLDIR}/casarest/{lib,bin}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/casarest/{build,src}" && \ apt-get purge -y git cmake g++ gfortran libboost-system-dev libboost-thread-dev libhdf5-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge # # ******************* @@ -111,7 +105,7 @@ RUN apt-get update && apt-get install -y git make g++ python-setuptools libboost bash -c "find ${INSTALLDIR}/python-casacore/lib -name '*.so' | xargs strip || true" && \ bash -c "rm -rf ${INSTALLDIR}/python-casacore/python-casacore" && \ apt-get purge -y git make g++ python-setuptools libboost-python-dev libcfitsio3-dev wcslib-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge # # ******************* @@ -132,7 +126,7 @@ RUN apt-get update && apt-get install -y subversion swig ruby ruby-dev python-de bash -c "strip /opt/qpid/{bin,lib}/* || true" && \ bash -c "rm -rf ~/sources" && \ apt-get purge -y subversion swig ruby ruby-dev python-dev libsasl2-dev pkg-config cmake libtool uuid-dev libxerces-c-dev libnss3-dev libnspr4-dev help2man fakeroot build-essential debhelper libsslcommon2-dev libxqilla-dev python-setuptools libboost-program-options${BOOST_VERSION}-dev libboost-filesystem${BOOST_VERSION}-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge # # ******************* # DAL @@ -147,7 +141,7 @@ RUN apt-get update && apt-get install -y git cmake g++ swig python-dev libhdf5-d bash -c "find ${INSTALLDIR}/DAL/lib -name '*.so' | xargs strip || true" && \ bash -c "rm -rf ${INSTALLDIR}/DAL/{src,build}" && \ apt-get purge -y git cmake g++ swig python-dev libhdf5-dev && \ - apt-get autoremove -y + apt-get autoremove -y --purge # # entry diff --git a/Docker/lofar-outputproc/Dockerfile.tmpl b/Docker/lofar-outputproc/Dockerfile.tmpl index c25fffe979a..818e940c861 100644 --- a/Docker/lofar-outputproc/Dockerfile.tmpl +++ b/Docker/lofar-outputproc/Dockerfile.tmpl @@ -10,7 +10,7 @@ FROM lofar-base:${LOFAR_TAG} # # Run-time dependencies -RUN apt-get update && apt-get install -y liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libboost-regex${BOOST_VERSION}.0 +RUN apt-get update && apt-get install -y binutils liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libboost-regex${BOOST_VERSION}.0 # Tell image build information ENV LOFAR_BRANCH=${LOFAR_BRANCH} \ @@ -18,7 +18,7 @@ ENV LOFAR_BRANCH=${LOFAR_BRANCH} \ LOFAR_BUILDVARIANT=gnucxx11_optarch # Install -RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libopenblas-dev && libcfitsio3-dev wcslib-dev \ +RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libboost-regex${BOOST_VERSION} binutils-dev libopenblas-dev libcfitsio3-dev wcslib-dev && \ mkdir -p ${INSTALLDIR}/lofar/build/${LOFAR_BUILDVARIANT} && \ cd ${INSTALLDIR}/lofar && \ svn --non-interactive -q co -r ${LOFAR_REVISION} -N ${LOFAR_BRANCH_URL} src; \ @@ -31,7 +31,7 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "ln -sfT /home/${USER}/lofar/var ${INSTALLDIR}/lofar/var" && \ bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ - setcap cap_sys_nice,cap_ipc_lock=ep ${INSTALLDIR}/lofar/bin/outputProc && \ - apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && libcfitsio3-dev wcslib-dev \ - apt-get autoremove -y + setcap cap_sys_nice,cap_sys_admin=ep ${INSTALLDIR}/lofar/bin/outputProc && \ + apt-get purge -y subversion cmake g++ gfortran bison flex autogen liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python${BOOST_VERSION}-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ + apt-get autoremove -y --purge diff --git a/Docker/lofar-pipeline/Dockerfile.tmpl b/Docker/lofar-pipeline/Dockerfile.tmpl index 94c6299eeff..cd2c9a16f53 100644 --- a/Docker/lofar-pipeline/Dockerfile.tmpl +++ b/Docker/lofar-pipeline/Dockerfile.tmpl @@ -6,11 +6,11 @@ FROM lofar-base:${LOFAR_TAG} ENV AOFLAGGER_VERSION=2.8.0 # Run-time dependencies -RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib && \ +RUN apt-get update && apt-get install -y python-xmlrunner python-scipy liblog4cplus-1.1-9 libxml2 libboost-thread${BOOST_VERSION}.0 libboost-filesystem${BOOST_VERSION}.0 libboost-date-time${BOOST_VERSION}.0 libboost-signals${BOOST_VERSION}.0 libpng12-0 libsigc++-2.0-dev libxml++2.6-2v5 libgsl2 openssh-client libboost-regex${BOOST_VERSION}.0 gettext-base rsync python-matplotlib ipython slurm-client && \ apt-get -y install python-pip python-dev && \ pip install pyfits pywcs python-monetdb && \ apt-get -y purge python-pip python-dev && \ - apt-get -y autoremove + apt-get -y autoremove --purge # # ******************* @@ -29,7 +29,7 @@ RUN apt-get update && apt-get install -y wget cmake g++ libxml++2.6-dev libpng12 bash -c "rm -rf ${INSTALLDIR}/aoflagger/{build,aoflagger-${AOFLAGGER_VERSION}}" && \ bash -c "rm -rf ${INSTALLDIR}/aoflagger/aoflagger-${AOFLAGGER_VERSION}.tar.bz2" && \ apt-get -y purge wget cmake g++ libxml++2.6-dev libpng12-dev libfftw3-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-signals${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev libcfitsio3-dev libopenblas-dev && \ - apt-get -y autoremove + apt-get -y autoremove --purge # # ******************* @@ -58,5 +58,4 @@ RUN apt-get update && apt-get install -y subversion cmake g++ gfortran bison fle bash -c "strip ${INSTALLDIR}/lofar/{bin,sbin,lib64}/* || true" && \ bash -c "rm -rf ${INSTALLDIR}/lofar/{build,src}" && \ apt-get purge -y subversion cmake g++ gfortran bison flex liblog4cplus-dev libhdf5-dev libblitz0-dev libboost-dev libboost-python-dev python-dev libxml2-dev pkg-config libpng12-dev libfftw3-dev libunittest++-dev libxml++2.6-dev libgsl-dev libboost-filesystem${BOOST_VERSION}-dev libboost-date-time${BOOST_VERSION}-dev libboost-thread${BOOST_VERSION}-dev binutils-dev libcfitsio3-dev wcslib-dev libopenblas-dev && \ - apt-get autoremove -y - + apt-get autoremove -y --purge -- GitLab From ffdc76a32af1f9894c73c601cc4f1a3b5748fbc3 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Fri, 14 Oct 2016 13:56:19 +0000 Subject: [PATCH 921/933] Task #8693: update INSTALL: refer to Gijs Molenaar's KERN repo --- INSTALL | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/INSTALL b/INSTALL index 90de95b38ac..5cfd1ae8eec 100644 --- a/INSTALL +++ b/INSTALL @@ -14,9 +14,11 @@ Supported Systems Install from Ubuntu Packages ---------------------------- -To install LOFAR Offline processing tools the easy way for a specific Ubuntu version, see: +To install LOFAR Offline processing tools (and many more radio astro packages) +the easy way for Ubuntu LTS version(s), see: - https://launchpad.net/~radio-astro/+archive/ubuntu/main + KERN: The Radio Astronomy Software Suite + http://kernsuite.info/ Install using Docker Scripts @@ -26,6 +28,10 @@ To create a Docker container with Ubuntu or CentOS and LOFAR Offline processing https://github.com/lofar-astron/lofar-deploy +or, again for Ubuntu LTS: + + http://kernsuite.info/ + Dependencies for Manual Build from Source ----------------------------------------- -- GitLab From b6df826be745d5f46e3e66b0c1375b22783b0ce9 Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 14 Oct 2016 15:28:06 +0000 Subject: [PATCH 922/933] Task #10003: fix string replacements in subpipelines (by Andreas) --- CEP/Pipeline/recipes/sip/bin/genericpipeline.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/CEP/Pipeline/recipes/sip/bin/genericpipeline.py b/CEP/Pipeline/recipes/sip/bin/genericpipeline.py index feca430e942..2528dcadcc4 100755 --- a/CEP/Pipeline/recipes/sip/bin/genericpipeline.py +++ b/CEP/Pipeline/recipes/sip/bin/genericpipeline.py @@ -287,17 +287,20 @@ class GenericPipeline(control): val = subpipeline_parset[k] if not str(k).startswith('!') and not str(k).startswith('pipeline.replace.'): for item in checklist: - if item in str(val): + if item+".output" in str(val): val = str(val).replace(item, stepname + '-' + item) self.parset.add(stepname + '-' + k, str(val)) else: + # remove replacements strings to prevent loading the same key twice + if k in self._keys(self.parset): + self.parset.remove(k) self.parset.add(k, str(val)) for i, item in enumerate(subpipeline_steplist): subpipeline_steplist[i] = stepname + '-' + item for item in step_parset_obj[stepname].keys(): for k in self._keys(self.parset): - if str(k).startswith('!') and item in k or str(k).startswith('pipeline.replace.') and item in k: + if str(k).startswith('!') and item == str(k).strip("! ") or str(k).startswith('pipeline.replace.') and item == str(k)[17:].strip(): self.parset.remove(k) self.parset.add('! ' + item, str(step_parset_obj[stepname][item])) self._replace_values() @@ -308,10 +311,6 @@ class GenericPipeline(control): step_control_dict[name] = step_control_dict[j] step_name_list.insert(0, name) - # remove replacements strings to prevent loading the same key twice - for k in copy.deepcopy(self.parset.keywords()): - if str(k).startswith('!'): - self.parset.remove(k) # loop if kind_of_step == 'loop': -- GitLab From d8be1a0bb6c6b409a5b025b28ceb9d64e7f18fa6 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 18 Oct 2016 12:58:27 +0000 Subject: [PATCH 923/933] Task #10013: Added scripts to ping international stations to lofarsys crontab --- .gitattributes | 2 ++ RTCP/Cobalt/InputProc/src/CMakeLists.txt | 1 + RTCP/Cobalt/InputProc/src/ping_intl.sh | 32 +++++++++++++++++++ .../Online_Cobalt/install/lofarsys/crontab | 3 ++ .../install/postinstall_lofarsys.sh | 6 ++++ 5 files changed, 44 insertions(+) create mode 100755 RTCP/Cobalt/InputProc/src/ping_intl.sh create mode 100644 SubSystems/Online_Cobalt/install/lofarsys/crontab diff --git a/.gitattributes b/.gitattributes index 9b1a2c2b47a..b774469a1dc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4640,6 +4640,7 @@ RTCP/Cobalt/GPUProc/test/t_generate_globalfs_locations.in_parset -text RTCP/Cobalt/GPUProc/test/t_generate_globalfs_locations.sh eol=lf RTCP/Cobalt/InputProc/doc/Cobalt-New-InputSection.jpg -text svneol=unset#image/jpeg RTCP/Cobalt/InputProc/src/Delays/printDelays.log_prop -text +RTCP/Cobalt/InputProc/src/ping_intl.sh -text RTCP/Cobalt/InputProc/test/tMPI.run eol=lf RTCP/Cobalt/InputProc/test/tMPI.sh eol=lf RTCP/Cobalt/InputProc/test/tMPIUtil2.run eol=lf @@ -5543,6 +5544,7 @@ SubSystems/Online_Cobalt/install/install_casacore.sh eol=lf SubSystems/Online_Cobalt/install/install_qpid.sh eol=lf SubSystems/Online_Cobalt/install/lofarsys/bash_profile -text SubSystems/Online_Cobalt/install/lofarsys/bashrc -text +SubSystems/Online_Cobalt/install/lofarsys/crontab -text SubSystems/Online_Cobalt/install/postinstall.sh eol=lf SubSystems/Online_Cobalt/install/postinstall_lofarbuild.sh eol=lf SubSystems/Online_Cobalt/install/postinstall_lofarsys.sh eol=lf diff --git a/RTCP/Cobalt/InputProc/src/CMakeLists.txt b/RTCP/Cobalt/InputProc/src/CMakeLists.txt index bcbe83a534e..ff6c208a3ad 100644 --- a/RTCP/Cobalt/InputProc/src/CMakeLists.txt +++ b/RTCP/Cobalt/InputProc/src/CMakeLists.txt @@ -49,3 +49,4 @@ configure_file( ${CMAKE_BINARY_DIR}/bin/mpirun.sh @ONLY) lofar_add_bin_scripts(${CMAKE_BINARY_DIR}/bin/mpirun.sh) +lofar_add_bin_scripts(ping_intl.sh) diff --git a/RTCP/Cobalt/InputProc/src/ping_intl.sh b/RTCP/Cobalt/InputProc/src/ping_intl.sh new file mode 100755 index 00000000000..7634f03c8b4 --- /dev/null +++ b/RTCP/Cobalt/InputProc/src/ping_intl.sh @@ -0,0 +1,32 @@ +#!/bin/bash -u +echo "Begin `date`" +echo "Host `hostname`" + +HOSTNAME=`hostname` +RSPCONNECTIONS=$LOFARROOT/etc/StaticMetaData/RSPConnections_Cobalt.dat + +# International stations have the name xx6xx. +# +# For each International Station VLAN, Cobalt has IP 10.x.x.50, +# and we need to ping 10.x.x.1. +SRCIPS=` + <$RSPCONNECTIONS fgrep $HOSTNAME | + grep -v "^#" | + perl -ne ' + if (/^..60?(..?) .* 10[.]([0-9]+)[.]([0-9]+)[.]1../) { + if ($3 == 1) { + print "10.$2.$1.1\n" + } else { + print "10.$2.$3.1\n" + } + } ' + +echo "IPs $SRCIPS" + +for IP in $SRCIPS +do + # Ping this RSP board, but don't wait for an answer + ping -p 10fa -q -n -c 1 -w 1 $IP +done + +echo "End `date`" diff --git a/SubSystems/Online_Cobalt/install/lofarsys/crontab b/SubSystems/Online_Cobalt/install/lofarsys/crontab new file mode 100644 index 00000000000..6bfa90d6e70 --- /dev/null +++ b/SubSystems/Online_Cobalt/install/lofarsys/crontab @@ -0,0 +1,3 @@ +# m h dom mon dow command +* * * * * source /opt/lofar/lofarinit.sh && ping_intl.sh >> $HOME/ping_intl.`hostname`.log 2>&1 + diff --git a/SubSystems/Online_Cobalt/install/postinstall_lofarsys.sh b/SubSystems/Online_Cobalt/install/postinstall_lofarsys.sh index c91db6aeb72..21f9e857155 100755 --- a/SubSystems/Online_Cobalt/install/postinstall_lofarsys.sh +++ b/SubSystems/Online_Cobalt/install/postinstall_lofarsys.sh @@ -31,3 +31,9 @@ ssh localhost true # ******************************************** echo "Configuring /opt/lofar/var..." mkdir -p ~lofarsys/lofar/var/{log,run} + +# ******************************************** +# Schedule periodic tasks +# ******************************************** +echo "Configuring crontab..." +crontab lofarsys/crontab -- GitLab From 089fa9fd4677ca0082c21cb1e948d5ee9c641f9e Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Mon, 24 Oct 2016 09:44:02 +0000 Subject: [PATCH 924/933] Task #9924: Replacing host name in license file for CS302 to CS302C --- MAC/Deployment/data/PVSS/License/CS302N_option.txt | 2 +- MAC/Deployment/data/PVSS/License/shield.CS302N.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MAC/Deployment/data/PVSS/License/CS302N_option.txt b/MAC/Deployment/data/PVSS/License/CS302N_option.txt index 1da874f8837..2cb576524e5 100644 --- a/MAC/Deployment/data/PVSS/License/CS302N_option.txt +++ b/MAC/Deployment/data/PVSS/License/CS302N_option.txt @@ -1,5 +1,5 @@ [license] -code = "CS302N.control.lofar 32748977553" +code = "CS302C.control.lofar 32748977553" version = 31400002 comment = "Core Station CS302N" sn = "471_3031_2_Astron_Gen_II_1_38" diff --git a/MAC/Deployment/data/PVSS/License/shield.CS302N.txt b/MAC/Deployment/data/PVSS/License/shield.CS302N.txt index dea6224460a..a8b434ad8d8 100644 --- a/MAC/Deployment/data/PVSS/License/shield.CS302N.txt +++ b/MAC/Deployment/data/PVSS/License/shield.CS302N.txt @@ -1,5 +1,5 @@ [license] -code = "CS302N.control.lofar 20356783426" +code = "CS302C.control.lofar 20356783426" version = 31400002 sn = "471_3031_2_Astron_Gen_II_3_314/2" date = 2016.09.29;16:32:35,000 -- GitLab From 5f107a1d5ed8b67485f4128849f8bfdba4ba9c5a Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Mon, 24 Oct 2016 12:19:01 +0000 Subject: [PATCH 925/933] Task #6553: readStationConfigs need to handle new antennaConfig file layout --- MAC/Navigator2/scripts/readStationConfigs.ctl | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/MAC/Navigator2/scripts/readStationConfigs.ctl b/MAC/Navigator2/scripts/readStationConfigs.ctl index 5b11a8801c6..5cf1a84512c 100644 --- a/MAC/Navigator2/scripts/readStationConfigs.ctl +++ b/MAC/Navigator2/scripts/readStationConfigs.ctl @@ -105,8 +105,8 @@ main() continue; } - if (bDebug) DebugN("working on: ",dynStr_fileContent[index-1]); - if (bDebug) DebugN("str1: ",str1," str2: ",str2); +// if (bDebug) DebugN("working on: ",dynStr_fileContent[index-1]); +// if (bDebug) DebugN("str1: ",str1," str2: ",str2); if (strtoupper(str1) == "NORMAL_VECTOR" ) { if (strtoupper(str2) != "LBA" && strtoupper(str2) != "HBA0" && strtoupper(str2) != "HBA1" && strtoupper(str2) != "HBA") { @@ -359,7 +359,9 @@ dyn_string lto_getFile_asDynStr(string aFileName) { void processNormalVector(string aS) { float fX=0,fY=0,fZ=0; - sscanf(dynStr_fileContent[index++],"%*d %*s %lf %lf %lf",fX,fY,fZ); + int dummy, lenx, leny; + + sscanf(dynStr_fileContent[index++],"(%d,%d) x (%d,%d) %*s %lf %lf %lf",dummy,lenx,dummy,leny,fX,fY,fZ); if (bDebug) DebugN("Reading NORMAL_VECTOR "+aS+" X,Y,Z :" + fX + " " + fY + " " + fZ); if (aS == "LBA" ) { norVecLBAFound=true; @@ -386,9 +388,12 @@ void processNormalVector(string aS) { void processRotationMatrix(string aS) { dyn_float fX,fY,fZ; - int nr_rows=0; + int dummy,nr_rows=0; + + // read nr of rows - sscanf(dynStr_fileContent[index++],"%d",nr_rows); + sscanf(dynStr_fileContent[index++],"(%d,%d)",dummy,nr_rows); + nr_rows+=1; if (bDebug) DebugN("index: "+(index-1)+" nr_rows: "+nr_rows); for (int i = 1; i <= nr_rows; i++) { sscanf(dynStr_fileContent[index++],"%lf %lf %lf",fX[i],fY[i],fZ[i]); @@ -420,7 +425,8 @@ void processRotationMatrix(string aS) { void processFieldCenter(string aS) { float fX=0,fY=0,fZ=0; - sscanf(dynStr_fileContent[index++],"%*d %*s %lf %lf %lf",fX,fY,fZ); + int dummy; + sscanf(dynStr_fileContent[index++],"(%d,%d) %*s %f %f %f",dummy, dummy, fX, fY, fZ); if (bDebug) DebugN("Reading fieldcenter "+aS+"X,Y,Z:" + fX + " " + fY + " " + fZ); if (aS== "LBA") { centerLBAFound=true; @@ -453,11 +459,12 @@ void processFieldDeltas(string aS) { dyn_float antConfFileX; dyn_float antConfFileY; dyn_float antConfFileZ; + int dummy; - // read nr of antennas - sscanf(dynStr_fileContent[index++],"%d",nr_ofAnt); - + sscanf(dynStr_fileContent[index++],"(%d,%d)",dummy, nr_ofAnt); + nr_ofAnt+=1; + if (aS== "LBA") { deltasLBAFound=true; nr_LBA=nr_ofAnt; @@ -515,8 +522,7 @@ void calcRotated(string aS) { float centerX,centerY,centerZ; float X,Y,Z; float x1=0,x2=0,y1=0,y2=0,x3=0,x4=0,y3=0,y4=0; - - + if (aS=="LBA") { dpGet("LOFAR_PIC_StationInfo.LBA.RotationMatrix.X",rotX,"LOFAR_PIC_StationInfo.LBA.RotationMatrix.Y",rotY,"LOFAR_PIC_StationInfo.LBA.RotationMatrix.Z",rotZ, "LOFAR_PIC_StationInfo.LBA.centerX",centerX,"LOFAR_PIC_StationInfo.LBA.centerY",centerY,"LOFAR_PIC_StationInfo.LBA.centerZ",centerZ); -- GitLab From 9ab66d2aa8879654c9b10894b98335ecab09a7f0 Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Tue, 25 Oct 2016 11:32:41 +0000 Subject: [PATCH 926/933] Task #6553: typo in configfile, removed error popup, readStationcontrol for Blitz 0.10 --- .gitattributes | 3 +- ... .navigator.3.10 => config.navigator.3.10} | 0 MAC/Navigator2/config/progs.navigator_3.14 | 6 -- .../observationFlow_cobaltNodeProcesses.pnl | 3 - MAC/Navigator2/scripts/readStationConfigs.ctl | 60 +++++++++++++------ 5 files changed, 44 insertions(+), 28 deletions(-) rename MAC/Navigator2/config/{config .navigator.3.10 => config.navigator.3.10} (100%) delete mode 100644 MAC/Navigator2/config/progs.navigator_3.14 diff --git a/.gitattributes b/.gitattributes index b774469a1dc..ed5fdc651f8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3879,7 +3879,6 @@ MAC/Navigator2/bin/linux-64/WebView.ewo -text MAC/Navigator2/bin/windows-64/WebView.ewo -text MAC/Navigator2/bin/windows/WebView.ewo -text MAC/Navigator2/colorDB/Lofar[!!-~]colors -text -MAC/Navigator2/config/config[!!-~].navigator.3.10 -text MAC/Navigator2/config/config.ccu -text MAC/Navigator2/config/config.dist.station -text MAC/Navigator2/config/config.dist_test.station -text @@ -3888,6 +3887,7 @@ MAC/Navigator2/config/config.level.ccu -text MAC/Navigator2/config/config.level.maincu -text MAC/Navigator2/config/config.level.station -text MAC/Navigator2/config/config.maincu -text +MAC/Navigator2/config/config.navigator.3.10 -text MAC/Navigator2/config/config.navigator.3.14 -text MAC/Navigator2/config/config.sas099 -text MAC/Navigator2/config/config.smu002 -text @@ -3896,7 +3896,6 @@ MAC/Navigator2/config/progs.ccu -text MAC/Navigator2/config/progs.dist.station -text MAC/Navigator2/config/progs.maincu -text MAC/Navigator2/config/progs.navigator -text -MAC/Navigator2/config/progs.navigator_3.14 -text MAC/Navigator2/config/progs.standalone.station -text MAC/Navigator2/data/PVSS[!!-~]performance[!!-~]results.xls -text MAC/Navigator2/dplist/maincu_system.dpl -text diff --git a/MAC/Navigator2/config/config .navigator.3.10 b/MAC/Navigator2/config/config.navigator.3.10 similarity index 100% rename from MAC/Navigator2/config/config .navigator.3.10 rename to MAC/Navigator2/config/config.navigator.3.10 diff --git a/MAC/Navigator2/config/progs.navigator_3.14 b/MAC/Navigator2/config/progs.navigator_3.14 deleted file mode 100644 index a14d7216309..00000000000 --- a/MAC/Navigator2/config/progs.navigator_3.14 +++ /dev/null @@ -1,6 +0,0 @@ -version 1 - -auth "" "" -#Manager | Start | SecKill | Restart# | ResetMin | Options -WCCILpmon | manual | 30 | 3 | 1 | -WCCOAui | once | 30 | 3 | 1 | -p vision/login.pnl diff --git a/MAC/Navigator2/panels/objects/Processes/observationFlow_cobaltNodeProcesses.pnl b/MAC/Navigator2/panels/objects/Processes/observationFlow_cobaltNodeProcesses.pnl index 5f50303641c..6620462b792 100644 --- a/MAC/Navigator2/panels/objects/Processes/observationFlow_cobaltNodeProcesses.pnl +++ b/MAC/Navigator2/panels/objects/Processes/observationFlow_cobaltNodeProcesses.pnl @@ -169,9 +169,6 @@ void connectCobaltNodesAndProcesses(string runState) { dpConnect(\"cobaltNodesAndProcessesCB\",TRUE,stateDPs); dyn_errClass err = getLastError(); //test whether an error occurred if(dynlen(err) > 0) { - errorDialog(err); - // open dialog box with errors - throwError(err); // write errors to stderr LOG_ERROR(\"ObservationFlow_cobaltNodeProcesses.pnl:connectCobaltNodesAndProcesses| ERROR: connect fails:\"+ stateDPs); } else { connectedStates = true; diff --git a/MAC/Navigator2/scripts/readStationConfigs.ctl b/MAC/Navigator2/scripts/readStationConfigs.ctl index 5cf1a84512c..0187896ccd4 100644 --- a/MAC/Navigator2/scripts/readStationConfigs.ctl +++ b/MAC/Navigator2/scripts/readStationConfigs.ctl @@ -39,8 +39,13 @@ * and Antenna positions in OL-NB-Height Offsets from the fieldCenter. * As FieldCenter now the GPS coordinates are taken, this is not correct * + * With the new LCU's we need to use the new Blitz 0.10 version that has a change in array use. + * So for now a Blitz010 bool is used to check if it is the new Blitz or not. + * The files for the new Blitz version are indicated with the line : + * + * # Blitz010 formatted + * * For future compatibility we have to consider other earth coordinates also - * RemoteStation.conf * This will fill the RemoteStation point with all data available for this station. * @@ -66,6 +71,7 @@ global bool deltasHBAFound=false; global bool centerHBA0Found=false; global bool centerHBA1Found=false; + global bool Blitz010=false; main() @@ -85,7 +91,6 @@ main() return; } -// string strAntArrayConfFile = strDataDir+"AntennaArrays.conf"; string strAntArrayConfFile = strDataDir+"AntennaField.conf"; string strRemoteStationConfFile = strDataDir+"RemoteStation.conf"; @@ -102,11 +107,14 @@ main() string str1="",str2=""; sscanf(dynStr_fileContent[index++],"%s %s",str1,str2); if (strlen(str1) <= 0 || str1[0] == " " || str1[0] == "#") { + if (strpos(str1,"Blitz-0.10 formatted")) { + Blitz010=true; + } continue; } -// if (bDebug) DebugN("working on: ",dynStr_fileContent[index-1]); -// if (bDebug) DebugN("str1: ",str1," str2: ",str2); + if (bDebug) DebugN("working on: ",dynStr_fileContent[index-1]); + if (bDebug) DebugN("str1: ",str1," str2: ",str2); if (strtoupper(str1) == "NORMAL_VECTOR" ) { if (strtoupper(str2) != "LBA" && strtoupper(str2) != "HBA0" && strtoupper(str2) != "HBA1" && strtoupper(str2) != "HBA") { @@ -359,9 +367,13 @@ dyn_string lto_getFile_asDynStr(string aFileName) { void processNormalVector(string aS) { float fX=0,fY=0,fZ=0; - int dummy, lenx, leny; + if (Blitz010) { + int dummy, lenx, leny; + sscanf(dynStr_fileContent[index++],"(%d,%d) x (%d,%d) %*s %lf %lf %lf",dummy,lenx,dummy,leny,fX,fY,fZ); + } else { + sscanf(dynStr_fileContent[index++],"%*d %*s %lf %lf %lf",fX,fY,fZ); + } - sscanf(dynStr_fileContent[index++],"(%d,%d) x (%d,%d) %*s %lf %lf %lf",dummy,lenx,dummy,leny,fX,fY,fZ); if (bDebug) DebugN("Reading NORMAL_VECTOR "+aS+" X,Y,Z :" + fX + " " + fY + " " + fZ); if (aS == "LBA" ) { norVecLBAFound=true; @@ -388,12 +400,16 @@ void processNormalVector(string aS) { void processRotationMatrix(string aS) { dyn_float fX,fY,fZ; - int dummy,nr_rows=0; + int nr_rows=0; - - // read nr of rows - sscanf(dynStr_fileContent[index++],"(%d,%d)",dummy,nr_rows); - nr_rows+=1; + if (Blitz010) { + int dummy; + // read nr of rows + sscanf(dynStr_fileContent[index++],"(%d,%d)",dummy,nr_rows); + nr_rows+=1; + } else { + sscanf(dynStr_fileContent[index++],"%d",nr_rows); + } if (bDebug) DebugN("index: "+(index-1)+" nr_rows: "+nr_rows); for (int i = 1; i <= nr_rows; i++) { sscanf(dynStr_fileContent[index++],"%lf %lf %lf",fX[i],fY[i],fZ[i]); @@ -425,8 +441,12 @@ void processRotationMatrix(string aS) { void processFieldCenter(string aS) { float fX=0,fY=0,fZ=0; - int dummy; - sscanf(dynStr_fileContent[index++],"(%d,%d) %*s %f %f %f",dummy, dummy, fX, fY, fZ); + if (Blitz010) { + int dummy; + sscanf(dynStr_fileContent[index++],"(%d,%d) %*s %f %f %f",dummy, dummy, fX, fY, fZ); + } else { + sscanf(dynStr_fileContent[index++],"%*d %*s %lf %lf %lf",fX,fY,fZ); + } if (bDebug) DebugN("Reading fieldcenter "+aS+"X,Y,Z:" + fX + " " + fY + " " + fZ); if (aS== "LBA") { centerLBAFound=true; @@ -459,11 +479,17 @@ void processFieldDeltas(string aS) { dyn_float antConfFileX; dyn_float antConfFileY; dyn_float antConfFileZ; - int dummy; - // read nr of antennas - sscanf(dynStr_fileContent[index++],"(%d,%d)",dummy, nr_ofAnt); - nr_ofAnt+=1; + if (Blitz010) { + int dummy; + + // read nr of antennas + sscanf(dynStr_fileContent[index++],"(%d,%d)",dummy, nr_ofAnt); + nr_ofAnt+=1; + } else { + // read nr of antennas + sscanf(dynStr_fileContent[index++],"%d",nr_ofAnt); + } if (aS== "LBA") { deltasLBAFound=true; -- GitLab From 0ffffb9a1956888d1d02ecb981f6ec72057050fb Mon Sep 17 00:00:00 2001 From: Arthur Coolen <coolen@astron.nl> Date: Tue, 25 Oct 2016 15:03:23 +0000 Subject: [PATCH 927/933] Task #6553:readStationConfigs works on both old as Blitz 0.1 files --- MAC/Navigator2/scripts/readStationConfigs.ctl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/MAC/Navigator2/scripts/readStationConfigs.ctl b/MAC/Navigator2/scripts/readStationConfigs.ctl index 0187896ccd4..38d70a65fd9 100644 --- a/MAC/Navigator2/scripts/readStationConfigs.ctl +++ b/MAC/Navigator2/scripts/readStationConfigs.ctl @@ -107,7 +107,8 @@ main() string str1="",str2=""; sscanf(dynStr_fileContent[index++],"%s %s",str1,str2); if (strlen(str1) <= 0 || str1[0] == " " || str1[0] == "#") { - if (strpos(str1,"Blitz-0.10 formatted")) { + if (strpos(dynStr_fileContent[index-1],"Blitz-0.10 formatted") >= 0) { + DebugN("Blitz: ", str2); Blitz010=true; } continue; @@ -408,7 +409,7 @@ void processRotationMatrix(string aS) { sscanf(dynStr_fileContent[index++],"(%d,%d)",dummy,nr_rows); nr_rows+=1; } else { - sscanf(dynStr_fileContent[index++],"%d",nr_rows); + sscanf(dynStr_fileContent[index++],"%d",nr_rows); } if (bDebug) DebugN("index: "+(index-1)+" nr_rows: "+nr_rows); for (int i = 1; i <= nr_rows; i++) { @@ -440,6 +441,7 @@ void processRotationMatrix(string aS) { } void processFieldCenter(string aS) { + float fX=0,fY=0,fZ=0; if (Blitz010) { int dummy; @@ -472,6 +474,7 @@ void processFieldCenter(string aS) { } void processFieldDeltas(string aS) { + int nr_ofAnt = 0; float deltaX; float deltaY; -- GitLab From e9b7548123004caf4999ea93c5b4698d89a05218 Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Wed, 26 Oct 2016 22:12:02 +0000 Subject: [PATCH 928/933] Task #8691: fix typo in comment --- RTCP/Cobalt/OutputProc/src/MSWriter.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RTCP/Cobalt/OutputProc/src/MSWriter.cc b/RTCP/Cobalt/OutputProc/src/MSWriter.cc index 1c354398df6..8880d7a15fb 100644 --- a/RTCP/Cobalt/OutputProc/src/MSWriter.cc +++ b/RTCP/Cobalt/OutputProc/src/MSWriter.cc @@ -1,4 +1,4 @@ -//# MSMriter.cc: Base classs for MS writer +//# MSMriter.cc: Base class for MS writer //# Copyright (C) 2008-2013 ASTRON (Netherlands Institute for Radio Astronomy) //# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands //# -- GitLab From 1d7a891189985e90169d56e22a83f6f66d9c307b Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Wed, 26 Oct 2016 22:30:38 +0000 Subject: [PATCH 929/933] Task #8691: move IOPriority.h to LCS/Common. Maybe we should de-inline funcs into a .cc, but just make it avail for APERTIF for now. Build tested. --- LCS/Common/include/Common/CMakeLists.txt | 1 + .../src => LCS/Common/include/Common}/IOPriority.h | 14 ++++++++++---- RTCP/Cobalt/OutputProc/src/GPUProcIO.cc | 2 +- RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc | 8 ++++---- 4 files changed, 16 insertions(+), 9 deletions(-) rename {RTCP/Cobalt/OutputProc/src => LCS/Common/include/Common}/IOPriority.h (94%) diff --git a/LCS/Common/include/Common/CMakeLists.txt b/LCS/Common/include/Common/CMakeLists.txt index a0843f5c949..90b424ba9c9 100644 --- a/LCS/Common/include/Common/CMakeLists.txt +++ b/LCS/Common/include/Common/CMakeLists.txt @@ -24,6 +24,7 @@ install(FILES FileLocator.h hexdump.h InputParSet.h + IOPriority.h i4complex.h KVpair.h lofar_algorithm.h diff --git a/RTCP/Cobalt/OutputProc/src/IOPriority.h b/LCS/Common/include/Common/IOPriority.h similarity index 94% rename from RTCP/Cobalt/OutputProc/src/IOPriority.h rename to LCS/Common/include/Common/IOPriority.h index 66eb128e9ac..0c79fbcbab5 100644 --- a/RTCP/Cobalt/OutputProc/src/IOPriority.h +++ b/LCS/Common/include/Common/IOPriority.h @@ -1,5 +1,6 @@ -//# IOPriority.h: define some Linux specific IO priority macro's -//# Copyright (C) 2011-2013 ASTRON (Netherlands Institute for Radio Astronomy) +//# IOPriority.h: Linux specific priority functions +//# Copyright (C) 2011-2013, 2016 +//# ASTRON (Netherlands Institute for Radio Astronomy) //# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands //# //# This file is part of the LOFAR software suite. @@ -18,8 +19,8 @@ //# //# $Id$ -#ifndef LOFAR_STORAGE_IOPRIORITY_H -#define LOFAR_STORAGE_IOPRIORITY_H +#ifndef LOFAR_COMMON_IOPRIORITY_H +#define LOFAR_COMMON_IOPRIORITY_H #define IOPRIO_BITS (16) #define IOPRIO_CLASS_SHIFT (13) @@ -44,6 +45,9 @@ #include <linux/version.h> #endif +namespace LOFAR +{ + enum { IOPRIO_WHO_PROCESS = 1, IOPRIO_WHO_PGRP, @@ -172,5 +176,7 @@ inline void lockInMemory(rlim_t memLockLimit = RLIM_INFINITY) } } +} // namespace LOFAR + #endif diff --git a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc index 0491edb1815..5f786a10ad8 100644 --- a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc +++ b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc @@ -35,6 +35,7 @@ #include <Common/StringUtil.h> #include <Common/SystemUtil.h> #include <Common/Exceptions.h> +#include <Common/IOPriority.h> #include <MessageBus/ToBus.h> #include <MessageBus/Protocols/TaskFeedbackDataproducts.h> #include <Stream/PortBroker.h> @@ -50,7 +51,6 @@ #include <CoInterface/SelfDestructTimer.h> #include "SubbandWriter.h" #include "OutputThread.h" -#include "IOPriority.h" using namespace LOFAR; using namespace LOFAR::Cobalt; diff --git a/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc b/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc index 48a9fed67c4..2f862ac77ff 100644 --- a/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc +++ b/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc @@ -40,6 +40,7 @@ #include <Common/LofarLogger.h> #include <Common/StringUtil.h> +#include <Common/IOPriority.h> #include <ApplCommon/StationConfig.h> #include <ApplCommon/AntField.h> #include <CoInterface/Exceptions.h> @@ -51,7 +52,6 @@ #endif #include "TBB_Writer.h" -#include "IOPriority.h" #define TBB_DEFAULT_BASE_PORT 0x7bb0 // i.e. tbb0 #define TBB_DEFAULT_LAST_PORT 0x7bbb // 0x7bbf for NL, 0x7bbb for int'l stations @@ -516,9 +516,9 @@ int main(int argc, char* argv[]) // We don't run alone, so try to increase the QoS we get from the OS to decrease the chance of data loss. if (parset.settings.realTime) { - setIOpriority(); // reqs CAP_SYS_NICE - setRTpriority(); // reqs CAP_SYS_ADMIN - lockInMemory(); // reqs CAP_IPC_LOCK + LOFAR::setIOpriority(); // reqs CAP_SYS_NICE + LOFAR::setRTpriority(); // reqs CAP_SYS_ADMIN + LOFAR::lockInMemory(); // reqs CAP_IPC_LOCK } LOFAR::Cobalt::StationMetaDataMap stMdMap(getExternalStationMetaData(parset, args.staticMetaDataDir)); -- GitLab From 79a91d3de504a8e6e317760e5b1ffccd23e4c4ae Mon Sep 17 00:00:00 2001 From: Alexander van Amesfoort <amesfoort@astron.nl> Date: Wed, 26 Oct 2016 23:31:15 +0000 Subject: [PATCH 930/933] Task #8691: IOPriority.h: allow to increase IO prio without RT: does not req CAP_SYS_ADMIN --- LCS/Common/include/Common/IOPriority.h | 8 ++++++-- RTCP/Cobalt/OutputProc/src/GPUProcIO.cc | 2 +- RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/LCS/Common/include/Common/IOPriority.h b/LCS/Common/include/Common/IOPriority.h index 0c79fbcbab5..8a367ff5778 100644 --- a/LCS/Common/include/Common/IOPriority.h +++ b/LCS/Common/include/Common/IOPriority.h @@ -89,9 +89,13 @@ inline int ioprio_get(int which, int who) #endif } -inline void setIOpriority() +// realTime true requires CAP_SYS_ADMIN. false does not require a capability, +// but does set to the more favorable prio 0 with IOPRIO_CLASS_BE (default 4). +inline void setIOpriority(bool realTime) { - if (ioprio_set(IOPRIO_WHO_PROCESS, getpid(), IOPRIO_PRIO_VALUE(IOPRIO_CLASS_RT,7)) != 0) { + if (ioprio_set(IOPRIO_WHO_PROCESS, getpid(), + realTime ? IOPRIO_PRIO_VALUE(IOPRIO_CLASS_RT, 7) : + IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 0)) != 0) { switch (errno) { case EPERM: { diff --git a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc index 5f786a10ad8..68c9b7b91f3 100644 --- a/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc +++ b/RTCP/Cobalt/OutputProc/src/GPUProcIO.cc @@ -115,7 +115,7 @@ bool process(Stream &controlStream) */ // Acquire elevated IO and CPU priorities - setIOpriority(); + setIOpriority(true); setRTpriority(); // Prevent swapping of our buffers diff --git a/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc b/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc index 2f862ac77ff..c6e99cd0c8a 100644 --- a/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc +++ b/RTCP/Cobalt/OutputProc/src/TBB_Writer_main.cc @@ -516,8 +516,8 @@ int main(int argc, char* argv[]) // We don't run alone, so try to increase the QoS we get from the OS to decrease the chance of data loss. if (parset.settings.realTime) { - LOFAR::setIOpriority(); // reqs CAP_SYS_NICE - LOFAR::setRTpriority(); // reqs CAP_SYS_ADMIN + LOFAR::setIOpriority(true); // reqs CAP_SYS_ADMIN iff passing realTime is true + LOFAR::setRTpriority(); // reqs CAP_SYS_NICE LOFAR::lockInMemory(); // reqs CAP_IPC_LOCK } -- GitLab From b9ec7b26b0438e72260e241dc9d22b8b5f214b22 Mon Sep 17 00:00:00 2001 From: Nico Vermaas <vermaas@astron.nl> Date: Thu, 27 Oct 2016 06:43:17 +0000 Subject: [PATCH 931/933] Task #10023: Changed the naming for lcs023 from MOM_USER to MOM_SYSTEM (dop303 is MOM_USER) --- SAS/QPIDInfrastructure/bin/populateDB.sh | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/SAS/QPIDInfrastructure/bin/populateDB.sh b/SAS/QPIDInfrastructure/bin/populateDB.sh index be19626bd87..dea9d87798d 100755 --- a/SAS/QPIDInfrastructure/bin/populateDB.sh +++ b/SAS/QPIDInfrastructure/bin/populateDB.sh @@ -37,7 +37,7 @@ if $PROD; then SCU=scu001.control.lofar SAS=sas001.control.lofar - MOM_USER=lcs023.control.lofar + MOM_SYSTEM=lcs023.control.lofar MOM_INGEST=lcs029.control.lofar COBALT="`seq -f cbm%03.0f.control.lofar 1 8`" @@ -53,7 +53,7 @@ else SCU=scu099.control.lofar SAS=sas099.control.lofar - MOM_USER=lcs028.control.lofar + MOM_SYSTEM=lcs028.control.lofar MOM_INGEST=lcs028.control.lofar COBALT="cbm009.control.lofar" @@ -117,9 +117,9 @@ done # MessageRouter -> MoM # ----------------------------------------- -addtoQPIDDB.py --broker $CCU --queue ${PREFIX}mom.task.feedback.dataproducts --federation $MOM_USER -addtoQPIDDB.py --broker $CCU --queue ${PREFIX}mom.task.feedback.processing --federation $MOM_USER -addtoQPIDDB.py --broker $CCU --queue ${PREFIX}mom.task.feedback.state --federation $MOM_USER +addtoQPIDDB.py --broker $CCU --queue ${PREFIX}mom.task.feedback.dataproducts --federation $MOM_SYSTEM +addtoQPIDDB.py --broker $CCU --queue ${PREFIX}mom.task.feedback.processing --federation $MOM_SYSTEM +addtoQPIDDB.py --broker $CCU --queue ${PREFIX}mom.task.feedback.state --federation $MOM_SYSTEM # ----------------------------------------- # MessageRouter -> OTDB @@ -139,31 +139,31 @@ addtoQPIDDB.py --broker $CCU --exchange ${PREFIX}mac.task.feedback.state # ----------------------------------------- addtoQPIDDB.py --broker $MCU --queue ${PREFIX}lofar.task.specification.system --federation $CCU -addtoQPIDDB.py --broker $CCU --queue ${PREFIX}mom.task.specification.system --federation $MOM_USER +addtoQPIDDB.py --broker $CCU --queue ${PREFIX}mom.task.specification.system --federation $MOM_SYSTEM # ----------------------------------------- # MoM <-> MoM-OTDB-Adapter # ----------------------------------------- -addtoQPIDDB.py --broker $SAS --queue mom.command --federation $MOM_USER -addtoQPIDDB.py --broker $SAS --queue mom.importxml --federation $MOM_USER -addtoQPIDDB.py --broker $MOM_USER --queue mom-otdb-adapter.importxml --federation $SAS +addtoQPIDDB.py --broker $SAS --queue mom.command --federation $MOM_SYSTEM +addtoQPIDDB.py --broker $SAS --queue mom.importxml --federation $MOM_SYSTEM +addtoQPIDDB.py --broker $MOM_SYSTEM --queue mom-otdb-adapter.importxml --federation $SAS # ----------------------------------------- # MoM Services # ----------------------------------------- -addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.bus +addtoQPIDDB.py --broker $MOM_SYSTEM --exchange ${PREFIX}lofar.mom.bus addtoQPIDDB.py --broker $MOM_INGEST --exchange ${PREFIX}lofar.mom.bus -addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.command -addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.notification +addtoQPIDDB.py --broker $MOM_SYSTEM --exchange ${PREFIX}lofar.mom.command +addtoQPIDDB.py --broker $MOM_SYSTEM --exchange ${PREFIX}lofar.mom.notification # ----------------------------------------- # MoM Services <-> ResourceAssignment # ----------------------------------------- -addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.mom.bus --federation $MOM_USER -addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.mom.command --federation $MOM_USER -addtoQPIDDB.py --broker $MOM_USER --exchange ${PREFIX}lofar.mom.notification --federation $SCU +addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.mom.bus --federation $MOM_SYSTEM +addtoQPIDDB.py --broker $SCU --exchange ${PREFIX}lofar.mom.command --federation $MOM_SYSTEM +addtoQPIDDB.py --broker $MOM_SYSTEM --exchange ${PREFIX}lofar.mom.notification --federation $SCU # ----------------------------------------- # ResourceAssignment -- GitLab From a290c3ba87380b5d0913e944a6a6d4d82e25e2ce Mon Sep 17 00:00:00 2001 From: Tammo Jan Dijkema <dijkema@astron.nl> Date: Fri, 28 Oct 2016 09:12:30 +0000 Subject: [PATCH 932/933] Task #9964: fix pybdsm runtime error with c++11 --- CEP/PyBDSM/src/c++/MGFunction1.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEP/PyBDSM/src/c++/MGFunction1.cc b/CEP/PyBDSM/src/c++/MGFunction1.cc index b37c5a41018..9aacf8e516e 100644 --- a/CEP/PyBDSM/src/c++/MGFunction1.cc +++ b/CEP/PyBDSM/src/c++/MGFunction1.cc @@ -197,7 +197,7 @@ boost::python::tuple MGFunction::py_find_peak() int x1 = mm_data[pidx].x1; int x2 = mm_data[pidx].x2; - return boost::python::make_tuple(peak, make_tuple(x1, x2)); + return boost::python::make_tuple(peak, boost::python::make_tuple(x1, x2)); } -- GitLab From af8ff659f5ac8132f9dd0a1d355dd6bcaa7c8feb Mon Sep 17 00:00:00 2001 From: Arno Schoenmakers <schoenmakers@astron.nl> Date: Mon, 31 Oct 2016 13:41:34 +0000 Subject: [PATCH 933/933] Task 10047: Changing default image for SE607 to 5. --- MAC/Deployment/data/StaticMetaData/RSPImage.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAC/Deployment/data/StaticMetaData/RSPImage.conf b/MAC/Deployment/data/StaticMetaData/RSPImage.conf index dca5dd030da..a11ccc6cbb8 100644 --- a/MAC/Deployment/data/StaticMetaData/RSPImage.conf +++ b/MAC/Deployment/data/StaticMetaData/RSPImage.conf @@ -51,7 +51,7 @@ DE603C 4 DE604C 4 DE605C 5 FR606C 4 -SE607C 4 +SE607C 5 UK608C 4 DE609C 4 FI609C 4 -- GitLab