#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2016-2017 Lenovo
#
# 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 optparse
import os
import signal
import sys
import time

try:
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
    pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
    sys.path.append(path)

import confluent.client as client
import confluent.screensqueeze as sq

exitcode = 0


def printfirm(node, prefix, data):
    if 'model' in data and data['model']:
        prefix += ' ' + data['model']
    builddesc = []
    if 'build' in data and data['build']:
        builddesc.append(data['build'])
    if 'date' in data and data['date']:
        builddesc.append(data['date'])
    if 'version' in data and data['version']:
        version = data['version']
        if builddesc:
            version += ' ({0})'.format(' '.join(builddesc))
    else:
        version = ' '.join(builddesc)
    print('{0}: {1}: {2}'.format(node, prefix, version))

components = ['all']

argparser = optparse.OptionParser(
    usage="Usage: "
          "%prog <noderange> [list][updatestatus][update [--backup <file>]]|[<components>]")
argparser.add_option('-b', '--backup', action='store_true',
                     help='Target a backup bank rather than primary')
argparser.add_option('-p', '--parameterfile', type='string',
                     help='When updating, use the specified parameter file')
argparser.add_option('-m', '--maxnodes', type='int',
                     help='When updating, prompt if more than the specified '
                          'number of servers will be affected')
             
(options, args) = argparser.parse_args()
upfile = None
querystatus = False
try:
    noderange = args[0]
    if len(args) > 1:
        if args[1] == 'update':
            upfile = args[2]
        else:
            comps = []
            if args[1] == 'list':
                comps = args[2:]
            elif args[1] == 'updatestatus':
                querystatus = True
            else:
                comps = args[1:]
            components = []
            for arg in comps:
                components += arg.split(',')
            if not components:
                components = ['all']

except IndexError:
    argparser.print_help()
    sys.exit(1)
client.check_globbing(noderange)

def get_update_progress(session, url):
    for res in session.read(url):
        status = res.get('phase', 'error')
        percent = res.get('progress', None)
        detail = res.get('detail', repr(res)),
        if status == 'error':
            text = 'error!'
        else:
            text = '{0}: {1:3.0f}%'.format(status, percent)
    return text, status, detail

def update_firmware(session, filename):
    global exitcode
    session.stop_if_noderange_over(noderange, options.maxnodes)
    output = sq.ScreenPrinter(noderange, session)
    nodeurls = {}
    filename = os.path.abspath(filename)
    resource = '/noderange/{0}/inventory/firmware/updates/active'.format(
        noderange)
    upargs = {'filename': filename}
    if options.backup:
        upargs['bank'] = 'backup'
    if options.parameterfile:
        with open(options.parameterfile, 'rb') as pf:
            pfdata = pf.read()
        upargs['parameterdata'] = pfdata
    noderrs = {}
    if session.unixdomain:
        filesbynode = {}
        for exp in session.create('/noderange/{0}/attributes/expression'.format(noderange),
                        {'expression': filename}):
            if 'error' in exp:
                sys.stderr.write(exp['error'] + '\n')
                exitcode |= exp.get('errorcode', 1)
            ex = exp.get('databynode', ())
            for node in ex:
                filesbynode[node] = ex[node]['value']
                if not isinstance(filesbynode[node], bytes) and not isinstance(filesbynode[node], str):
                    filesbynode[node] = filesbynode[node].encode('utf-8')
        for node in filesbynode:
            endfilename = filesbynode[node]
            of = open(endfilename, 'rb')
            try:
                session.add_file(endfilename, of.fileno(), 'rb')
            except Exception:
                pass
    for res in session.create(resource, upargs):
        if 'created' not in res:
            for nodename in res.get('databynode', ()):
                output.set_output(nodename, 'error!')
                noderrs[nodename] = res['databynode'][nodename].get(
                    'error', 'Unknown Error')
            continue
        watchurl = res['created']
        currnode = watchurl.split('/')[1]
        nodeurls[currnode] = '/' + watchurl
    while nodeurls:
        for node in list(nodeurls):
            progress, status, err = get_update_progress(
                session, nodeurls[node])
            if status == 'error':
                exitcode = 1
                noderrs[node] = err
            if status in ('error', 'complete', 'pending'):
                list(session.delete(nodeurls[node]))
                del nodeurls[node]
            output.set_output(node, progress)
        time.sleep(2)
    allerrnodes = ','.join(noderrs)
    if noderrs:
        sys.stderr.write(
            'Nodes had errors updating ({0})!\n'.format(allerrnodes))
    for node in noderrs:
        sys.stderr.write('{0}: {1}\n'.format(node, noderrs[node]))

def show_firmware(session):
    global exitcode
    firmware_shown = False
    nodes_matched = False
    for component in components:
        category = 'all'
        if component in ('adapters', 'disks', 'misc', 'core'):
            category = component
            component = 'all'    
        for res in session.read(
                '/noderange/{0}/inventory/firmware/{2}/{1}'.format(
                    noderange, component, category)):
            nodes_matched = True
            exitcode |= client.printerror(res)
            if 'databynode' not in res:
                continue
            for node in res['databynode']:
                if 'firmware' not in res['databynode'][node]:
                    continue
                for inv in res['databynode'][node]['firmware']:
                    for prefix in inv:
                        firmware_shown = True
                        printfirm(node, prefix, inv[prefix])
    if not nodes_matched:
        sys.stderr.write('No matching nodes for noderange "{0}"\n'.format(noderange))
    elif not firmware_shown and not exitcode:
        argparser.print_help()


try:
    session = client.Command()
    if querystatus:
        for res in session.read(
                '/noderange/{0}/inventory/firmware/updatestatus'.format(noderange)):
            for node in res.get('databynode', {}):
                currstat = res['databynode'][node].get('status', None)
                if currstat:
                    print('{}: {}'.format(node, currstat))
                else:
                    print(repr(res))
    elif upfile is None:
        show_firmware(session)
    else:
        update_firmware(session, upfile)
except KeyboardInterrupt:
    print('')
sys.exit(exitcode)
