1457 字
7 分钟
OpenStack平台Python运维开发
OpenStack 平台 Python 运维开发
API 端点速查
| 服务 | 默认端口 | API 路径 | 用途 |
|---|---|---|---|
| Keystone | 5000 | /v3/auth/tokens, /v3/users | 认证、用户管理 |
| Glance | 9292 | /v2/images, /v2/images/{id}/file | 镜像管理 |
| Nova | 8774 | /v2.1/flavors, /v2.1/servers | 云主机、规格 |
| Cinder | 8776 | /v2/{project}/volumes | 云硬盘 |
| Neutron | 9696 | /v2.0/networks, /v2.0/subnets | 网络 |
两种开发方式:RESTful API(requests 库,灵活可控)和 Python SDK(openstacksdk,代码更简洁)。
1. Keystone 认证模块
所有 RESTful API 调用都需要先获取 Token。以下函数返回带 Token 的 Header。
import json, requests
def get_auth_token(controller_ip, domain, user, password): """获取 Keystone Token,返回带认证信息的 Header""" url = f"http://{controller_ip}:5000/v3/auth/tokens" body = { "auth": { "identity": { "methods": ["password"], "password": { "user": { "domain": {"name": domain}, "name": user, "password": password } } }, "scope": { "project": { "domain": {"name": domain}, "name": user } } } } headers = {"Content-Type": "application/json"} resp = requests.post(url, data=json.dumps(body), headers=headers) token = resp.headers['X-Subject-Token'] return {"X-Auth-Token": token, "Content-Type": "application/json"}
# 使用示例headers = get_auth_token("192.168.1.10", "demo", "admin", "000000")2. 用户管理(RESTful API)
操作 Keystone /v3/users 端点,实现用户的增删改查。
import json, requests
class KeystoneUserManager: def __init__(self, headers: dict, controller_ip: str): self.headers = headers self.url = f"http://{controller_ip}:5000/v3/users"
def create_user(self, name, password, description=""): """创建用户""" body = {"user": {"name": name, "password": password, "description": description}} resp = requests.post(self.url, data=json.dumps(body), headers=self.headers) return resp.text
def list_users(self): """查询所有用户""" return requests.get(self.url, headers=self.headers).text
def get_user_id(self, name): """根据名称查找用户 ID""" result = json.loads(self.list_users()) for user in result['users']: if user['name'] == name: return user['id'] return None
def get_user(self, user_id): """根据 ID 查询用户详情""" resp = requests.get(f"{self.url}/{user_id}", headers=self.headers) return json.loads(resp.text)
def delete_user(self, name): """根据名称删除用户""" user_id = self.get_user_id(name) if not user_id: return {"error": "User not found"} resp = requests.delete(f"{self.url}/{user_id}", headers=self.headers) if resp.status_code == 204: return {"UserDeletedSuccess": True} return json.loads(resp.text)
def update_password(self, user_id, original_password, new_password): """修改用户密码""" body = {"user": {"password": new_password, "original_password": original_password}} resp = requests.post(f"{self.url}/{user_id}/password", data=json.dumps(body), headers=self.headers) if resp.status_code == 204: return {"PasswordUpdateSuccess": True} return json.loads(resp.text)3. 镜像管理(RESTful API)
操作 Glance /v2/images 端点。镜像上传分两步:先创建元数据,再 PUT 二进制数据。
import json, requests
class GlanceImageManager: def __init__(self, headers: dict, controller_ip: str): self.headers = headers.copy() self.url = f"http://{controller_ip}:9292/v2/images"
def create_image(self, name, container_format="bare", disk_format="qcow2"): """创建镜像(仅元数据)""" body = { "container_format": container_format, "disk_format": disk_format, "name": name } resp = requests.post(self.url, data=json.dumps(body), headers=self.headers) if resp.status_code == 201: return json.loads(resp.text) # 返回镜像信息(含 id) return {"error": resp.text}
def get_image_id(self, name): """根据名称查找镜像 ID""" result = json.loads(requests.get(self.url, headers=self.headers).text) for img in result['images']: if img['name'] == name: return img['id'] return None
def upload_image_data(self, image_id, file_path): """上传镜像二进制数据""" upload_url = f"{self.url}/{image_id}/file" upload_headers = self.headers.copy() upload_headers['Content-Type'] = "application/octet-stream" with open(file_path, 'rb') as f: resp = requests.put(upload_url, data=f.read(), headers=upload_headers) if resp.status_code == 204: return {"ImageUploadSuccess": True} return {"error": resp.text}
def list_images(self): """查询所有镜像""" return requests.get(self.url, headers=self.headers).text
def get_image(self, image_id): """查询单个镜像详情""" resp = requests.get(f"{self.url}/{image_id}", headers=self.headers) return resp.text
def delete_image(self, image_id): """删除镜像""" resp = requests.delete(f"{self.url}/{image_id}", headers=self.headers) if resp.status_code == 204: return {"ImageDeletedSuccess": True} return json.loads(resp.text)4. Flavor 管理(RESTful API)
操作 Nova /v2.1/flavors 端点。
import json, requests
class NovaFlavorManager: def __init__(self, headers: dict, controller_ip: str): self.headers = headers self.url = f"http://{controller_ip}:8774/v2.1/flavors"
def create_flavor(self, name, ram, vcpus, disk, flavor_id): """创建 Flavor""" body = {"flavor": { "name": name, "ram": ram, "vcpus": vcpus, "disk": disk, "id": flavor_id }} resp = requests.post(self.url, data=json.dumps(body), headers=self.headers) return resp.text
def list_flavors(self): """查询所有 Flavor""" resp = requests.get(self.url, headers=self.headers) return json.loads(resp.text)
def get_flavor(self, flavor_id): """查询单个 Flavor""" resp = requests.get(f"{self.url}/{flavor_id}", headers=self.headers) return json.loads(resp.text)
def delete_flavor(self, flavor_id): """删除 Flavor""" resp = requests.delete(f"{self.url}/{flavor_id}", headers=self.headers) if resp.status_code == 202: return {"FlavorDeletedSuccess": True} return json.loads(resp.text)5. Cinder 卷管理(Python SDK)
使用 openstacksdk 实现卷创建、扩容、导出为镜像。
import openstack, time
def wait_for_resource_status(conn, resource_type, resource_id, target_status, timeout=120): """等待资源达到目标状态""" for _ in range(timeout // 2): if resource_type == 'volume': vol = conn.block_storage.get_volume(resource_id) if vol.status == target_status: return True elif resource_type == 'image': img = conn.image.get_image(resource_id) if img.status == target_status: return True time.sleep(2) raise TimeoutError(f"{resource_type} {resource_id} 未达到 {target_status}")
def main(): conn = openstack.connect( auth_url='http://<controller>:5000/v3', username='admin', password='000000', project_name='admin', user_domain_name='demo', project_domain_name='demo' )
# 清理同名旧卷 for vol in conn.block_storage.volumes(name='cinder01'): conn.block_storage.delete_volume(vol)
# 创建 2G 卷 volume = conn.block_storage.create_volume( name='cinder01', size=2, description='Created by Python' ) wait_for_resource_status(conn, 'volume', volume.id, 'available')
# 扩容到 6G conn.block_storage.extend_volume(volume=volume.id, size=6) wait_for_resource_status(conn, 'volume', volume.id, 'available')
# 导出为镜像 action_body = { "os-volume_upload_image": { "image_name": "cinder_image", "disk_format": "raw", "container_format": "bare", "force": True } } resp = conn.block_storage.post(f"/volumes/{volume.id}/action", json=action_body) image_id = resp.json()['os-volume_upload_image']['image_id'] wait_for_resource_status(conn, 'image', image_id, 'active') print(f"镜像导出成功: {image_id}")6. 云主机管理(Python SDK)
使用 openstacksdk 创建云主机,代码更简洁。
import openstack, json
def create_server(): conn = openstack.connect( auth_url='http://<controller>:5000/v3', user_domain_name='demo', username='admin', password='000000' )
# 删除同名云主机 existing = conn.compute.find_server('server001') if existing: conn.compute.delete_server(existing.id) print("已删除同名云主机")
# 获取资源引用 flavor = conn.compute.find_flavor('m1.tiny') image = conn.image.find_image('cirros001') network = conn.network.find_network('net')
# 创建云主机 server = conn.compute.create_server( name='server001', flavor_id=flavor.id, image_id=image.id, networks=[{'uuid': network.id}] ) server = conn.compute.wait_for_server(server) print(f"云主机创建成功: {server.id}") print(json.dumps(server.to_dict(), default=str, indent=2))7. SDK 封装管理类
基于 openstacksdk 封装常用操作,方便在其他脚本中复用。
class ServerManager: def __init__(self, conn): self.conn = conn
def list_servers(self): return list(self.conn.compute.servers())
def get_server(self, name): return self.conn.compute.find_server(name)
def create_server(self, name, image_name, flavor_name, network_name): flavor = self.conn.compute.find_flavor(flavor_name) image = self.conn.image.find_image(image_name) network = self.conn.network.find_network(network_name) server = self.conn.compute.create_server( name=name, flavor_id=flavor.id, image_id=image.id, networks=[{'uuid': network.id}] ) return self.conn.compute.wait_for_server(server)
def delete_server(self, name): server = self.get_server(name) if server: self.conn.compute.delete_server(server.id) return True return False
class ImageManager: def __init__(self, conn): self.conn = conn
def list_images(self): return list(self.conn.image.images())
def get_image(self, name): return self.conn.image.find_image(name)
class FlavorManager: def __init__(self, conn): self.conn = conn
def list_flavors(self): return list(self.conn.compute.flavors())
def get_flavor(self, name): return self.conn.compute.find_flavor(name)
class NetworkManager: def __init__(self, conn): self.conn = conn
def list_networks(self): return list(self.conn.network.networks())
def get_network(self, name): return self.conn.network.find_network(name)8. 命令行工具开发
用户管理 CLI(argparse + RESTful API)
import argparse, json, sysfrom api_user_manager import KeystoneUserManager, get_auth_token
def main(): parser = argparse.ArgumentParser(description='OpenStack 用户管理工具') sub = parser.add_subparsers(dest='command')
# create p_create = sub.add_parser('create', help='创建用户') p_create.add_argument('-i', '--input', required=True, help='JSON 格式用户信息')
# getall p_getall = sub.add_parser('getall', help='查询所有用户') p_getall.add_argument('-o', '--output', help='输出文件路径')
# get p_get = sub.add_parser('get', help='查询单个用户') p_get.add_argument('-n', '--name', required=True, help='用户名') p_get.add_argument('-o', '--output', help='输出文件路径')
# delete p_del = sub.add_parser('delete', help='删除用户') p_del.add_argument('-n', '--name', required=True, help='用户名')
args = parser.parse_args() headers = get_auth_token("192.168.1.10", "demo", "admin", "000000") mgr = KeystoneUserManager(headers, "192.168.1.10")
if args.command == 'create': info = json.loads(args.input) print(mgr.create_user(info['name'], info['password'], info.get('description', ''))) elif args.command == 'getall': result = mgr.list_users() if args.output: with open(args.output, 'w') as f: f.write(result) else: print(result) elif args.command == 'get': uid = mgr.get_user_id(args.name) if uid: result = mgr.get_user(uid) if args.output: with open(args.output, 'w') as f: json.dump(result, f, indent=2) else: print(json.dumps(result, indent=2)) elif args.command == 'delete': print(mgr.delete_user(args.name))
if __name__ == '__main__': main()Flavor 管理 CLI
# 创建python flavor_manager.py create -n test-flavor -m 1024 -v 2 -d 20 -id 100
# 查询所有python flavor_manager.py getall
# 查询单个python flavor_manager.py get -id 100
# 删除python flavor_manager.py delete -id 1009. MySQL 数据库管理(SQLAlchemy)
from sqlalchemy import create_engine, Column, Integer, Stringfrom sqlalchemy.orm import declarative_base, sessionmaker
Base = declarative_base()
class Member(Base): __tablename__ = 'member' id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(256)) level = Column(Integer) place = Column(String(256))
class DBManager: def __init__(self, db_name, host='<host>', password='000000'): self.engine = create_engine(f"mysql+pymysql://root:{password}@{host}:3306/{db_name}") self.Session = sessionmaker(bind=self.engine)
def create_table(self): Base.metadata.create_all(self.engine) print("表创建成功")
def drop_table(self): Base.metadata.drop_all(self.engine) print("表删除成功")
def add_member(self, name, level, place): session = self.Session() member = Member(name=name, level=level, place=place) session.add(member) session.commit() session.close()
def get_all_members(self): session = self.Session() members = session.query(Member).all() session.close() return members OpenStack平台Python运维开发
https://blog.xeu.asia/posts/openstack-python-ops/