#!/usr/bin/python3
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright 2021 Raritan Inc. All rights reserved.

import sys
from raritan.rpc import Agent, cascading, HttpException

HTTP_AGENT_TIMEOUT = 5

if len(sys.argv) < 8 or len(sys.argv) > 9:
    print("Usage: link_cascade_units.py IP LOGIN PASSWORD LINK_IDS CASCADE_NODES NODE_LOGIN NODE_PASSWORD [POSITION_DEPENDENT]")
    print("")
    print("  IP                  IP address of primary unit")
    print("  LOGIN               Administrator login for primary unit")
    print("  PASSWORD            Administrator password for primary unit")
    print("  LINK_IDS            Comma-separated list of link IDs that will be used for the nodes")
    print("  CASCADE_NODES       Comma-separated list of port forwarding nodes that should be linked.")
    print("                      The special word \"all\" tries to link as many port forwarding nodes")
    print("                      as possible and stops after the first failure")
    print("  NODE_LOGIN          Administrator login for all nodes")
    print("  NODE_PASSWORD       Administrator password for all nodes")
    print("  POSITION_DEPENDENT  If true, host names that depend on the nodex index will be used.")
    print("                      If false, unique link-local IPv6 addresses will be used for linking")
    print("                      defaults to false (using link-local IPv6 addresses)")
    sys.exit(1)

linkIdsTokens = sys.argv[4].split(",")
linkIds = []

for linkId in linkIdsTokens:
    if linkId.isnumeric() and int(linkId) >= 2 and int(linkId) <= 8:
        linkIds.append(int(linkId))
    else:
        print("Invalid linkId ID: %s" % linkId)
        sys.exit(2)

nodes = []
tryAllNodes = (sys.argv[5] == "all")

if tryAllNodes:
    for i in range(1, 8, 1): # max 7 links possible
        nodes.append(i)
else:
    nodesTokens = sys.argv[5].split(",")
    for node in nodesTokens:
        if node.isnumeric() and int(node) >= 1 and int(node) <= 15:
            nodes.append(int(node))
        else:
            print("Invalid node ID: %s" % node)
            sys.exit(2)

if not tryAllNodes and len(linkIds) != len(nodes):
    print("Error: LINK_IDS and CASCADE_NODES need to be of same length.")
    sys.exit(2)

positionDependent = False
if len(sys.argv) == 9:
    if sys.argv[8] == "true":
        positionDependent = True
    elif sys.argv[8] != "false":
        print("Invalid value for POSITION_DEPENDENT (accepts true/false)")
        sys.exit(2)

ip = sys.argv[1]
login = sys.argv[2]
password = sys.argv[3]
node_login = sys.argv[6]
node_password = sys.argv[7]

error_messages = {
    cascading.CascadeManager.ERR_INVALID_PARAM:             "Invalid parameters",
    cascading.CascadeManager.ERR_UNSUPPORTED_ON_LINK_UNIT:  "Operation not supported on link unit",
    cascading.CascadeManager.ERR_LINK_ID_IN_USE:            "The specified link ID is already in use",
    cascading.CascadeManager.ERR_HOST_IN_USE:               "The specified host is already in use",
    cascading.CascadeManager.ERR_LINK_UNIT_UNREACHABLE:     "Connection to the link unit failed",
    cascading.CascadeManager.ERR_LINK_UNIT_ACCESS_DENIED:   "The credentials for the link unit were invalid",
    cascading.CascadeManager.ERR_LINK_UNIT_REFUSED:         "The remote device refused to become our link unit",
    cascading.CascadeManager.ERR_UNIT_BUSY:                 "This unit is currently busy with handling another request",
    cascading.CascadeManager.ERR_NOT_SUPPORTED:             "Operation not supported on this device",
    cascading.CascadeManager.ERR_PASSWORD_CHANGE_REQUIRED:  "The specified link unit requires a password change",
    cascading.CascadeManager.ERR_PASSWORD_POLICY:           "The new password did not meet the requirements",
    cascading.CascadeManager.ERR_LINK_UNIT_COMM_FAILED:     "Communication with the link unit failed",
    cascading.CascadeManager.ERR_LINK_UNIT_NOT_SUPPORTED:   "Link unit does not support cascading",
    cascading.CascadeManager.ERR_FIRMWARE_VERSION_MISMATCH: "The firmware version of the link unit does not match that of the primary unit"
}

result = ""
try:
    agent = Agent("https", ip, login, password, disable_certificate_verification = True, timeout = HTTP_AGENT_TIMEOUT)
    cm = cascading.CascadeManager("/cascade", agent)
    for i in range(0, len(linkIds), 1): # linkIds could be shorter then nodes if "all" was used
        nodeIndex = nodes[i]
        linkId = linkIds[i]
        print("Add port forwarding node %d with link ID %d" % (nodeIndex, linkId))

        idlRet = cm.addCascadeLinkUnit(linkId, nodeIndex, node_login, node_password, positionDependent)
        if idlRet == 0:
            print("Link unit successfully added.")
        else:
            if idlRet in error_messages:
                print("Adding link unit failed: %s" % error_messages[idlRet])
            else:
                print("Adding link unit failed: unknown error: %d" % idlRet)

            if tryAllNodes:
                print("Could not link port forwarding node %d, terminate." % nodeIndex)
                break

except HttpException as httpExc:
    if "HTTP Error 401" in str(httpExc):
        result = "Authentication failed"
    elif "HTTP Error" in str(httpExc):
        result = "HTTP request failed"
    else:
        result = "HTTP connection failed"
except:
    result = "Unknown error"

print(result)
