Skip to content

Commit 3b31a48

Browse files
authored
Pull request update/250303
499deb8 OSN-684. Updated README 9765df5 OSN-665. Fixed OOMKilled on getting Azure flavors 9e1610f OSN-660. Handle HttpError for Alibaba resources discovery d128dd0 OSN-587. Limit/offset for clean_expenses and list_organizations APIs
2 parents 109ba7a + 499deb8 commit 3b31a48

File tree

13 files changed

+358
-86
lines changed

13 files changed

+358
-86
lines changed

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ source ~/.profile
144144
```
145145
to add local ~/bin path to the system $PATH variable
146146

147-
You can build local images running
147+
**Note:** you can build local images running
148148

149149
```
150150
cd .. && ./build.sh --use-nerdctl
@@ -159,13 +159,19 @@ Pay attention to "service_credentials" parameter, as OptScale uses it to retriev
159159

160160
#### Cluster installation
161161

162-
run the following command:
162+
Run the following command to start cluster from the required version:
163163

164164
```
165165
./runkube.py --with-elk -o overlay/user_template.yml -- <deployment name> <version>
166166
```
167167

168-
or if you want to use socket:
168+
or use `--no-pull` to start cluster from local images:
169+
170+
```
171+
./runkube.py --with-elk --no-pull -o overlay/user_template.yml -- <deployment name> local
172+
```
173+
174+
If you want to use socket:
169175

170176
```
171177
./runkube.py --use-socket --with-elk -o overlay/user_template.yml -- <deployment name> <version>
@@ -189,7 +195,7 @@ If you have insecure registry (with self-signed certificate) you can use --insec
189195
**please note**: if you use key authentication, you should have the required key (id_rsa) on the machine
190196

191197
Check the state of the pods using `kubectl get pods` command.
192-
When all of the pods are running your OptScale is ready to use. Try to access it by `https://<ip address>`.
198+
When all the pods are running your OptScale is ready to use. Try to access it by `https://<ip address>`.
193199

194200
#### Cluster update
195201

docker_images/resource_discovery/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ WORKDIR /usr/src/app/
44
ENV PYTHONPATH /usr/src/app/
55

66
COPY optscale_client/config_client optscale_client/config_client
7+
COPY optscale_client/insider_client optscale_client/insider_client
78
COPY optscale_client/rest_api_client optscale_client/rest_api_client
89
COPY tools/cloud_adapter tools/cloud_adapter
910
COPY tools/optscale_time tools/optscale_time
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
kombu==5.3.4
2-
32
# OptScale packages
43
-e optscale_client/config_client
4+
-e optscale_client/insider_client
55
-e optscale_client/rest_api_client
66
-e tools/cloud_adapter
77
-e tools/optscale_time

docker_images/resource_discovery/worker.py

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
from tools.cloud_adapter.exceptions import InvalidResourceTypeException
1818
from tools.cloud_adapter.model import ResourceTypes, RES_MODEL_MAP
1919
from optscale_client.config_client.client import Client as ConfigClient
20+
from optscale_client.insider_client.client import Client as InsiderClient
2021
from optscale_client.rest_api_client.client_v2 import Client as RestClient
2122
from tools.optscale_time import utcnow, utcnow_timestamp
2223

2324

25+
BYTES_IN_MB = 1024 * 1024
2426
CHUNK_SIZE = 200
2527
EXCHANGE_NAME = 'resource-discovery'
2628
QUEUE_NAME = 'discovery'
@@ -32,15 +34,19 @@
3234

3335

3436
class ResourcesSaver:
35-
def __init__(self, rest_cl, limit, timeout, pause_timeout):
37+
def __init__(self, rest_cl, insider_cl, limit, timeout, pause_timeout):
3638
queue_len = int(limit / CHUNK_SIZE) if CHUNK_SIZE else 0
3739
self.queue = queue.Queue(queue_len)
40+
self.insider_cl = insider_cl
3841
self.rest_cl = rest_cl
3942
self.timeout = timeout
4043
self.pause_timeout = pause_timeout
4144
self.recording_available = Event()
4245
self.empty = Event()
4346
self._proc = None
47+
self._cloud_account_id = None
48+
self._cloud_type = None
49+
self._resource_type = None
4450
self.start()
4551

4652
def __del__(self):
@@ -95,9 +101,9 @@ def is_finished(self):
95101
def _save_chunks(self):
96102
while True:
97103
try:
98-
chunk, resource_type, cloud_acc_id = self.queue.get(timeout=1)
104+
chunk = self.queue.get(timeout=1)
99105
self.empty.clear()
100-
self.save_bulk_resources(chunk, resource_type, cloud_acc_id)
106+
self.save_bulk_resources(chunk)
101107
except queue.Empty:
102108
self.empty.set()
103109
except Exception as exc:
@@ -110,26 +116,72 @@ def get_resource_type_model(resource_type):
110116
except KeyError:
111117
raise Exception(f'Invalid resource type {resource_type}')
112118

113-
def build_payload(self, resource, resource_type):
119+
def build_payload(self, resource):
114120
obj = {}
115121
for field in resource.fields(meta_fields_incl=False):
116122
val = getattr(resource, field)
117123
if val is not None and (isinstance(val, bool) or val):
118124
obj[field] = val
119125
obj.pop('resource_id', None)
120126
obj.pop('organization_id', None)
121-
obj['resource_type'] = getattr(ResourceTypes, resource_type).value
127+
obj['resource_type'] = getattr(ResourceTypes, self.resource_type).value
122128
obj['last_seen'] = utcnow_timestamp()
123129
obj['active'] = True
124130
return obj
125131

126-
def save_bulk_resources(self, resources, resource_type, cloud_acc_id):
132+
@property
133+
def resource_type(self):
134+
return self._resource_type
135+
136+
@resource_type.setter
137+
def resource_type(self, value):
138+
self._resource_type = value
139+
140+
@property
141+
def cloud_account_id(self):
142+
return self._cloud_account_id
143+
144+
@cloud_account_id.setter
145+
def cloud_account_id(self, value):
146+
self._cloud_account_id = value
147+
148+
@property
149+
def cloud_type(self):
150+
return self._cloud_type
151+
152+
@cloud_type.setter
153+
def cloud_type(self, value):
154+
self._cloud_type = value
155+
156+
def process_resource_obj(self, resources):
157+
if self.resource_type != 'instance' or self.cloud_type != 'azure_cnr':
158+
return resources
159+
flavors = {}
160+
for resource in resources:
161+
flavor = flavors.get(resource.flavor)
162+
if not flavor:
163+
try:
164+
_, flavor = self.insider_cl.find_flavor(
165+
self.cloud_type, self.resource_type, resource.region,
166+
{'source_flavor_id': resource.flavor}, 'current',
167+
cloud_account_id=self.cloud_account_id)
168+
except Exception as exc:
169+
LOG.exception(exc)
170+
continue
171+
if flavor:
172+
flavors[resource.flavor] = flavor
173+
resource.cpu_count = flavor['cpu']
174+
resource.ram = flavor['ram'] * BYTES_IN_MB
175+
return resources
176+
177+
def save_bulk_resources(self, resources):
127178
payload = []
179+
resources = self.process_resource_obj(resources)
128180
for rss in resources:
129-
payload.append(self.build_payload(rss, resource_type))
181+
payload.append(self.build_payload(rss))
130182
if payload:
131183
_, response = self.rest_cl.cloud_resource_create_bulk(
132-
cloud_acc_id, {'resources': payload},
184+
self.cloud_account_id, {'resources': payload},
133185
behavior='update_existing', return_resources=True)
134186
for resource in resources:
135187
try:
@@ -143,6 +195,7 @@ def __init__(self, connection, config_cl):
143195
self.connection = connection
144196
self.config_cl = config_cl
145197
self.set_discover_settings()
198+
self._insider_cl = None
146199
self._rest_cl = None
147200
self._res_saving = None
148201
self.running = True
@@ -153,6 +206,14 @@ def __del__(self):
153206
if self._res_saving:
154207
self.res_saving.shutdown()
155208

209+
@property
210+
def insider_cl(self):
211+
if not self._insider_cl:
212+
self._insider_cl = InsiderClient(
213+
url=self.config_cl.insider_url(),
214+
secret=self.config_cl.cluster_secret())
215+
return self._insider_cl
216+
156217
@property
157218
def rest_cl(self):
158219
if self._rest_cl is None:
@@ -166,6 +227,7 @@ def rest_cl(self):
166227
def res_saving(self):
167228
if self._res_saving is None:
168229
self._res_saving = ResourcesSaver(
230+
insider_cl=self.insider_cl,
169231
rest_cl=self.rest_cl,
170232
limit=self.discover_size,
171233
timeout=self.timeout,
@@ -256,7 +318,10 @@ def _discover_resources(self, cloud_acc_id, resource_type):
256318
return
257319
config = self.get_config(cloud_acc_id)
258320
gen_list = self.discover(config, resource_type)
259-
discovered_resources = []
321+
self.res_saving.cloud_account_id = cloud_acc_id
322+
self.res_saving.cloud_type = config['type']
323+
self.res_saving.resource_type = resource_type
324+
discovered_resources = set()
260325
resources_count = 0
261326
max_parallel_requests = self.max_parallel_requests(config)
262327
errors = set()
@@ -279,18 +344,16 @@ def _discover_resources(self, cloud_acc_id, resource_type):
279344
gen_list_chunk.remove(gen)
280345
errors.add(str(res))
281346
elif res:
282-
discovered_resources.append(res)
347+
discovered_resources.add(res)
283348
else:
284349
gen_list_chunk.remove(gen)
285350
if len(discovered_resources) >= CHUNK_SIZE:
286351
resources_count += len(discovered_resources)
287-
self.res_saving.send((discovered_resources.copy(),
288-
resource_type, cloud_acc_id))
352+
self.res_saving.send(discovered_resources)
289353
discovered_resources.clear()
290354
if len(discovered_resources):
291355
resources_count += len(discovered_resources)
292-
self.res_saving.send((
293-
discovered_resources.copy(), resource_type, cloud_acc_id))
356+
self.res_saving.send(discovered_resources)
294357
LOG.info("%s %s resources have been discovered for cloud %s",
295358
resources_count, resource_type, cloud_acc_id)
296359
self.res_saving.pause()

rest_api/rest_api_server/controllers/expense.py

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -934,12 +934,50 @@ def _process_clustered_expenses(self, expenses, clustered_resources_map):
934934
return list(result.values())
935935

936936
def get_expenses(self, cloud_account_ids, resource_ids, start_date,
937-
end_date, limit=None) -> tuple:
937+
end_date, limit=0, offset=0) -> tuple:
938938
return self._get_expenses_clickhouse(
939-
cloud_account_ids, resource_ids, start_date, end_date, limit)
939+
cloud_account_ids, resource_ids, start_date, end_date, limit, offset)
940+
941+
def _get_offset_resource_ids(self, cloud_account_ids, resource_ids,
942+
start_date, end_date, offset):
943+
if not offset:
944+
return []
945+
query = """
946+
SELECT resource_id, SUM(cost * sign) AS total_cost
947+
FROM expenses
948+
WHERE cloud_account_id IN cloud_account_ids
949+
AND resource_id IN resource_ids
950+
AND date >= %(start_date)s
951+
AND date <= %(end_date)s
952+
GROUP BY resource_id
953+
HAVING SUM(sign) > 0
954+
ORDER BY total_cost DESC
955+
LIMIT %(limit)s
956+
"""
957+
result = self.execute_clickhouse(
958+
query=query,
959+
params={
960+
'start_date': start_date,
961+
'end_date': end_date,
962+
'limit': offset,
963+
},
964+
external_tables=[
965+
{
966+
'name': 'resource_ids',
967+
'structure': [('_id', 'String')],
968+
'data': [{'_id': r_id} for r_id in resource_ids]
969+
},
970+
{
971+
'name': 'cloud_account_ids',
972+
'structure': [('_id', 'String')],
973+
'data': [{'_id': r_id} for r_id in cloud_account_ids]
974+
}
975+
],
976+
)
977+
return [x[0] for x in result]
940978

941979
def _get_expenses_clickhouse(self, cloud_account_ids, resource_ids,
942-
start_date, end_date, limit) -> tuple:
980+
start_date, end_date, limit, offset) -> tuple:
943981
query = """
944982
SELECT
945983
cloud_account_id,
@@ -957,12 +995,15 @@ def _get_expenses_clickhouse(self, cloud_account_ids, resource_ids,
957995
"""
958996
if limit:
959997
query += 'LIMIT %(limit)s'
998+
if offset:
999+
query += ' OFFSET %(offset)s'
9601000
result = self.execute_clickhouse(
9611001
query=query,
9621002
params={
9631003
'start_date': start_date,
9641004
'end_date': end_date,
965-
'limit': limit
1005+
'limit': limit,
1006+
'offset': offset
9661007
},
9671008
external_tables=[
9681009
{
@@ -1046,7 +1087,8 @@ def _split_params(self, organization_id, params):
10461087
def split_params(self, organization_id, params):
10471088
query_filters, data_filters, extra_filters = self._split_params(
10481089
organization_id, params)
1049-
extra_filters['limit'] = query_filters.pop('limit', None)
1090+
extra_filters['limit'] = query_filters.pop('limit', 0)
1091+
extra_filters['offset'] = query_filters.pop('offset', 0)
10501092
return query_filters, data_filters, extra_filters
10511093

10521094
@staticmethod
@@ -1384,12 +1426,13 @@ def process_data(self, resources_data, organization_id, filters, **kwargs):
13841426
total_cost = 0
13851427
cloud_account_ids = kwargs['cloud_account_id']
13861428
limit = kwargs['limit']
1429+
offset = kwargs['offset']
13871430
_, organization_cloud_accs = self.get_organization_and_cloud_accs(
13881431
organization_id)
13891432
if not_clustered_resources:
13901433
not_clustered_expenses, cost = self.get_expenses(
13911434
cloud_account_ids, not_clustered_resources, self.start_date,
1392-
self.end_date, limit)
1435+
self.end_date, limit=limit, offset=offset)
13931436
total_cost += cost
13941437
if clustered_resources_map:
13951438
all_account_ids = list(map(
@@ -1406,6 +1449,13 @@ def process_data(self, resources_data, organization_id, filters, **kwargs):
14061449
resource_ids.update(
14071450
list(map(lambda x: x.get('resource_id'), expenses))
14081451
)
1452+
if offset and len(resource_ids) < limit:
1453+
offset_ids = self._get_offset_resource_ids(
1454+
cloud_account_ids, not_clustered_resources, self.start_date,
1455+
self.end_date, offset)
1456+
joined_ids = list(filter(
1457+
lambda x: x not in offset_ids, joined_ids
1458+
))[offset - len(offset_ids):]
14091459
for r_id in joined_ids:
14101460
if len(resource_ids) == limit:
14111461
break
@@ -1430,6 +1480,8 @@ def process_data(self, resources_data, organization_id, filters, **kwargs):
14301480
}
14311481
if limit:
14321482
res['limit'] = limit
1483+
if offset:
1484+
res['offset'] = offset
14331485
return res
14341486

14351487
def handle_filters(self, params, filters, organization_id):
@@ -1640,7 +1692,7 @@ def process_data(self, resources_data, organization_id, filters, **kwargs):
16401692
return res
16411693

16421694
def get_expenses(self, cloud_account_ids, resource_ids, start_date,
1643-
end_date, limit=None) -> tuple:
1695+
end_date, limit=0, offset=0) -> tuple:
16441696
start = datetime.fromtimestamp(start_date)
16451697
end = datetime.fromtimestamp(end_date)
16461698
(

0 commit comments

Comments
 (0)