1.添加对于license排队的支持,不过在编码中
This commit is contained in:
parent
243376e785
commit
552828ba21
267
license.py
Normal file
267
license.py
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
import json
|
||||||
|
import queue
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import asyncio
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from typing import Dict, List
|
||||||
|
from datetime import datetime
|
||||||
|
from utils import *
|
||||||
|
import logger
|
||||||
|
log = logger.get_logger()
|
||||||
|
|
||||||
|
def _get_ip_address(domain):
|
||||||
|
try:
|
||||||
|
ip_address = socket.gethostbyname(domain)
|
||||||
|
# log.debug(f"get_ip_address {domain} {ip_address}")
|
||||||
|
return ip_address
|
||||||
|
except socket.gaierror as e:
|
||||||
|
# print(f"Error retrieving IP address: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_valid_licenses():
|
||||||
|
lic_id = 0
|
||||||
|
while True:
|
||||||
|
lic_id += 1
|
||||||
|
domain = f"szmaslic{lic_id:02d}"
|
||||||
|
if _get_ip_address(domain) is None:
|
||||||
|
break
|
||||||
|
yield "27003@" + domain
|
||||||
|
|
||||||
|
class UsedInfo:
|
||||||
|
def __init__(self):
|
||||||
|
self.user = ''
|
||||||
|
self.host = ''
|
||||||
|
self.lic = ''
|
||||||
|
self.start_time:datetime = None
|
||||||
|
def __str__(self):
|
||||||
|
time = datetime.now() - self.start_time
|
||||||
|
login_time = (time.days * 24 + time.seconds / 3600.0)
|
||||||
|
obj = {
|
||||||
|
"user":self.user,
|
||||||
|
"host":self.host,
|
||||||
|
'lic':self.lic,
|
||||||
|
'login':f'{login_time:.2f}h'
|
||||||
|
}
|
||||||
|
return str(obj)
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
class LicenseInfo:
|
||||||
|
def __init__(self, name='', total=0, used=0):
|
||||||
|
self.name = name
|
||||||
|
self.total = total
|
||||||
|
self.used = used
|
||||||
|
self.used_info:List[UsedInfo] = []
|
||||||
|
|
||||||
|
def available(self):
|
||||||
|
return self.total - self.used
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
obj = {
|
||||||
|
"name":self.name,
|
||||||
|
"total":self.total,
|
||||||
|
"used":self.used,
|
||||||
|
"used_info":self.used_info
|
||||||
|
}
|
||||||
|
return str(obj)
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.wait_queue = queue.Queue()
|
||||||
|
self.ack_queue = queue.Queue()
|
||||||
|
self.licenses_stat = LicenseStatistic()
|
||||||
|
self.licenses:Dict[str,LicenseInfo] = {}
|
||||||
|
#启动定时获取
|
||||||
|
self.licenses_stat.lic_info_updater()
|
||||||
|
|
||||||
|
def lic_info_updater(self):
|
||||||
|
self.update()
|
||||||
|
if self.wait_queue.qsize() > 0:
|
||||||
|
for lic, available_cnt in self.licenses_stat.available():
|
||||||
|
for i in range(available_cnt):
|
||||||
|
req = self.wait_queue.get()
|
||||||
|
if req == None:
|
||||||
|
return
|
||||||
|
req["lic"] = lic
|
||||||
|
self.ack_queue.put(req)
|
||||||
|
threading.Timer(10, self.lic_info_updater).start() #每间隔一段时间更新一次lic的使用情况
|
||||||
|
|
||||||
|
def _in_wait_queue(self, user):
|
||||||
|
return any(user == _.user for _ in list(self.wait_queue))
|
||||||
|
|
||||||
|
def request(self, user:str, host:str):
|
||||||
|
#如果已经是在线的状态就不允许申请
|
||||||
|
if any(user == _.user for _ in self.licenses_stat.online_users()):
|
||||||
|
log.warning(f"{user} is online, please try again later")
|
||||||
|
return False
|
||||||
|
#如果是已经在队列中则返回等待
|
||||||
|
if self._in_wait_queue(user):
|
||||||
|
return None
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"user":user,
|
||||||
|
"host":host,
|
||||||
|
"start_time":time.now()
|
||||||
|
}
|
||||||
|
self.wait_queue.put(data)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def release(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_wait_queue(self):
|
||||||
|
return [_ for _ in self.wait_queue.queue]
|
||||||
|
|
||||||
|
class LicenseStatistic:
|
||||||
|
def __init__(self):
|
||||||
|
self.licenses:Dict[str,LicenseInfo] = {}
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
# 更新license信息,使用异步获取
|
||||||
|
tasks = []
|
||||||
|
for lic in _get_valid_licenses():
|
||||||
|
log.trace(f'find valid lic: {lic}')
|
||||||
|
tasks.append(get_license_used_info(lic))
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
infos = loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
|
||||||
|
for info in infos:
|
||||||
|
if info is not None:
|
||||||
|
self.licenses[info.name] = info
|
||||||
|
if not self.licenses:
|
||||||
|
log.warning("not vcast lic found")
|
||||||
|
def get_lic_list(self):
|
||||||
|
# 获取所有的license名称
|
||||||
|
return self.licenses.keys()
|
||||||
|
|
||||||
|
def available(self):
|
||||||
|
# 获取所有的可用数量
|
||||||
|
for _,lic in self.licenses.items():
|
||||||
|
value = lic.available()
|
||||||
|
if value > 0:
|
||||||
|
yield (lic.name, value) #lic的name和个数
|
||||||
|
|
||||||
|
def online_users(self):
|
||||||
|
# 获取在线用户
|
||||||
|
for used_info in self.used():
|
||||||
|
yield used_info.user #返回的是user name
|
||||||
|
|
||||||
|
def used(self):
|
||||||
|
# 获取所有的已使用信息
|
||||||
|
for _,info in self.licenses.items():
|
||||||
|
for used in info.used_info:
|
||||||
|
yield used #返回的是UsedInfo对象
|
||||||
|
|
||||||
|
def sorted(self):
|
||||||
|
info = {}
|
||||||
|
for used_info in self.used():
|
||||||
|
time = datetime.now() - used_info.start_time
|
||||||
|
login_time = (time.days * 24 + time.seconds / 3600.0)
|
||||||
|
info[used_info.user] = login_time
|
||||||
|
sorted_info = dict(sorted(info.items(), key=lambda item: item[1], reverse=True))
|
||||||
|
return sorted_info #返回的是字典,key为username value为登录时间
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
string = str(self.licenses).replace("'", '"')
|
||||||
|
log.info(string)
|
||||||
|
obj = json.loads(string)
|
||||||
|
string = json.dumps(obj, indent = 4)
|
||||||
|
return string
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
"""
|
||||||
|
{"27003@szmaslic03": {"name": "27003@szmaslic03", "total": 2, "used": 1, "used_info": "[{"name": "haoxiang.ran", "host": "szl3bc12808", "lic": "szmaslic03", "login": "0.41"}]"}, "27003@szmaslic04": {"name": "27003@szmaslic04", "total": 2, "used": 1, "used_info": "[{"name": "macro.yang", "host": "szl3bc12804", "lic": "szmaslic04", "login": "52.28"}]"}, "27003@szmaslic06": {"name": "27003@szmaslic06", "total": 2, "used": 2, "used_info": "[{"name": "zhenhao.he", "host": "szl3bc12804", "lic": "szmaslic06", "login": "0.20"}, {"name": "phillip.chan", "host": "szl3bc12810", "lic": "szmaslic06", "login": "0.16"}]"}, "27003@szmaslic07": {"name": "27003@szmaslic07", "total": 2, "used": 1, "used_info": "[{"name": "zhiyi.li", "host": "szl3bc12806", "lic": "szmaslic07", "login": "2.96"}]"}, "27003@szmaslic08": {"name": "27003@szmaslic08", "total": 2, "used": 2, "used_info": "[{"name": "soo.liu", "host": "szl3bc12809", "lic": "szmaslic08", "login": "6.55"}, {"name": "haichao.ou", "host": "szl3bc12806", "lic": "szmaslic08", "login": "5.93"}]"}, "27003@szmaslic09": {"name": "27003@szmaslic09", "total": 2, "used": 2, "used_info": "[{"name": "louie.liang", "host": "szl3bc12810", "lic": "szmaslic09", "login": "8.31"}, {"name": "zabbix", "host": "szl3bc06409", "lic": "szmaslic09", "login": "0.90"}]"}, "27003@szmaslic10": {"name": "27003@szmaslic10", "total": 2, "used": 2, "used_info": "[{"name": "harvey.li", "host": "szl3bc12810", "lic": "szmaslic10", "login": "57.38"}, {"name": "kw.hu", "host": "szl3bc12809", "lic": "szmaslic10", "login": "1.41"}]"}}
|
||||||
|
"""
|
||||||
|
async def get_license_used_info(lic):
|
||||||
|
server_used_info = f"timeout 1 /tools/software/vcast/flexlm/lmutil lmstat -a -c {lic} -a"
|
||||||
|
ret,out,err = shell(server_used_info, checkret=False)
|
||||||
|
# log.info(f"get_license_used_info {lic} {out} {ret} {err}")
|
||||||
|
if ret != 0:
|
||||||
|
return None
|
||||||
|
""" data like:
|
||||||
|
Users of VCAST_C_ENT_0800: (Total of 2 licenses issued; Total of 2 licenses in use)
|
||||||
|
|
||||||
|
"VCAST_C_ENT_0800" v23, vendor: vector, expiry: permanent(no expiration date)
|
||||||
|
vendor_string: CUST:64925:
|
||||||
|
floating license
|
||||||
|
|
||||||
|
zhenhao.he szl3bc12804 /dev/tty (v23) (szmaslic06/27003 337), start Thu 1/2 8:52
|
||||||
|
phillip.chan szl3bc12810 /dev/tty (v23) (szmaslic06/27003 233), start Thu 1/2 14:59
|
||||||
|
|
||||||
|
Users of CCAST_0801: (Total of 2 licenses issued; Total of 0 licenses in use)
|
||||||
|
|
||||||
|
"""
|
||||||
|
status = 'idle'
|
||||||
|
info = LicenseInfo(lic)
|
||||||
|
for line in out.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if status == 'idle':
|
||||||
|
#Error getting status: Cannot find license file. (-1,359:2 "No such file or directory")
|
||||||
|
if line.startswith("Error getting status"):
|
||||||
|
log.warning(f"{lic}")
|
||||||
|
return None
|
||||||
|
if not line.startswith("Users of VCAST_C_ENT_0800:"):
|
||||||
|
continue
|
||||||
|
status = 'start'
|
||||||
|
#[Users of VCAST_C_ENT_0800: (Total of 2 licenses issued; Total of 1 license in use)
|
||||||
|
pattern = r"\(Total of (\d+).*Total of (\d+).*\)"
|
||||||
|
ret = re.findall(pattern, line)
|
||||||
|
if not ret:
|
||||||
|
log.warning(f"{lic} parser [{line}] fail")
|
||||||
|
return None
|
||||||
|
(total, used) = ret[0]
|
||||||
|
info.total = int(total)
|
||||||
|
info.used = int(used)
|
||||||
|
elif status == 'start':
|
||||||
|
#zhenhao.he szl3bc12804 /dev/tty (v23) (szmaslic06/27003 337), start Thu 1/2 8:52
|
||||||
|
pattern = '(\S+) (\S+).*\((\S+)/.*\), start (.*)'
|
||||||
|
ret = re.findall(pattern, line)
|
||||||
|
if not ret:
|
||||||
|
continue
|
||||||
|
used = UsedInfo()
|
||||||
|
(used.user,used.host,used.lic,time_string) = ret[0]
|
||||||
|
#处理跨年的情况
|
||||||
|
year = datetime.now().year
|
||||||
|
start_time = datetime.strptime(f"{year} {time_string}", "%Y %a %m/%d %H:%M")
|
||||||
|
if start_time > datetime.now():
|
||||||
|
year -= 1
|
||||||
|
start_time = datetime.strptime(f"{year} {time_string}", "%Y %a %m/%d %H:%M")
|
||||||
|
used.start_time = start_time
|
||||||
|
# log.info(f"{lic} => {used}")
|
||||||
|
info.used_info.append(used)
|
||||||
|
# Next section
|
||||||
|
elif line.startswith('Users of '):
|
||||||
|
break
|
||||||
|
return info
|
||||||
|
|
||||||
|
def draw_time_grpah(license_manager):
|
||||||
|
info = license_manager.sorted()
|
||||||
|
max_time = max(info.values())
|
||||||
|
name_len = max([len(_) for _ in info.keys()])
|
||||||
|
top = 100
|
||||||
|
for user, time in info.items():
|
||||||
|
curr = '█' * max(int(top * time / max_time), 1)
|
||||||
|
name_remain = ' ' * (name_len - len(user))
|
||||||
|
log.info(f"{user}{name_remain} :{time:5.2f}h {curr}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
log.info("License status query...")
|
||||||
|
lm = LicenseStatistic()
|
||||||
|
log.info(lm)
|
||||||
|
available_lic = lm.available()
|
||||||
|
if not available_lic:
|
||||||
|
log.info("No license available")
|
||||||
|
log.info(f"All licenses is: {[str(_) for _ in lm.get_lic_list()]}")
|
||||||
|
for lic,number in lm.available():
|
||||||
|
log.info(f"{lic}: {number} lic available")
|
||||||
|
draw_time_grpah(lm)
|
124
server.py
124
server.py
|
@ -5,38 +5,120 @@ import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
sample_data = {
|
sample_data = {
|
||||||
"27003@szmis03": {
|
"27003@szmaslic03": {
|
||||||
"name": "27003@szmis03",
|
"name": "27003@szmaslic03",
|
||||||
|
"total": 2,
|
||||||
|
"used": 1,
|
||||||
|
"used_info": [
|
||||||
|
{
|
||||||
|
"user": "haoxiang.ran",
|
||||||
|
"host": "szl3bc12808",
|
||||||
|
"lic": "szmaslic03",
|
||||||
|
"login": "0.41h"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"27003@szmaslic04": {
|
||||||
|
"name": "27003@szmaslic04",
|
||||||
|
"total": 2,
|
||||||
|
"used": 1,
|
||||||
|
"used_info": [
|
||||||
|
{
|
||||||
|
"user": "macro.yang",
|
||||||
|
"host": "szl3bc12804",
|
||||||
|
"lic": "szmaslic04",
|
||||||
|
"login": "52.28h"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"27003@szmaslic06": {
|
||||||
|
"name": "27003@szmaslic06",
|
||||||
"total": 2,
|
"total": 2,
|
||||||
"used": 2,
|
"used": 2,
|
||||||
"used_info": [
|
"used_info": [
|
||||||
{
|
{
|
||||||
"user": "ekko.bao",
|
"user": "zhenhao.he",
|
||||||
"host": "szl3bc1808",
|
"host": "szl3bc12804",
|
||||||
"login_time": "58.9h"
|
"lic": "szmaslic06",
|
||||||
|
"login": "0.20h"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user": "zzc",
|
"user": "phillip.chan",
|
||||||
"host": "szl3bc1808",
|
"host": "szl3bc12810",
|
||||||
"login_time": "58.9h"
|
"lic": "szmaslic06",
|
||||||
|
"login": "0.16h"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"27003@szmis05": {
|
"27003@szmaslic07": {
|
||||||
"name": "27003@szmis05",
|
"name": "27003@szmaslic07",
|
||||||
"total": 2,
|
|
||||||
"used": 0,
|
|
||||||
"used_info": []
|
|
||||||
},
|
|
||||||
"27003@szmis10": {
|
|
||||||
"name": "27003@szmis10",
|
|
||||||
"total": 2,
|
"total": 2,
|
||||||
"used": 1,
|
"used": 1,
|
||||||
"used_info": [{
|
"used_info": [
|
||||||
"user": "zzc33333",
|
{
|
||||||
"host": "szl3bc1808",
|
"user": "zhiyi.li",
|
||||||
"login_time": "58.9h"
|
"host": "szl3bc12806",
|
||||||
}]
|
"lic": "szmaslic07",
|
||||||
|
"login": "2.96h"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"27003@szmaslic08": {
|
||||||
|
"name": "27003@szmaslic08",
|
||||||
|
"total": 2,
|
||||||
|
"used": 2,
|
||||||
|
"used_info": [
|
||||||
|
{
|
||||||
|
"user": "soo.liu",
|
||||||
|
"host": "szl3bc12809",
|
||||||
|
"lic": "szmaslic08",
|
||||||
|
"login": "6.55h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user": "haichao.ou",
|
||||||
|
"host": "szl3bc12806",
|
||||||
|
"lic": "szmaslic08",
|
||||||
|
"login": "5.93h"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"27003@szmaslic09": {
|
||||||
|
"name": "27003@szmaslic09",
|
||||||
|
"total": 2,
|
||||||
|
"used": 2,
|
||||||
|
"used_info": [
|
||||||
|
{
|
||||||
|
"user": "louie.liang",
|
||||||
|
"host": "szl3bc12810",
|
||||||
|
"lic": "szmaslic09",
|
||||||
|
"login": "8.31h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user": "zabbix",
|
||||||
|
"host": "szl3bc06409",
|
||||||
|
"lic": "szmaslic09",
|
||||||
|
"login": "0.90h"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"27003@szmaslic10": {
|
||||||
|
"name": "27003@szmaslic10",
|
||||||
|
"total": 2,
|
||||||
|
"used": 2,
|
||||||
|
"used_info": [
|
||||||
|
{
|
||||||
|
"user": "harvey.li",
|
||||||
|
"host": "szl3bc12810",
|
||||||
|
"lic": "szmaslic10",
|
||||||
|
"login": "57.38h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user": "kw.hu",
|
||||||
|
"host": "szl3bc12809",
|
||||||
|
"lic": "szmaslic10",
|
||||||
|
"login": "1.41h"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,12 @@ const mockData = {
|
||||||
{
|
{
|
||||||
"user": "ekko.bao",
|
"user": "ekko.bao",
|
||||||
"host": "szl3bc1808",
|
"host": "szl3bc1808",
|
||||||
"login_time": "3.4h"
|
"login": "3.4h"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user": "zzc",
|
"user": "zzc",
|
||||||
"host": "szl3bc1808",
|
"host": "szl3bc1808",
|
||||||
"login_time": "6.9h"
|
"login": "6.9h"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -32,7 +32,7 @@ const mockData = {
|
||||||
"used_info": [{
|
"used_info": [{
|
||||||
"user": "zzc33333",
|
"user": "zzc33333",
|
||||||
"host": "szl3bc1808",
|
"host": "szl3bc1808",
|
||||||
"login_time": "58.9h"
|
"login": "58.9h"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { convertTimeToMinutes } from './timeConverter.js';
|
||||||
function getMaxDuration(license) {
|
function getMaxDuration(license) {
|
||||||
if (!license.used_info.length) return 0;
|
if (!license.used_info.length) return 0;
|
||||||
return Math.max(...license.used_info.map(info =>
|
return Math.max(...license.used_info.map(info =>
|
||||||
convertTimeToMinutes(info.login_time)
|
convertTimeToMinutes(info.login)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ export function transformLicenseData(data) {
|
||||||
usageDetails: license.used_info.map(info => ({
|
usageDetails: license.used_info.map(info => ({
|
||||||
userName: info.user,
|
userName: info.user,
|
||||||
hostName: info.host,
|
hostName: info.host,
|
||||||
duration: convertTimeToMinutes(info.login_time)
|
duration: convertTimeToMinutes(info.login)
|
||||||
}))
|
}))
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => b.maxDuration - a.maxDuration); // 按最大使用时长降序排序
|
.sort((a, b) => b.maxDuration - a.maxDuration); // 按最大使用时长降序排序
|
||||||
|
|
Loading…
Reference in New Issue
Block a user