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
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: end-of-file-fixer
- id: check-yaml
- id: trailing-whitespace
- id: debug-statements

- repo: https://github.com/psf/black
rev: 24.8.0
rev: 25.1.0
hooks:
- id: black

- repo: https://github.com/PyCQA/bandit
rev: 1.7.9
rev: 1.8.6
hooks:
- id: bandit
exclude: ^tests/

- repo: https://github.com/PyCQA/flake8
rev: 7.1.1
rev: 7.3.0
hooks:
- id: flake8
additional_dependencies: [flake8-bugbear, pep8-naming]
Expand All @@ -32,7 +32,7 @@ repos:
args: ["--profile", "black"]

- repo: https://github.com/PyCQA/pylint
rev: v3.2.7
rev: v3.3.8
hooks:
- id: pylint
additional_dependencies:
Expand Down
25 changes: 17 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,25 @@ and optional details. If no amphoras match the filter criteria, it will indicate
## Example

```bash
$ usage: openstack-lb-info [-h] [-o {plain,rich,json}] -t {lb,amphora} [--name NAME] [--id ID]
[--tags TAGS] [--flavor-id FLAVOR_ID] [--vip-address VIP_ADDRESS]
[--availability-zone AVAILABILITY_ZONE] [--vip-network-id VIP_NETWORK_ID]
[--vip-subnet-id VIP_SUBNET_ID] [--details] [--max-workers MAX_WORKERS]
$ 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]
[--availability-zone AVAILABILITY_ZONE]
[--vip-network-id VIP_NETWORK_ID]
[--vip-subnet-id VIP_SUBNET_ID] [--details]
[--no-members] [--max-workers MAX_WORKERS]

A script to show OpenStack load balancers information.

options:
-h, --help show this help message and exit
-o {plain,rich,json}, --output-format {plain,rich,json}
Output format: 'plain', 'rich' or 'json'
--os-cloud OS_CLOUD Name of the cloud to load from clouds.yaml.
(Default 'envvars', which uses OS_* env vars)
-t {lb,amphora}, --type {lb,amphora}
Show information about load balancers or amphoras
-o {plain,rich,json}, --output-format {plain,rich,json}
Output format. (default: rich)
--name NAME Filter load balancers name
--id ID Filter load balancers id (UUID)
--tags TAGS Filter load balancers tags
Expand All @@ -66,9 +72,12 @@ options:
Filter load balancers network id (UUID)
--vip-subnet-id VIP_SUBNET_ID
Filter load balancers subnet id (UUID)
--details Show all load balancers/amphora details
--details Show all load balancers/amphora details. (default: False)
--no-members Do not show load balancers pool members information.
(default: False)
--max-workers MAX_WORKERS
Max number of concurrent threads to fetch members details (1-32). (default: 4)
Max number of concurrent threads to fetch members details (1-32).
(default: 4)

Example of use:
openstack-lb-info
Expand Down
6 changes: 4 additions & 2 deletions src/openstack_lb_info/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ def add_pool_to_tree(self, parent_tree, pool):
f"protocol:[magenta]{pool.protocol}[/magenta] "
f"algorithm:[magenta]{pool.lb_algorithm}[/magenta] "
f"prov_status:{self.format_status(pool.provisioning_status)} "
f"oper_status:{self.format_status(pool.operating_status)}"
f"oper_status:{self.format_status(pool.operating_status)} "
f"number_members:[cyan]{len(pool.members)}[/]"
)
return self._add_to_tree(parent_tree, message)

Expand Down Expand Up @@ -336,7 +337,8 @@ def add_pool_to_tree(self, parent_tree, pool):
f"protocol:{pool.protocol} "
f"algorithm:{pool.lb_algorithm} "
f"prov_status:{self.format_status(pool.provisioning_status)} "
f"oper_status:{self.format_status(pool.operating_status)}"
f"oper_status:{self.format_status(pool.operating_status)} "
f"number_members:{len(pool.members)}"
)
return self._add_to_tree(parent_tree, message)

Expand Down
23 changes: 19 additions & 4 deletions src/openstack_lb_info/loadbalancer_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ class for interacting with the OpenStack environment and uses `OutputFormatter`
about the amphorae associated with a Load Balancer.
"""
import concurrent.futures
import logging
from dataclasses import dataclass

from .formatters import OutputFormatter
from .openstack_api import OpenStackAPI

log = logging.getLogger(__name__)


@dataclass
class ProcessingContext:
Expand All @@ -33,11 +36,13 @@ class ProcessingContext:
details (bool): If True, displays detailed attributes of the Load Balancer.
formatter (OutputFormatter): An instance of a formatter class for output formatting.
max_workers (int): Max number of concurrent threads to fetch members details.
no_members (bool): Do not show load balancer pool members information.
"""

openstack_api: OpenStackAPI
details: bool
max_workers: int
no_members: bool
formatter: OutputFormatter


Expand All @@ -59,8 +64,10 @@ def __init__(self, lb, context):
self.formatter = context.formatter
self.openstack_api = context.openstack_api
self.max_workers = context.max_workers
self.no_members = context.no_members
# The root of the display tree for the formatter
self.lb_tree = None
log.info("Processing info for Load Balancer ID: %s (Name: %s)", self.lb.id, self.lb.name)

def create_lb_tree(self):
"""
Expand Down Expand Up @@ -118,10 +125,11 @@ def add_pool_info(self, listener_tree, pool_id):
else:
self.formatter.add_empty_node(pool_tree, "Health Monitor")

if pool.members:
self.add_pool_members(pool_tree, pool.id, pool.members)
else:
self.formatter.add_empty_node(pool_tree, "Member")
if not self.no_members:
if pool.members:
self.add_pool_members(pool_tree, pool.id, pool.members)
else:
self.formatter.add_empty_node(pool_tree, "Member")
else:
self.formatter.add_empty_node(listener_tree, "Pool")

Expand Down Expand Up @@ -155,6 +163,12 @@ def add_pool_members(self, pool_tree, pool_id, pool_members):
"""
# Avoid spinning up extra idle threads
max_workers = min(self.max_workers, len(pool_members))
log.debug(
"Using %s workers to fetch details of %s members (pool_id=%s)",
max_workers,
len(pool_members),
pool_id,
)

with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
# Create future for each member IDs
Expand Down Expand Up @@ -206,6 +220,7 @@ def display_lb_info(self):
f"[b]Loadbalancer ID: {self.lb.id} [bright_blue]({self.lb.name})[/]",
align="center",
)
log.info("Displaying final tree for Load Balancer ID: %s", self.lb.id)
self.formatter.print_tree(self.lb_tree)
self.formatter.print("")

Expand Down
75 changes: 67 additions & 8 deletions src/openstack_lb_info/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

import argparse
import ipaddress
import logging
import sys
import uuid

Expand All @@ -44,6 +45,8 @@
from .loadbalancer_info import AmphoraInfo, LoadBalancerInfo, ProcessingContext
from .openstack_api import OpenStackAPI

log = logging.getLogger(__name__)

# Max allowed threads for --max-workers
MAX_WORKERS_LIMIT = 32

Expand Down Expand Up @@ -73,11 +76,20 @@ def parse_parameters():
)

parser.add_argument(
"-o",
"--output-format",
help="Output format: 'plain', 'rich' or 'json'",
choices=("plain", "rich", "json"),
default="rich",
"-d",
"--debug",
help="Enable debug log messages. (default: %(default)s)",
action="store_true",
required=False,
)
parser.add_argument(
"--os-cloud",
help=(
"Name of the cloud to load from clouds.yaml. "
"(Default '%(default)s', which uses OS_* env vars)"
),
type=str,
default="envvars",
required=False,
)
parser.add_argument(
Expand All @@ -87,6 +99,14 @@ def parse_parameters():
choices=("lb", "amphora"),
required=True,
)
parser.add_argument(
"-o",
"--output-format",
help="Output format. (default: %(default)s)",
choices=("plain", "rich", "json"),
default="rich",
required=False,
)
parser.add_argument("--name", help="Filter load balancers name", type=str, required=False)
parser.add_argument(
"--id", help="Filter load balancers id (UUID)", type=validate_uuid, required=False
Expand Down Expand Up @@ -121,7 +141,13 @@ def parse_parameters():
)
parser.add_argument(
"--details",
help="Show all load balancers/amphora details",
help="Show all load balancers/amphora details. (default: %(default)s)",
action="store_true",
required=False,
)
parser.add_argument(
"--no-members",
help="Do not show load balancers pool members information. (default: %(default)s)",
action="store_true",
required=False,
)
Expand Down Expand Up @@ -211,6 +237,23 @@ def validate_ip_address(value_str):
raise argparse.ArgumentTypeError(f"Invalid IP address: {value_str!r}") from exc


def setup_logging(log_level):
"""Setup logging configuration."""
datefmt = "%Y-%m-%d %H:%M:%S"
msg_fmt = "%(asctime)s - %(module)s.%(funcName)s - [%(levelname)s] - %(message)s"

formatter = logging.Formatter(
fmt=msg_fmt,
datefmt=datefmt,
)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)

root_logger = logging.getLogger()
root_logger.setLevel(log_level)
root_logger.addHandler(handler)


def query_openstack_lbs(openstackapi, args, formatter):
"""
Query OpenStack Load Balancers based on user-defined filters.
Expand Down Expand Up @@ -238,6 +281,7 @@ def query_openstack_lbs(openstackapi, args, formatter):
}.items()
if v is not None
}
log.debug("Retrieve load balancers filter: %s", filter_criteria)

with formatter.status("Querying load balancers and applying filters..."):
filtered_lbs_tmp = openstackapi.retrieve_load_balancers(filter_criteria)
Expand Down Expand Up @@ -283,6 +327,10 @@ def main():

args = parse_parameters()

log_level = logging.DEBUG if args.debug else logging.WARNING
setup_logging(log_level)
log.debug("CMD line args: %s", args)

if args.output_format == "rich" and not RICH_AVAILABLE:
sys.exit(
"Error: 'rich' library is not installed. "
Expand All @@ -293,9 +341,18 @@ def main():
formatter = get_formatter(args.output_format)

# Create an instance of OpenStackAPI
openstackapi = OpenStackAPI()
try:
openstackapi = OpenStackAPI(args.os_cloud)
except RuntimeError as exc:
sys.exit(f"Error: {exc}")

try:
filtered_lbs = query_openstack_lbs(openstackapi, args, formatter)
except Exception as exc: # pylint: disable=broad-exception-caught
log.debug("Error to query openstack:", exc_info=True)
sys.exit(f"Error: {exc}")

filtered_lbs = query_openstack_lbs(openstackapi, args, formatter)
log.info("Found %d load balancer(s) to process.", len(filtered_lbs))

if not filtered_lbs:
formatter.print("No load balancer(s) found.")
Expand All @@ -305,8 +362,10 @@ def main():
openstack_api=openstackapi,
details=args.details,
max_workers=args.max_workers,
no_members=args.no_members,
formatter=formatter,
)
log.debug("Process context: %s", context)

for lb in filtered_lbs:
if args.type == "amphora":
Expand Down
Loading