diff --git a/common_utils.py b/common_utils.py index 13771921a449cad0b1d502a4c581871aa362c36e..67ba73848f97b4b5cfcf8b31cb35f740b4932b51 100644 --- a/common_utils.py +++ b/common_utils.py @@ -21,7 +21,6 @@ import settings import xml.parsers as parsers import abc import reports -import subprocess import pam from errors import ReportItemSeverity @@ -67,6 +66,31 @@ def get_cluster_property_from_xml(etree_el): prop["longdesc"] = "" return prop +def get_resource_info_from_xml(cl, et): + #从xml格式中获取单个资源信息 + if cl == "group": + prop = [] + for e in et.findall("primitive"): + prop.append(e.get("id", "")) + if cl == "primitive": + prop = { + "id": et.get("id", ""), + "category": et.get("category", ""), + "class": et.get("class", ""), + "type": et.get("type", ""), + "provider": et.get("provider", "") + } + if cl == "clone": + et = et.find("primitive") + prop = et.get("id", "") + if cl == "meta" or cl == "inst": + prop = {} + for item in et.findall("./nvpair"): + name = item.get("name") + value = item.get("value") + prop[name] = value + return prop + def shell_quote(s): """Return a shell-escaped version of the string *s*.""" if not s: @@ -406,6 +430,31 @@ def get_cluster_properties_definition(): )) return data +def get_resource_info(ct, xml_data): + etree = ET.fromstring(xml_data) + try: + data = {} + #根据分类 + if ct == "primitive": + data = get_resource_info_from_xml("primitive", etree) + if ct == "group": + data["rscs"] = get_resource_info_from_xml("group", etree) + if ct == "clone": + data["rsc_id"] = get_resource_info_from_xml("clone", etree) + #获取meta_attributes + e = etree.find("meta_attributes") + if e: + prop = get_resource_info_from_xml("meta", e) + data["meta_attributes"] = prop + #获取instance_attributes + e = etree.find("instance_attributes") + if e: + prop = get_resource_info_from_xml("inst", e) + data["instance_attributes"] = prop + except ET.ParseError as e: + err("Unable to parse resource information: " + e) + return data + def run_cmd(cmd): try: status, output = subprocess.getstatusoutput(cmd) @@ -419,4 +468,4 @@ def check_auth(username, password): S_PASS = password return True else: - return False + return False \ No newline at end of file diff --git a/resource.py b/resource.py index e1486e71ede09fc01b004bddf4468e17d1bae4ca..2067ba5d3f8512654b0467a025dab9fb1916b68e 100644 --- a/resource.py +++ b/resource.py @@ -1,11 +1,262 @@ -''' +# -*-coding:utf-8-*- +# !/usr/bin/python +# coding: iso-8859-15 import common_utils -def get_resouce_add(): - status, output = common_utils.run_cmd("crm_resource --list-standards") - print(output) -''' +import xml.etree.ElementTree as ET +def update_resource_attributes(rsc_id, data): + #修改资源meta_attributes和instance_attributes属性 + """ + Example data: + { + "category": "primitive", + "actions":[ + { + "interval":"100", + "name":"start" + } + ], + "meta_attributes":{ + "resource-stickiness":"104", + "is-managed":"true", + "target-role":"Started" + }, + "type":"Filesystem", + "id":"iscisi", + "provider":"heartbeat", + "instance_attributes":{ + "device":"/dev/sda1", + "directory":"/var/lib/mysql", + "fstype":"ext4" + }, + "class":"ocf" + } + """ + if data == {} or data is None: + return {'action': False, 'error': _('No input data')} + if "meta_attributes" in data: + meta = data["meta_attributes"] + for key in meta.keys(): + value = meta[key] + cmd_str = "crm_resource -m -r " + str(rsc_id) + " -p " + str(key) + " -v " + str(value) + status, output = common_utils.run_cmd(cmd_str) + if status != 0: + return {'action': False, 'error': _(output)} + if "instance_attributes" in data: + inst = data["instance_attributes"] + cmd_str = "crm_resource --resource" + rsc_id + "--set-parameter " + for key in inst.keys(): + value = inst[key] + cmd = cmd_str + str(key) + " --parameter-value " + str(value) + status, output = common_utils.run_cmd(cmd) + if status != 0: + return {'action': False, 'error': _(output)} + if data["category"] == "group": + #组资源加入新资源 + """ + { + "id":"group1", + "category":"group", + "rscs":["iscisi", "test1" ], + "meta_attributes":{ + "target-role":"Started" + } + } + """ + rscs = data["rscs"] + cmd_str = "pcs resource group add " + str(data["id"]) + for r in rscs: + cmd_str = cmd_str + " " + str(r) + common_utils.run_cmd(cmd_str) + if status != 0: + return {'action': False, 'error': _(output)} + return {'action': True, 'info': _('Update resource attributes Success')} + +def create_resource(data): + #创建新资源,分三种:primitive, group, clone + """ + { + "category": "primitive", //资源范畴 + "meta_attributes":{ + "target-role":"Stopped" + }, + "type":"CTDB", //资源类型 + "class":"ocf", + "provider":"heartbeat", + "instance_attributes":{ + "ctdb_recovery_lock":"lock" + }, + "id":"test1" + } + """ + if data == {} or data is None: + return {'action': False, 'error': _('No input data')} + rsc_id = str(data["id"]) + cate = str(data["category"]) + #判断category + if cate == "primitive": + rsc_type = "\" type=\"" + str(data["type"]) + rsc_class = "\" class=\"" + str(data["class"]) + provider = "\" provider=\"" + str(data["provider"]) + cmd = "cibadmin --create -o resources --xml-text '<" + cmd_str = cmd + cate + " id=\"" + rsc_id + rsc_class + rsc_type + provider + "\">'" + + elif cate == "group": + """ + { + "category": "group", + "id":"tomcat_group", + "rscs":[ + "tomcat6", + "tomcat7" + ], + "meta_attributes":{ + "target-role":"Stopped" + } + } + """ + rscs = data["rscs"] + cmd_str = "pcs resource group add " + rsc_id + for r in rscs: + cmd_str = cmd_str + " " + str(r) + + elif cate == "clone": + """ + { + "category": "clone", + "id":"test5", + "rsc_id":"test4", + "meta_attributes":{ + "target-role":"Stopped" + } + } + *id is unused + """ + ori_id = str(data["rsc_id"]) + cmd_str = "pcs resource clone " + ori_id + + #通用步骤 + status, output = common_utils.run_cmd(cmd_str) + if status != 0: + return {'action': False, 'error': _(output)} + #更新属性 + update_resource_attributes(rsc_id, data) + return {"action":True, "info":"Add " + str(cate) + " resource success"} + + +def resource_action(rsc_id, action, data): + #单个资源操作 + """ + operations: start stop delete cleanup + Example input: + {} + """ + cmd = "crm_resource --resource " + if str(action) == "start": + cmd_str = cmd + str(rsc_id) + " --set-parameter target-role --meta --parameter-value started" + if str(action) == "stop": + cmd_str = cmd + str(rsc_id) + " --set-parameter target-role --meta --parameter-value stopped" + if str(action) == "delete": + #通过id查询category + if get_resource_category(rsc_id) == "group": + cmd_str = "crm_resource -D -r " + str(rsc_id) + " -t group" + else: + cmd_str = "crm_resource --resource " + str(rsc_id) + " --delete -t primitive" + if str(action) == "cleanup": + cmd_str = cmd + str(rsc_id) + " --clear" + + """ + Example input: + migrate: + {"is_force": True, "to_node": "ns187", "period": "PYMDTHM3S"} + """ + if str(action) == "migrate": + #迁移节点 + duration = "" + if "period" in data.keys() and data["period"] != "PYMDT0HMS": + duration = str(data["period"]) #持续时间 + to_node = str(data["to_node"]) #迁移到指定节点 + #提取duration + duration = duration.replace('THMS', '').replace('MS', 'M').replace('HM', 'H').replace('TH', 'T') + duration = duration.replace('PYMD', 'P').replace('MD', 'M').replace('YM', 'Y').replace('PY', 'P') + cmd_head = "crm_resource --resource " + cmd_str = cmd_head + str(rsc_id) + " --move -N " + to_node + " -u " + duration + + """ + unmigrate: + {"rsc_id": "kk1", "is_all_rscs": False} + """ + if str(action) == "unmigrate": + #迁离当前节点 + #is_all_rscs = str(data["is_all_rscs"]) + cmd_str = "crm_resource --resource " + str(data["rsc_id"]) + " --move" + + """ + location: + {"node_level": [{"node": "ns187", "level": "Master Node"}, {"node": "ns188", "level": "Slave 1"}]} + """ + if str(action) == "location": + mn = "" + sn = "" + for item in data["node_level"]: + if item["level"] == "Master Node": + mn = str(item["node"]) + else: + sn = str(item["node"]) + cmd_str = "pcs constraint location " + sn + " prefers " + mn + + """ + colocation: + {"same_node": ["test1234"],"diff_node": ["group_tomcat"]} + """ + if str(action) == "colocation": + sn = str(data["same_node"]) + dn = str(data["diff_node"]) + cmd_str = "pcs constraint colocation add " + dn + " with " + sn + + """ + {"before_rscs": ["test1234"],"after_rscs": ["group-fs-ps"]} + """ + if str(action) == "order": + br = str(data["before_rscs"]) + ar = str(data["after_rscs"]) + cmd_str = "pcs constraint order start " + br + " then " + ar + + #相同操作 + status, output = common_utils.run_cmd(cmd_str) + if status != 0: + return {'action': False, 'error': _(output)} + return {"action": True, "info":"Action on resource success"} + +def get_resource_info_by_rsc_id(rsc_id): + cmd_str = "crm_resource --resource " + str(rsc_id) + " --query-xml" + status, output = common_utils.run_cmd(cmd_str) #输出为xml格式 + if status != 0: + return {'action': False, 'error': _(output)} + #format + xml_data = output.split(":\n") + xml = xml_data[1] + etree = ET.fromstring(xml) + ct = etree.tag + data = common_utils.get_resource_info(ct, xml) #获取资源信息并按格式输出 + data["id"] = str(rsc_id) + data["category"] = str(ct) + ret = {} + if data is not None: + ret['data'] = data + ret['action'] = True + else: + ret['action'] = False + ret['error'] = data + return ret + +def get_all_resource_info(): + cmd_str = "crm_resource --list" + status, output = common_utils.run_cmd(cmd_str) + if status != 0: + return "false" + return output \ No newline at end of file diff --git a/run.py b/run.py index 20a58326bb780d89e6f19bb3e26d0dae4df83304..2c3385293c00725a388109a21f57abc1414d3568 100644 --- a/run.py +++ b/run.py @@ -10,13 +10,14 @@ import common_utils import re from flask import jsonify import node +import resource API_VERSION = "v1" app_name = "ha-api" gettext.bindtextdomain(app_name) gettext.textdomain(app_name) gettext.install(app_name, "/usr/share/locale") -gettext.translation(app_name, "/usr/share/locale", languages=["zh_CN"]).install(True) +#gettext.translation(app_name, "/usr/share/locale", languages=["zh_CN"]).install(True) def create_app(test_config=None): @@ -90,6 +91,35 @@ def create_app(test_config=None): if request.method == 'GET': return json.dumps(node.get_node_info()) + #资源 + @app.route('/api/' + API_VERSION + '/haclusters/1/resources', methods=['GET', 'POST', 'PUT']) + def resources_ops(): + if request.method == 'GET': + return resource.get_all_resource_info() + if request.method == 'POST': #添加资源 primitive group clone + data = request.json + ret = resource.create_resource(data) + return ret + else: + return + #修改资源 + @app.route('/api/' + API_VERSION + '/haclusters/1/resources/', methods=['GET', 'POST', 'PUT']) + def resource_ops_by_id(rsc_id): + if request.method == 'GET': + return json.dumps(resource.get_resource_info_by_rsc_id(rsc_id)) + if request.method == 'PUT': + data = request.json + ret = resource.update_resource_attributes(rsc_id, data) + return json.dumps(ret) + + #单个资源操作start、stop、delete、cleanup、migrate、unmigrate、location、order、colocation + @app.route('/api/' + API_VERSION + '/haclusters/1/resources//', methods=['GET', 'POST', 'PUT']) + def resource_ops(rsc_id, action): + if request.method == 'PUT': #按id修改资源 + data = request.json + ret = resource.resource_action(rsc_id, action, data) + return json.dumps(ret) + #根据id取单个node @app.route('/api/' + API_VERSION + '/haclusters/1/nodes/', methods=['GET', 'POST', 'PUT']) def get_nodeid(nodeid):