-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #RHIROS-1231, #RHIROS-1172, #RHIROS-1175, #RHIROS-1177 (#339)
- Store suggested instance type and price in separate columns - New suggested_instance_types api endpoint with sorting and filtering capabilities.
- Loading branch information
1 parent
b6c7c74
commit 0d6bbc0
Showing
13 changed files
with
799 additions
and
21 deletions.
There are no files selected for viewing
31 changes: 31 additions & 0 deletions
31
...ersions/93262eab7d77_update_top_candidate_and_top_candidate_price_for_existing_records.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
"""Update top_candidate and top_candidate_price for existing records of PerformanceProfile | ||
Revision ID: 93262eab7d77 | ||
Revises: c6f2cd708cea | ||
Create Date: 2023-07-31 19:41:07.252297 | ||
""" | ||
from alembic import op | ||
import sqlalchemy as sa | ||
|
||
|
||
# revision identifiers, used by Alembic. | ||
revision = '93262eab7d77' | ||
down_revision = 'c6f2cd708cea' | ||
branch_labels = None | ||
depends_on = None | ||
|
||
|
||
def upgrade(): | ||
try: | ||
print('Updating top_candidate and top_candidate_price for existing PerformanceProfile records!') | ||
op.execute( | ||
"update performance_profile set top_candidate = subquery.top_candidate, top_candidate_price = cast(" | ||
"subquery.top_candidate_price as double precision) from (select rule_hit_details #> '{0,details," | ||
"candidates,0,0}' as top_candidate, rule_hit_details #> '{0,details,candidates,0," | ||
"1}' as top_candidate_price, system_id from performance_profile) as subquery where " | ||
"performance_profile.system_id = subquery.system_id") | ||
except sa.exc.SQLAlchemyError as err: | ||
print(f"Failed to update table with error {err}!") | ||
else: | ||
print('Successfully updated top_candidate and top_candidate_price for existing PerformanceProfile records!') |
36 changes: 36 additions & 0 deletions
36
migrations/versions/c6f2cd708cea_add_top_candidate_top_candidate_price_.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
"""Add top_candidate, top_candidate_price on PerformanceProfile | ||
Revision ID: c6f2cd708cea | ||
Revises: 1051e0d7e62e | ||
Create Date: 2023-07-31 19:39:02.205466 | ||
""" | ||
from alembic import op | ||
import sqlalchemy as sa | ||
|
||
|
||
# revision identifiers, used by Alembic. | ||
revision = 'c6f2cd708cea' | ||
down_revision = '1051e0d7e62e' | ||
branch_labels = None | ||
depends_on = None | ||
|
||
|
||
def upgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
with op.batch_alter_table('performance_profile', schema=None) as batch_op: | ||
batch_op.add_column(sa.Column('top_candidate', sa.String(length=25), nullable=True)) | ||
batch_op.add_column(sa.Column('top_candidate_price', sa.Float(), nullable=True)) | ||
batch_op.create_index('top_candidate_idx', ['top_candidate'], unique=False) | ||
|
||
# ### end Alembic commands ### | ||
|
||
|
||
def downgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
with op.batch_alter_table('performance_profile', schema=None) as batch_op: | ||
batch_op.drop_index('top_candidate_idx') | ||
batch_op.drop_column('top_candidate_price') | ||
batch_op.drop_column('top_candidate') | ||
|
||
# ### end Alembic commands ### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from ros.lib.aws_instance_types import INSTANCE_TYPES | ||
from ros.extensions import cache | ||
|
||
|
||
@cache.cached(timeout=0) | ||
def instance_types_desc_dict(): | ||
instance_and_descriptions = {} | ||
for instance, info in INSTANCE_TYPES.items(): | ||
processor = info['extra']['physicalProcessor'] | ||
v_cpu = info['extra']['vcpu'] | ||
memory = info['extra']['memory'] | ||
instance_and_descriptions[instance] = f"{processor} instance with {v_cpu} vCPUs and {memory} RAM" | ||
return instance_and_descriptions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
from flask import request | ||
from sqlalchemy import func | ||
from flask_restful import Resource, fields, marshal_with, abort | ||
from ros.api.common.add_group_filter import group_filtered_query | ||
from ros.lib.utils import ( | ||
system_ids_by_org_id, | ||
org_id_from_identity_header, | ||
) | ||
|
||
from ros.lib.models import ( | ||
db, | ||
PerformanceProfile, | ||
) | ||
|
||
from ros.api.common.pagination import ( | ||
limit_value, | ||
offset_value, | ||
build_paginated_system_list_response | ||
) | ||
from ros.api.common.instance_types_helper import instance_types_desc_dict | ||
from ros.api.common.utils import sorting_order | ||
|
||
|
||
def non_null_suggested_instance_types(): | ||
org_id = org_id_from_identity_header(request) | ||
systems_query = group_filtered_query(system_ids_by_org_id(org_id)) | ||
return db.session.query(PerformanceProfile.top_candidate, | ||
func.count(PerformanceProfile.system_id).label('system_count')).filter( | ||
PerformanceProfile.top_candidate.is_not(None)).filter( | ||
PerformanceProfile.system_id.in_(systems_query)).group_by(PerformanceProfile.top_candidate) | ||
|
||
|
||
class SuggestedInstanceTypes(Resource): | ||
data = { | ||
'instance_type': fields.String, | ||
'cloud_provider': fields.String, | ||
'system_count': fields.Integer, | ||
'description': fields.String, | ||
} | ||
meta_fields = { | ||
'count': fields.Integer, | ||
'limit': fields.Integer, | ||
'offset': fields.Integer | ||
} | ||
links_fields = { | ||
'first': fields.String, | ||
'last': fields.String, | ||
'next': fields.String, | ||
'previous': fields.String | ||
} | ||
output = { | ||
'meta': fields.Nested(meta_fields), | ||
'links': fields.Nested(links_fields), | ||
'data': fields.List(fields.Nested(data)) | ||
} | ||
|
||
@marshal_with(output) | ||
def get(self): | ||
limit = limit_value() | ||
offset = offset_value() | ||
order_by = ( | ||
request.args.get('order_by') or 'system_count' | ||
).strip().lower() | ||
order_how = (request.args.get('order_how') or 'desc').strip().lower() | ||
sort_expression = self.build_sort_expression(order_how, order_by) | ||
query = non_null_suggested_instance_types().filter(*self.build_instance_filters()).order_by(*sort_expression) | ||
count = query.count() | ||
query = query.limit(limit).offset(offset) | ||
query_result = query.all() | ||
suggested_instance_types = [] | ||
for row in query_result: | ||
# FIXME: As of now we only support AWS cloud, so statically adding it to the dict. Fix this code block | ||
# upon supporting multiple clouds. | ||
record = {'instance_type': row.top_candidate, 'cloud_provider': 'AWS', 'system_count': row.system_count, | ||
'description': instance_types_desc_dict()[row.top_candidate]} | ||
suggested_instance_types.append(record) | ||
return build_paginated_system_list_response(limit, offset, suggested_instance_types, count) | ||
|
||
@staticmethod | ||
def build_instance_filters(): | ||
filters = [] | ||
if filter_instance_type := request.args.get('instance_type'): | ||
filters.append(PerformanceProfile.top_candidate.ilike(f'%{filter_instance_type}')) | ||
# TODO: once ROS has multi cloud support, add cloud provider filter | ||
return filters | ||
|
||
def build_sort_expression(self, order_how, order_method): | ||
"""Build sort expression.""" | ||
sort_order = sorting_order(order_how) | ||
|
||
if order_method == 'instance_type': | ||
return (sort_order(PerformanceProfile.top_candidate),) | ||
|
||
if order_method == 'system_count': | ||
return (sort_order(func.count(PerformanceProfile.system_id)),) | ||
|
||
abort(403, message="Unexpected sort method {}".format(order_method)) | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.