Skip to content

Commit 8991da0

Browse files
authored
refactor: Simplify v2 recommender (#265)
This PR simplifies the v2 recommender implementation. Here is a flow, - Call respective insight service - Get details(github, latest_version,..) for the recommended packages(step-1) from graph - Prepare response (version selection is based on latest_non_cve_version from Package node) Note: Package will be dropped off from the recommendation incase if latest_non_cve_version is empty Fixes https://issues.redhat.com/browse/APPAI-1694
1 parent 1367cf9 commit 8991da0

14 files changed

Lines changed: 798 additions & 864 deletions

openshift/template.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ objects:
5656
value: f8a-pypi-insights
5757
- name: GOLANG_SERVICE_HOST
5858
value: f8a-golang-insights
59+
- name: MAVEN_SERVICE_HOST
60+
value: f8a-hpf-insights-maven
5961
- name: HPF_SERVICE_HOST
6062
value: f8a-hpf-insights
6163
- name: SERVICE_PORT

scripts/entrypoint.sh

100644100755
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/usr/bin/bash
22

33
# Start API backbone service with time out
4-
gunicorn --pythonpath /src/ -b 0.0.0.0:$API_BACKBONE_SERVICE_PORT -t $API_BACKBONE_SERVICE_TIMEOUT -k $CLASS_TYPE -w $NUMBER_WORKER_PROCESS rest_api:app
4+
gunicorn --pythonpath /src/ -b 0.0.0.0:$API_BACKBONE_SERVICE_PORT -t $API_BACKBONE_SERVICE_TIMEOUT -k $CLASS_TYPE -w $NUMBER_WORKER_PROCESS rest_api:app --reload --log-level $FLASK_LOGGING_LEVEL

src/rest_api.py

Lines changed: 45 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,28 @@
11
"""Implementation of the REST API for the backbone service."""
22

3-
import os
4-
import logging
53
import flask
4+
import logging
5+
import os
66
import time
7+
78
from f8a_worker.setup_celery import init_selinon
89
from flask import Flask, request
910
from flask_cors import CORS
1011
from raven.contrib.flask import Sentry
1112

1213
from src.recommender import RecommendationTask as RecommendationTaskV1
1314
from src.stack_aggregator import StackAggregator as StackAggregatorV1
15+
from src.utils import push_data, total_time_elapsed
1416
from src.v2.recommender import RecommendationTask as RecommendationTaskV2
1517
from src.v2.stack_aggregator import StackAggregator as StackAggregatorV2
16-
from src.utils import push_data, total_time_elapsed, get_time_delta
17-
18-
19-
def setup_logging(flask_app):
20-
"""Perform the setup of logging (file, log level) for this application."""
21-
if not flask_app.debug:
22-
handler = logging.StreamHandler()
23-
handler.setFormatter(logging.Formatter(
24-
'[%(asctime)s] %(levelname)s in %(pathname)s:%(lineno)d: %(message)s'))
25-
log_level = os.environ.get('FLASK_LOGGING_LEVEL', logging.getLevelName(logging.WARNING))
26-
handler.setLevel(log_level)
27-
28-
flask_app.logger.addHandler(handler)
29-
flask_app.config['LOGGER_HANDLER_POLICY'] = 'never'
30-
flask_app.logger.setLevel(logging.DEBUG)
3118

19+
logger = logging.getLogger(__name__)
3220

3321
app = Flask(__name__)
34-
setup_logging(app)
3522
CORS(app)
3623
SENTRY_DSN = os.environ.get("SENTRY_DSN", "")
3724
sentry = Sentry(app, dsn=SENTRY_DSN, logging=True, level=logging.ERROR)
3825

39-
logger = logging.getLogger(__name__)
40-
4126
init_selinon()
4227

4328

@@ -68,29 +53,25 @@ def _recommender(handler):
6853
}
6954

7055
input_json = request.get_json()
71-
if input_json and 'external_request_id' in input_json and input_json['external_request_id']:
72-
external_request_id = input_json['external_request_id']
73-
logger.info('%s recommender/ request with payload: %s', external_request_id, input_json)
74-
75-
try:
76-
check_license = request.args.get('check_license', 'false') == 'true'
77-
persist = request.args.get('persist', 'true') == 'true'
78-
r = handler.execute(input_json, persist=persist,
79-
check_license=check_license)
80-
except Exception as e:
81-
r = {
82-
'recommendation': 'unexpected error',
83-
'external_request_id': input_json.get('external_request_id'),
84-
'message': '%s' % e
85-
}
86-
metrics_payload['status_code'] = 400
87-
logger.error('%s failed %s', external_request_id, r)
56+
external_request_id = input_json['external_request_id']
57+
logger.info('%s recommender/ request with payload: %s', external_request_id, input_json)
8858

8959
try:
90-
metrics_payload['value'] = get_time_delta(audit_data=r['result']['_audit'])
60+
check_license = request.args.get('check_license', 'false') == 'true'
61+
persist = request.args.get('persist', 'true') == 'true'
62+
r = handler.execute(input_json, persist=persist,
63+
check_license=check_license)
64+
except Exception as e:
65+
r = {
66+
'recommendation': 'unexpected error',
67+
'external_request_id': input_json.get('external_request_id'),
68+
'message': '%s' % e
69+
}
70+
metrics_payload['status_code'] = 400
71+
metrics_payload['value'] = 0
9172
push_data(metrics_payload)
92-
except KeyError:
93-
pass
73+
logger.error('%s failed %s', external_request_id, r)
74+
raise e
9475

9576
logger.info('%s took %0.2f seconds for _recommender',
9677
external_request_id, time.time() - recommender_started_at)
@@ -114,38 +95,31 @@ def _stack_aggregator(handler):
11495
'status_code': 200
11596
}
11697

117-
if input_json and 'external_request_id' in input_json \
118-
and input_json['external_request_id']:
119-
external_request_id = input_json['external_request_id']
120-
logger.info('%s stack_aggregator/ request with payload: %s',
121-
external_request_id, input_json)
122-
123-
try:
124-
persist = request.args.get('persist', 'true') == 'true'
125-
s = handler.execute(input_json, persist=persist)
126-
if s is not None and s.get('result') and s.get('result').get('_audit'):
127-
# Creating and Pushing Total Metrics Data to Accumulator
128-
metrics_payload['value'] = total_time_elapsed(
129-
sa_audit_data=s['result']['_audit'],
130-
external_request_id=input_json['external_request_id'])
131-
push_data(metrics_payload)
132-
133-
except Exception as e:
134-
s = {
135-
'stack_aggregator': 'unexpected error',
136-
'external_request_id': input_json.get('external_request_id'),
137-
'message': '%s' % e
138-
}
139-
metrics_payload['status_code'] = 400
140-
logger.error('%s failed %s', external_request_id, s)
141-
142-
try:
143-
# Pushing Individual Metrics Data to Accumulator
144-
metrics_payload['value'] = get_time_delta(audit_data=s['result']['_audit'])
145-
metrics_payload['endpoint'] = request.endpoint
98+
external_request_id = input_json['external_request_id']
99+
logger.info('%s stack_aggregator/ request with payload: %s',
100+
external_request_id, input_json)
101+
102+
try:
103+
persist = request.args.get('persist', 'true') == 'true'
104+
s = handler.execute(input_json, persist=persist)
105+
if s.get('result', {}).get('_audit'):
106+
# Creating and Pushing Total Metrics Data to Accumulator
107+
metrics_payload['value'] = total_time_elapsed(
108+
sa_audit_data=s['result']['_audit'],
109+
external_request_id=input_json['external_request_id'])
146110
push_data(metrics_payload)
147-
except KeyError:
148-
pass
111+
except Exception as e:
112+
s = {
113+
'stack_aggregator': 'unexpected error',
114+
'external_request_id': input_json.get('external_request_id'),
115+
'message': '%s' % e
116+
}
117+
metrics_payload['status_code'] = 400
118+
# Pushing Individual Metrics Data to Accumulator
119+
metrics_payload['value'] = 0
120+
push_data(metrics_payload)
121+
logger.error('%s failed %s', external_request_id, s)
122+
raise e
149123

150124
logger.info('%s took %0.2f seconds for _stack_aggregators',
151125
external_request_id, time.time() - stack_aggregator_started_at)

src/settings.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,21 @@
55
from pydantic import BaseSettings, HttpUrl
66

77

8-
# (fixme) Move all settings to read from here.
9-
class Settings(BaseSettings):
8+
class AggregatorSettings(BaseSettings):
109
"""Create Settings from env."""
1110

12-
snyk_package_url_format: HttpUrl = 'https://snyk.io/vuln/{ecosystem}:{package}'
13-
snyk_signin_url: HttpUrl = 'https://snyk.io/login'
11+
snyk_package_url_format: HttpUrl = "https://snyk.io/vuln/{ecosystem}:{package}"
12+
snyk_signin_url: HttpUrl = "https://snyk.io/login"
1413
snyk_ecosystem_map: Dict[str, str] = {"pypi": "pip"}
1514
disable_unknown_package_flow: bool = False
15+
16+
17+
class RecommenderSettings(BaseSettings):
18+
"""Create Recommender Settings from env."""
19+
20+
chester_service_host: str = "notset"
21+
pypi_service_host: str = "notset"
22+
maven_service_host: str = "notset"
23+
service_port: int = 6006
24+
unknown_packages_threshold: float = 0.3
25+
max_companion_packages: int = 5

src/stack_aggregator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import requests
1313
import copy
1414
from collections import defaultdict
15-
from src.settings import Settings
15+
from src.settings import AggregatorSettings
1616
from src.utils import (select_latest_version, server_create_analysis, LICENSE_SCORING_URL_REST,
1717
post_http_request, GREMLIN_SERVER_URL_REST, persist_data_in_db,
1818
GREMLIN_QUERY_SIZE, format_date)
@@ -663,7 +663,7 @@ def execute(aggregated=None, persist=True):
663663
'result': stack_data}
664664
# Ingestion of Unknown dependencies
665665
logger.info("Unknown ingestion flow process initiated.")
666-
if Settings().disable_unknown_package_flow:
666+
if AggregatorSettings().disable_unknown_package_flow:
667667
logger.warning('Skipping unknown flow %s', unknown_dep_list)
668668
else:
669669
try:

src/utils.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -324,21 +324,21 @@ def post_http_request(url: str, payload: Dict):
324324

325325
def post_gremlin(query: str, bindings: Dict = None) -> Dict:
326326
"""Post the given query and bindings to gremlin endpoint."""
327+
payload = {
328+
'gremlin': query,
329+
}
330+
if bindings:
331+
payload['bindings'] = bindings
327332
try:
328-
payload = {
329-
'gremlin': query,
330-
}
331-
if bindings:
332-
payload['bindings'] = bindings
333333
response = get_session_retry().post(url=GREMLIN_SERVER_URL_REST, json=payload)
334334
response.raise_for_status()
335-
return response.json()
336335
except Exception as e:
337336
logger.error(traceback.format_exc())
338-
logger.error(
339-
"HTTP error {code}. Error retrieving data for {query}.".format(
340-
code=response.status_code, query=payload))
337+
logger.error("HTTP error %d. Error retrieving data for %s.",
338+
response.status_code, payload)
341339
raise GremlinExeception from e
340+
else:
341+
return response.json()
342342

343343

344344
def get_response_data(json_response, data_default):

src/v2/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ class UsedByItem(BaseModel): # noqa: D101
120120

121121
class GitHubDetails(BaseModel): # noqa: D101
122122
watchers: Optional[str] = None
123-
first_release_date: Optional[str] = None
124123
total_releases: Optional[str] = None
125124
issues: Optional[Dict[str, Any]] = None
126125
pull_requests: Optional[Dict[str, Any]] = None
@@ -222,13 +221,14 @@ class StackRecommendationResult(BaseModel): # noqa: D101
222221
registration_status: str
223222
recommendation_status: 'RecommendationStatus' = 'success'
224223
companion: List['RecommendedPackageData']
225-
usage_outliers: List[Dict[str, Any]]
224+
usage_outliers: List[Dict[str, Any]] = []
226225

227226

228227
class RecommenderRequest(StackAggregatorRequest): # noqa: D101
229228
pass
230229

231230

232231
Package.update_forward_refs()
232+
PackageDetails.update_forward_refs()
233233
PackageDataWithVulnerabilities.update_forward_refs()
234234
RecommendedPackageData.update_forward_refs()

src/v2/openapi.yaml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,6 @@ components:
360360
watchers:
361361
type: string
362362
example: "100K"
363-
first_release_date:
364-
type: string
365363
total_releases:
366364
type: string
367365
example: "12"
@@ -487,7 +485,6 @@ components:
487485
- recommendation_status
488486
- companion
489487
- manifest_file_path
490-
- usage_outliers
491488
properties:
492489
_audit:
493490
$ref: '#/components/schemas/Audit'
@@ -513,11 +510,6 @@ components:
513510
manifest_file_path:
514511
type: string
515512
example: pom.xml
516-
usage_outliers:
517-
type: array
518-
items:
519-
type: object
520-
properties: {}
521513
description: Application stack recommendations
522514

523515
Audit:

0 commit comments

Comments
 (0)