Skip to content

Commit

Permalink
Merge pull request #150 from anish-mudaraddi/openstack-query/flavor-q…
Browse files Browse the repository at this point in the history
…uery

ENH: add flavor query
  • Loading branch information
meoflynn authored Oct 11, 2023
2 parents d988908 + 2906a92 commit b4bd2f6
Show file tree
Hide file tree
Showing 18 changed files with 706 additions and 83 deletions.
68 changes: 68 additions & 0 deletions lib/enums/query/props/flavor_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from enum import auto
from enums.query.props.prop_enum import PropEnum
from exceptions.parse_query_error import ParseQueryError
from exceptions.query_property_mapping_error import QueryPropertyMappingError


class FlavorProperties(PropEnum):
"""
An enum class for all flavor properties
"""

FLAVOR_DESCRIPTION = auto()
FLAVOR_DISK = auto()
FLAVOR_EPHEMERAL = auto()
FLAVOR_ID = auto()
FLAVOR_IS_DISABLED = auto()
FLAVOR_IS_PUBLIC = auto()
FLAVOR_NAME = auto()
FLAVOR_RAM = auto()
FLAVOR_SWAP = auto()
FLAVOR_VCPU = auto()

@staticmethod
def from_string(val: str):
"""
Converts a given string in a case-insensitive way to the enum values
"""
try:
return FlavorProperties[val.upper()]
except KeyError as err:
raise ParseQueryError(
f"Could not find Flavor Property {val}. "
f"Available properties are {','.join([prop.name for prop in FlavorProperties])}"
) from err

@staticmethod
def get_prop_mapping(prop):
"""
Method that returns the property function if function mapping exists for a given FlavorProperty Enum
how to get specified property from an openstacksdk Flavor object is documented here:
https://docs.openstack.org/openstacksdk/latest/user/resources/compute/v2/flavor.html#openstack.compute.v2.flavor.Flavor
:param prop: A FlavorProperty Enum for which a function may exist for
"""
mapping = {
FlavorProperties.FLAVOR_DESCRIPTION: lambda a: a["description"],
FlavorProperties.FLAVOR_DISK: lambda a: a["disk"],
FlavorProperties.FLAVOR_EPHEMERAL: lambda a: a["ephemeral"],
FlavorProperties.FLAVOR_ID: lambda a: a["id"],
FlavorProperties.FLAVOR_IS_DISABLED: lambda a: a["is_disabled"],
FlavorProperties.FLAVOR_IS_PUBLIC: lambda a: a["is_public"],
FlavorProperties.FLAVOR_NAME: lambda a: a["name"],
FlavorProperties.FLAVOR_RAM: lambda a: a["ram"],
FlavorProperties.FLAVOR_SWAP: lambda a: a["swap"],
FlavorProperties.FLAVOR_VCPU: lambda a: a["vcpus"],
}
try:
return mapping[prop]
except KeyError as exp:
raise QueryPropertyMappingError(
f"Error: failed to get property mapping, property {prop.name} is not supported in FlavorProperties"
) from exp

@staticmethod
def get_marker_prop_func():
"""
A getter method to return marker property function for pagination
"""
return FlavorProperties.get_prop_mapping(FlavorProperties.FLAVOR_ID)
1 change: 1 addition & 0 deletions lib/openstack_query/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
from .api.query_objects import ServerQuery
from .api.query_objects import UserQuery
from .api.query_objects import FlavorQuery

# Create logger
openstack_query_loggers = logging.getLogger(__name__)
Expand Down
8 changes: 8 additions & 0 deletions lib/openstack_query/api/query_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from openstack_query.query_factory import QueryFactory
from openstack_query.mappings.user_mapping import UserMapping
from openstack_query.mappings.server_mapping import ServerMapping
from openstack_query.mappings.flavor_mapping import FlavorMapping


def get_common(query_mapping: Type[MappingInterface]) -> QueryAPI:
Expand Down Expand Up @@ -31,3 +32,10 @@ def UserQuery() -> QueryAPI:
Simple helper function to setup a query using a factory
"""
return get_common(UserMapping)


def FlavorQuery() -> QueryAPI:
"""
Simple helper function to setup a query using a factory
"""
return get_common(FlavorMapping)
10 changes: 4 additions & 6 deletions lib/openstack_query/handlers/server_side_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,11 @@ def _check_filter_mapping(
:param filter_func: lambda filter func to check
:param filter_params: a dictionary of params to check if valid for filter func
"""
logger.debug(
"checking server-side filter function against provided parameters\n\t%s",
"\n\t".join([f"{key}: '{value}'" for key, value in filter_params.items()]),
)
try:
logger.debug(
"checking server-side filter function against provided parameters\n\t%s",
"\n\t".join(
[f"{key}: '{value}'" for key, value in filter_params.items()]
),
)
filter_func(**filter_params)
except KeyError as err:
return (
Expand Down
114 changes: 114 additions & 0 deletions lib/openstack_query/mappings/flavor_mapping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from typing import Type
from structs.query.query_client_side_handlers import QueryClientSideHandlers

from enums.query.props.flavor_properties import FlavorProperties
from enums.query.query_presets import (
QueryPresetsGeneric,
QueryPresetsInteger,
QueryPresetsString,
)

from openstack_query.handlers.server_side_handler import ServerSideHandler
from openstack_query.handlers.client_side_handler_generic import (
ClientSideHandlerGeneric,
)
from openstack_query.handlers.client_side_handler_integer import (
ClientSideHandlerInteger,
)
from openstack_query.handlers.client_side_handler_string import ClientSideHandlerString

from openstack_query.mappings.mapping_interface import MappingInterface
from openstack_query.runners.flavor_runner import FlavorRunner


class FlavorMapping(MappingInterface):
"""
Mapping class for querying Openstack Flavor objects
Define property mappings, kwarg mappings and filter function mappings, and runner mapping related to flavors here
"""

@staticmethod
def get_runner_mapping() -> Type[FlavorRunner]:
"""
Returns a mapping to associated Runner class for the Query (FlavorRunner)
"""
return FlavorRunner

@staticmethod
def get_prop_mapping() -> Type[FlavorProperties]:
"""
Returns a mapping of valid presets for server side attributes (FlavorProperties)
"""
return FlavorProperties

@staticmethod
def get_server_side_handler() -> ServerSideHandler:
"""
method to configure a server handler which can be used to get 'filter' keyword arguments that
can be passed to openstack function conn.compute.flavors() to filter results for a valid preset-property pair
valid filters documented here:
https://docs.openstack.org/openstacksdk/latest/user/proxies/compute.html
https://docs.openstack.org/api-ref/compute/?expanded=list-servers-detail#show-flavor-details
"""
return ServerSideHandler(
{
QueryPresetsGeneric.EQUAL_TO: {
FlavorProperties.FLAVOR_IS_PUBLIC: lambda value: {
"is_public": value
}
},
QueryPresetsGeneric.NOT_EQUAL_TO: {
FlavorProperties.FLAVOR_IS_PUBLIC: lambda value: {
"is_public": not value
}
},
QueryPresetsInteger.LESS_THAN_OR_EQUAL_TO: {
FlavorProperties.FLAVOR_DISK: lambda value: {"minDisk": int(value)},
FlavorProperties.FLAVOR_RAM: lambda value: {"minRam": int(value)},
},
}
)

@staticmethod
def get_client_side_handlers() -> QueryClientSideHandlers:
"""
method to configure a set of client-side handlers which can be used to get local filter functions
corresponding to valid preset-property pairs. These filter functions can be used to filter results after
listing all servers.
"""

integer_prop_list = [
FlavorProperties.FLAVOR_RAM,
FlavorProperties.FLAVOR_DISK,
FlavorProperties.FLAVOR_EPHEMERAL,
FlavorProperties.FLAVOR_SWAP,
FlavorProperties.FLAVOR_VCPU,
]

return QueryClientSideHandlers(
# set generic query preset mappings
generic_handler=ClientSideHandlerGeneric(
{
QueryPresetsGeneric.EQUAL_TO: ["*"],
QueryPresetsGeneric.NOT_EQUAL_TO: ["*"],
QueryPresetsGeneric.ANY_IN: ["*"],
QueryPresetsGeneric.NOT_ANY_IN: ["*"],
}
),
# set string query preset mappings
string_handler=ClientSideHandlerString(
{QueryPresetsString.MATCHES_REGEX: [FlavorProperties.FLAVOR_NAME]}
),
# set datetime query preset mappings
datetime_handler=None,
# set integer query preset mappings
integer_handler=ClientSideHandlerInteger(
{
QueryPresetsInteger.LESS_THAN: integer_prop_list,
QueryPresetsInteger.LESS_THAN_OR_EQUAL_TO: integer_prop_list,
QueryPresetsInteger.GREATER_THAN: integer_prop_list,
QueryPresetsInteger.GREATER_THAN_OR_EQUAL_TO: integer_prop_list,
}
),
)
54 changes: 54 additions & 0 deletions lib/openstack_query/runners/flavor_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from typing import Optional, List
import logging

from openstack.compute.v2.flavor import Flavor
from exceptions.parse_query_error import ParseQueryError

from openstack_api.openstack_connection import OpenstackConnection
from openstack_query.runners.runner_wrapper import RunnerWrapper

from custom_types.openstack_query.aliases import ServerSideFilters

logger = logging.getLogger(__name__)

# pylint:disable=too-few-public-methods


class FlavorRunner(RunnerWrapper):
"""
Runner class for openstack flavor resource
FlavorRunner encapsulates running any openstacksdk Flavor commands
"""

RESOURCE_TYPE = Flavor

def _parse_meta_params(self, _: OpenstackConnection, **__):
logger.error(
"FlavorQuery doesn't take any meta-params, if you think it should,"
"please raise an issue with the repo maintainers"
)
raise ParseQueryError("FlavorQuery has no meta-params available")

def _run_query(
self,
conn: OpenstackConnection,
filter_kwargs: Optional[ServerSideFilters] = None,
**_,
) -> List[Flavor]:
"""
This method runs the query by running openstacksdk commands
For FlavorQuery, this command finds all flavors that match a given set of filter_kwargs
:param conn: An OpenstackConnection object - used to connect to openstacksdk
:param filter_kwargs: An Optional list of filter kwargs to pass to conn.compute.flavors()
to limit the flavors being returned.
- see https://docs.openstack.org/api-ref/compute/#list-flavors-with-details
"""
if not filter_kwargs:
# return all info
filter_kwargs = {"details": True}
logger.debug(
"running openstacksdk command conn.compute.flavors(%s)",
",".join(f"{key}={value}" for key, value in filter_kwargs.items()),
)
return self._run_paginated_query(conn.compute.flavors, filter_kwargs)
48 changes: 28 additions & 20 deletions lib/openstack_query/runners/runner_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
OpenstackResourceObj,
ClientSideFilterFunc,
)
from exceptions.parse_query_error import ParseQueryError
from openstack_api.openstack_connection import OpenstackConnection
from openstack_api.openstack_wrapper_base import OpenstackWrapperBase

Expand All @@ -27,6 +28,7 @@ class RunnerWrapper(OpenstackWrapperBase):
# Sets the limit for getting values from openstack
_LIMIT_FOR_PAGINATION = 1000
_PAGINATION_CALL_LIMIT = 1000
RESOURCE_TYPE = type(None)

def __init__(self, marker_prop_func: PropFunc, connection_cls=OpenstackConnection):
OpenstackWrapperBase.__init__(self, connection_cls)
Expand Down Expand Up @@ -65,30 +67,30 @@ def run(

logger.debug("making connection to openstack")
start = time.time()
with self._connection_cls(cloud_account) as conn:

if from_subset:
logger.info("'from_subset' meta param given - running query on subset")
resource_objects = self._parse_subset(
subset=from_subset,
)
logger.debug(
"openstack connection established - using cloud account '%s'",
cloud_account,
"subset parsed. (time elapsed: %0.4f seconds)", time.time() - start
)
if from_subset:
logger.info("'from_subset' meta param given - running query on subset")
resource_objects = self._parse_subset(
conn=conn,
subset=from_subset,
)
else:
logger.info("running query using openstacksdk and server-side filters")
with self._connection_cls(cloud_account) as conn:
logger.debug(
"subset parsed. (time elapsed: %0.4f seconds)", time.time() - start
"openstack connection established - using cloud account '%s'",
cloud_account,
)
else:
logger.info("running query using openstacksdk and server-side filters")
resource_objects = self._run_with_openstacksdk(
conn=conn, server_filters=server_side_filters, **kwargs
)
logger.debug(
"server-side quer(y/ies) completed - found %s items. (time elapsed: %0.4f seconds)",
len(resource_objects),
time.time() - start,
)
logger.debug(
"server-side quer(y/ies) completed - found %s items. (time elapsed: %0.4f seconds)",
len(resource_objects),
time.time() - start,
)

logger.info("applying client side filters - if any")
if client_side_filters:
Expand Down Expand Up @@ -246,16 +248,22 @@ def _run_query(
openstacksdk query is run - these kwargs are specific to the resource runner.
"""

@abstractmethod
def _parse_subset(
self, conn: OpenstackConnection, subset: List[OpenstackResourceObj]
self, subset: List[OpenstackResourceObj]
) -> List[OpenstackResourceObj]:
"""
This method is a helper function that will check a subset of openstack objects and check their validity
:param conn: An OpenstackConnection object - used to connect to openstacksdk
:param _: An OpenstackConnection object - not used right now
:param subset: A list of openstack objects to parse
"""

# connection object may need to be used if we want to run validation checks
if any(not isinstance(i, self.RESOURCE_TYPE) for i in subset):
raise ParseQueryError(
f"'from_subset' only accepts openstack objects of type {self.RESOURCE_TYPE}"
)
return subset

@abstractmethod
def _parse_meta_params(self, conn: OpenstackConnection, **kwargs) -> Dict[str, str]:
"""
Expand Down
Loading

0 comments on commit b4bd2f6

Please sign in to comment.