Of course! Here is a comprehensive guide to using OpenStack with Python 3, covering the main libraries, authentication, and practical examples.

Understanding the Landscape: OpenStack Python Clients
When you talk about "OpenStack Python3," you're typically referring to one of two things:
- Official OpenStack Clients: These are the primary, recommended libraries for interacting with OpenStack services (like Nova, Glance, Neutron, etc.) from a Python script. They are actively maintained and support Python 3.
- OpenStack SDK (oslo): This is a lower-level library that provides common functionalities like configuration loading, logging, and messaging. While powerful, you won't use it directly to create a virtual machine. It's the foundation upon which the official clients are built.
For most tasks, you'll want to use the Official OpenStack Clients.
The Official OpenStack Clients (The Main Tools)
These are a set of libraries, one for each major OpenStack service. The most common ones you'll need are:
python-openstacksdk: A high-level, unified client that can interact with multiple services. This is the recommended starting point for most new projects.python-novaclient: For managing compute instances (VMs).python-glanceclient: For managing images.python-neutronclient: For managing networks, routers, and IPs.python-cinderclient: For managing block storage (volumes).python-keystoneauth1: The core authentication library used by almost all other clients.
Installation
You can install these clients using pip. It's best practice to do this in a Python virtual environment.

# Create and activate a virtual environment python3 -m venv openstack-env source openstack-env/bin/activate # Install the recommended high-level client pip install openstacksdk # Or, install specific clients if you prefer pip install python-novaclient python-glanceclient python-keystoneauth1
Authentication: The Key to Everything
Before you can do anything, you need to authenticate with the OpenStack Identity service (Keystone) to get an authentication token.
Method 1: Using Environment Variables (Recommended for Scripts)
This is the cleanest method. You create a file named clouds.yaml in your home directory (~/.config/openstack/clouds.yaml) or in your project directory. This file stores your connection details.
Example ~/.config/openstack/clouds.yaml:
clouds:
my_openstack_cloud:
auth:
auth_url: https://my-openstack-api.com:5000/v3
project_name: my_project
username: my_user
password: my_secret_password
user_domain_name: Default
project_domain_name: Default
region_name: RegionOne
identity_api_version: 3
Why this is great: Your Python code doesn't need to hardcode credentials. It will automatically look for this file.

Method 2: Using a Cloud YAML File in Code
You can explicitly point the SDK to your clouds.yaml file.
Method 3: Passing Credentials Directly (Less Secure)
You can pass credentials as a dictionary, but this is not recommended for production scripts.
Practical Examples with openstacksdk
Let's walk through common tasks using the modern openstacksdk. This library uses a connection object that handles authentication for you.
Step 1: Establish a Connection
First, you need to create a connection object. The SDK will automatically find and use your clouds.yaml file.
import openstack
# Create a connection object using the clouds.yaml file
# The 'cloud' argument should match the name in your clouds.yaml file.
conn = openstack.connect(cloud='my_openstack_cloud')
# You can verify the connection
print(f"Connected to project: {conn.current_project_name}")
print(f"Auth Token: {conn.auth_token[:20]}...") # Print first 20 chars of token
Example 1: List All Compute Instances (VMs)
This is a very common task. The conn.compute.servers() method returns a generator of server objects.
print("\n--- Listing Compute Instances ---")
for server in conn.compute.servers():
print(f"Name: {server.name}, ID: {server.id}, Status: {server.status}")
Example 2: Create a New Compute Instance
Creating an instance requires specifying an image, a flavor (VM size), and a network.
print("\n--- Creating a New Instance ---")
# Find a suitable image and flavor
image = conn.compute.find_image("ubuntu-22.04") # Or use an image ID
flavor = conn.compute.find_flavor("m1.small") # Or use a flavor ID
# Find a network to launch the instance in
network = conn.network.find_network("private-net") # Or use a network ID
# Define the server configuration
server_name = "my-python3-test-vm"
image_id = image.id
flavor_id = flavor.id
network_id = network['id']
# Create the server
server = conn.compute.create_server(
name=server_name,
image_id=image_id,
flavor_id=flavor_id,
networks=[{"uuid": network_id}],
)
print(f"Server creation initiated. Name: {server.name}, ID: {server.id}")
# The 'server' object is initially in a 'BUILD' state.
# You can wait for it to become active.
server = conn.compute.wait_for_server(server)
print(f"Server is now active with IP: {server.access_ipv4}")
Example 3: Upload a New Image
This example shows how to upload a local disk image file (like a .qcow2 or .iso) to the Glance image service.
print("\n--- Uploading a New Image ---")
image_name = "my-custom-ubuntu-image"
image_filename = "/path/to/my/ubuntu-image.qcow2" # <-- IMPORTANT: Change this path
# Open the file in binary mode
with open(image_filename, 'rb') as image_file:
image = conn.image.create_image(
name=image_name,
data=image_file,
disk_format='qcow2',
container_format='bare',
is_public=False,
)
print(f"Image upload initiated. Name: {image.name}, ID: {image.id}")
# Wait for the image to become active
image = conn.image.wait_for_image(image)
print(f"Image is now active.")
Example 4: Create a Network and a Router
This example demonstrates interacting with the Neutron network service.
print("\n--- Creating Network and Router ---")
# Create a private network
network_name = "my-python-network"
network = conn.network.create_network(name=network_name)
print(f"Created network: {network.name} (ID: {network.id})")
# Create a subnet within that network
subnet_name = "my-python-subnet"
subnet = conn.network.create_subnet(
name=subnet_name,
network_id=network.id,
ip_version='4',
cidr='192.168.101.0/24',
)
print(f"Created subnet: {subnet.name} (ID: {subnet.id})")
# Create a router and attach it to the external network (and our new subnet)
# First, find the external network
ext_net = conn.network.find_network("ext-net", ignore_missing=False)
router_name = "my-python-router"
router = conn.network.create_router(
name=router_name,
external_gateway_info={'network_id': ext_net.id},
)
print(f"Created router: {router.name} (ID: {router.id})")
# Attach the subnet to the router
conn.network.add_interface_to_router(router, subnet_id=subnet.id)
print(f"Attached subnet {subnet.name} to router {router.name}")
Important Considerations
-
Error Handling: OpenStack API calls can fail (e.g., invalid image ID, quota exceeded). Always wrap your code in
try...exceptblocks. Theopenstacksdkraises specific exceptions.from openstack.exceptions import ResourceNotFound try: server = conn.compute.find_server("non-existent-server") except ResourceNotFound: print("Server not found, as expected.") -
Pagination: For large lists of resources (like hundreds of servers), the API returns results in pages. The
openstacksdkgenerators (conn.compute.servers()) handle pagination for you automatically, which is a huge advantage. -
Asynchronous Operations: Many actions (like creating a server or volume) are asynchronous. The SDK provides convenient methods like
wait_for_server()orwait_for_volume()to block your script until the resource reaches the desired state. -
API Versioning: OpenStack services have multiple API versions. The
openstacksdkis generally good at picking the best available version, but you can specify it in yourclouds.yamlif needed. For older clients likenovaclient, you might need to specify the API version explicitly in your code.
Summary and Best Practices
- Use
openstacksdk: For any new project, start with theopenstacksdk. It's modern, well-documented, and handles many complexities for you. - Use
clouds.yaml: Store your credentials in aclouds.yamlfile. Do not hardcode them in your Python scripts. - Iterate with Generators: Use the
for item in conn.service.resources()pattern to list resources. - Handle Errors: Use
try...exceptto gracefully handle API errors. - Wait for Completion: Use the
wait_for_*methods to ensure your script waits for long-running tasks to finish.
