Skip to content

Commit e5d757d

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents 44e9e38 + cae660b commit e5d757d

File tree

7 files changed

+142
-74
lines changed

7 files changed

+142
-74
lines changed

.github/workflows/dispatch_build_dev.yaml

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -30,51 +30,51 @@ jobs:
3030
author_name: Github Action Slack
3131

3232
docker:
33-
if: github.repository_owner == 'cloudforet-io'
34-
needs: versioning
35-
runs-on: ubuntu-latest
36-
env:
37-
VERSION: ${{ needs.versioning.outputs.version }}
38-
steps:
39-
- name: Checkout
40-
uses: actions/checkout@v3
41-
with:
42-
token: ${{ secrets.PAT_TOKEN }}
43-
44-
- name: get service name
45-
run: |
46-
echo "SERVICE=$(echo ${{ github.repository }} | cut -d '/' -f2)" >> $GITHUB_ENV
47-
48-
- name: Set up QEMU
49-
uses: docker/setup-qemu-action@v2
50-
51-
- name: Set up Docker Buildx
52-
uses: docker/setup-buildx-action@v2
53-
54-
- name: Login to Docker Hub
55-
uses: docker/login-action@v2
56-
with:
57-
username: ${{ secrets.DOCKER_USERNAME }}
58-
password: ${{ secrets.DOCKER_PASSWORD }}
59-
60-
- name: Build and push to pyengine
61-
uses: docker/build-push-action@v4
62-
with:
63-
context: .
64-
push: true
65-
tags: pyengine/${{ env.SERVICE }}:${{ env.VERSION }}
66-
67-
- name: Notice when job fails
68-
if: failure()
69-
uses: 8398a7/action-slack@v3.2.0
70-
with:
71-
status: ${{job.status}}
72-
fields: repo,workflow,job
73-
author_name: Github Action Slack
33+
if: github.repository_owner == 'cloudforet-io'
34+
needs: versioning
35+
runs-on: ubuntu-latest
36+
env:
37+
VERSION: ${{ needs.versioning.outputs.version }}
38+
steps:
39+
- name: Checkout
40+
uses: actions/checkout@v3
41+
with:
42+
token: ${{ secrets.PAT_TOKEN }}
43+
44+
- name: get service name
45+
run: |
46+
echo "SERVICE=$(echo ${{ github.repository }} | cut -d '/' -f2)" >> $GITHUB_ENV
47+
48+
- name: Set up QEMU
49+
uses: docker/setup-qemu-action@v2
50+
51+
- name: Set up Docker Buildx
52+
uses: docker/setup-buildx-action@v2
53+
54+
- name: Login to Docker Hub
55+
uses: docker/login-action@v2
56+
with:
57+
username: ${{ secrets.DOCKER_USERNAME }}
58+
password: ${{ secrets.DOCKER_PASSWORD }}
59+
60+
- name: Build and push to pyengine
61+
uses: docker/build-push-action@v4
62+
with:
63+
context: .
64+
push: true
65+
tags: pyengine/${{ env.SERVICE }}:${{ env.VERSION }}
66+
67+
- name: Notice when job fails
68+
if: failure()
69+
uses: 8398a7/action-slack@v3.2.0
70+
with:
71+
status: ${{job.status}}
72+
fields: repo,workflow,job
73+
author_name: Github Action Slack
7474

7575
scan:
76-
needs: [versioning, docker]
77-
runs-on: ubuntu-20.04
76+
needs: [ versioning, docker ]
77+
runs-on: ubuntu-latest
7878
env:
7979
VERSION: ${{ needs.versioning.outputs.version }}
8080
steps:
@@ -90,7 +90,7 @@ jobs:
9090
severity: 'CRITICAL,HIGH'
9191

9292
- name: Upload Trivy scan results to GitHub Security tab
93-
uses: github/codeql-action/upload-sarif@v2
93+
uses: github/codeql-action/upload-sarif@v3
9494
with:
9595
sarif_file: 'trivy-results.sarif'
9696

@@ -102,7 +102,7 @@ jobs:
102102
echo "$count"
103103
104104
- name: slack
105-
if: ${{ steps.vulnerabilities.outputs.result_count != 0 }}
105+
if: ${{ steps.vulnerabilities.outputs.result_count != 0 }}
106106
uses: 8398a7/action-slack@v3
107107
with:
108108
status: custom
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: "[Pull Request] Base Check"
2+
3+
on:
4+
pull_request_target:
5+
6+
jobs:
7+
check-pull-request:
8+
name: Check Pull Request
9+
runs-on: ubuntu-latest
10+
permissions:
11+
pull-requests: write
12+
steps:
13+
- name: Check signed commits
14+
id: review
15+
uses: cloudforet-io/check-pr-action@v1
16+
with:
17+
token: ${{ secrets.GITHUB_TOKEN }}
18+
19+
- name: Notify Result
20+
if: ${{ steps.review.outputs.signedoff == 'false' }}
21+
run: |
22+
echo "The review result is ${{ steps.review.outputs.signedoff }}"
23+
exit 1

src/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.3.15
1+
1.3.21

src/cloudforet/cost_analysis/connector/azure_cost_mgmt_connector.py

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
_LOGGER = logging.getLogger("spaceone")
2626

27-
_PAGE_SIZE = 7000
27+
_PAGE_SIZE = 5000
2828

2929

3030
def azure_exception_handler(func):
@@ -33,12 +33,23 @@ def wrapper(*args, **kwargs) -> Union[dict, list]:
3333
return_type = get_type_hints(func).get("return")
3434
try:
3535
return func(*args, **kwargs)
36+
except ServiceResponseError as error:
37+
_print_error_log(error)
38+
time.sleep(10)
39+
return func(*args, **kwargs)
40+
3641
except ResourceNotFoundError as error:
3742
_print_error_log(error)
3843
return _get_empty_value(return_type)
3944
except HttpResponseError as error:
4045
if error.status_code in ["404", "412"]:
4146
_print_error_log(error)
47+
elif error.status_code == "429":
48+
_LOGGER.error(f"(RateLimit Error) => {error.message}")
49+
50+
wait_time = _extract_wait_time_for_retry(error.message)
51+
time.sleep(wait_time)
52+
return func(*args, **kwargs)
4253
else:
4354
_print_error_log(error)
4455
return _get_empty_value(return_type)
@@ -49,6 +60,17 @@ def wrapper(*args, **kwargs) -> Union[dict, list]:
4960
return wrapper
5061

5162

63+
def _extract_wait_time_for_retry(message: str) -> int:
64+
default_wait_time = 60
65+
pattern = r"\d+"
66+
67+
matches = re.findall(pattern, message)
68+
69+
if len(matches) > 1:
70+
default_wait_time = int(matches[1])
71+
return default_wait_time
72+
73+
5274
def _get_empty_value(return_type: object) -> Any:
5375
return_type_name = getattr(return_type, "__name__")
5476
empty_values = {
@@ -65,8 +87,9 @@ def _get_empty_value(return_type: object) -> Any:
6587
return empty_values.get(return_type_name, None)
6688

6789

68-
def _print_error_log(error):
69-
_LOGGER.error(f"(Error) => {error.message} {error}", exc_info=True)
90+
def _print_error_log(error) -> None:
91+
status_code = getattr(error, "status_code", "Unknown")
92+
_LOGGER.error(f"(Error) => {status_code} {error.message} {error}", exc_info=True)
7093

7194

7295
class AzureCostMgmtConnector(BaseConnector):
@@ -154,11 +177,9 @@ def query_usage_http(
154177
BENEFIT_GROUPING + BENEFIT_GROUPING_MCA
155178
)
156179

157-
_LOGGER.debug(f"[query_usage] parameters: {parameters}")
158-
159180
while self.next_link:
160181
url = self.next_link
161-
headers = self._make_request_headers()
182+
headers = self._make_request_headers(secret_data)
162183

163184
_LOGGER.debug(f"[query_usage] url:{url}, parameters: {parameters}")
164185
response = requests.post(url=url, headers=headers, json=parameters)
@@ -298,8 +319,8 @@ def convert_nested_dictionary(self, cloud_svc_object):
298319

299320
return cloud_svc_dict
300321

301-
def _make_request_headers(self, client_type=None):
302-
access_token = self._get_access_token()
322+
def _make_request_headers(self, secret_data: dict, client_type=None):
323+
access_token = self._get_access_token(secret_data)
303324
headers = {
304325
"Authorization": f"Bearer {access_token}",
305326
"Content-Type": "application/json",
@@ -311,7 +332,7 @@ def _make_request_headers(self, client_type=None):
311332

312333
def _retry_request(self, response, url, headers, json, retry_count, method="post"):
313334
try:
314-
_LOGGER.error(f"{datetime.utcnow()}[INFO] retry_request {response.headers}")
335+
_LOGGER.error(f"[INFO] retry_request {response.headers}")
315336
if retry_count == 0:
316337
raise ERROR_UNKNOWN(
317338
message=f"[ERROR] retry_request failed {response.json()}"
@@ -366,12 +387,26 @@ def _get_sleep_time(response_headers):
366387
return sleep_time + 1
367388

368389
@staticmethod
369-
def _get_access_token():
390+
def _get_access_token(secret_data: dict) -> str:
370391
try:
371-
credential = DefaultAzureCredential(logging_enable=True)
372-
scopes = ["https://management.azure.com/.default"]
373-
token_info = credential.get_token(*scopes)
374-
return token_info.token
392+
header = {
393+
"Content-Type": "application/x-www-form-urlencoded",
394+
}
395+
data = {
396+
"client_id": secret_data["client_id"],
397+
"client_secret": secret_data["client_secret"],
398+
"grant_type": "client_credentials",
399+
"resource": "https://management.azure.com",
400+
"scope": "https://management.azure.com/.default",
401+
}
402+
403+
response = requests.post(
404+
f"https://login.microsoftonline.com/{secret_data['tenant_id']}/oauth2/token",
405+
data=data,
406+
headers=header,
407+
)
408+
access_token = response.json().get("access_token")
409+
return access_token
375410
except Exception as e:
376411
_LOGGER.error(f"[ERROR] _get_access_token :{e}")
377412
raise ERROR_INVALID_TOKEN(token=e)

src/cloudforet/cost_analysis/main.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def cost_get_data(params: dict) -> Generator[dict, None, None]:
161161

162162
params["schema"] = params.pop("schema_name", None)
163163
params["secret_data"] = __get_secret_data(secret_data, task_options)
164+
164165
is_benefit_job = task_options.get("is_benefit_job", False)
165166
cost_metric = options.get("cost_metric", "ActualCost")
166167

@@ -201,14 +202,15 @@ def __get_secret_data(secret_data: dict, task_options: dict) -> dict:
201202
if len(secrets) == 1:
202203
return secrets[0]
203204

204-
tenant_id = task_options.get(
205-
"billing_tenant_id",
206-
)
205+
billing_tenant_id = task_options["billing_tenant_id"]
207206

208207
for _secret_data in secrets:
209-
if _secret_data["tenant_id"] == tenant_id:
210-
secret_data = _secret_data
211-
elif _secret_data.get("subscription_id") == task_options.get("subscription_id"):
212-
secret_data = _secret_data
208+
if _secret_data["tenant_id"] == billing_tenant_id:
209+
return _secret_data
210+
211+
elif _secret_data.get("subscription_id") and _secret_data.get(
212+
"subscription_id"
213+
) == task_options.get("subscription_id"):
214+
return _secret_data
213215

214-
return secret_data
216+
return secrets[0]

src/cloudforet/cost_analysis/manager/cost_manager.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def get_data(
7070
schema: str,
7171
task_options: dict,
7272
domain_id: str,
73-
) -> list:
73+
) -> Generator[list, Any, None]:
7474
self.azure_cm_connector.create_session(options, secret_data, schema)
7575
self._check_task_options(task_options)
7676

@@ -104,6 +104,13 @@ def get_data(
104104
scope, parameters
105105
)
106106

107+
if not blobs:
108+
_LOGGER.debug(f"[get_data] blobs: {blobs}")
109+
_LOGGER.info(
110+
f"[get_data] #{idx + 1} {tenant_id} tenant collect skipped, domain_id: {domain_id}"
111+
)
112+
continue
113+
107114
response_stream = self.azure_cm_connector.get_cost_data(blobs, options)
108115
for results in response_stream:
109116
yield self._make_cost_data(
@@ -132,7 +139,6 @@ def get_data(
132139
_LOGGER.info(
133140
f"[get_data] all collect is done in {int(end_time - start_time)} seconds"
134141
)
135-
yield []
136142

137143
def _make_cost_data(
138144
self,
@@ -336,7 +342,7 @@ def _get_additional_info(self, result: dict, options: dict, tenant_id: str = Non
336342
additional_info["Term"] = term
337343

338344
if azure_additional_info := result.get("additionalinfo"):
339-
azure_additional_info: dict = json.loads(azure_additional_info)
345+
azure_additional_info = json.loads(azure_additional_info)
340346
if ri_normalization_ratio := azure_additional_info.get(
341347
"RINormalizationRatio"
342348
):
@@ -397,11 +403,14 @@ def _make_benefit_cost_data(
397403
account_agreement_type: str = None,
398404
) -> list:
399405
benefit_costs_data = []
406+
total_count = 0
407+
400408
try:
401409
combined_results = self._combine_rows_and_columns_from_results(
402410
results.get("properties").get("rows"),
403411
results.get("properties").get("columns"),
404412
)
413+
total_count += len(combined_results)
405414
for cb_result in combined_results:
406415
billed_at = self._set_billed_date(cb_result.get("UsageDate", end))
407416
if not billed_at:
@@ -413,13 +422,12 @@ def _make_benefit_cost_data(
413422
except Exception as e:
414423
_LOGGER.error(f"[_make_cost_data] make data error: {e}", exc_info=True)
415424
raise e
416-
425+
_LOGGER.info(f"[get_benefit_data] total count: {total_count}")
417426
return benefit_costs_data
418427

419428
def _make_benefit_cost_info(self, result: dict, billed_at: str) -> dict:
420429
additional_info = {
421430
"Pricing Model": result.get("PricingModel"),
422-
"Frequency": result.get("BillingFrequency"),
423431
"Benefit Id": result.get("BenefitId"),
424432
"Benefit Name": result.get("BenefitName"),
425433
"Reservation Id": result.get("ReservationId"),

src/cloudforet/cost_analysis/manager/job_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
_LOGGER = logging.getLogger("spaceone")
1414

15-
_TASK_LIST_SIZE = 5
15+
_TASK_LIST_SIZE = 4
1616

1717

1818
class JobManager(BaseManager):

0 commit comments

Comments
 (0)