Skip to content

Commit

Permalink
feat: modify aggregate cost_report api
Browse files Browse the repository at this point in the history
  • Loading branch information
ImMin5 committed Jan 25, 2024
1 parent 4a02219 commit e2c8913
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 46 deletions.
24 changes: 24 additions & 0 deletions src/spaceone/cost_analysis/interface/grpc/cost_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,27 @@ def send(self, request, context):
cost_report_svc = CostReportService(metadata)
cost_report_svc.send(params)
return self.empty()

def get_url(self, request, context):
params, metadata = self.parse_request(request, context)
cost_report_svc = CostReportService(metadata)
response: dict = cost_report_svc.get_url(params)
return self.dict_to_message(response)

def get(self, request, context):
params, metadata = self.parse_request(request, context)
cost_report_svc = CostReportService(metadata)
response: dict = cost_report_svc.get(params)
return self.dict_to_message(response)

def list(self, request, context):
params, metadata = self.parse_request(request, context)
cost_report_svc = CostReportService(metadata)
response: dict = cost_report_svc.list(params)
return self.dict_to_message(response)

def stat(self, request, context):
params, metadata = self.parse_request(request, context)
cost_report_svc = CostReportService(metadata)
response: dict = cost_report_svc.stat(params)
return self.dict_to_message(response)
165 changes: 119 additions & 46 deletions src/spaceone/cost_analysis/service/cost_report_serivce.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import logging
from dateutil.relativedelta import relativedelta
from datetime import datetime
from typing import Tuple
from typing import Tuple, Union

import pandas as pd
from mongoengine import QuerySet
from spaceone.core.service import *
from spaceone.cost_analysis.model.cost_report_config.database import CostReportConfig
Expand All @@ -16,6 +17,7 @@
)
from spaceone.cost_analysis.manager.cost_manager import CostManager
from spaceone.cost_analysis.manager.cost_report_manager import CostReportManager
from spaceone.cost_analysis.manager.data_source_manager import DataSourceManager
from spaceone.cost_analysis.manager.email_manager import EmailManager
from spaceone.cost_analysis.manager.identity_manager import IdentityManager

Expand Down Expand Up @@ -57,7 +59,7 @@ def send(self, params: CostReportSendRequest) -> None:
domain_id=domain_id,
workspace_id=workspace_id,
status="SUCCESS",
)
)[0]

# Get Cost Report Config
cost_report_config_id = cost_report_vo.cost_report_config_id
Expand Down Expand Up @@ -103,12 +105,21 @@ def send(self, params: CostReportSendRequest) -> None:
email = user_info.get("email", user_id)
email_mgr.send_cost_report_email(user_id, email)

def get_url(self, params: CostReportGetUrlRequest) -> dict:
"""Get cost report url"""

cost_report_vo = self.cost_report_mgr.get_cost_report(
params.cost_report_id, params.domain_id, params.workspace_id
)

return {}

@transaction(
permission="cost-analysis:CostReport.read",
role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"],
)
@convert_model
def get(self, params: CostReportGetRequest) -> CostReportResponse:
def get(self, params: CostReportGetRequest) -> Union[CostReportResponse, dict]:
"""Get cost report"""

cost_report_vo = self.cost_report_mgr.get_cost_report(
Expand All @@ -117,34 +128,6 @@ def get(self, params: CostReportGetRequest) -> CostReportResponse:

return CostReportResponse(**cost_report_vo.to_dict())

def create_cost_report(self, cost_report_config_vo: CostReportConfig):
workspace_name_map = self._get_workspace_name_map(
cost_report_config_vo.domain_id
)

workspace_ids = [workspace_id for workspace_id in workspace_name_map.keys()]
current_month, last_month = self._get_current_and_last_month()
issue_day = self._get_issue_day(cost_report_config_vo)

if issue_day == datetime.utcnow().day:
self._aggregate_monthly_cost_report(
cost_report_config_vo,
workspace_name_map,
workspace_ids,
last_month,
issue_day,
"SUCCESS",
)

self._aggregate_monthly_cost_report(
cost_report_config_vo,
workspace_name_map,
workspace_ids,
current_month,
issue_day,
"IN_PROGRESS",
)

@transaction(
permission="cost-analysis:CostReport.read",
role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"],
Expand All @@ -165,7 +148,9 @@ def create_cost_report(self, cost_report_config_vo: CostReportConfig):
]
)
@convert_model
def list(self, params: CostReportSearchQueryRequest) -> CostReportsResponse:
def list(
self, params: CostReportSearchQueryRequest
) -> Union[CostReportsResponse, dict]:
"""List cost reports"""

query = params.query or {}
Expand Down Expand Up @@ -198,27 +183,66 @@ def list(self, params: CostReportSearchQueryRequest) -> CostReportsResponse:
def stat(self, params: CostReportDataStatQueryRequest) -> dict:
"""Stat cost reports"""

query = params.query or {}
return self.cost_report_mgr.stat_cost_reports(params.query)

def create_cost_report(self, cost_report_config_vo: CostReportConfig):
# currency_mgr = CurrencyManager()
# currency_rate_map: dict = currency_mgr.get_currency(
# cost_report_config_vo.currency
# )
domain_id = cost_report_config_vo.domain_id
data_source_filter = cost_report_config_vo.data_source_filter or {}

workspace_name_map, workspace_ids = self._get_workspace_name_map(domain_id)
data_source_currency_map, data_source_ids = self._get_data_source_currency_map(
data_source_filter, domain_id
)

current_month, last_month = self._get_current_and_last_month()
issue_day = self._get_issue_day(cost_report_config_vo)

if issue_day == datetime.utcnow().day:
self._aggregate_monthly_cost_report(
cost_report_config_vo,
workspace_name_map,
workspace_ids,
data_source_currency_map,
data_source_ids,
last_month,
issue_day,
"SUCCESS",
)

self._aggregate_monthly_cost_report(
cost_report_config_vo,
workspace_name_map,
workspace_ids,
data_source_currency_map,
data_source_ids,
current_month,
issue_day,
"IN_PROGRESS",
)

def _aggregate_monthly_cost_report(
self,
cost_report_config_vo: CostReportConfig,
workspace_name_map: dict,
workspace_ids: list,
data_source_currency_map: dict,
data_source_ids: list,
report_month: str,
issue_day: int,
status: str = None,
) -> None:
domain_id = cost_report_config_vo.domain_id
currency = cost_report_config_vo.currency
report_year = report_month.split("-")[0]
data_sources = cost_report_config_vo.data_source_filter.get("data_sources", [])
domain_id = cost_report_config_vo.domain_id
# collect enabled data sources

query = {
"group_by": [
"workspace_id",
"billed_year",
],
"group_by": ["workspace_id", "billed_year", "data_source_id"],
"fields": {
"cost": {"key": "cost", "operator": "sum"},
},
Expand All @@ -228,23 +252,24 @@ def _aggregate_monthly_cost_report(
{"k": "domain_id", "v": domain_id, "o": "eq"},
{"k": "billed_year", "v": report_year, "o": "eq"},
{"k": "billed_month", "v": report_month, "o": "eq"},
{"k": "data_source_id", "v": data_source_ids, "o": "in"},
{"k": "workspace_id", "v": workspace_ids, "o": "in"},
],
}

if data_sources:
query["filter"].append(
{"k": "data_source_id", "v": data_sources, "o": "in"}
)
_LOGGER.debug(f"[aggregate_monthly_cost_report] query: {query}")

response = self.cost_mgr.analyze_monthly_costs(
query, domain_id, target="PRIMARY"
)
results = response.get("results", [])
for aggregated_cost_report in results:
# todo: convert currency
aggregated_cost_report["cost"] = {"KRW": aggregated_cost_report.pop("cost")}
ag_cost_report_currency = data_source_currency_map.get(
aggregated_cost_report.pop("data_source_id")
)
aggregated_cost_report["cost"] = {
ag_cost_report_currency: aggregated_cost_report.pop("cost")
}
aggregated_cost_report["status"] = status
aggregated_cost_report["currency"] = currency
aggregated_cost_report["report_number"] = self.generate_report_number(
Expand All @@ -263,6 +288,11 @@ def _aggregate_monthly_cost_report(
"cost_report_config_id"
] = cost_report_config_vo.cost_report_config_id
aggregated_cost_report["domain_id"] = cost_report_config_vo.domain_id

aggregated_cost_report_results = self._aggregate_result_by_currency(results)

for aggregated_cost_report in aggregated_cost_report_results:
# todo: apply currency cost
self.cost_report_mgr.create_cost_report(aggregated_cost_report)

_LOGGER.debug(
Expand Down Expand Up @@ -319,13 +349,56 @@ def generate_report_number(report_month: str, issue_day: int) -> str:
return f"CostReport_{date_object.strftime('%y%m%d%H%M')}"

@staticmethod
def _get_workspace_name_map(domain_id: str) -> dict:
def _get_workspace_name_map(domain_id: str) -> Tuple[dict, list]:
identity_mgr = IdentityManager()
workspace_name_map = {}
workspaces = identity_mgr.list_workspaces(
{"query": {"filter": [{"k": "state", "v": "ENABLED", "o": "eq"}]}},
domain_id,
)
workspace_ids = []
for workspace in workspaces.get("results", []):
workspace_name_map[workspace["workspace_id"]] = workspace["name"]
return workspace_name_map
workspace_ids.append(workspace["workspace_id"])
return workspace_name_map, workspace_ids

@staticmethod
def _get_data_source_currency_map(
data_source_filter: dict, domain_id: str
) -> Tuple[dict, list]:
data_source_currency_map = {}
data_source_mgr = DataSourceManager()

query = {"filter": [{"k": "domain_id", "v": domain_id, "o": "eq"}]}
if data_sources := data_source_filter.get("data_sources"):
query["filter"].append(
{"k": "data_source_id", "v": data_sources, "o": "in"}
)

if data_source_state := data_source_filter.get("state", "ENABLED"):
query["filter"].append({"k": "state", "v": data_source_state, "o": "eq"})

_LOGGER.debug(f"[get_data_source_currency_map] query: {query}")

data_source_vos, total_count = data_source_mgr.list_data_sources(query)
data_source_ids = []
for data_source_vo in data_source_vos:
data_source_currency_map[
data_source_vo.data_source_id
] = data_source_vo.plugin_info["metadata"]["currency"]
data_source_ids.append(data_source_vo.data_source_id)

return data_source_currency_map, data_source_ids

@staticmethod
def _aggregate_result_by_currency(results: list) -> list:
workspace_result_map = {}
for result in results:
workspace_id = result["workspace_id"]
if workspace_id in workspace_result_map:
for currency, cost in result["cost"].items():
workspace_result_map[workspace_id]["cost"][currency] += cost
else:
workspace_result_map[workspace_id] = result.copy()

return [workspace_result for workspace_result in workspace_result_map.values()]

0 comments on commit e2c8913

Please sign in to comment.