Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API management refactoring. #15

Merged
merged 8 commits into from
Jul 29, 2023
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
3 changes: 2 additions & 1 deletion azext_edge/edge/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from knack.help_files import helps
from .providers.edge_api import E4K_ACTIVE_API
from .providers.support_bundle import COMPAT_BLUEFIN_APIS, COMPAT_E4K_APIS, COMPAT_OPCUA_APIS
from .providers.support_bundle import COMPAT_BLUEFIN_APIS, COMPAT_E4K_APIS, COMPAT_OPCUA_APIS, COMPAT_SYMPHONY_APIS


def load_iotedge_help():
Expand Down Expand Up @@ -42,6 +42,7 @@ def load_iotedge_help():
{COMPAT_E4K_APIS.as_str()}
{COMPAT_OPCUA_APIS.as_str()}
{COMPAT_BLUEFIN_APIS.as_str()}
{COMPAT_SYMPHONY_APIS.as_str()}
"""

helps[
Expand Down
5 changes: 0 additions & 5 deletions azext_edge/edge/commands_edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,8 @@ def check(
edge_service: str = "e4k",
) -> Union[dict, None]:
load_config_context(context_name=context_name)
from .providers.edge_api import E4K_ACTIVE_API
from .providers.checks import run_checks

# Currently check is only supported for e4k
if edge_service == "e4k":
digimaun marked this conversation as resolved.
Show resolved Hide resolved
E4K_ACTIVE_API.is_deployed(raise_on_404=True)

run_pre = True
run_post = True
if pre_deployment_checks and not post_deployment_checks:
Expand Down
26 changes: 13 additions & 13 deletions azext_edge/edge/providers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from kubernetes.client.exceptions import ApiException
from kubernetes.client.models import V1APIResourceList, V1Pod, V1PodList, V1Service

from .edge_api import EdgeResourceApi, EdgeResource

DEFAULT_NAMESPACE: str = "default"

Expand Down Expand Up @@ -88,18 +87,18 @@ def get_namespaced_pods_by_prefix(
_namespaced_object_cache: dict = {}


def get_namespaced_custom_objects(resource: EdgeResource, namespace: str) -> Union[List[dict], None]:
target_resource_key = (resource, resource.plural)
def get_namespaced_custom_objects(group: str, version: str, namespace: str, plural: str) -> Union[List[dict], None]:
target_resource_key = (group, version, namespace, plural)
if target_resource_key in _namespaced_object_cache:
return _namespaced_object_cache[target_resource_key]

try:
custom_client = client.CustomObjectsApi()
_namespaced_object_cache[target_resource_key] = custom_client.list_namespaced_custom_object(
group=resource.api.group,
version=resource.api.version,
group=group,
version=version,
namespace=namespace,
plural=resource.plural,
plural=plural,
)
except ApiException as ae:
logger.debug(str(ae))
Expand All @@ -110,21 +109,22 @@ def get_namespaced_custom_objects(resource: EdgeResource, namespace: str) -> Uni
_cluster_resource_api_cache: dict = {}


def get_cluster_custom_api(resource_api: EdgeResourceApi, raise_on_404: bool = False) -> Union[V1APIResourceList, None]:
if resource_api in _cluster_resource_api_cache:
return _cluster_resource_api_cache[resource_api]
def get_cluster_custom_api(group: str, version: str, raise_on_404: bool = False) -> Union[V1APIResourceList, None]:
target_resource_api_key = (group, version)
if target_resource_api_key in _cluster_resource_api_cache:
return _cluster_resource_api_cache[target_resource_api_key]

try:
custom_client = client.CustomObjectsApi()
_cluster_resource_api_cache[resource_api] = custom_client.get_api_resources(
group=resource_api.group, version=resource_api.version
_cluster_resource_api_cache[target_resource_api_key] = custom_client.get_api_resources(
group=group, version=version
)
except ApiException as ae:
logger.debug(msg=str(ae))
if int(ae.status) == 404 and raise_on_404:
raise ResourceNotFoundError(f"{resource_api.as_str()} resource API is not detected on the cluster.")
raise ResourceNotFoundError(f"{group}/{version} resource API is not detected on the cluster.")
else:
return _cluster_resource_api_cache[resource_api]
return _cluster_resource_api_cache[target_resource_api_key]


class PodRequest:
Expand Down
42 changes: 15 additions & 27 deletions azext_edge/edge/providers/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
# --------------------------------------------------------------------------------------------

from functools import partial
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Optional, Tuple, Union
from enum import Enum

from knack.log import get_logger
from kubernetes.client.exceptions import ApiException
Expand All @@ -26,12 +27,11 @@
ResourceState,
)

from ..providers.edge_api import E4K_ACTIVE_API, EdgeResource, E4kResourceKinds
from ..providers.edge_api import E4K_ACTIVE_API, E4kResourceKinds

from .base import (
client,
get_cluster_custom_api,
get_namespaced_custom_objects,
get_namespaced_pods_by_prefix,
get_namespaced_service,
)
Expand Down Expand Up @@ -191,12 +191,8 @@ def evaluate_broker_diagnostics(
check_manager.add_target(
target_name=target_diag, conditions=["len(brokerdiagnostics)<=1", "spec", "valid(spec.brokerRef)"]
)
valid_broker_refs = _get_valid_references(
resource=E4K_ACTIVE_API.get_resource(E4kResourceKinds.BROKER), namespace=namespace
)
diagnostics_list: dict = get_namespaced_custom_objects(
resource=E4K_ACTIVE_API.get_resource(E4kResourceKinds.BROKER_DIAGNOSTIC), namespace=namespace
)
valid_broker_refs = _get_valid_references(kind=E4kResourceKinds.BROKER, namespace=namespace)
diagnostics_list: dict = E4K_ACTIVE_API.get_resources(kind=E4kResourceKinds.BROKER_DIAGNOSTIC, namespace=namespace)
if not diagnostics_list:
check_manager.add_target_eval(
target_name=target_diag,
Expand Down Expand Up @@ -289,8 +285,8 @@ def evaluate_broker_diagnostics(
)

if not evaluated_diagnostic_services:
diagnostics_service_list: dict = get_namespaced_custom_objects(
resource=E4K_ACTIVE_API.get_resource(E4kResourceKinds.DIAGNOSTIC_SERVICE), namespace=namespace
diagnostics_service_list: dict = E4K_ACTIVE_API.get_resources(
E4kResourceKinds.DIAGNOSTIC_SERVICE, namespace=namespace
)
evaluated_diagnostic_services = True
diagnostics_service_resources = diagnostics_service_list.get("items", [])
Expand Down Expand Up @@ -460,16 +456,12 @@ def evaluate_broker_listeners(
listener_conditions = ["len(brokerlisteners)>=1", "spec", "valid(spec.brokerRef)", "spec.serviceName", "status"]
check_manager.add_target(target_name=target_listeners, conditions=listener_conditions)

valid_broker_refs = _get_valid_references(
resource=E4K_ACTIVE_API.get_resource(E4kResourceKinds.BROKER), namespace=namespace
)
listener_list: dict = get_namespaced_custom_objects(
resource=E4K_ACTIVE_API.get_resource(E4kResourceKinds.BROKER_LISTENER), namespace=namespace
)
valid_broker_refs = _get_valid_references(kind=E4kResourceKinds.BROKER, namespace=namespace)
listener_list: dict = E4K_ACTIVE_API.get_resources(E4kResourceKinds.BROKER_LISTENER, namespace=namespace)

if not listener_list:
fetch_listeners_error_text = (
f"Unable to fetch {E4K_ACTIVE_API.get_resource(E4kResourceKinds.BROKER_LISTENER).plural}."
f"Unable to fetch {E4kResourceKinds.BROKER_LISTENER.value}s."
)
check_manager.add_target_eval(
target_name=target_listeners, status=CheckTaskStatus.error.value, value=fetch_listeners_error_text
Expand Down Expand Up @@ -664,13 +656,9 @@ def evaluate_brokers(
broker_conditions = ["len(brokers)==1", "status", "spec.mode"]
check_manager.add_target(target_name=target_brokers, conditions=broker_conditions)

broker_list: dict = get_namespaced_custom_objects(
resource=E4K_ACTIVE_API.get_resource(E4kResourceKinds.BROKER), namespace=namespace
)
broker_list: dict = E4K_ACTIVE_API.get_resources(E4kResourceKinds.BROKER, namespace=namespace)
if not broker_list:
fetch_brokers_error_text = (
f"Unable to fetch namespace {E4K_ACTIVE_API.get_resource(E4kResourceKinds.BROKER).plural}."
)
fetch_brokers_error_text = f"Unable to fetch namespace {E4kResourceKinds.BROKER.value}s."
check_manager.add_target_eval(
target_name=target_brokers, status=CheckTaskStatus.error.value, value=fetch_brokers_error_text
)
Expand Down Expand Up @@ -826,7 +814,7 @@ def enumerate_e4k_resources(
check_manager = CheckManager(check_name="enumerateE4kApi", check_desc="Enumerate E4K API resources")
check_manager.add_target(target_name=target_api)

api_resources: V1APIResourceList = get_cluster_custom_api(resource_api=E4K_ACTIVE_API)
api_resources: V1APIResourceList = get_cluster_custom_api(group=E4K_ACTIVE_API.group, version=E4K_ACTIVE_API.version)

if not api_resources:
check_manager.add_target_eval(target_name=target_api, status=CheckTaskStatus.skipped.value)
Expand Down Expand Up @@ -1027,9 +1015,9 @@ def _decorate_resource_status(status: str) -> str:
return f"[green]{status}[/green]"


def _get_valid_references(resource: EdgeResource, namespace: str):
def _get_valid_references(kind: Union[Enum, str], namespace: str):
result = {}
custom_objects: dict = get_namespaced_custom_objects(resource=resource, namespace=namespace)
custom_objects = E4K_ACTIVE_API.get_resources(kind=kind, namespace=namespace)
if custom_objects:
objects: List[dict] = custom_objects.get("items", [])
for object in objects:
Expand Down
3 changes: 1 addition & 2 deletions azext_edge/edge/providers/edge_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
# Private distribution for NDA customers only. Governed by license terms at https://preview.e4k.dev/docs/use-terms/
# --------------------------------------------------------------------------------------------

from .base import EdgeResourceApi, EdgeResource, EdgeApiManager
from .base import EdgeResourceApi, EdgeApiManager
from .e4k import E4K_ACTIVE_API, E4K_API_V1A2, E4K_API_V1A3, E4kResourceKinds
from .bluefin import BLUEFIN_API_V1, BluefinResourceKinds
from .opcua import OPCUA_API_V1, OpcuaResourceKinds
from .symphony import SYMPHONY_API_V1, SymphonyResourceKinds

__all__ = [
"EdgeResourceApi",
"EdgeResource",
"EdgeApiManager",
"E4kResourceKinds",
"E4K_ACTIVE_API",
Expand Down
60 changes: 38 additions & 22 deletions azext_edge/edge/providers/edge_api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,56 @@
# --------------------------------------------------------------------------------------------

from enum import Enum
from typing import Dict, FrozenSet, Iterable, List, NamedTuple, Union
from typing import Dict, FrozenSet, Iterable, List, Union
from kubernetes.client.models import V1APIResourceList
from ...providers.base import get_cluster_custom_api, get_namespaced_custom_objects

from azure.cli.core.azclierror import ResourceNotFoundError


class EdgeResourceApi(NamedTuple):
group: str
version: str
moniker: str
kinds: FrozenSet[str]
class EdgeResourceApi:
def __init__(self, group: str, version: str, moniker: str):
self.group: str = group
self.version: str = version
self.moniker: str = moniker
self._api: V1APIResourceList = None
self._kinds: FrozenSet[str] = None

def as_str(self) -> str:
return f"{self.group}/{self.version}"

def get_resource(self, kind: Union[str, Enum]) -> Union["EdgeResource", None]:
if isinstance(kind, Enum):
kind = kind.value
if kind in self.kinds:
return EdgeResource(api=self, kind=kind)

def is_deployed(self, raise_on_404: bool = False) -> bool:
from ...providers.base import get_cluster_custom_api
return self._get_api(raise_on_404) is not None

return get_cluster_custom_api(resource_api=self, raise_on_404=raise_on_404) is not None
def _get_api(self, raise_on_404: bool = False):
self._api = get_cluster_custom_api(group=self.group, version=self.version, raise_on_404=raise_on_404)
return self._api

@property
def kinds(self) -> Union[FrozenSet[str], None]:
if self._kinds:
return self._kinds

class EdgeResource(NamedTuple):
api: EdgeResourceApi
kind: str
if not self._api:
self._get_api()

@property
def plural(self) -> str:
return f"{self.kind}s"
if self._api:
self._kinds = frozenset(r.kind.lower() for r in self._api.resources)
return self._kinds

def get_resources(self, kind: Union[str, Enum], namespace: str):
if isinstance(kind, Enum):
kind = kind.value

if self.kinds and kind in self.kinds:
return get_namespaced_custom_objects(
group=self.group, version=self.version, namespace=namespace, plural=f"{kind}s"
)


class EdgeApiManager:
def __init__(self, ResourceApis: Iterable[EdgeResourceApi]):
self.resource_apis: FrozenSet[EdgeResourceApi] = frozenset(ResourceApis)
def __init__(self, resource_apis: Iterable[EdgeResourceApi]):
self.resource_apis: FrozenSet[EdgeResourceApi] = frozenset(resource_apis)
self.api_group_map: Dict[str, List[str]] = {}
for api in self.resource_apis:
if api.group not in self.api_group_map:
Expand All @@ -66,3 +78,7 @@ def get_deployed(self, raise_on_404: bool = False) -> Iterable[EdgeResourceApi]:
raise ResourceNotFoundError(error_msg)

return result

@property
def apis(self) -> FrozenSet[EdgeResourceApi]:
return self.resource_apis
4 changes: 1 addition & 3 deletions azext_edge/edge/providers/edge_api/bluefin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,4 @@ class BluefinResourceKinds(ListableEnum):
PIPELINE = "pipeline"


BLUEFIN_API_V1 = EdgeResourceApi(
group="bluefin.az-bluefin.com", version="v1", moniker="bluefin", kinds=frozenset(BluefinResourceKinds.list())
)
BLUEFIN_API_V1 = EdgeResourceApi(group="bluefin.az-bluefin.com", version="v1", moniker="bluefin")
8 changes: 2 additions & 6 deletions azext_edge/edge/providers/edge_api/e4k.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@ class E4kResourceKinds(ListableEnum):
MQTT_BRIDGE_CONNECTOR = "mqttbridgeconnector"


E4K_API_V1A2 = EdgeResourceApi(
group="az-edge.com", version="v1alpha2", moniker="e4k", kinds=frozenset(E4kResourceKinds.list())
)
E4K_API_V1A3 = EdgeResourceApi(
group="az-edge.com", version="v1alpha3", moniker="e4k", kinds=frozenset(E4kResourceKinds.list())
)
E4K_API_V1A2 = EdgeResourceApi(group="az-edge.com", version="v1alpha2", moniker="e4k")
E4K_API_V1A3 = EdgeResourceApi(group="az-edge.com", version="v1alpha3", moniker="e4k")

E4K_ACTIVE_API = E4K_API_V1A2
4 changes: 1 addition & 3 deletions azext_edge/edge/providers/edge_api/opcua.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,4 @@ class OpcuaResourceKinds(ListableEnum):
ASSET = "asset"


OPCUA_API_V1 = EdgeResourceApi(
group="e4i.microsoft.com", version="v1", moniker="opcua", kinds=frozenset(OpcuaResourceKinds.list())
)
OPCUA_API_V1 = EdgeResourceApi(group="e4i.microsoft.com", version="v1", moniker="opcua")
4 changes: 1 addition & 3 deletions azext_edge/edge/providers/edge_api/symphony.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,4 @@ class SymphonyResourceKinds(ListableEnum):
TARGET = "target"


SYMPHONY_API_V1 = EdgeResourceApi(
group="symphony.microsoft.com", version="v1", moniker="symphony", kinds=frozenset(SymphonyResourceKinds.list())
)
SYMPHONY_API_V1 = EdgeResourceApi(group="symphony.microsoft.com", version="v1", moniker="symphony")
Loading