'''
Copyright 2017 Dell Inc. or its subsidiaries. All Rights Reserved.

This script tests arbitrary payload of the RackHD API 2.0 OS bootstrap workflows.
The default case is running a minimum payload Windows OS install.
Other Windows-type OS install cases can be specified by creating a payload file and specifiying it using the '-extra' argument.
This test takes 30-45 minutes to run.

Example payload file (installed in configuration dir):

{"bootstrap-payload":
    {"name": "Graph.InstallWindowsServer",
           "options": {"defaults": {"version": "2012",
                                    "repo": "http://172.31.128.1:8080/repo/winpe",
                                    "smbRepo": "\\\\172.31.128.1\\windowsServer2012",
                                    "productkey": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX",
                                    "username": "rackhduser",
                                    "password": "RackHDRocks",
                                    "smbUser": "vagrant",
                                    "smbPassword": "vagrant"}}}
}

Example command line using external payload file:

python run_tests.py -stack 4 -test tests/bootstrap/test_api20_windows_bootstrap.py -extra base_windows_2012_install.json

RackHD Windows installation workflow requires special configuration of the RackHD server:
- A customized WinPE environment installed on RackHD server as documented here:
        https://github.com/RackHD/on-tools/tree/master/winpe
- Samba installed on the RackHD server and configured as documented here:
        http://rackhd.readthedocs.io/en/latest/rackhd/install_os.html?highlight=os%20install
- Windows 2012 installation distro installed on RackHD server or equivalent NFS mount.
- Windows 2012 activation key in the installation payload file.

'''

import fit_path  # NOQA: unused import
from nose.plugins.attrib import attr
import fit_common
import flogging
import random
import json
import time
from nosedep import depends
from datetime import datetime
log = flogging.get_loggers()

# sample default base payload
PAYLOAD = {"name": "Graph.InstallWindowsServer",
           "options": {"defaults": {"version": "2012",
                                    "repo": "http://172.31.128.1:8080/repo/winpe",
                                    "smbRepo": "\\\\172.31.128.1\\windowsServer2012",
                                    "productkey": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX",
                                    "username": "rackhduser",
                                    "password": "RackHDRocks",
                                    "smbUser": "vagrant",
                                    "smbPassword": "vagrant"}}}
# if an external payload file is specified, use that
config = fit_common.fitcfg().get('bootstrap-payload', None)
if config:
    PAYLOAD = config


# function to return the value of a field from the workflow response
def findall(obj, key):
    if isinstance(obj, dict):
        for k, v in obj.items():
            if k == key:
                log.error(" workflow error: %s", v)
            findall(v, key)
    elif isinstance(obj, list):
        for item in obj:
            findall(item, key)
    else:
        pass


# this routine polls a workflow task ID for completion
def wait_for_workflow_complete(instanceid, start_time, waittime=3200, cycle=30):
    log.info_1(" Workflow started at time: " + str(datetime.fromtimestamp(start_time)))
    while time.time() - start_time < waittime:  # limit test to waittime seconds
        result = fit_common.rackhdapi("/api/2.0/workflows/" + instanceid)
        if result['status'] != 200:
            log.error(" HTTP error: " + result['text'])
            return False
        if result['json']['status'] in ['running', 'pending']:
            log.info_5("{} workflow status: {}".format(result['json']['injectableName'], result['json']['status']))
            fit_common.time.sleep(cycle)
        elif result['json']['status'] == 'succeeded':
            log.info_1("{} workflow status: {}".format(result['json']['injectableName'], result['json']['status']))
            end_time = time.time()
            log.info_1(" Workflow completed at time: " + str(datetime.fromtimestamp(end_time)))
            log.info_1(" Workflow duration: " + str(end_time - start_time))
            return True
        else:
            end_time = time.time()
            log.info_1(" Workflow failed at time: " + str(datetime.fromtimestamp(end_time)))
            log.info_1(" Workflow duration: " + str(end_time - start_time))
            try:
                res = json.loads(result['text'])
                findall(res, "error")
            except:
                res = result['text']
            log.error(" Workflow failed: status: %s", result['json']['status'])
            log.error(" Data: %s", json.dumps(res, indent=4, separators=(',', ':')))
            return False
    try:
        res = json.loads(result['text'])
    except:
        res = result['text']
    log.error(" Workflow Timeout: " + json.dumps(res, indent=4, separators=(',', ':')))
    return False


# ------------------------ Tests -------------------------------------


@attr(all=False)
class api20_bootstrap_windows(fit_common.unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # Get the list of nodes
        NODECATALOG = fit_common.node_select()
        assert (len(NODECATALOG) != 0), "There are no nodes currently discovered"

        # Select one node at random
        cls.__NODE = NODECATALOG[random.randint(0, len(NODECATALOG) - 1)]

        # Print node Id, node BMC mac ,node type
        nodeinfo = fit_common.rackhdapi('/api/2.0/nodes/' + cls.__NODE)['json']
        nodesku = fit_common.rackhdapi(nodeinfo.get('sku'))['json']['name']
        monurl = "/api/2.0/nodes/" + cls.__NODE + "/catalogs/bmc"
        mondata = fit_common.rackhdapi(monurl, action="get")
        catalog = mondata['json']
        bmcresult = mondata['status']
        if bmcresult != 200:
            log.info_1(" Node ID: " + cls.__NODE)
            log.info_1(" Error on catalog/bmc command")
        else:
            log.info_1(" Node ID: " + cls.__NODE)
            log.info_1(" Node SKU: " + nodesku)
            log.info_1(" Node BMC Mac: %s", catalog.get('data')['MAC Address'])
            log.info_1(" Node BMC IP Addr: %s", catalog.get('data')['IP Address'])
            log.info_1(" Node BMC IP Addr Src: %s", catalog.get('data')['IP Address Source'])

        # delete active workflows for specified node
        result = fit_common.cancel_active_workflows(cls.__NODE)
        assert (result is True), "There are still some active workflows running against the node"

    def test01_node_check(self):
        # Log node data
        nodeinfo = fit_common.rackhdapi('/api/2.0/nodes/' + self.__class__.__NODE)['json']
        nodesku = fit_common.rackhdapi(nodeinfo.get('sku'))['json']['name']
        log.info_1(" Node ID: %s ", self.__class__.__NODE)
        log.info_1(" Node SKU: %s ", nodesku)
        log.info_1(" Graph Name: Graph.PowerOn.Node")

        # Ensure the compute node is powered on and reachable
        result = fit_common.rackhdapi('/api/2.0/nodes/' +
                                      self.__class__.__NODE +
                                      '/workflows',
                                      action='post', payload={"name": "Graph.PowerOn.Node"})
        self.assertEqual(result['status'], 201, "Node Power on workflow API failed, see logs.")
        self.assertTrue(wait_for_workflow_complete(result['json']['instanceId'], time.time(), 50, 5),
                        "Node Power on workflow failed, see logs.")

    @depends(after=test01_node_check)
    def test02_os_install(self):
        # Log node data
        nodeinfo = fit_common.rackhdapi('/api/2.0/nodes/' + self.__class__.__NODE)['json']
        nodesku = fit_common.rackhdapi(nodeinfo.get('sku'))['json']['name']
        log.info_1(" Node ID: " + self.__class__.__NODE)
        log.info_1(" Node SKU: " + nodesku)
        log.info_1(" Graph Name: Graph.InstallWindowsServer")
        log.info_1(" Payload: " + fit_common.json.dumps(PAYLOAD))

        # launch workflow
        workflowid = None
        result = fit_common.rackhdapi('/api/2.0/nodes/' +
                                      self.__class__.__NODE +
                                      '/workflows',
                                      action='post', payload=PAYLOAD)
        if result['status'] == 201:
            # workflow running
            log.info_1(" InstanceID: " + result['json']['instanceId'])
            workflowid = result['json']['instanceId']
        else:
            # workflow failed with response code
            log.error(" InstanceID: " + result['text'])
            self.fail("Workflow failed with response code: " + result['status'])
        self.assertTrue(wait_for_workflow_complete(workflowid, time.time()), "OS Install workflow failed, see logs.")


if __name__ == '__main__':
    fit_common.unittest.main()

[((46, 6, 46, 28), 'flogging.get_loggers', 'flogging.get_loggers', ({}, {}), '()', False, 'import flogging\n'), ((118, 1, 118, 16), 'nose.plugins.attrib.attr', 'attr', (), '', False, 'from nose.plugins.attrib import attr\n'), ((167, 5, 167, 37), 'nosedep.depends', 'depends', (), '', False, 'from nosedep import depends\n'), ((195, 4, 195, 30), 'fit_common.unittest.main', 'fit_common.unittest.main', ({}, {}), '()', False, 'import fit_common\n'), ((59, 9, 59, 28), 'fit_common.fitcfg', 'fit_common.fitcfg', ({}, {}), '()', False, 'import fit_common\n'), ((82, 17, 82, 73), 'fit_common.rackhdapi', 'fit_common.rackhdapi', ({(82, 38, 82, 72): "'/api/2.0/workflows/' + instanceid"}, {}), "('/api/2.0/workflows/' + instanceid)", False, 'import fit_common\n'), ((108, 14, 108, 40), 'json.loads', 'json.loads', ({(108, 25, 108, 39): "result['text']"}, {}), "(result['text'])", False, 'import json\n'), ((123, 22, 123, 46), 'fit_common.node_select', 'fit_common.node_select', ({}, {}), '()', False, 'import fit_common\n'), ((133, 18, 133, 60), 'fit_common.rackhdapi', 'fit_common.rackhdapi', (), '', False, 'import fit_common\n'), ((147, 17, 147, 63), 'fit_common.cancel_active_workflows', 'fit_common.cancel_active_workflows', ({(147, 52, 147, 62): 'cls.__NODE'}, {}), '(cls.__NODE)', False, 'import fit_common\n'), ((159, 17, 162, 92), 'fit_common.rackhdapi', 'fit_common.rackhdapi', (), '', False, 'import fit_common\n'), ((179, 17, 182, 69), 'fit_common.rackhdapi', 'fit_common.rackhdapi', (), '', False, 'import fit_common\n'), ((81, 10, 81, 21), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((88, 12, 88, 40), 'fit_common.time.sleep', 'fit_common.time.sleep', ({(88, 34, 88, 39): 'cycle'}, {}), '(cycle)', False, 'import fit_common\n'), ((111, 38, 111, 86), 'json.dumps', 'json.dumps', (), '', False, 'import json\n'), ((130, 19, 130, 71), 'fit_common.rackhdapi', 'fit_common.rackhdapi', ({(130, 40, 130, 70): "('/api/2.0/nodes/' + cls.__NODE)"}, {}), "('/api/2.0/nodes/' + cls.__NODE)", False, 'import fit_common\n'), ((152, 19, 152, 82), 'fit_common.rackhdapi', 'fit_common.rackhdapi', ({(152, 40, 152, 81): "('/api/2.0/nodes/' + self.__class__.__NODE)"}, {}), "('/api/2.0/nodes/' + self.__class__.__NODE)", False, 'import fit_common\n'), ((170, 19, 170, 82), 'fit_common.rackhdapi', 'fit_common.rackhdapi', ({(170, 40, 170, 81): "('/api/2.0/nodes/' + self.__class__.__NODE)"}, {}), "('/api/2.0/nodes/' + self.__class__.__NODE)", False, 'import fit_common\n'), ((80, 51, 80, 85), 'datetime.datetime.fromtimestamp', 'datetime.fromtimestamp', ({(80, 74, 80, 84): 'start_time'}, {}), '(start_time)', False, 'from datetime import datetime\n'), ((91, 23, 91, 34), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((96, 23, 96, 34), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((164, 81, 164, 92), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((175, 34, 175, 64), 'fit_common.json.dumps', 'fit_common.json.dumps', ({(175, 56, 175, 63): 'PAYLOAD'}, {}), '(PAYLOAD)', False, 'import fit_common\n'), ((191, 63, 191, 74), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((100, 22, 100, 48), 'json.loads', 'json.loads', ({(100, 33, 100, 47): "result['text']"}, {}), "(result['text'])", False, 'import json\n'), ((105, 35, 105, 83), 'json.dumps', 'json.dumps', (), '', False, 'import json\n'), ((92, 61, 92, 93), 'datetime.datetime.fromtimestamp', 'datetime.fromtimestamp', ({(92, 84, 92, 92): 'end_time'}, {}), '(end_time)', False, 'from datetime import datetime\n'), ((97, 58, 97, 90), 'datetime.datetime.fromtimestamp', 'datetime.fromtimestamp', ({(97, 81, 97, 89): 'end_time'}, {}), '(end_time)', False, 'from datetime import datetime\n')]