Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ openstack-lb-info - A command-line tool for displaying OpenStack Load Balancer r

# About

This Python script is designed to interact with an OpenStack cloud infrastructure and retrieve information about
This Python script interacts with an OpenStack cloud infrastructure and retrieve information about
load balancers and their components such as listeners, pools, health monitors, members, and amphorae.
It displays the information in a visually appealing and user-friendly way and provide a clear representation
of the load balancer resources.
Expand Down Expand Up @@ -37,10 +37,11 @@ about amphoras associated with load balancers. Amphoras are responsible for hand
information includes amphora IDs, roles, status, load balancer network IP addresses, associated images, server information,
and optional details. If no amphoras match the filter criteria, it will indicate that no amphoras were found.

## Example
## CLI Options

```bash
$ usage: openstack-lb-info [-h] [-d] [--os-cloud OS_CLOUD] -t {lb,amphora}
$ openstack-lb-info --help
usage: openstack-lb-info [-h] [-d] [--os-cloud OS_CLOUD] -t {lb,amphora}
[-o {plain,rich,json}] [--name NAME] [--id ID]
[--tags TAGS] [--flavor-id FLAVOR_ID]
[--vip-address VIP_ADDRESS]
Expand Down Expand Up @@ -87,31 +88,44 @@ options:
openstack-lb-info --type amphora --id load_balancer_id --details

```

## Example

![example](img/example.png)

## Authentication Methods

##### Environment Variables
You can manually set the required environment variables or use an OpenStack RC file to simplify the process.
You can manually set the required environment variables or source an OpenStack RC file.

##### clouds.yaml Configuration
Alternatively, you can use a *clouds.yaml* and export "*OS_CLOUD*" variable to pass the cloud name.
Alternatively, you can use a *clouds.yaml* and export "*OS_CLOUD*" environment variable to pass the cloud name,
or specify it directly using the `--os-cloud` option.

For more information: https://docs.openstack.org/python-openstackclient/latest/cli/man/openstack.html

## Installation

Clone or download the repository to your local machine.
Install from PyPI:

#### Development mode using pip
```bash
$ pip install -e .
pip install openstack-lb-info
```
Or, clone the repository and install from source in development mode:

#### Development mode using pipx
```bash
$ pipx install -e .
git clone https://github.com/thobiast/openstack-loadbalancer-info.git
cd openstack-loadbalancer-info
python3 -m venv venv
source venv/bin/activate
pip install -e .
```
You can also use **pipx** to install it in an isolated environment automatically:

```bash
pipx install -e .
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
name = "openstack-lb-info"
description = "A script to display OpenStack Load Balancer resource details."
readme = "README.md"
license = {file = "LICENSE"}
license = "MIT"
license-files = ["LICENSE"]
dynamic = ["version", "dependencies"]
requires-python = ">=3.7"
authors = [
Expand Down
2 changes: 1 addition & 1 deletion src/openstack_lb_info/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
"""openstack-lb-info module."""

__version__ = "0.2.1"
__version__ = "0.2.2"
65 changes: 64 additions & 1 deletion src/openstack_lb_info/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ def add_listener_to_tree(self, parent_tree, listener):
def add_pool_to_tree(self, parent_tree, pool):
"""Add a formatted pool node to a parent tree."""

@abstractmethod
def add_l7policy_to_tree(self, parent_tree, l7policy):
"""Add a formatted L7 Policy node to a parent tree."""

@abstractmethod
def add_l7rule_to_tree(self, parent_tree, l7rule):
"""Add a formatted L7 Rule node to a parent tree."""

@abstractmethod
def add_health_monitor_to_tree(self, parent_tree, hm):
"""Add a formatted health monitor node to a parent tree."""
Expand Down Expand Up @@ -195,7 +203,8 @@ def add_listener_to_tree(self, parent_tree, listener):
f"([blue b]{listener.name}[/]) "
f"port:[cyan]{listener.protocol}/{listener.protocol_port}[/] "
f"prov_status:{self.format_status(listener.provisioning_status)} "
f"oper_status:{self.format_status(listener.operating_status)}"
f"oper_status:{self.format_status(listener.operating_status)} "
f"default_pool_id:[b white]{listener.default_pool_id}[/]"
)
return self._add_to_tree(parent_tree, message)

Expand All @@ -211,6 +220,37 @@ def add_pool_to_tree(self, parent_tree, pool):
)
return self._add_to_tree(parent_tree, message)

def add_l7policy_to_tree(self, parent_tree, l7policy):
"""Add a styled L7 Policy node to the tree."""
message = (
f"[b green]L7 Policy:[/] [b white]{l7policy.id}[/] "
f"([blue b]{l7policy.name}[/]) "
f"position:[magenta]{l7policy.position}[/magenta] "
f"action:[magenta]{l7policy.action}[/magenta] "
f"prov_status:{self.format_status(l7policy.provisioning_status)} "
f"oper_status:{self.format_status(l7policy.operating_status)}"
)
redir_attrs = ["redirect_pool_id", "redirect_prefix", "redirect_url"]
for attr in redir_attrs:
value = getattr(l7policy, attr, None)
if value is not None:
message += f" {attr}:{value}"
return self._add_to_tree(parent_tree, message)

def add_l7rule_to_tree(self, parent_tree, l7rule):
"""Add a styled L7 Rule node to the tree."""
message = (
f"[b green]L7 Rule:[/] [b white]{l7rule.id}[/] "
f"compare_type:[magenta]{l7rule.compare_type}[/magenta] "
f"invert:[magenta]{l7rule.invert}[/magenta] "
f"key:[magenta]{l7rule.key}[/magenta] "
f"type:[magenta]{l7rule.type}[/magenta] "
f"rule_value:[magenta]{l7rule.rule_value}[/magenta] "
f"prov_status:{self.format_status(l7rule.provisioning_status)} "
f"oper_status:{self.format_status(l7rule.operating_status)}"
)
return self._add_to_tree(parent_tree, message)

def add_health_monitor_to_tree(self, parent_tree, hm):
"""Add a styled health monitor node to the tree."""
message = (
Expand Down Expand Up @@ -342,6 +382,23 @@ def add_pool_to_tree(self, parent_tree, pool):
)
return self._add_to_tree(parent_tree, message)

def add_l7policy_to_tree(self, parent_tree, l7policy):
message = f"L7 Policy: {l7policy.id} "
return self._add_to_tree(parent_tree, message)

def add_l7rule_to_tree(self, parent_tree, l7rule):
message = (
f"L7 Rule: {l7rule.id} "
f"compare_type:{l7rule.compare_type} "
f"invert:{l7rule.invert} "
f"key:{l7rule.key} "
f"type:{l7rule.type} "
f"rule_value:{l7rule.rule_value} "
f"prov_status:{self.format_status(l7rule.provisioning_status)} "
f"oper_status:{self.format_status(l7rule.operating_status)}"
)
return self._add_to_tree(parent_tree, message)

def add_health_monitor_to_tree(self, parent_tree, hm):
message = (
f"Health Monitor: {hm.id} "
Expand Down Expand Up @@ -437,6 +494,12 @@ def add_listener_to_tree(self, parent_tree, listener):
def add_pool_to_tree(self, parent_tree, pool):
return self._add_node_from_obj(parent_tree, "pool", pool)

def add_l7policy_to_tree(self, parent_tree, l7policy):
return self._add_node_from_obj(parent_tree, "l7policy", l7policy)

def add_l7rule_to_tree(self, parent_tree, l7rule):
return self._add_node_from_obj(parent_tree, "l7rule", l7rule)

def add_health_monitor_to_tree(self, parent_tree, hm):
return self._add_node_from_obj(parent_tree, "health_monitor", hm)

Expand Down
69 changes: 64 additions & 5 deletions src/openstack_lb_info/loadbalancer_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,16 @@ def create_lb_tree(self):
if self.details:
self.formatter.add_details_to_tree(self.lb_tree, self.lb.to_dict())

def add_listener_info(self, lb_tree, listener_id):
def add_listener_info(self, lb_tree, listener_id, listener_pool_ids):
"""
Add information about the Listener to the Load Balancer's tree.

Args:
lb_tree (object): The root tree node for the load balancer.
listener_id (str): The ID of the Listener for which to retrieve and display
information.
listener_pool_ids (set[str]): A set of pool IDs associated with this
listener.
"""
with self.formatter.status(f"Getting Listener details id [b]{listener_id}[/b]"):
listener = self.openstack_api.retrieve_listener(listener_id)
Expand All @@ -97,13 +99,62 @@ def add_listener_info(self, lb_tree, listener_id):
if self.details:
self.formatter.add_details_to_tree(listener_tree, listener.to_dict())

if listener.default_pool_id:
self.add_pool_info(listener_tree, listener.default_pool_id)
if listener.l7_policies:
for l7policy in listener.l7_policies:
self.add_l7policy_info(listener_tree, l7policy["id"])
else:
self.formatter.add_empty_node(listener_tree, "Pool")
self.formatter.add_empty_node(listener_tree, "L7 Policy")

for pool_id in listener_pool_ids:
self.add_pool_info(listener_tree, pool_id)
else:
self.formatter.add_empty_node(lb_tree, "Listener")

def add_l7policy_info(self, listener_tree, l7policy_id):
"""
Add information about the L7 Policy to the listener's tree.

Args:
listener_tree (object): The tree representing the listener.
l7policy_id (str): The ID of the L7 Policy for which to retrieve and display.
"""
with self.formatter.status(f"Getting L7 Policy details id [b]{l7policy_id}[/b]"):
l7policy = self.openstack_api.retrieve_l7_policy(l7policy_id)

if l7policy:
l7_tree = self.formatter.add_l7policy_to_tree(listener_tree, l7policy)

if self.details:
self.formatter.add_details_to_tree(l7_tree, l7policy.to_dict())

self.add_l7rules_info(l7_tree, l7policy)

def add_l7rules_info(self, l7_tree, l7policy):
"""
Add information about the L7 Rules to the L7 Policy's tree.

Args:
l7_tree (object): The tree representing the l7 policy.
l7policy (openstack.load_balancer.v2.l7_policy.L7Policy): The L7 Policy
for which to retrieve and display the rules.
"""
rule_ids = [rule["id"] for rule in l7policy.rules if "id" in rule]
if not rule_ids:
self.formatter.add_empty_node(l7_tree, "L7 Rule")
return

for rule_id in rule_ids:
with self.formatter.status(f"Getting L7 Rule details id [b]{rule_id}[/b]"):
l7rule = self.openstack_api.retrieve_l7_rule(rule_id, l7policy.id)

if not l7rule:
self.formatter.add_empty_node(l7_tree, f"L7 Rule ({rule_id})")
continue

l7rule_tree = self.formatter.add_l7rule_to_tree(l7_tree, l7rule)
if self.details:
self.formatter.add_details_to_tree(l7rule_tree, l7rule.to_dict())

def add_pool_info(self, listener_tree, pool_id):
"""
Add information about the Pool to the listener's tree.
Expand Down Expand Up @@ -213,8 +264,16 @@ def display_lb_info(self):
if not self.lb.listeners:
self.formatter.add_empty_node(self.lb_tree, "Listener")
else:
# Get all pools associated with this load balancer
lb_pools = list(self.openstack_api.retrieve_pools(loadbalancer_id=self.lb.id))
for listener in self.lb.listeners:
self.add_listener_info(self.lb_tree, listener["id"])
# Filter the ids of the pools attached to this specific listener
listener_pool_ids = {
pool.id
for pool in lb_pools
if any(lstn.get("id") == listener["id"] for lstn in (pool.listeners or []))
}
self.add_listener_info(self.lb_tree, listener["id"], listener_pool_ids)

self.formatter.rule(
f"[b]Loadbalancer ID: {self.lb.id} [bright_blue]({self.lb.name})[/]",
Expand Down
50 changes: 50 additions & 0 deletions src/openstack_lb_info/openstack_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,22 @@ def retrieve_pool(self, pool_id):
log.debug("Retrieving pool with ID: %s", pool_id)
return self.os_conn.load_balancer.find_pool(pool_id)

def retrieve_pools(self, loadbalancer_id):
"""
Retrieve pools associated with an OpenStack load balancer.

Args:
loadbalancer_id (str): The ID of the load balancer for which pools are
to be retrieved.

Returns:
Generator[openstack.load_balancer.v2.pool.Pool]: A generator of OpenStack
pool objects representing the pools associated with the specified
load balancer.
"""
log.debug("Retrieving pools for load balancer ID: %s", loadbalancer_id)
return self.os_conn.load_balancer.pools(loadbalancer_id=loadbalancer_id)

def retrieve_health_monitor(self, health_monitor_id):
"""
Retrieve details of an OpenStack load balancer health monitor.
Expand All @@ -101,6 +117,40 @@ def retrieve_health_monitor(self, health_monitor_id):
log.debug("Retrieving health monitor with ID: %s", health_monitor_id)
return self.os_conn.load_balancer.find_health_monitor(health_monitor_id)

def retrieve_l7_policy(self, l7_policy_id):
"""
Retrieve details of an L7 Policy.

Args:
l7_policy_id (str): The ID of the L7 Policy to retrieve.

Returns:
openstack.load_balancer.v2.l7_policy.L7Policy | None:
The L7 Policy object if found, otherwise None.
"""
log.debug("Retrieving L7 Policy with ID: %s", l7_policy_id)
return self.os_conn.load_balancer.find_l7_policy(l7_policy_id)

def retrieve_l7_rule(self, l7_rule_id, l7_policy_id, ignore_missing=True):
"""
Retrieve details of an L7 Rule.

Args:
l7_rule_id (str): The ID of the L7 Rule to retrieve.
l7_policy_id (str): The ID of the L7 Policy that the l7rule belongs to.
ignore_missing (bool, optional): If True, returns None if the l7rule
is not found. If False, raises an exception if the rule
does not exist. Defaults to True.

Returns:
openstack.load_balancer.v2.l7_rule.L7Rule | None:
The L7 Rule object if found, otherwise None.
"""
log.debug("Retrieving L7 Rule %s for L7 Policy %s", l7_rule_id, l7_policy_id)
return self.os_conn.load_balancer.find_l7_rule(
l7_rule_id, l7_policy_id, ignore_missing=ignore_missing
)

def retrieve_member(self, member_id, pool_id):
"""
Retrieve details of an load balancer member by its ID and associated pool.
Expand Down