结合python 和 pve 的api,方便的管理虚拟机,可以单独的开发个小脚本,也可以嵌入到任何其他系统内,作为一个小功能。
介绍
关于pve rest api的官方介绍和 api文档:
https://pve.proxmox.com/wiki/Proxmox_VE_API
https://pve.proxmox.com/pve-docs/api-viewer/index.html#/nodes/{node}/qemu/{vmid}/status/stop
调用pve api ,有两种认证方式
- Ticket Cookie
先发送一个包含用户名密码的POST,然后server会返回json格式认证信息,提取必要信息并附加到下次http request请求头部即可
该ticket权限等同于对应用户的权限,两小时后超时失效
shell 命令
curl -k -d 'username=root@pam' --data-urlencode 'password=xxxxxxxx' https://192.168.0.101:8006/api2/json/access/ticket
python脚本
import requests
# solving self-sign CA warning
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
path = '/api2/json/access/ticket'
url = 'https://' + ip + ':' + port + path
r = requests.post(url=url, json={"username": username, "password": password}, verify=False)
print(r.json())
Postman(Talend API Tester )
返回格式
{
"data":{
"ticket": "PVE:root@pam:62EF3DBE::UPduHl969t0bv/eVUs54Ez5z86epC86WvUi81kAgGbs/zw2aZbD7l2loroqOV58/u+iZFrCFRlEsvIKDYskSx0NWM4eW7081u4eMHquHPOnfwm1fblUnIClfs0tNjxwA7ZmFTVsNDgvEPFG5Uw98K8sYDvHLHixSHQEU8+MUyRubeYaJCTNSzNW2FhX+gVZU3xe7hUZSAO2wo0BI5Ciz2tK8WFtAoh7o4UyLR0nJb8qCf1XV6SWUxrq4NpwnmcqEUg7GIjS4ueZ874tjbvnLUdBF62aiKVdECZjAtbxv3ocqKUlw1jN/Gl6xDfSEpgwQiQCKjG2gMdPrNdu9Fp2Tow==",
"cap":{"vms":{"VM.Clone": 1, "VM.Backup": 1, "VM.Config.Options": 1, "VM.Snapshot": 1,…},
"CSRFPreventionToken": "62EF3DBE:/7meKBIZRMV6lpaQ9G9uylNI+/5qHYq3+gDj1Oie6Tw",
"username": "root@pam"
}
}
- API Tokens
手动从PVE GUI控制台创建,并赋予合适的权限,可以设置超期时间(可以永久),记下token信息,后续把token信息附件到HTTP request 头部即可
添加token
添加权限
通过API操作PVE
例如,认证信息获取之后,通过api 查看当前pve node 虚拟机列表
- Ticket Cookie 方式
查询所有节点名称
ticket = {.................}
path = '/api2/json/nodes'
url = 'https://' + ip + ':' + port + path
headers = {'CSRFPreventionToken': ticket['data']['CSRFPreventionToken'], 'Cookie': 'PVEAuthCookie=' + ticket['data']['ticket']}
r = requests.get(url=url, headers=headers, verify=False)
print(r.json())
返回格式
{
"data": [
{
"id": "node/pve01",
"maxcpu": 4,
"status": "online",
"type": "node",
"node": "pve01",
"maxdisk": 100861726720,
"cpu": 0.0333665338645418,
"disk": 41166532608,
"uptime": 2235423,
"mem": 6703284224,
"level": "",
"maxmem": 8073363456,
"ssl_fingerprint": "1F:D1:48:1A:46:19:2F:92:A1:70:3B:4A:EC:FA:67:FA:91:83:4E:5B:AF:68:92:4A:6C:8A:6F:83:D6:66:96:CB"
}
]
}
查询特定节点下虚拟机列表
ticket = {.................}
node = 'pve01'
path = f'/api2/json/nodes/{node}/qemu'
url = 'https://' + ip + ':' + port + path
headers = {'CSRFPreventionToken': ticket['data']['CSRFPreventionToken'], 'Cookie': 'PVEAuthCookie=' + ticket['data']['ticket']}
r = requests.get(url=url, headers=headers, verify=False)
print(r.json())
返回格式
{
"data":[
{"pid": 2648205, "cpus": 2, "status": "running", "name": "OpenMediaVault",…},
{"disk": 0, "mem": 1316534861, "cpu": 0.0445637817533544, "diskread": 0, "diskwrite": 0,…},
{"mem": 3197312520, "cpu": 0.118665826973757, "disk": 0, "maxmem": 4294967296, "maxdisk": 53687091200,…},
{"pid": 1400, "cpus": 1, "status": "running", "name": "OpenWRT",…},
{"cpus": 1, "status": "stopped", "name": "openwrt-test", "vmid": 104,…}
]
}
- Token 方式
查询所有节点名称
path = '/api2/json/nodes'
token = '...........'
url = 'https://' + ip + ':' + port + path
headers = {'Authorization': 'PVEAPIToken=' + token} # Authorization : PVEAPIToken=root@pam!myid=1e1b7cbd-e7eqweq553141525564c-c324519041e0
r = requests.get(url=url, headers=headers, verify=False)
print(r.json())
查询特定节点下虚拟机列表
token = '...........'
node = 'pve01'
path = f'/api2/json/nodes/{node}/qemu'
url = 'https://' + ip + ':' + port + path
headers = {'Authorization': 'PVEAPIToken=' + token} # Authorization : PVEAPIToken=root@pam!myid=1e1b7cbd-e7eqweq553141525564c-c324519041e0
r = requests.get(url=url, headers=headers, verify=False)
print(r.json())
附上完整代码,
import requests
# self-sign CA warning
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class Pve_api(object):
def __init__(self, ip, username = None, password = None, port = '8006'):
self.ip = ip
self.username = username
self.password = password
self.port = port
self.ticket = None
'''
####### ticket data structure
{
"data":{
"ticket": "PVE:root@pam:62EF3DBE::UPduHl969t0bv/eVUs54Ez5z86epC86WvUi81kAgGbs/zw2aZbD7l2loroqOV58/u+iZFrCFRlEsvIKDYskSx0NWM4eW7081u4eMHquHPOnfwm1fblUnIClfs0tNjxwA7ZmFTVsNDgvEPFG5Uw98K8sYDvHLHixSHQEU8+MUyRubeYaJCTNSzNW2FhX+gVZU3xe7hUZSAO2wo0BI5Ciz2tK8WFtAoh7o4UyLR0nJb8qCf1XV6SWUxrq4NpwnmcqEUg7GIjS4ueZ874tjbvnLUdBF62aiKVdECZjAtbxv3ocqKUlw1jN/Gl6xDfSEpgwQiQCKjG2gMdPrNdu9Fp2Tow==",
"cap":{"vms":{"VM.Clone": 1, "VM.Backup": 1, "VM.Config.Options": 1, "VM.Snapshot": 1,…},
"CSRFPreventionToken": "62EF3DBE:/7meKBIZRMV6lpaQ9G9uylNI+/5qHYq3+gDj1Oie6Tw",
"username": "root@pam"
}
}
'''
def get_ticket(self):
path = '/api2/json/access/ticket'
url = 'https://' + self.ip + ':' + self.port + path
r = requests.post(url=url, json={"username": self.username, "password": self.password}, verify=False)
self.ticket = r.json() # dict rather than string
return r.json()
def ticket_node_list(self):
path = '/api2/json/nodes'
url = 'https://' + self.ip + ':' + self.port + path
headers = {'CSRFPreventionToken': self.ticket['data']['CSRFPreventionToken'], 'Cookie': 'PVEAuthCookie=' + self.ticket['data']['ticket']}
r = requests.get(url=url, headers=headers, verify=False)
return r.json()
def ticket_vm_list(self, node):
path = f'/api2/json/nodes/{node}/qemu'
url = 'https://' + self.ip + ':' + self.port + path
headers = {'CSRFPreventionToken': self.ticket['data']['CSRFPreventionToken'], 'Cookie': 'PVEAuthCookie=' + self.ticket['data']['ticket']}
r = requests.get(url=url, headers=headers, verify=False)
return r.json()
def ticket_vm_current(self, node, vmid):
path = f'/api2/json/nodes/{node}/qemu/{vmid}/status/current'
url = 'https://' + self.ip + ':' + self.port + path
headers = {'CSRFPreventionToken': self.ticket['data']['CSRFPreventionToken'], 'Cookie': 'PVEAuthCookie=' + self.ticket['data']['ticket']}
r = requests.get(url=url, headers=headers, verify=False)
return r.json()
def ticket_vm_start(self, node, vmid):
path = f'/api2/json/nodes/{node}/qemu/{vmid}/status/start'
url = 'https://' + self.ip + ':' + self.port + path
headers = {'CSRFPreventionToken': self.ticket['data']['CSRFPreventionToken'], 'Cookie': 'PVEAuthCookie=' + self.ticket['data']['ticket']}
r = requests.post(url=url, headers=headers, verify=False)
return r.json()
def token_vm_stop(self, node, vmid, token):
path = f'/api2/json/nodes/{node}/qemu/{vmid}/status/stop'
url = 'https://' + self.ip + ':' + self.port + path
headers = {'Authorization': 'PVEAPIToken=' + token} # Authorization : PVEAPIToken=root@pam!myid=1e1b7cbd-e7553141525564c-c324519041e0
r = requests.post(url=url, headers=headers, verify=False)
return r.json()
if __name__ == '__main__':
op = Pve_api(ip='192.168.0.101', username='root@pam', password='xxxxxx')
print(op.ticket)
# it must run 'get_ticket()' first to get a new ticket when you do the 'op' down below
# op.get_ticket()
# print(op.ticket)
# print('@'*10)
# print(op.ticket_node_list())
# print('@'*10)
# print(op.ticket_vm_list('pve01'))
# print('@'*10)
# print(op.ticket_vm_current('pve01', '104'))
# print('@'*10)
# print(op.ticket_vm_start('pve01', '104'))
# print('@'*10)
# print(op.token_vm_stop('pve01', '104', 'root@pam!myid=1e1b7cbd-e755-4171-b32c-c324519041e0'))
更多相关代码和资料在这个仓库
One thought on “PVE | Managing Virtual Machines via rest api”