Skip to content

Commit 485a38f

Browse files
committed
feat: Add Azure Service Health service
1 parent f88e836 commit 485a38f

File tree

11 files changed

+394
-0
lines changed

11 files changed

+394
-0
lines changed

pkg/pip_requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
spaceone-api
22
azure-identity
33
azure-mgmt-resource
4+
azure-mgmt-resourcehealth
45
azure-mgmt-compute
56
azure-mgmt-network
67
azure-mgmt-sql

src/plugin/connector/base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
PostgreSQLManagementClient as PostgreSQLFlexibleManagementClient,
2222
)
2323
from azure.mgmt.resource import ResourceManagementClient, SubscriptionClient
24+
from azure.mgmt.resourcehealth import ResourceHealthMgmtClient
2425
from azure.mgmt.sql import SqlManagementClient
2526
from azure.mgmt.storage import StorageManagementClient
2627
from azure.mgmt.webpubsub import WebPubSubManagementClient
@@ -41,6 +42,7 @@ def __init__(self, *args, **kwargs):
4142
self.monitor_client = None
4243
self.container_instance_client = None
4344
self.resource_client = None
45+
self.resource_health_client = None
4446
self.storage_client = None
4547
self.cosmosdb_client = None
4648
self.postgre_sql_client = None
@@ -51,6 +53,7 @@ def __init__(self, *args, **kwargs):
5153
self.mysql_flexible_client = None
5254
self.advisor_client = None
5355
self.cognitive_services_client = None
56+
self.next_link = None
5457

5558
def set_connect(self, secret_data: dict):
5659
subscription_id = secret_data["subscription_id"]
@@ -81,6 +84,9 @@ def set_connect(self, secret_data: dict):
8184
self.resource_client = ResourceManagementClient(
8285
credential=self.credential, subscription_id=subscription_id
8386
)
87+
self.resource_health_client = ResourceHealthMgmtClient(
88+
credential=self.credential, subscription_id=subscription_id
89+
)
8490
self.storage_client = StorageManagementClient(
8591
credential=self.credential, subscription_id=subscription_id
8692
)

src/plugin/connector/service_health/__init__.py

Whitespace-only changes.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import logging
2+
3+
from plugin.connector.base import AzureBaseConnector
4+
5+
_LOGGER = logging.getLogger("spaceone")
6+
7+
8+
class ServiceHealthConnector(AzureBaseConnector):
9+
10+
def __init__(self, **kwargs):
11+
super().__init__(**kwargs)
12+
self.set_connect(kwargs.get("secret_data"))
13+
14+
def list_health_history(self, query_start_time: str = None):
15+
return self.resource_health_client.events.list_by_subscription_id(
16+
query_start_time=query_start_time, api_version="2018-07-01"
17+
)

src/plugin/manager/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .postgre_sql_servers import *
1515
from .public_ip_addresses import *
1616
from .public_ip_prefiexes import *
17+
from .service_health import *
1718
from .snapshots import *
1819
from .sql_databases import *
1920
from .sql_servers import *
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .health_history_manager import HealthHistoryManager
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import logging
2+
from datetime import datetime
3+
4+
from dateutil.relativedelta import relativedelta
5+
from spaceone.inventory.plugin.collector.lib import *
6+
7+
from plugin.conf.cloud_service_conf import ICON_URL
8+
from plugin.connector.service_health.service_health_connector import (
9+
ServiceHealthConnector,
10+
)
11+
from plugin.connector.subscriptions.subscriptions_connector import (
12+
SubscriptionsConnector,
13+
)
14+
from plugin.manager.base import AzureBaseManager
15+
16+
_LOGGER = logging.getLogger("spaceone")
17+
18+
19+
class HealthHistoryManager(AzureBaseManager):
20+
cloud_service_group = "ServiceHealth"
21+
cloud_service_type = "HealthHistory"
22+
service_code = "/Microsoft.ResourceHealth/events"
23+
24+
def create_cloud_service(self, options: dict, secret_data: dict, schema: str):
25+
cloud_services = []
26+
error_responses = []
27+
28+
external_link_format = (
29+
"https://app.azure.com/h/{resource_id}/"
30+
+ secret_data["subscription_id"][:3]
31+
+ secret_data["subscription_id"][-3:]
32+
)
33+
34+
health_history_conn = ServiceHealthConnector(secret_data=secret_data)
35+
subscription_conn = SubscriptionsConnector(secret_data=secret_data)
36+
37+
subscription_obj = subscription_conn.get_subscription(
38+
secret_data["subscription_id"]
39+
)
40+
subscription_info = self.convert_nested_dictionary(subscription_obj)
41+
query_start_time = self._get_three_month_ago_date()
42+
43+
# if not SubscriptionsConnector.region_display_map:
44+
# locations = subscription_conn.list_location_info(
45+
# secret_data["subscription_id"]
46+
# )
47+
# SubscriptionsConnector.region_display_map = (
48+
# self._create_region_display_map_with_locations_info(locations)
49+
# )
50+
health_histories = health_history_conn.list_health_history(query_start_time)
51+
for health_history in health_histories:
52+
try:
53+
health_history_info = self.convert_nested_dictionary(health_history)
54+
health_history_info.update(
55+
{
56+
"tenant_id": subscription_info.get("tenant_id"),
57+
"subscription_id": subscription_info.get("subscription_id"),
58+
"subscription_name": subscription_info.get("display_name"),
59+
}
60+
)
61+
62+
# add impacted service info at impacted region
63+
health_history_info["impact_display"] = []
64+
impacted_services_display = []
65+
impacted_regions_display = []
66+
impact_updates_display = []
67+
impacted_subscriptions_display = []
68+
69+
for impact in health_history_info.get("impact", []):
70+
71+
impacted_service = impact["impacted_service"]
72+
impacted_regions = impact.get("impacted_regions", [])
73+
74+
impacted_services_display.append(impacted_service)
75+
for impacted_region in impacted_regions:
76+
impacted_region["impacted_service_display"] = impacted_service
77+
impacted_regions_display.append(
78+
impacted_region.get("impacted_region")
79+
)
80+
impacted_subscriptions_display.extend(
81+
impacted_region.get("impacted_subscriptions", [])
82+
)
83+
84+
updates = impacted_region.get("updates") or []
85+
impact_updates = self._create_impact_updates_display(
86+
updates, impacted_service, impacted_region
87+
)
88+
if impact_updates:
89+
impact_updates_display.extend(impact_updates)
90+
del impacted_region["updates"]
91+
92+
if impacted_regions:
93+
health_history_info["impact_display"].extend(impacted_regions)
94+
del impact["impacted_regions"]
95+
96+
if impacted_services_display:
97+
health_history_info["impacted_services_display"] = list(
98+
set(impacted_services_display)
99+
)
100+
101+
if impacted_subscriptions_display:
102+
health_history_info["impacted_subscriptions_display"] = list(
103+
set(impacted_subscriptions_display)
104+
)
105+
106+
if impacted_regions_display:
107+
health_history_info["impacted_regions_display"] = list(
108+
set(impacted_regions_display)
109+
)
110+
111+
if impact_updates_display:
112+
health_history_info["impact_updates_display"] = (
113+
impact_updates_display
114+
)
115+
116+
if len(impacted_regions_display) > 1:
117+
region = "Multi Regions"
118+
else:
119+
region = impacted_regions_display[0]
120+
121+
cloud_services.append(
122+
make_cloud_service(
123+
name=health_history_info.get("title"),
124+
account=secret_data["subscription_id"],
125+
cloud_service_type=self.cloud_service_type,
126+
cloud_service_group=self.cloud_service_group,
127+
provider=self.provider,
128+
region_code=region,
129+
data=health_history_info,
130+
reference=self.make_reference(
131+
health_history_info.get("id"), external_link_format
132+
),
133+
data_format="dict",
134+
)
135+
)
136+
except Exception as e:
137+
_LOGGER.error(f"[create_cloud_service] Error {self.service} {e}")
138+
error_responses.append(
139+
make_error_response(
140+
error=e,
141+
provider=self.provider,
142+
cloud_service_group=self.cloud_service_group,
143+
cloud_service_type=self.cloud_service_type,
144+
)
145+
)
146+
147+
return cloud_services, error_responses
148+
149+
def create_cloud_service_type(self):
150+
return make_cloud_service_type(
151+
name=self.cloud_service_type,
152+
group=self.cloud_service_group,
153+
provider=self.provider,
154+
service_code=self.service_code,
155+
metadata_path=self.get_metadata_path(),
156+
is_primary=True,
157+
is_major=True,
158+
labels=["Management"],
159+
tags={"spaceone:icon": f"{ICON_URL}/azure-service-health.svg"},
160+
)
161+
162+
@staticmethod
163+
def _create_impact_updates_display(
164+
updates: list, impacted_service: str, impacted_region: dict
165+
) -> list:
166+
impact_updates_display = []
167+
for update in updates:
168+
update.update(
169+
{
170+
"impacted_service_display": impacted_service,
171+
"impacted_region_display": impacted_region.get("impacted_region"),
172+
}
173+
)
174+
impact_updates_display.append(update)
175+
return impact_updates_display
176+
177+
@staticmethod
178+
def _get_three_month_ago_date() -> str:
179+
current_date = datetime.utcnow()
180+
three_months_ago_date = current_date - relativedelta(months=3)
181+
first_day_three_months_ago = three_months_ago_date.replace(day=1)
182+
return first_day_three_months_ago.strftime("%Y/%m/%d")
183+
184+
def _create_region_display_map_with_locations_info(self, locations) -> dict:
185+
region_display_map = {}
186+
for location in locations:
187+
location_info = self.convert_nested_dictionary(location)
188+
display_name = location_info.get("display_name")
189+
region_name = location_info.get("name")
190+
region_display_map[display_name] = region_name
191+
return region_display_map
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
search:
3+
fields:
4+
- Tenant ID: data.tenant_id
5+
- Subscription Name: data.subscription_name
6+
- Subscription ID: data.subscription_id
7+
- Tracking ID: data.name
8+
- Event type: data.event_type
9+
- Level: data.level
10+
- Status: data.status
11+
- Last updated at: data.last_update_time
12+
- Impact start time: data.impact_start_time
13+
- End time: data.impact_mitigation_time
14+
15+
16+
table:
17+
sort:
18+
key: data.impact_start_time
19+
desc: true
20+
fields:
21+
- Tracking ID: data.name
22+
- Event type: data.event_type
23+
- Level: data.level
24+
- Status: data.status
25+
- Last updated at: data.last_update_time
26+
- Impact start time: data.impact_start_time
27+
- End time: data.impact_mitigation_time
28+
29+
30+
tabs.0:
31+
name: Summary
32+
type: item
33+
fields:
34+
- Tenant ID: data.tenant_id
35+
- Subscription Name: data.subscription_name
36+
- Subscription ID: data.subscription_id
37+
- Tracking ID: data.name
38+
- Status: data.status
39+
- Event type: data.event_type
40+
- Start time: data.impact_start_time
41+
- End time: data.impact_mitigation_time
42+
- Last update time: data.last_update_time
43+
- Impacted services: data.impacted_services_display
44+
type: list
45+
options:
46+
delimiter: ', '
47+
- Impacted subscriptions: data.impacted_subscriptions_display
48+
type: list
49+
options:
50+
delimiter: ', '
51+
- Impacted regions: data.impacted_regions_display
52+
type: list
53+
options:
54+
delimiter: ', '
55+
tabs.1:
56+
name: Description
57+
type: html
58+
root_path: data.description
59+
60+
tabs.2:
61+
name: Impacted Services
62+
type: query-search-table
63+
root_path: data.impact_display
64+
fields:
65+
- Impacted service: impacted_service_display
66+
- Status: status
67+
- Impacted region: impacted_region
68+
- Impacted subscriptions: impacted_subscriptions
69+
type: list
70+
options:
71+
delimiter: ', '
72+
- Last update time: last_update_time
73+
74+
75+
tabs.3:
76+
name: Issue Updates
77+
type: query-search-table
78+
root_path: data.impact_updates_display
79+
fields:
80+
- Service: impacted_service_display
81+
- Region: impacted_region_display
82+
type: list
83+
options:
84+
delimiter: ', '
85+
- Summary: summary
86+
type: html
87+
- Update time: update_date_time
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
metric_id: metric-azure-service-health-health-history
3+
name: Health History Count
4+
metric_type: GAUGE
5+
resource_type: inventory.CloudService:azure.ServiceHealth.HealthHistory
6+
query_options:
7+
unwind:
8+
path: data.impacted_regions_display
9+
group_by:
10+
- key: data.event_type
11+
name: Event Type
12+
default: true
13+
- key: data.level
14+
name: Level
15+
default: true
16+
- key: data.status
17+
name: Status
18+
default: true
19+
- key: region_code
20+
name: Region
21+
reference:
22+
resource_type: inventory.Region
23+
reference_key: region_code
24+
default: true
25+
- key: data.impacted_regions_display
26+
name: Impacted Region
27+
- key: data.tenant_id
28+
name: Tenant ID
29+
- key: data.subscription_name
30+
name: Subscription Name
31+
- key: account
32+
name: Subscription ID
33+
- key: data.title
34+
name: Title
35+
- key: data.name
36+
name: Tracking ID
37+
fields:
38+
value:
39+
operator: count
40+
unit: Count
41+
namespace_id: ns-azure-service-health-health-history
42+
version: '1.2'

0 commit comments

Comments
 (0)