Skip to content

Instantly share code, notes, and snippets.

@robkooper
Last active January 16, 2026 03:19
Show Gist options
  • Select an option

  • Save robkooper/2a16b4a3923e036a3921c1a0198b1c16 to your computer and use it in GitHub Desktop.

Select an option

Save robkooper/2a16b4a3923e036a3921c1a0198b1c16 to your computer and use it in GitHub Desktop.
openstack rebind
#!/usr/bin/env python3
"""
OpenStack Floating IP Rebinding Script
This script removes and re-adds a floating IP to a VM while
preserving the MAC address of the interface.
Usage:
python rebind_floating_ip.py --vm <vm_name> --floating-ip <ip> --network <network_name> [--dry-run]
"""
import argparse
import sys
import time
from typing import Optional
try:
import openstack
from openstack.exceptions import OpenStackCloudException
except ImportError:
print("Error: openstack SDK not installed. Install with: pip install openstacksdk")
sys.exit(1)
class FloatingIPManager:
def __init__(self, dry_run: bool = False):
self.dry_run = dry_run
self.conn = None
def connect(self):
"""Initialize OpenStack connection"""
try:
self.conn = openstack.connect()
if self.dry_run:
print("[DRY-RUN] Connected to OpenStack cloud")
else:
print("Connected to OpenStack cloud")
except Exception as e:
print(f"Error connecting to OpenStack: {e}")
sys.exit(1)
def get_server(self, vm_name: str):
"""Get server by name"""
print(f"Looking for VM: {vm_name}")
server = self.conn.compute.find_server(vm_name)
if not server:
print(f"Error: VM '{vm_name}' not found")
sys.exit(1)
print(f"Found VM: {server.name} (ID: {server.id})")
return server
def get_floating_ip(self, ip_address: str):
"""Get floating IP object"""
print(f"Looking for floating IP: {ip_address}")
floating_ip = None
for fip in self.conn.network.ips():
if fip.floating_ip_address == ip_address:
floating_ip = fip
break
if not floating_ip:
print(f"Error: Floating IP '{ip_address}' not found")
sys.exit(1)
print(f"Found floating IP: {floating_ip.floating_ip_address} (ID: {floating_ip.id})")
return floating_ip
def get_network(self, network_name: str):
"""Get network by name"""
print(f"Looking for network: {network_name}")
network = self.conn.network.find_network(network_name)
if not network:
print(f"Error: Network '{network_name}' not found")
sys.exit(1)
print(f"Found network: {network.name} (ID: {network.id})")
return network
def find_port_for_floating_ip(self, server, floating_ip):
"""Find the port currently associated with the floating IP"""
if floating_ip.port_id:
port = self.conn.network.get_port(floating_ip.port_id)
print(f"Floating IP is currently attached to port: {port.id}")
print(f"Port MAC address: {port.mac_address}")
if port.security_group_ids:
print(f"Port security groups: {', '.join(port.security_group_ids)}")
else:
print("Port has no security groups")
return port
else:
print("Floating IP is not currently attached to any port")
return None
def find_server_ports(self, server, network_id: str):
"""Find all ports for a server on a specific network"""
ports = []
for port in self.conn.network.ports(device_id=server.id):
if port.network_id == network_id:
ports.append(port)
return ports
def auto_detect_floating_ip(self, server):
"""Auto-detect floating IP attached to the server"""
print("\n=== Auto-detecting Floating IP ===")
floating_ips = []
# Get all floating IPs and check if they're attached to this server
for fip in self.conn.network.ips():
if fip.port_id:
port = self.conn.network.get_port(fip.port_id)
if port.device_id == server.id:
floating_ips.append(fip)
if len(floating_ips) == 0:
print("Error: No floating IPs found attached to this VM")
sys.exit(1)
elif len(floating_ips) == 1:
fip = floating_ips[0]
print(f"Found floating IP: {fip.floating_ip_address}")
return fip.floating_ip_address
else:
print(f"Found {len(floating_ips)} floating IPs attached to this VM:")
for i, fip in enumerate(floating_ips, 1):
print(f" {i}. {fip.floating_ip_address}")
print("\nError: Multiple floating IPs found. Please specify which one with --floating-ip")
sys.exit(1)
def auto_detect_network(self, server, floating_ip_address: str):
"""Auto-detect network from the floating IP's port"""
print("\n=== Auto-detecting Network ===")
# Get the floating IP to find its port
floating_ip = None
for fip in self.conn.network.ips():
if fip.floating_ip_address == floating_ip_address:
floating_ip = fip
break
if not floating_ip or not floating_ip.port_id:
print("Error: Could not find port for floating IP")
sys.exit(1)
# Get the port to find its network
port = self.conn.network.get_port(floating_ip.port_id)
network = self.conn.network.get_network(port.network_id)
print(f"Found network: {network.name}")
return network.name
def remove_floating_ip(self, floating_ip):
"""Step 1: Remove floating IP"""
print("\n=== Step 1: Remove Floating IP ===")
if floating_ip.port_id:
if self.dry_run:
print(f"[DRY-RUN] Would disassociate floating IP {floating_ip.floating_ip_address} from port {floating_ip.port_id}")
else:
print(f"Disassociating floating IP {floating_ip.floating_ip_address}...")
self.conn.network.update_ip(floating_ip, port_id=None)
print("Floating IP disassociated")
time.sleep(2)
else:
print("Floating IP is already disassociated")
def remove_interface(self, server, port):
"""Step 2: Remove interface from VM"""
print("\n=== Step 2: Remove Interface from VM ===")
if self.dry_run:
print(f"[DRY-RUN] Would detach port {port.id} (MAC: {port.mac_address}) from server {server.name}")
print(f"[DRY-RUN] Would delete port {port.id}")
else:
print(f"Detaching port {port.id} from server {server.name}...")
self.conn.compute.delete_server_interface(port.id, server=server.id)
print("Interface detached")
time.sleep(3)
# Delete the old port to free up the MAC address
print(f"Deleting old port {port.id} to free MAC address...")
self.conn.network.delete_port(port.id)
print("Old port deleted")
# Wait for port deletion to complete
print("Waiting for port deletion to complete...")
max_wait = 30
waited = 0
while waited < max_wait:
try:
self.conn.network.get_port(port.id)
time.sleep(2)
waited += 2
except Exception:
# Port is gone
break
if waited >= max_wait:
print("Warning: Port deletion taking longer than expected, but continuing...")
else:
print(f"Port deletion confirmed (waited {waited}s)")
time.sleep(2) # Extra safety margin
def add_interface(self, server, network, mac_address: str, security_group_ids: list):
"""Step 3: Add interface to VM with same MAC address and security groups"""
print("\n=== Step 3: Add Interface to VM ===")
if self.dry_run:
print(f"[DRY-RUN] Would create new port on network {network.name} with MAC {mac_address}")
if security_group_ids:
print(f"[DRY-RUN] Would apply security groups: {', '.join(security_group_ids)}")
print(f"[DRY-RUN] Would attach port to server {server.name}")
# Return a dummy port for dry-run
class DummyPort:
def __init__(self, mac, sg_ids):
self.id = "dry-run-port-id"
self.mac_address = mac
self.security_group_ids = sg_ids
return DummyPort(mac_address, security_group_ids)
else:
# Create port with specific MAC address and security groups
print(f"Creating new port on network {network.name} with MAC {mac_address}...")
port_args = {
'network_id': network.id,
'mac_address': mac_address
}
if security_group_ids:
port_args['security_group_ids'] = security_group_ids
print(f"Applying security groups: {', '.join(security_group_ids)}")
port = self.conn.network.create_port(**port_args)
print(f"Created port: {port.id} (MAC: {port.mac_address})")
# Attach port to server
print(f"Attaching port to server {server.name}...")
self.conn.compute.create_server_interface(server=server.id, port_id=port.id)
print("Interface attached")
time.sleep(3)
return port
def add_floating_ip(self, floating_ip, port):
"""Step 4: Add floating IP back"""
print("\n=== Step 4: Add Floating IP Back ===")
if self.dry_run:
print(f"[DRY-RUN] Would associate floating IP {floating_ip.floating_ip_address} to port {port.id}")
else:
print(f"Associating floating IP {floating_ip.floating_ip_address} to port {port.id}...")
self.conn.network.update_ip(floating_ip, port_id=port.id)
print("Floating IP associated")
time.sleep(2)
def reboot_vm(self, server):
"""Step 5: Reboot VM"""
print("\n=== Step 5: Reboot VM ===")
if self.dry_run:
print(f"[DRY-RUN] Would reboot server {server.name}")
else:
print(f"Rebooting server {server.name}...")
self.conn.compute.reboot_server(server.id, reboot_type='SOFT')
print("Reboot initiated")
def rebind_floating_ip(self, vm_name: str, floating_ip_address: str, network_name: str):
"""Main workflow to rebind floating IP"""
print("=" * 60)
if self.dry_run:
print("DRY-RUN MODE - No changes will be made")
else:
print("LIVE MODE - Changes will be applied")
print("=" * 60)
# Get resources
server = self.get_server(vm_name)
floating_ip = self.get_floating_ip(floating_ip_address)
network = self.get_network(network_name)
# Find current port and save MAC address
current_port = self.find_port_for_floating_ip(server, floating_ip)
if not current_port:
print("Error: Cannot determine MAC address - floating IP not currently attached")
sys.exit(1)
mac_address = current_port.mac_address
print(f"\nMAC address to preserve: {mac_address}")
# Save security groups
security_group_ids = current_port.security_group_ids if current_port.security_group_ids else []
if security_group_ids:
print(f"Security groups to preserve: {', '.join(security_group_ids)}")
else:
print("No security groups to preserve")
# Execute workflow
self.remove_floating_ip(floating_ip)
self.remove_interface(server, current_port)
new_port = self.add_interface(server, network, mac_address, security_group_ids)
self.add_floating_ip(floating_ip, new_port)
self.reboot_vm(server)
print("\n" + "=" * 60)
if self.dry_run:
print("DRY-RUN COMPLETE - No changes were made")
else:
print("OPERATION COMPLETE")
print("=" * 60)
def main():
parser = argparse.ArgumentParser(
description='Rebind OpenStack floating IP while preserving MAC address',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Auto-detect floating IP and network from VM (simplest)
python rebind_floating_ip.py --vm my-server --dry-run
# Specify specific floating IP and network
python rebind_floating_ip.py --vm my-server --floating-ip 203.0.113.10 --network private-net --dry-run
# Live mode (makes actual changes)
python rebind_floating_ip.py --vm my-server
Note: Ensure your OpenStack credentials are configured via environment variables or clouds.yaml
"""
)
parser.add_argument('--vm', required=True, help='VM/Server name')
parser.add_argument('--floating-ip', help='Floating IP address (auto-detected if not specified)')
parser.add_argument('--network', help='Network name for the interface (auto-detected if not specified)')
parser.add_argument('--dry-run', action='store_true', help='Show what would be done without making changes')
args = parser.parse_args()
# Create manager and execute
manager = FloatingIPManager(dry_run=args.dry_run)
manager.connect()
# Auto-detect floating IP and network if not provided
floating_ip_address = args.floating_ip
network_name = args.network
if not floating_ip_address or not network_name:
server = manager.get_server(args.vm)
if not floating_ip_address:
floating_ip_address = manager.auto_detect_floating_ip(server)
if not network_name:
network_name = manager.auto_detect_network(server, floating_ip_address)
manager.rebind_floating_ip(args.vm, floating_ip_address, network_name)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment