utils.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. import logging
  2. import json
  3. import sys
  4. import time
  5. import requests
  6. import asyncio
  7. from aliyunsdkcore.client import AcsClient
  8. from aliyunsdkecs.request.v20140526.RunInstancesRequest import RunInstancesRequest
  9. from aliyunsdkecs.request.v20140526.DescribeInstancesRequest import DescribeInstancesRequest
  10. from aliyunsdkecs.request.v20140526.DescribeNetworkInterfacesRequest import DescribeNetworkInterfacesRequest
  11. from aliyunsdkslb.request.v20140515.DescribeLoadBalancerAttributeRequest import DescribeLoadBalancerAttributeRequest
  12. from aliyunsdkecs.request.v20140526.RunCommandRequest import RunCommandRequest
  13. from aliyunsdkecs.request.v20140526.SendFileRequest import SendFileRequest
  14. from aliyunsdkecs.request.v20140526.StopInstancesRequest import StopInstancesRequest
  15. from aliyunsdkecs.request.v20140526.DeleteInstancesRequest import DeleteInstancesRequest
  16. from aliyunsdkecs.request.v20140526.DescribeInstanceStatusRequest import DescribeInstanceStatusRequest
  17. from aliyunsdkcore.request import CommonRequest
  18. logging.basicConfig(level=logging.INFO,
  19. format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
  20. datefmt='%a, %d %b %Y %H:%M:%S')
  21. def send_msg_to_feishu(webhook, key_word, msg_text):
  22. """发送消息到飞书"""
  23. headers = {'Content-Type': 'application/json'}
  24. payload_message = {
  25. "msg_type": "text",
  26. "content": {
  27. "text": '{}: {}'.format(key_word, msg_text)
  28. }
  29. }
  30. response = requests.request('POST', url=webhook, headers=headers, data=json.dumps(payload_message))
  31. logging.info(response.text)
  32. def connect_client(access_key_id, access_key_secret, region_id):
  33. """
  34. 初始化账号,连接客户端
  35. :param access_key_id: access key Id, type-string
  36. :param access_key_secret: access key secret, type-string
  37. :param region_id: region_id
  38. :return: clt
  39. """
  40. try:
  41. clt = AcsClient(ak=access_key_id, secret=access_key_secret, region_id=region_id)
  42. return clt
  43. except Exception as e:
  44. # 失败,记录报错信息,发送通知,停止并退出
  45. logging.error(e)
  46. sys.exit()
  47. def build_create_instances_request(image_id, vswitch_id, security_group_id, zone_id, instance_type, instance_name,
  48. disk_size, disk_category, key_pair_name, tags):
  49. """
  50. 购买服务器参数配置
  51. :param image_id: 使用的镜像信息 type-string
  52. :param vswitch_id: 选择的交换机 type-string
  53. :param security_group_id: 当前vpc类型的安全组 type-string
  54. :param zone_id: 服务器所在区域 type-string
  55. :param instance_type: 实例规格 type-string
  56. :param instance_name: 实例命名 type-string
  57. :param disk_size: 磁盘大小,单位:G,type-string
  58. :param disk_category: 磁盘类型 type-string
  59. :param key_pair_name: 密钥对名称 type-string
  60. :param tags: 标签 type-list, eg: [{"Key": "ecs", "Value": "rov-server.prod"}, ...]
  61. :return: request
  62. """
  63. request = RunInstancesRequest()
  64. request.set_ImageId(image_id)
  65. request.set_VSwitchId(vswitch_id)
  66. request.set_SecurityGroupId(security_group_id)
  67. request.set_ZoneId(zone_id)
  68. request.set_InstanceType(instance_type)
  69. request.set_InstanceName(instance_name)
  70. request.set_SystemDiskSize(disk_size)
  71. request.set_SystemDiskCategory(disk_category)
  72. request.set_KeyPairName(key_pair_name)
  73. request.set_Tags(tags)
  74. return request
  75. def send_request(client, request):
  76. """
  77. 发送API请求
  78. :param client: 客户端连接
  79. :param request: 请求配置
  80. :return: response
  81. """
  82. request.set_accept_format('json')
  83. try:
  84. response = client.do_action_with_exception(request)
  85. response = json.loads(response)
  86. # logging.info(response)
  87. return response
  88. except Exception as e:
  89. # 失败,记录报错信息,发送通知,停止并退出
  90. logging.error(e)
  91. sys.exit()
  92. def check_instance_running(client, instance_ids):
  93. """
  94. 检查服务器运行状态
  95. :param client: 客户端连接
  96. :param instance_ids: 实例id列表, type-list
  97. :return: running_count,Status为Running的实例数
  98. """
  99. try:
  100. request = DescribeInstancesRequest()
  101. request.set_InstanceIds(json.dumps(instance_ids))
  102. response = send_request(client=client, request=request)
  103. if response.get('Code') is None:
  104. instances_list = response.get('Instances').get('Instance')
  105. running_count = 0
  106. running_instances = []
  107. for instance_detail in instances_list:
  108. if instance_detail.get('Status') == "Running":
  109. running_count += 1
  110. running_instances.append(instance_detail.get('InstanceId'))
  111. return running_count, running_instances
  112. else:
  113. # 失败,记录报错信息,发送通知,停止并退出
  114. logging.error(response)
  115. sys.exit()
  116. except Exception as e:
  117. # 失败,记录报错信息,发送通知,停止并退出
  118. logging.error(e)
  119. sys.exit()
  120. def create_multiple_instances(amount, client,
  121. image_id, vswitch_id, security_group_id, zone_id, instance_type, instance_name,
  122. disk_size, disk_category, key_pair_name, tags):
  123. """
  124. 创建多个ECS实例
  125. :param amount: 创建实例数 type-int 取值范围:[1, 100]
  126. :param client: 购买机器客户端连接
  127. :param image_id: 使用的镜像信息 type-string
  128. :param vswitch_id: 选择的交换机 type-string
  129. :param security_group_id: 当前vpc类型的安全组 type-string
  130. :param zone_id: 服务器所在区域 type-string
  131. :param instance_type: 实例规格 type-string
  132. :param instance_name: 实例命名 type-string
  133. :param disk_size: 磁盘大小,单位:G,type-string
  134. :param disk_category: 磁盘类型 type-string
  135. :param key_pair_name: 密钥对名称 type-string
  136. :param tags: 标签 type-list, eg: [{"Key": "ecs", "Value": "rov-server.prod"}, ...]
  137. :return:
  138. """
  139. logging.info(f"create instances start, request amount: {amount}.")
  140. # 1. 连接客户端
  141. # create_instances_clt = connect_client(
  142. # access_key_id=access_key_id, access_key_secret=access_key_secret, region_id=region_id
  143. # )
  144. # 2. 请求参数配置
  145. request = build_create_instances_request(
  146. image_id=image_id, vswitch_id=vswitch_id, security_group_id=security_group_id, zone_id=zone_id,
  147. instance_type=instance_type, instance_name=instance_name, disk_size=disk_size, disk_category=disk_category,
  148. key_pair_name=key_pair_name, tags=tags
  149. )
  150. request.set_Amount(amount)
  151. # 3. 发送API请求,购买机器并启动
  152. response = send_request(client=client, request=request)
  153. if response.get('Code') is None:
  154. instance_ids = response.get('InstanceIdSets').get('InstanceIdSet')
  155. logging.info(f"success amount: {len(instance_ids)}, instance ids: {instance_ids}.")
  156. # 获取机器运行状态
  157. running_amount = 0
  158. while running_amount < amount:
  159. time.sleep(10)
  160. running_amount, running_instances = check_instance_running(client=client, instance_ids=instance_ids)
  161. logging.info(f"running amount: {running_amount}, running instances: {running_instances}.")
  162. return instance_ids
  163. else:
  164. # 失败,记录报错信息,发送通知,停止并退出
  165. logging.error(response)
  166. sys.exit()
  167. def run_command(client, instance_ids, command):
  168. """
  169. 批量执行命令
  170. :param client: 客户端连接
  171. :param instance_ids: 实例id列表, type-list
  172. :param command: 命令 type-string
  173. :return:
  174. """
  175. request = RunCommandRequest()
  176. request.set_accept_format('json')
  177. request.set_Type("RunShellScript")
  178. request.set_CommandContent(command)
  179. request.set_InstanceIds(instance_ids)
  180. response = send_request(client=client, request=request)
  181. logging.info(response)
  182. def get_instance_ids(client, slb_id):
  183. """
  184. 获取slb下所有服务器instanceId
  185. :param client: 客户端连接
  186. :param slb_id: 负载均衡id type-string
  187. :return: instance_ids type-list
  188. """
  189. request = DescribeLoadBalancerAttributeRequest()
  190. request.set_accept_format('json')
  191. request.set_LoadBalancerId(slb_id)
  192. response = send_request(client=client, request=request)
  193. instance_ids = [instance["ServerId"] for instance in response["BackendServers"]["BackendServer"]]
  194. return instance_ids
  195. def get_ip_address(client, instance_id):
  196. """
  197. 获取实例IP地址
  198. :param client: 客户端连接
  199. :param instance_id: 实例id, type-string
  200. :return: ip_address, type-string
  201. """
  202. request = DescribeNetworkInterfacesRequest()
  203. request.set_accept_format('json')
  204. request.set_InstanceId(instance_id)
  205. response = send_request(client=client, request=request)
  206. ip_address = response['NetworkInterfaceSets']['NetworkInterfaceSet'][0]['PrivateIpAddress']
  207. return ip_address
  208. def set_weight_for_instances(client, slb_id, instance_id_list, weight):
  209. """
  210. 同时设置多台服务器的slb权重,权重一样
  211. :param client: 客户端连接
  212. :param slb_id: slb_id
  213. :param instance_id_list: 服务器id list
  214. :param weight: 权重值
  215. :return: None
  216. """
  217. BackendServers = [{"ServerId": instance_id, "Weight": weight} for instance_id in instance_id_list]
  218. request = CommonRequest()
  219. request.set_accept_format('json')
  220. request.set_domain('slb.aliyuncs.com')
  221. request.set_version('2014-05-15')
  222. request.set_method('POST')
  223. request.set_action_name('SetBackendServers')
  224. request.add_query_param('BackendServers', BackendServers)
  225. request.add_query_param('LoadBalancerId', slb_id)
  226. response = send_request(client=client, request=request)
  227. return response
  228. def send_file_to_ecs(client, instance_id_list, target_dir, name, content):
  229. """
  230. 发送文件到ecs
  231. :param client:
  232. :param instance_id_list:
  233. :param target_dir: 文件存放目录 type-string
  234. :param name: 文件名 type-string
  235. :param content: 文件内容 type-string
  236. :return:
  237. """
  238. request = SendFileRequest()
  239. request.set_Content(content)
  240. request.set_TargetDir(target_dir)
  241. request.set_Name(name)
  242. request.set_Overwrite(True)
  243. request.set_InstanceIds(instance_id_list)
  244. response = send_request(client=client, request=request)
  245. return response
  246. def stop_instances(client, instance_ids, force_stop=False):
  247. """
  248. 停止实例
  249. :param client:
  250. :param instance_ids: 实例ID, type-list
  251. :param force_stop: 是否强制关机, True-强制关机, False-正常关机, type-bool
  252. :return:
  253. """
  254. request = StopInstancesRequest()
  255. request.set_InstanceIds(instance_ids)
  256. request.set_ForceStop(force_stop)
  257. response = send_request(client=client, request=request)
  258. return response
  259. def release_instances(client, instance_ids, force=False):
  260. """
  261. 释放实例
  262. :param client:
  263. :param instance_ids: instance_id, type-list
  264. :param force: 是否强制释放, True-强制释放, False-正常释放, type-bool
  265. :return:
  266. """
  267. request = DeleteInstancesRequest()
  268. request.set_InstanceIds(instance_ids)
  269. request.set_Force(force)
  270. response = send_request(client=client, request=request)
  271. return response
  272. def get_instances_status(client, instance_ids):
  273. """
  274. 获取实例运行状态
  275. :param client:
  276. :param instance_ids: instance_id, type-liist
  277. :return:
  278. """
  279. request = DescribeInstanceStatusRequest()
  280. request.set_InstanceIds(instance_ids)
  281. response = send_request(client=client, request=request)
  282. return response
  283. def rov_server_health_check(client, instance_id, max_wait_time=None):
  284. """
  285. 服务健康检查
  286. :param client: 客户端连接
  287. :param instance_id: instanceId
  288. :param max_wait_time: 最长等待时间,单位:s
  289. :return:
  290. """
  291. global health_instances
  292. start_time = time.time()
  293. ip_address = get_ip_address(client=client, instance_id=instance_id)
  294. while True:
  295. health_check_url = f"http://{ip_address}:5001/healthcheck"
  296. try:
  297. http_code = requests.get(health_check_url).status_code
  298. except:
  299. logging.info("images is downloading")
  300. http_code = 0
  301. if http_code == 200:
  302. health_instances.append((instance_id, ip_address))
  303. logging.info(f"health check success, instance: {instance_id}/{ip_address}")
  304. break
  305. elif max_wait_time is not None:
  306. now = time.time()
  307. if (now - start_time) >= max_wait_time:
  308. logging.info(f"health check error, instance: {instance_id}/{ip_address}")
  309. break
  310. else:
  311. time.sleep(10)
  312. else:
  313. time.sleep(10)
  314. def set_instance_weight_process(client, slb_id, instance_id_list, weight_list):
  315. """
  316. 修改服务器的权重值
  317. :param client: slb客户端连接
  318. :param slb_id: slb id
  319. :param instance_id_list: instance id list
  320. :param weight_list: 权重修改列表 type-list [(weight, sleep_time), ...]
  321. :return:
  322. """
  323. for weight, sleep_time in weight_list:
  324. flag = True
  325. while flag:
  326. try:
  327. set_weight_for_instances(client=client, slb_id=slb_id, instance_id_list=instance_id_list, weight=weight)
  328. time.sleep(sleep_time)
  329. flag = False
  330. except Exception as e:
  331. time.sleep(10)
  332. continue