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
1 change: 1 addition & 0 deletions pkg/pip_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
spaceone-api
azure-identity
azure-mgmt-resource
azure-mgmt-resourcehealth
azure-mgmt-compute
azure-mgmt-network
azure-mgmt-sql
Expand Down
6 changes: 6 additions & 0 deletions src/plugin/connector/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
PostgreSQLManagementClient as PostgreSQLFlexibleManagementClient,
)
from azure.mgmt.resource import ResourceManagementClient, SubscriptionClient
from azure.mgmt.resourcehealth import ResourceHealthMgmtClient
from azure.mgmt.sql import SqlManagementClient
from azure.mgmt.storage import StorageManagementClient
from azure.mgmt.webpubsub import WebPubSubManagementClient
Expand All @@ -41,6 +42,7 @@ def __init__(self, *args, **kwargs):
self.monitor_client = None
self.container_instance_client = None
self.resource_client = None
self.resource_health_client = None
self.storage_client = None
self.cosmosdb_client = None
self.postgre_sql_client = None
Expand All @@ -51,6 +53,7 @@ def __init__(self, *args, **kwargs):
self.mysql_flexible_client = None
self.advisor_client = None
self.cognitive_services_client = None
self.next_link = None

def set_connect(self, secret_data: dict):
subscription_id = secret_data["subscription_id"]
Expand Down Expand Up @@ -81,6 +84,9 @@ def set_connect(self, secret_data: dict):
self.resource_client = ResourceManagementClient(
credential=self.credential, subscription_id=subscription_id
)
self.resource_health_client = ResourceHealthMgmtClient(
credential=self.credential, subscription_id=subscription_id
)
self.storage_client = StorageManagementClient(
credential=self.credential, subscription_id=subscription_id
)
Expand Down
Empty file.
17 changes: 17 additions & 0 deletions src/plugin/connector/service_health/service_health_connector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import logging

from plugin.connector.base import AzureBaseConnector

_LOGGER = logging.getLogger("spaceone")


class ServiceHealthConnector(AzureBaseConnector):

def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_connect(kwargs.get("secret_data"))

def list_health_history(self, query_start_time: str = None):
return self.resource_health_client.events.list_by_subscription_id(
query_start_time=query_start_time, api_version="2018-07-01"
)
1 change: 1 addition & 0 deletions src/plugin/manager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .postgre_sql_servers import *
from .public_ip_addresses import *
from .public_ip_prefiexes import *
from .service_health import *
from .snapshots import *
from .sql_databases import *
from .sql_servers import *
Expand Down
1 change: 1 addition & 0 deletions src/plugin/manager/service_health/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .health_history_manager import HealthHistoryManager
191 changes: 191 additions & 0 deletions src/plugin/manager/service_health/health_history_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import logging
from datetime import datetime

from dateutil.relativedelta import relativedelta
from spaceone.inventory.plugin.collector.lib import *

from plugin.conf.cloud_service_conf import ICON_URL
from plugin.connector.service_health.service_health_connector import (
ServiceHealthConnector,
)
from plugin.connector.subscriptions.subscriptions_connector import (
SubscriptionsConnector,
)
from plugin.manager.base import AzureBaseManager

_LOGGER = logging.getLogger("spaceone")


class HealthHistoryManager(AzureBaseManager):
cloud_service_group = "ServiceHealth"
cloud_service_type = "HealthHistory"
service_code = "/Microsoft.ResourceHealth/events"

def create_cloud_service(self, options: dict, secret_data: dict, schema: str):
cloud_services = []
error_responses = []

external_link_format = (
"https://app.azure.com/h/{resource_id}/"
+ secret_data["subscription_id"][:3]
+ secret_data["subscription_id"][-3:]
)

health_history_conn = ServiceHealthConnector(secret_data=secret_data)
subscription_conn = SubscriptionsConnector(secret_data=secret_data)

subscription_obj = subscription_conn.get_subscription(
secret_data["subscription_id"]
)
subscription_info = self.convert_nested_dictionary(subscription_obj)
query_start_time = self._get_three_month_ago_date()

# if not SubscriptionsConnector.region_display_map:
# locations = subscription_conn.list_location_info(
# secret_data["subscription_id"]
# )
# SubscriptionsConnector.region_display_map = (
# self._create_region_display_map_with_locations_info(locations)
# )
health_histories = health_history_conn.list_health_history(query_start_time)
for health_history in health_histories:
try:
health_history_info = self.convert_nested_dictionary(health_history)
health_history_info.update(
{
"tenant_id": subscription_info.get("tenant_id"),
"subscription_id": subscription_info.get("subscription_id"),
"subscription_name": subscription_info.get("display_name"),
}
)

# add impacted service info at impacted region
health_history_info["impact_display"] = []
impacted_services_display = []
impacted_regions_display = []
impact_updates_display = []
impacted_subscriptions_display = []

for impact in health_history_info.get("impact", []):

impacted_service = impact["impacted_service"]
impacted_regions = impact.get("impacted_regions", [])

impacted_services_display.append(impacted_service)
for impacted_region in impacted_regions:
impacted_region["impacted_service_display"] = impacted_service
impacted_regions_display.append(
impacted_region.get("impacted_region")
)
impacted_subscriptions_display.extend(
impacted_region.get("impacted_subscriptions", [])
)

updates = impacted_region.get("updates") or []
impact_updates = self._create_impact_updates_display(
updates, impacted_service, impacted_region
)
if impact_updates:
impact_updates_display.extend(impact_updates)
del impacted_region["updates"]

if impacted_regions:
health_history_info["impact_display"].extend(impacted_regions)
del impact["impacted_regions"]

if impacted_services_display:
health_history_info["impacted_services_display"] = list(
set(impacted_services_display)
)

if impacted_subscriptions_display:
health_history_info["impacted_subscriptions_display"] = list(
set(impacted_subscriptions_display)
)

if impacted_regions_display:
health_history_info["impacted_regions_display"] = list(
set(impacted_regions_display)
)

if impact_updates_display:
health_history_info["impact_updates_display"] = (
impact_updates_display
)

if len(impacted_regions_display) > 1:
region = "Multi Regions"
else:
region = impacted_regions_display[0]

cloud_services.append(
make_cloud_service(
name=health_history_info.get("title"),
account=secret_data["subscription_id"],
cloud_service_type=self.cloud_service_type,
cloud_service_group=self.cloud_service_group,
provider=self.provider,
region_code=region,
data=health_history_info,
reference=self.make_reference(
health_history_info.get("id"), external_link_format
),
data_format="dict",
)
)
except Exception as e:
_LOGGER.error(f"[create_cloud_service] Error {self.service} {e}")
error_responses.append(
make_error_response(
error=e,
provider=self.provider,
cloud_service_group=self.cloud_service_group,
cloud_service_type=self.cloud_service_type,
)
)

return cloud_services, error_responses

def create_cloud_service_type(self):
return make_cloud_service_type(
name=self.cloud_service_type,
group=self.cloud_service_group,
provider=self.provider,
service_code=self.service_code,
metadata_path=self.get_metadata_path(),
is_primary=True,
is_major=True,
labels=["Management"],
tags={"spaceone:icon": f"{ICON_URL}/azure-service-health.svg"},
)

@staticmethod
def _create_impact_updates_display(
updates: list, impacted_service: str, impacted_region: dict
) -> list:
impact_updates_display = []
for update in updates:
update.update(
{
"impacted_service_display": impacted_service,
"impacted_region_display": impacted_region.get("impacted_region"),
}
)
impact_updates_display.append(update)
return impact_updates_display

@staticmethod
def _get_three_month_ago_date() -> str:
current_date = datetime.utcnow()
three_months_ago_date = current_date - relativedelta(months=3)
first_day_three_months_ago = three_months_ago_date.replace(day=1)
return first_day_three_months_ago.strftime("%Y/%m/%d")

def _create_region_display_map_with_locations_info(self, locations) -> dict:
region_display_map = {}
for location in locations:
location_info = self.convert_nested_dictionary(location)
display_name = location_info.get("display_name")
region_name = location_info.get("name")
region_display_map[display_name] = region_name
return region_display_map
87 changes: 87 additions & 0 deletions src/plugin/metadata/service_health/health_history.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
search:
fields:
- Tenant ID: data.tenant_id
- Subscription Name: data.subscription_name
- Subscription ID: data.subscription_id
- Tracking ID: data.name
- Event type: data.event_type
- Level: data.level
- Status: data.status
- Last updated at: data.last_update_time
- Impact start time: data.impact_start_time
- End time: data.impact_mitigation_time


table:
sort:
key: data.impact_start_time
desc: true
fields:
- Tracking ID: data.name
- Event type: data.event_type
- Level: data.level
- Status: data.status
- Last updated at: data.last_update_time
- Impact start time: data.impact_start_time
- End time: data.impact_mitigation_time


tabs.0:
name: Summary
type: item
fields:
- Tenant ID: data.tenant_id
- Subscription Name: data.subscription_name
- Subscription ID: data.subscription_id
- Tracking ID: data.name
- Status: data.status
- Event type: data.event_type
- Start time: data.impact_start_time
- End time: data.impact_mitigation_time
- Last update time: data.last_update_time
- Impacted services: data.impacted_services_display
type: list
options:
delimiter: ', '
- Impacted subscriptions: data.impacted_subscriptions_display
type: list
options:
delimiter: ', '
- Impacted regions: data.impacted_regions_display
type: list
options:
delimiter: ', '
tabs.1:
name: Description
type: html
root_path: data.description

tabs.2:
name: Impacted Services
type: query-search-table
root_path: data.impact_display
fields:
- Impacted service: impacted_service_display
- Status: status
- Impacted region: impacted_region
- Impacted subscriptions: impacted_subscriptions
type: list
options:
delimiter: ', '
- Last update time: last_update_time


tabs.3:
name: Issue Updates
type: query-search-table
root_path: data.impact_updates_display
fields:
- Service: impacted_service_display
- Region: impacted_region_display
type: list
options:
delimiter: ', '
- Summary: summary
type: html
- Update time: update_date_time
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
metric_id: metric-azure-service-health-health-history
name: Health History Count
metric_type: GAUGE
resource_type: inventory.CloudService:azure.ServiceHealth.HealthHistory
query_options:
unwind:
path: data.impacted_regions_display
group_by:
- key: data.event_type
name: Event Type
default: true
- key: data.level
name: Level
default: true
- key: data.status
name: Status
default: true
- key: region_code
name: Region
reference:
resource_type: inventory.Region
reference_key: region_code
default: true
- key: data.impacted_regions_display
name: Impacted Region
- key: data.tenant_id
name: Tenant ID
- key: data.subscription_name
name: Subscription Name
- key: account
name: Subscription ID
- key: data.title
name: Title
- key: data.name
name: Tracking ID
fields:
value:
operator: count
unit: Count
namespace_id: ns-azure-service-health-health-history
version: '1.2'
Loading
Loading