2018-03-09 06:26:38 +03:00
|
|
|
import json
|
|
|
|
from collections import namedtuple
|
2018-03-13 12:24:55 +03:00
|
|
|
|
|
|
|
import requests
|
2018-07-20 05:44:04 +03:00
|
|
|
import urllib3
|
2018-06-12 09:02:57 +03:00
|
|
|
from requests.exceptions import HTTPError
|
2018-06-27 07:36:41 +03:00
|
|
|
from typing import Iterable
|
2018-03-13 12:24:55 +03:00
|
|
|
|
2018-03-09 07:31:51 +03:00
|
|
|
import logging_aux
|
2018-03-09 06:26:38 +03:00
|
|
|
|
2018-07-20 05:44:04 +03:00
|
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
|
|
|
2018-03-09 06:26:38 +03:00
|
|
|
GrowDecision = namedtuple("GrowDecision", "cores_to_grow nodes_to_grow sockets_to_grow")
|
2018-03-22 11:34:56 +03:00
|
|
|
IdleNode = namedtuple("IdleNode", "node_name timestamp server_name")
|
2018-03-09 06:26:38 +03:00
|
|
|
|
2018-06-27 07:36:41 +03:00
|
|
|
|
2018-06-12 12:09:35 +03:00
|
|
|
# TODO: change all method inputs to either all in json format or not
|
2018-03-13 12:24:55 +03:00
|
|
|
|
2018-06-19 10:33:03 +03:00
|
|
|
|
2018-06-27 07:36:41 +03:00
|
|
|
def _return_json_from_res(res):
|
|
|
|
jobj = json.loads(res.content)
|
|
|
|
return jobj
|
|
|
|
|
|
|
|
|
2018-03-20 10:34:50 +03:00
|
|
|
class HpcRestClient(object):
|
2018-06-19 10:33:03 +03:00
|
|
|
DEFAULT_NODEGROUP_TOKEN = "DEFAULT"
|
2018-06-06 10:27:27 +03:00
|
|
|
DEFAULT_COMPUTENODE_TEMPLATE = "Default ComputeNode Template"
|
2018-06-04 12:16:52 +03:00
|
|
|
# auto-scale api set
|
|
|
|
GROW_DECISION_API_ROUTE = "https://{}/HpcManager/api/auto-scale/grow-decision"
|
|
|
|
CHECK_NODES_IDLE_ROUTE = "https://{}/HpcManager/api/auto-scale/check-nodes-idle"
|
|
|
|
# node management api set
|
2018-06-06 11:12:44 +03:00
|
|
|
BRING_NODES_ONLINE_ROUTE = "https://{}/HpcManager/api/nodes/bringOnline"
|
|
|
|
TAKE_NODES_OFFLINE_ROUTE = "https://{}/HpcManager/api/nodes/takeOffline"
|
|
|
|
ASSIGN_NODES_TEMPLATE_ROUTE = "https://{}/HpcManager/api/nodes/assignTemplate"
|
|
|
|
REMOVE_NODES_ROUTE = "https://{}/HpcManager/api/nodes/remove"
|
2018-06-12 12:09:35 +03:00
|
|
|
NODE_STATUS_EXACT_ROUTE = "https://{}/HpcManager/api/nodes/status/getExact"
|
2018-06-11 11:21:28 +03:00
|
|
|
# node group api set
|
|
|
|
NODE_GROUPS_ROOT_ROUTE = "https://{}/HpcManager/api/node-groups"
|
|
|
|
LIST_NODE_GROUPS_ROUTE = NODE_GROUPS_ROOT_ROUTE
|
|
|
|
ADD_NEW_GROUP_ROUTE = NODE_GROUPS_ROOT_ROUTE
|
|
|
|
ADD_NODES_TO_NODE_GROUP_ROUTE = NODE_GROUPS_ROOT_ROUTE.format("{{}}") + "/{group_name}"
|
2018-06-04 12:16:52 +03:00
|
|
|
|
2018-06-14 07:33:40 +03:00
|
|
|
# constants in result
|
|
|
|
NODE_STATUS_NODE_NAME_KEY = "Name"
|
|
|
|
NODE_STATUS_NODE_STATE_KEY = "NodeState"
|
|
|
|
NODE_STATUS_NODE_STATE_ONLINE_VALUE = "Online"
|
|
|
|
NODE_STATUS_NODE_STATE_OFFLINE_VALUE = "Offline"
|
|
|
|
|
|
|
|
NODE_STATUS_NODE_HEALTH_KEY = "NodeHealth"
|
|
|
|
NODE_STATUS_NODE_HEALTH_UNAPPROVED_VALUE = "Unapproved"
|
|
|
|
NODE_STATUS_NODE_GROUP_KEY = "Groups"
|
|
|
|
|
2018-06-26 09:46:31 +03:00
|
|
|
def __init__(self, pem, hostname="localhost"):
|
2018-07-09 10:50:32 +03:00
|
|
|
# type: (str) -> None
|
2018-03-09 06:26:38 +03:00
|
|
|
self.hostname = hostname
|
2018-03-09 07:31:51 +03:00
|
|
|
self.logger = logging_aux.init_logger_aux("hpcframework.restclient", 'hpcframework.restclient.log')
|
2018-06-26 09:46:31 +03:00
|
|
|
self._pem = pem
|
2018-03-09 06:26:38 +03:00
|
|
|
|
2018-06-04 12:16:52 +03:00
|
|
|
def _log_error(self, function_name, res):
|
2018-06-06 10:27:27 +03:00
|
|
|
self.logger.error("{}: status_code:{} content:{}".format(function_name, res.status_code, res.content))
|
2018-06-04 12:16:52 +03:00
|
|
|
|
|
|
|
def _log_info(self, function_name, res):
|
|
|
|
self.logger.info(function_name + ":" + res.content)
|
|
|
|
|
2018-06-14 07:33:40 +03:00
|
|
|
# TODO: consolidate these ceremonies.
|
2018-06-11 11:21:28 +03:00
|
|
|
def _get(self, function_name, function_route, params):
|
|
|
|
headers = {"Content-Type": "application/json"}
|
|
|
|
url = function_route.format(self.hostname)
|
2018-06-26 09:46:31 +03:00
|
|
|
res = requests.get(url, headers=headers, verify=False, params=params, cert=self._pem)
|
2018-06-12 09:02:57 +03:00
|
|
|
try:
|
|
|
|
res.raise_for_status()
|
2018-06-11 11:21:28 +03:00
|
|
|
self._log_info(function_name, res)
|
2018-06-12 09:02:57 +03:00
|
|
|
return res
|
2018-06-19 10:33:03 +03:00
|
|
|
except HTTPError:
|
2018-06-11 11:21:28 +03:00
|
|
|
self._log_error(function_name, res)
|
2018-06-12 09:02:57 +03:00
|
|
|
raise
|
2018-06-11 11:21:28 +03:00
|
|
|
|
2018-06-04 12:16:52 +03:00
|
|
|
def _post(self, function_name, function_route, data):
|
|
|
|
headers = {"Content-Type": "application/json"}
|
|
|
|
url = function_route.format(self.hostname)
|
2018-06-26 09:46:31 +03:00
|
|
|
res = requests.post(url, data=data, headers=headers, verify=False, cert=self._pem)
|
2018-06-12 09:02:57 +03:00
|
|
|
try:
|
|
|
|
res.raise_for_status()
|
2018-06-04 12:16:52 +03:00
|
|
|
self._log_info(function_name, res)
|
2018-06-12 09:02:57 +03:00
|
|
|
return res
|
2018-06-19 10:33:03 +03:00
|
|
|
except HTTPError:
|
2018-06-04 12:16:52 +03:00
|
|
|
self._log_error(function_name, res)
|
2018-06-12 09:02:57 +03:00
|
|
|
raise
|
2018-06-04 12:16:52 +03:00
|
|
|
|
2018-06-06 10:27:27 +03:00
|
|
|
# Starts auto-scale api
|
2018-06-19 10:33:03 +03:00
|
|
|
def get_grow_decision(self, node_group_name=""):
|
2018-06-04 12:16:52 +03:00
|
|
|
url = self.GROW_DECISION_API_ROUTE.format(self.hostname)
|
2018-06-26 09:46:31 +03:00
|
|
|
res = requests.post(url, verify=False, cert=self._pem, timeout=15)
|
2018-03-09 06:26:38 +03:00
|
|
|
if res.ok:
|
|
|
|
self.logger.info(res.content)
|
2018-06-27 07:36:41 +03:00
|
|
|
grow_decision_dict = {k.upper(): v for k, v in json.loads(res.content).items()}
|
2018-06-19 10:33:03 +03:00
|
|
|
if node_group_name == "":
|
|
|
|
jobj = grow_decision_dict[self.DEFAULT_NODEGROUP_TOKEN]
|
|
|
|
elif node_group_name.upper() in grow_decision_dict:
|
|
|
|
jobj = grow_decision_dict[node_group_name.upper()]
|
|
|
|
else:
|
|
|
|
return GrowDecision(0, 0, 0)
|
2018-03-09 06:26:38 +03:00
|
|
|
return GrowDecision(jobj['CoresToGrow'], jobj['NodesToGrow'], jobj['SocketsToGrow'])
|
|
|
|
else:
|
|
|
|
self.logger.error("status_code:{} content:{}".format(res.status_code, res.content))
|
2018-03-13 12:24:55 +03:00
|
|
|
|
2018-03-09 06:26:38 +03:00
|
|
|
def check_nodes_idle(self, nodes):
|
2018-06-26 09:46:31 +03:00
|
|
|
# type: (list[str]) -> list[IdleNode]
|
2018-06-14 07:33:40 +03:00
|
|
|
data = json.dumps(nodes)
|
2018-06-19 10:33:03 +03:00
|
|
|
res = self._post(self.check_nodes_idle.__name__, self.CHECK_NODES_IDLE_ROUTE, data)
|
2018-06-12 09:02:57 +03:00
|
|
|
jobjs = json.loads(res.content)
|
|
|
|
return [IdleNode(idle_info['NodeName'], idle_info['TimeStamp'], idle_info['ServerName']) for idle_info in jobjs]
|
2018-03-09 06:26:38 +03:00
|
|
|
|
2018-06-06 10:27:27 +03:00
|
|
|
# Starts node management api
|
2018-06-06 11:12:44 +03:00
|
|
|
def bring_nodes_online(self, nodes):
|
2018-06-12 12:09:35 +03:00
|
|
|
data = json.dumps(nodes)
|
2018-06-19 10:33:03 +03:00
|
|
|
res = self._post(self.bring_nodes_online.__name__, self.BRING_NODES_ONLINE_ROUTE, data)
|
2018-06-27 07:36:41 +03:00
|
|
|
return _return_json_from_res(res)
|
2018-03-13 12:24:55 +03:00
|
|
|
|
2018-06-06 11:12:44 +03:00
|
|
|
def take_nodes_offline(self, nodes):
|
2018-06-12 12:09:35 +03:00
|
|
|
data = json.dumps(nodes)
|
2018-06-19 10:33:03 +03:00
|
|
|
res = self._post(self.take_nodes_offline.__name__, self.TAKE_NODES_OFFLINE_ROUTE, data)
|
2018-06-27 07:36:41 +03:00
|
|
|
return _return_json_from_res(res)
|
2018-06-06 10:27:27 +03:00
|
|
|
|
2018-06-12 12:09:35 +03:00
|
|
|
def assign_default_compute_node_template(self, nodename_arr):
|
|
|
|
return self.assign_nodes_template(nodename_arr, self.DEFAULT_COMPUTENODE_TEMPLATE)
|
|
|
|
|
2018-06-06 11:12:44 +03:00
|
|
|
def assign_nodes_template(self, nodename_arr, template_name):
|
2018-06-06 10:27:27 +03:00
|
|
|
params = json.dumps({"nodeNames": nodename_arr, "templateName": template_name})
|
2018-06-12 09:02:57 +03:00
|
|
|
res = self._post(self.assign_nodes_template.__name__, self.ASSIGN_NODES_TEMPLATE_ROUTE, params)
|
2018-06-27 07:36:41 +03:00
|
|
|
return _return_json_from_res(res)
|
2018-06-06 11:12:44 +03:00
|
|
|
|
2018-06-11 11:21:28 +03:00
|
|
|
def remove_nodes(self, nodes):
|
2018-06-14 07:33:40 +03:00
|
|
|
data = json.dumps(nodes)
|
|
|
|
res = self._post(self.remove_nodes.__name__, self.REMOVE_NODES_ROUTE, data)
|
2018-06-27 07:36:41 +03:00
|
|
|
return _return_json_from_res(res)
|
2018-06-06 10:27:27 +03:00
|
|
|
|
2018-06-12 12:09:35 +03:00
|
|
|
def get_node_status_exact(self, node_names):
|
2018-06-27 07:36:41 +03:00
|
|
|
# type: (Iterable[str]) -> list[dict[str, any]]
|
2018-06-12 12:09:35 +03:00
|
|
|
params = json.dumps({"nodeNames": node_names})
|
|
|
|
res = self._post(self.get_node_status_exact.__name__, self.NODE_STATUS_EXACT_ROUTE, params)
|
2018-06-27 07:36:41 +03:00
|
|
|
return _return_json_from_res(res)
|
2018-06-12 12:09:35 +03:00
|
|
|
|
2018-06-11 11:21:28 +03:00
|
|
|
# Starts node group api
|
2018-06-19 10:33:03 +03:00
|
|
|
def list_node_groups(self, group_name=""):
|
2018-06-11 11:21:28 +03:00
|
|
|
params = {}
|
|
|
|
if group_name != "":
|
|
|
|
params['nodeGroupName'] = group_name
|
2018-06-19 10:33:03 +03:00
|
|
|
res = self._get(self.list_node_groups.__name__, self.LIST_NODE_GROUPS_ROUTE, params)
|
2018-06-27 07:36:41 +03:00
|
|
|
return _return_json_from_res(res)
|
2018-06-11 11:21:28 +03:00
|
|
|
|
|
|
|
def add_node_group(self, group_name, group_description=""):
|
|
|
|
params = json.dumps({"name": group_name, "description": group_description})
|
2018-06-12 09:02:57 +03:00
|
|
|
res = self._post(self.add_node_group.__name__, self.ADD_NEW_GROUP_ROUTE, params)
|
2018-06-27 07:36:41 +03:00
|
|
|
return _return_json_from_res(res)
|
2018-06-11 11:21:28 +03:00
|
|
|
|
|
|
|
def add_node_to_node_group(self, group_name, node_names):
|
2018-06-19 10:33:03 +03:00
|
|
|
res = self._post(self.add_node_to_node_group.__name__, self.ADD_NODES_TO_NODE_GROUP_ROUTE.format(
|
|
|
|
group_name=group_name), json.dumps(node_names))
|
2018-06-27 07:36:41 +03:00
|
|
|
return _return_json_from_res(res)
|