参考地址:
(1) [python quickstart](https://grpc.io/docs/quickstart/python.html#run-a-grpc-application) (2) https://blog.csdn.net/sunt2018/article/details/90176015 (3) https://www.jianshu.com/p/43fdfeb105ff?from=timeline&isappinstalled=0
// 说明使用proto3语法定义协议
syntax = "proto3";
package cmdcall;
// rpc服务的名字 ; 后面服务端会用到 , 客户端会用到
service CmdCall {
// Call方法 CallRequest:请求对象 CallResponse:响应对象
rpc Call (CallRequest) returns (CallResponse) {}
rpc CallWithResult (CallRequest) returns (CallResponse) {}
rpc CallAndTransferXmlToJson (CallRequest) returns (CallResponse) {}
rpc CallAndSplitKVToJson (CallRequest) returns (CallResponse) {}
rpc CallAndGetOutput (CallRequest) returns (CallResponse) {}
}
// Call方法请求对象参数(只有一个参数):cmd(string)
message CallRequest {
string cmd = 1;
}
// Call方法响应内容(只有一个响应内容):json(string)
message CallResponse {
string json = 1;
}
pip install grpcio
使用 protoc 编译 proto 文件, 生成 python 语言的实现
安装 python 下的 protoc 编译器
pip install grpcio-tools
python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. cmdcall.proto
备注:
(1)python -m grpc_tools.protoc: python 下的 protoc 编译器通过 python 模块(module) 实现, 所以说这一步非常省心
(2)--python_out=. : 编译生成处理 protobuf 相关的代码的路径, 这里生成到当前目录
(3)--grpc_python_out=. : 编译生成处理 grpc 相关的代码的路径, 这里生成到当前目录
(4)-I : 参数指定协议文件的查找目录,我们都将它们设置为当前目录./cmdcall.proto : proto 文件的路径, 这里的 proto 文件在当前目录
(1)compute_pb2.py里面有客户端和服务端消息序列化类
(2)compute_pb2_grpc.py包含了服务器Servicer类(CmdCallServicer)和客户端Stub类(CmdCallStub),以及待实现的服务RPC接口
【注意】
(1)编译生成的文件,不要随意更改
RPC:
+---proto
| cmdcall.proto
| cmdcall_pb2.py
| cmdcall_pb2_grpc.py
| README.md
| __init__.py
+---rpcClient
| client.py
| __init__.py
+---rpcService
| rpc_service.py
| rpc_util.py
| __init__.py
\---utils
constant.py
exception.py
excute.py
logger.py
net_util.py
util.py
__init__.py
rpc_service.py
import grpc
from concurrent import futures
from proto import cmdcall_pb2_grpc
from rpcService.rpc_util import CmdCallServicer
def daemonize():
# 多线程服务器
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
servicer = CmdCallServicer()
# 注册本地服务
cmdcall_pb2_grpc.add_CmdCallServicer_to_server(servicer, server)
# 监听ip端口
server.add_insecure_port("127.0.0.1:19990")
# 开始接收请求进行服务
server.start()
server.wait_for_termination()
if __name__ == '__main__':
daemonize()
rpc_util.py
import os
import traceback
from json import dumps
from proto import cmdcall_pb2_grpc, cmdcall_pb2
from utils import logger
from utils.constant import KUBESDS_RPC_SERVICE_LOG_FN
from utils.exception import ExecuteException
from utils.excute import Operation
class CmdCallServicer(cmdcall_pb2_grpc.CmdCallServicer):
@staticmethod
def getCmdCallServiceOperation(request, context):
return CmdCallServiceOperation(request, context)
@staticmethod
def deleteCmdCallServiceOperation(callServiceOperation):
del callServiceOperation
def Call(self, request, context):
callServiceOperation = self.getCmdCallServiceOperation(request, context)
result = callServiceOperation.call()
self.deleteCmdCallServiceOperation(callServiceOperation)
return result
def CallWithResult(self, request, context):
callServiceOperation = self.getCmdCallServiceOperation(request, context)
result = callServiceOperation.callWithResult()
self.deleteCmdCallServiceOperation(callServiceOperation)
return result
class CmdCallServiceOperation(object):
def __init__(self, request, context):
self.request = request
self.context = context
self.cmd = str(self.request.cmd)
self.with_result = 'with_result'
self.success_message = 'kubesds-rpc call cmd %s successful.'
self.failure_message = 'kubesds-rpc call cmd %s failure.'
@staticmethod
def get_result(cmd, params=None, with_result=False):
params = {} if params is None else params
logger.debug('CMD: %s' % cmd)
if with_result:
result = Operation(cmd, params, with_result=with_result).execute()
else:
result = Operation(cmd, params).execute()
return result
def call(self):
try:
self.get_result(self.cmd)
return cmdcall_pb2.CallResponse(
json=dumps({'result': {'code': 0, 'msg': self.success_message % self.cmd}, 'data': {}}))
except ExecuteException, e:
logger.debug(traceback.format_exc())
return cmdcall_pb2.CallResponse(
json=dumps({'result': {'code': 1, 'msg': self.failure_message % e.message}, 'data': {}}))
except Exception:
logger.debug(traceback.format_exc())
return cmdcall_pb2.CallResponse(
json=dumps({'result': {'code': 1, 'msg': self.failure_message % traceback.format_exc()}, 'data': {}}))
def callWithResult(self):
try:
result = self.get_result(self.cmd, with_result=True)
if result['result']['code'] == 0:
return cmdcall_pb2.CallResponse(json=dumps(result))
else:
result['result']['msg'] = self.failure_message % result['result']['msg']
return cmdcall_pb2.CallResponse(json=dumps(result))
except ExecuteException, e:
logger.debug(traceback.format_exc())
return cmdcall_pb2.CallResponse(
json=dumps({'result': {'code': 1, 'msg': self.failure_message % e.message}, 'data': {}}))
except Exception:
logger.debug(traceback.format_exc())
return cmdcall_pb2.CallResponse(
json=dumps({'result': {'code': 1, 'msg': self.failure_message % traceback.format_exc()}, 'data': {}}))
excute.py
import os
import subprocess
import traceback
from json import loads, dumps
import xmltodict
from utils import logger
from utils.constant import KUBESDS_RPC_SERVICE_LOG_FN, ERR_NAME
from utils.exception import ExecuteException
logger = logger.set_logger(os.path.basename(__file__), KUBESDS_RPC_SERVICE_LOG_FN)
class Operation(object):
def __init__(self, cmd, params, with_result=False, xml_to_json=False, kv_to_json=False, output=False, rbd=False):
if cmd is None or cmd == "":
raise Exception("plz give me right cmd.")
if not isinstance(params, dict):
raise Exception("plz give me right parameters.")
self.cmd = cmd
self.params = params
self.with_result = with_result
def get_cmd(self):
cmd = self.cmd
for key in self.params.keys():
cmd = "%s --%s %s " % (cmd, key, self.params[key])
return cmd
def execute(self):
cmd = self.get_cmd()
if not cmd:
logger.debug('No CMD to execute.')
return
if self.with_result:
return runCmdWithResult(cmd)
else:
return runCmd(cmd)
def runCmdWithResult(cmd):
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
std_out = p.stdout.readlines()
std_err = p.stderr.readlines()
if std_out:
msg = ''
for index, line in enumerate(std_out):
if not str.strip(line):
continue
msg = msg + str.strip(line)
msg = str.strip(msg)
logger.debug('MSG: ' + msg)
try:
result = loads(msg)
if isinstance(result, dict) and 'result' in result.keys():
if result['result']['code'] != 0:
if std_err:
error_msg = ''
for index, line in enumerate(std_err):
if not str.strip(line):
continue
error_msg = error_msg + str.strip(line)
error_msg = str.strip(error_msg).replace('"', "'")
result['result']['msg'] = '%s. cstor error output: %s' % (
result['result']['msg'], error_msg)
return result
except Exception:
logger.debug(cmd)
logger.debug(traceback.format_exc())
error_msg = ''
for index, line in enumerate(std_err):
if not str.strip(line):
continue
error_msg = error_msg + str.strip(line)
error_msg = str.strip(error_msg)
raise ExecuteException('RunCmdError',
'can not parse cstor-cli output to json----%s. %s' % (msg, error_msg))
if std_err:
msg = ''
for index, line in enumerate(std_err):
msg = msg + line + ', '
logger.debug(cmd)
logger.debug(msg)
logger.debug(traceback.format_exc())
if msg.strip() != '':
raise ExecuteException(ERR_NAME, msg)
finally:
p.stdout.close()
p.stderr.close()
def runCmd(cmd):
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
std_out = p.stdout.readlines()
std_err = p.stderr.readlines()
p.wait()
return_code = p.returncode
# 状态码异常(状态码不为0)才会打印
if return_code != 0:
logger.debug('p.returncode: %d' % p.returncode)
if std_out:
logger.debug(std_out)
if std_err:
msg = ''
for index, line in enumerate(std_err):
msg = msg + line
logger.debug("msg: %s" % msg)
if msg.strip() != '' and p.returncode != 0:
raise ExecuteException(ERR_NAME, msg)
return
finally:
p.stdout.close()
p.stderr.close()
client.py
import os
import traceback
from json import loads
import grpc
from proto import cmdcall_pb2_grpc, cmdcall_pb2
from utils import logger
from utils.exception import ExecuteException
logger = logger.set_logger(os.path.basename(__file__), KUBESDS_RPC_CLIENT_LOG_FN)
class RPCClient(object):
"""RPC服务客户端"""
def __init__(self, cmd):
self.cmd = cmd
self.with_result = 'with_result'
def rpcCall(self):
return self._runRpcCall('', self.cmd)
def rpcCallWithResult(self):
return self._runRpcCall(self.with_result, self.cmd)
def _runRpcCall(self, operation, cmd):
if not cmd:
raise ExecuteException(ERR_NAME, 'cmd is None.')
try:
host = get_docker0_IP()
channel = grpc.insecure_channel("127.0.0.1:19990")
client = cmdcall_pb2_grpc.CmdCallStub(channel)
# ideally, you should have try catch block here too
if operation == self.with_result:
response = client.CallWithResult(cmdcall_pb2.CallRequest(cmd=cmd))
else:
response = client.Call(cmdcall_pb2.CallRequest(cmd=cmd))
result = loads(str(response.json))
if self.with_result:
return result
else:
pass
except grpc.RpcError, e:
logger.debug(traceback.format_exc())
# ouch!
# lets print the gRPC error message
# which is "Length of `Name` cannot be more than 10 characters"
logger.debug(e.details())
# lets access the error code, which is `INVALID_ARGUMENT`
# `type` of `status_code` is `grpc.StatusCode`
status_code = e.code()
# should print `INVALID_ARGUMENT`
logger.debug(status_code.name)
# should print `(3, 'invalid argument')`
logger.debug(status_code.value)
# want to do some specific action based on the error?
if grpc.StatusCode.INVALID_ARGUMENT == status_code:
# do your stuff here
pass
raise ExecuteException(ERR_NAME, "excute cmd: %s failed!" % cmd)
except Exception:
logger.debug(traceback.format_exc())
raise ExecuteException(ERR_NAME, 'can not parse rpc response to json.')