blob: 3caab3ddeedc8c930eaac5d53273292c6a239175 [file] [log] [blame]
#
# Copyright 2015-2016 IBM Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import json
import httplib
import urllib
import time
from datetime import datetime, timedelta
import copy
from wskitem import Item
from wskutil import addAuthenticatedCommand, apiBase, bold, parseQName, getQName, request, responseError, getPrettyJson
# how many seconds to sleep between polls
SLEEP_SECONDS = 2
# a parameter -- how many seconds might it take for an activation
# completion to propagate into cloudant
SLACK_SECONDS = 5
# the timestamp of the latest activation we have already reported
lastTime = 0
#
# 'wsk activations' CLI
#
class Activation(Item):
def __init__(self):
super(Activation, self).__init__('activation', 'activations')
def getItemSpecificCommands(self, parser, props):
subcmd = parser.add_parser('list', help='retrieve activations')
subcmd.add_argument('name', nargs='?', help='the namespace or action to list')
addAuthenticatedCommand(subcmd, props)
subcmd.add_argument('-s', '--skip', help='skip this many entities from the head of the collection', type=int, default=0)
subcmd.add_argument('-l', '--limit', help='only return this many entities from the collection', type=int, default=30)
subcmd.add_argument('-f', '--full', help='return full documents for each activation', action='store_true')
subcmd.add_argument('--upto', help='return activations with timestamps earlier than UPTO; measured in milliseconds since Thu, 01 Jan 1970 00:00:00', type=long, default=0)
subcmd.add_argument('--since', help='return activations with timestamps later than SINCE; measured in milliseconds since Thu, 01 Jan 1970 00:00:00', type=long, default=0)
subcmd = parser.add_parser('get', help='get %s' % self.name)
subcmd.add_argument('name', help='the name of the %s' % self.name)
subcmd.add_argument('project', nargs='?', help='project only this property')
addAuthenticatedCommand(subcmd, props)
subcmd.add_argument('-s', '--summary', help='summarize entity details', action='store_true')
subcmd = parser.add_parser('logs', help='get the logs of an activation')
subcmd.add_argument('id', help='the activation id')
addAuthenticatedCommand(subcmd, props)
subcmd.add_argument('-s', '--strip', help='strip timestamp and stream information', action='store_true')
subcmd= parser.add_parser('result', help='get the result of an activation')
subcmd.add_argument('id', help='the invocation id')
addAuthenticatedCommand(subcmd, props)
# poll
subcmd= parser.add_parser('poll', help='poll continuously for log messages from currently running actions')
subcmd.add_argument('name', nargs='?', help='the namespace to poll')
addAuthenticatedCommand(subcmd, props)
subcmd.add_argument('-e', '--exit', help='exit after this many seconds', type=int, default=-1)
subcmd.add_argument('-ss', '--since-seconds', help='start polling for activations this many seconds ago', type=long, default=0, dest='since_secs')
subcmd.add_argument('-sm', '--since-minutes', help='start polling for activations this many minutes ago', type=long, default=0, dest='since_mins')
subcmd.add_argument('-sh' ,'--since-hours', help='start polling for activations this many hours ago', type=long, default=0, dest='since_hrs')
subcmd.add_argument('-sd' ,'--since-days', help='start polling for activations this many days ago', type=long, default=0, dest='since_days')
def cmd(self, args, props):
if args.subcmd == 'list':
return self.list(args, props)
elif args.subcmd == 'get':
return self.get(args, props)
elif args.subcmd == 'logs':
return self.logs(args, props)
elif args.subcmd == 'result':
return self.result(args, props)
elif args.subcmd == 'poll':
return self.poll(args, props)
else:
print 'error: unexpected sub command'
return 2
def create(self, args, props, update):
"""not allowed"""
return 2
def postProcess(self, entity):
#entity['logs'] = json.loads(entity['logs'])
return entity
def get(self, args, props):
args.name = getQName(args.name, '_') # kludge: use default namespace unless explicitly specified
return Item.get(self, args, props)
def list(self, args, props):
name = args.name if args.name else '/_'
args.name = getQName(name, '_') # kludge: use default namespace unless explicitly specified
res = self.listCmd(args, props)
if res.status == httplib.OK:
result = json.loads(res.read())
print bold('activations')
for a in result:
if args.full:
print getPrettyJson(a)
else:
print '{:<45}{:<40}'.format(a['activationId'], a['name'])
return 0
else:
return responseError(res)
def getEntitySummary(self, entity):
kind = self.name
fullName = getQName(entity['name'], entity['namespace'])
end = datetime.fromtimestamp(entity['end'] / 1000)
status = entity['response']['status']
result = getPrettyJson(entity['response']['result'])
summary = '%s result for %s (%s at %s):\n%s' % (kind, fullName, status, end, result)
return summary
def result(self, args, props):
fqid = getQName(args.id, '_') # kludge: use default namespace unless explicitly specified
namespace, aid = parseQName(fqid, props)
url = '%(apibase)s/namespaces/%(namespace)s/activations/%(id)s/result' % {
'apibase': apiBase(props),
'namespace': urllib.quote(namespace),
'id': aid
}
res = request('GET', url, auth=args.auth, verbose=args.verbose)
if res.status == httplib.OK:
response = json.loads(res.read())
if 'result' in response:
result = response['result']
print getPrettyJson(result)
return 0
else:
return responseError(res)
def logs(self, args, props):
fqid = getQName(args.id, '_') # kludge: use default namespace unless explicitly specified
namespace, aid = parseQName(fqid, props)
url = '%(apibase)s/namespaces/%(namespace)s/activations/%(id)s/logs' % {
'apibase': apiBase(props),
'namespace': urllib.quote(namespace),
'id': aid
}
res = request('GET', url, auth=args.auth, verbose=args.verbose)
if res.status == httplib.OK:
result = json.loads(res.read())
logs = result['logs']
if args.strip:
logs = map(stripTimeStampAndString, logs)
print '\n'.join(logs)
return 0
else:
return responseError(res)
# implementation of wsk activations poll
def poll(self, args, props):
name = args.name if args.name else '/_'
args.name = getQName(name, '_') # kludge: use default namespace unless explicitly specified
print 'Hit Ctrl-C to exit.'
try:
self.console(args, props)
except KeyboardInterrupt:
print ''
# return the result of a 'wsk activation list' call, an HTTPResponse
def listCmd(self, args, props):
namespace, pname = parseQName(args.name, props)
url = '%(apibase)s/namespaces/%(namespace)s/activations?docs=%(full)s&skip=%(skip)s&limit=%(limit)s&%(filter)s' % {
'apibase': apiBase(props),
'namespace': urllib.quote(namespace),
'collection': self.collection,
'full': 'true' if args.full else 'false',
'skip': args.skip,
'limit': args.limit,
'filter': 'name=%s' % urllib.quote(pname) if pname and len(pname) > 0 else ''
}
if args.upto > 0:
url = url + '&upto=' + str(args.upto)
if args.since > 0:
url = url + '&since=' + str(args.since)
res = request('GET', url, auth=args.auth, verbose=args.verbose)
return res
#
# main routine for polling loop
#
def console(self, args, props):
global lastTime
if args.since_secs == args.since_mins == args.since_hrs == args.since_days == 0:
# initialize lastTime to just past the most recent activation
lastActivation = self.fetchMostRecent(args, props)
if lastActivation != None:
lastTime = extractTimestamp(lastActivation) + 1 + SLACK_SECONDS * 1000
else:
# initialize lastTime to utcnow - args.since_[secs,mins,hrs,days]
n = datetime.utcnow()
d = timedelta(seconds=args.since_secs, minutes=args.since_mins, hours=args.since_hrs, days=args.since_days)
e = datetime(1970, 1, 1)
lastTime = int((n-d-e).total_seconds()*1000)
reported = set()
localStartTime = int(time.time())
print 'Polling for logs'
while True:
if (args.exit > 0):
localDuration = int(time.time()) - localStartTime
if (localDuration > args.exit):
return
L = self.fetchActivations(lastTime, args, props)
if L:
printLogs(reversed(L), reported)
time.sleep(SLEEP_SECONDS)
#
# Fetch the most recent activation
#
# Return the activation record, or None
#
def fetchMostRecent(self, args, props):
a = copy.deepcopy(args)
a.action = None
a.name = args.name
a.full = True
a.skip = 0
a.limit = 1
a.upto = 0
a.since = 0
pr = copy.deepcopy(props)
res = self.listCmd(a, pr)
if res.status == httplib.OK:
result = json.loads(res.read())
return None if len(result) == 0 else result[0]
else:
responseError(res)
return None
#
# Fetch all activation reconds since a timestamp
#
def fetchActivations(self, beginMillis, args, props):
# fetch all activations starting from SLACK_SECONDS seconds in the past
if beginMillis > SLACK_SECONDS * 1000:
beginMillis = beginMillis - (SLACK_SECONDS * 1000);
a = copy.deepcopy(args)
a.name = args.name
a.full = True
a.skip = 0
a.limit = 0
a.upto = 0
a.since = beginMillis
pr = copy.deepcopy(props)
res = self.listCmd(a, pr)
if res.status == httplib.OK:
result = json.loads(res.read())
return result
else:
responseError(res)
return None
#
# Extract the timestamp from an activation, or return 0
#
def extractTimestamp(activation):
return activation['start']
#
# Print all the logs for an activation record
#
# auth: whisk auth key
#
def printLogsForActivation(activation):
global lastTime
print 'Activation: %s (%s)' % (activation['name'], activation['activationId'])
if 'logs' in activation:
for element in activation['logs']:
print element
# update the lastTime global timestamp if necessary to record the latest
# start time yet fetched
if activation['start'] > lastTime:
lastTime = activation['start']
#
# Print all the logs for a list of activation records
#
# L: set of activation records
# reported: set of ids already reported to the user
#
def printLogs(L, reported):
for a in L:
if not a['activationId'] in reported:
reported.add(a['activationId'])
printLogsForActivation(a)
#
# Strips timestamp and stream information from log line
#
def stripTimeStampAndString(line):
# log line should be formatted to fixed width and stripping
# first 38 charecters should do
return line[39:]