openbmctool: Add setting thermal control mode

Allows an user a method to enable a supported thermal control mode.
A list of supported thermal control modes are defined available by the
thermal control application where a user can set the current mode to any
of those that are supported. Each mode is handled implementation
specific by the thermal control application.

Bumped tool version to 1.13

Tested:
    Used to set the current mode on zone 0 of an air cooled wspoon
    Error message presented when invalid zone given
    Error message given for attempting to set to an unsupported mode

Change-Id: I0969e2dc2c8d6cf861d32e85595d4798dee87d10
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/thalerj/openbmctool.py b/thalerj/openbmctool.py
index a9e8092..6647e1d 100755
--- a/thalerj/openbmctool.py
+++ b/thalerj/openbmctool.py
@@ -3529,6 +3529,140 @@
         return connectionErrHandler(args.json, "RequestException", err)
     return res.text
 
+
+def getThermalZones(host, args, session):
+    """
+        Get the available thermal control zones
+        @param host: string, the hostname or IP address of the bmc
+        @param args: contains additional arguments used to get the thermal
+               control zones
+        @param session: the active session to use
+        @return: Session object
+    """
+    url = "https://" + host + "/xyz/openbmc_project/control/thermal/enumerate"
+
+    try:
+        res = session.get(url, headers=jsonHeader, verify=False, timeout=30)
+    except(requests.exceptions.Timeout):
+        return(connectionErrHandler(args.json, "Timeout", None))
+    except(requests.exceptions.ConnectionError) as err:
+        return connectionErrHandler(args.json, "ConnectionError", err)
+    except(requests.exceptions.RequestException) as err:
+        return connectionErrHandler(args.json, "RequestException", err)
+
+    if (res.status_code == 404):
+        return "No thermal control zones found or system is in a" + \
+            " powered off state"
+
+    zonesDict = json.loads(res.text)
+    if not zonesDict['data']:
+        return "No thermal control zones found"
+    for zone in zonesDict['data']:
+        z = ",".join(str(zone.split('/')[-1]) for zone in zonesDict['data'])
+
+    return "Zones: [ " + z + " ]"
+
+
+def getThermalMode(host, args, session):
+    """
+        Get thermal control mode
+        @param host: string, the hostname or IP address of the bmc
+        @param args: contains additional arguments used to get the thermal
+               control mode
+        @param session: the active session to use
+        @param args.zone: the zone to get the mode on
+        @return: Session object
+    """
+    url = "https://" + host + "/xyz/openbmc_project/control/thermal/" + \
+        args.zone
+
+    try:
+        res = session.get(url, headers=jsonHeader, verify=False, timeout=30)
+    except(requests.exceptions.Timeout):
+        return(connectionErrHandler(args.json, "Timeout", None))
+    except(requests.exceptions.ConnectionError) as err:
+        return connectionErrHandler(args.json, "ConnectionError", err)
+    except(requests.exceptions.RequestException) as err:
+        return connectionErrHandler(args.json, "RequestException", err)
+
+    if (res.status_code == 404):
+        return "Thermal control zone(" + args.zone + ") not found or" + \
+            " system is in a powered off state"
+
+    propsDict = json.loads(res.text)
+    if not propsDict['data']:
+        return "No thermal control properties found on zone(" + args.zone + ")"
+    curMode = "Current"
+    supModes = "Supported"
+    result = "\n"
+    for prop in propsDict['data']:
+        if (prop.casefold() == curMode.casefold()):
+            result += curMode + " Mode: " + propsDict['data'][curMode] + "\n"
+        if (prop.casefold() == supModes.casefold()):
+            s = ", ".join(str(sup) for sup in propsDict['data'][supModes])
+            result += supModes + " Modes: [ " + s + " ]\n"
+
+    return result
+
+def setThermalMode(host, args, session):
+    """
+        Set thermal control mode
+        @param host: string, the hostname or IP address of the bmc
+        @param args: contains additional arguments used for setting the thermal
+               control mode
+        @param session: the active session to use
+        @param args.zone: the zone to set the mode on
+        @param args.mode: the mode to enable
+        @return: Session object
+    """
+    url = "https://" + host + "/xyz/openbmc_project/control/thermal/" + \
+        args.zone + "/attr/Current"
+
+    # Check args.mode against supported modes using `getThermalMode` output
+    modes = getThermalMode(host, args, session)
+    modes = os.linesep.join([m for m in modes.splitlines() if m])
+    modes = modes.replace("\n", ";").strip()
+    modesDict = dict(m.split(': ') for m in modes.split(';'))
+    sModes = ''.join(s for s in modesDict['Supported Modes'] if s not in '[ ]')
+    if args.mode.casefold() not in \
+            (m.casefold() for m in sModes.split(',')) or not args.mode:
+        result = ("Unsupported mode('" + args.mode + "') given, " +
+                  "select a supported mode: \n" +
+                  getThermalMode(host, args, session))
+        return result
+
+    data = '{"data":"' + args.mode + '"}'
+    try:
+        res = session.get(url, headers=jsonHeader, verify=False, timeout=30)
+    except(requests.exceptions.Timeout):
+        return(connectionErrHandler(args.json, "Timeout", None))
+    except(requests.exceptions.ConnectionError) as err:
+        return connectionErrHandler(args.json, "ConnectionError", err)
+    except(requests.exceptions.RequestException) as err:
+        return connectionErrHandler(args.json, "RequestException", err)
+
+    if (data and res.status_code != 404):
+        try:
+            res = session.put(url, headers=jsonHeader,
+                              data=data, verify=False,
+                              timeout=30)
+        except(requests.exceptions.Timeout):
+            return(connectionErrHandler(args.json, "Timeout", None))
+        except(requests.exceptions.ConnectionError) as err:
+            return connectionErrHandler(args.json, "ConnectionError", err)
+        except(requests.exceptions.RequestException) as err:
+            return connectionErrHandler(args.json, "RequestException", err)
+
+        if res.status_code == 403:
+            return "The specified thermal control zone(" + args.zone + ")" + \
+                " does not exist"
+
+        return res.text
+    else:
+        return "Setting thermal control mode(" + args.mode + ")" + \
+            " not supported or operation not available(system powered off?)"
+
+
 def createCommandParser():
     """
          creates the parser for the command line along with help for each command and subcommand
@@ -3576,6 +3710,24 @@
     sens_list.add_argument("sensNum", nargs='?', help="The Sensor number to get full details on" )
     sens_list.set_defaults(func=sensor)
 
+    #thermal control commands
+    parser_therm = subparsers.add_parser("thermal", help="Work with thermal control parameters")
+    therm_subparser=parser_therm.add_subparsers(title='subcommands', description='Thermal control actions to work with', help='Valid thermal control actions to work with', dest='command')
+    #thermal control zones
+    parser_thermZones = therm_subparser.add_parser("zones", help="Get a list of available thermal control zones")
+    parser_thermZones.set_defaults(func=getThermalZones)
+    #thermal control modes
+    parser_thermMode = therm_subparser.add_parser("modes", help="Work with thermal control modes")
+    thermMode_sub = parser_thermMode.add_subparsers(title='subactions', description='Work with thermal control modes', help="Work with thermal control modes")
+    #get thermal control mode
+    parser_getThermMode = thermMode_sub.add_parser("get", help="Get current and supported thermal control modes")
+    parser_getThermMode.add_argument('-z', '--zone', required=True, help='Thermal zone to work with')
+    parser_getThermMode.set_defaults(func=getThermalMode)
+    #set thermal control mode
+    parser_setThermMode = thermMode_sub.add_parser("set", help="Set the thermal control mode")
+    parser_setThermMode.add_argument('-z', '--zone', required=True, help='Thermal zone to work with')
+    parser_setThermMode.add_argument('-m', '--mode', required=True, help='The supported thermal control mode')
+    parser_setThermMode.set_defaults(func=setThermalMode)
 
     #sel command
     parser_sel = subparsers.add_parser("sel", help="Work with platform alerts")
@@ -4055,7 +4207,7 @@
          main function for running the command line utility as a sub application
     """
     global toolVersion
-    toolVersion = "1.12"
+    toolVersion = "1.13"
     parser = createCommandParser()
     args = parser.parse_args(argv)