Skip to content

Commit 46dd9e9

Browse files
authored
Merge pull request #72 from G-Lenz/main
Release v2.7.1
2 parents 2ab62c9 + 2ad4fd9 commit 46dd9e9

16 files changed

+185
-83
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [2.7.1] - 2024-08
8+
### Added
9+
- Security.md file
10+
11+
### Fixed
12+
- Workspace analysis failing when pervious data is recorded in database but empty.
13+
- Workspace analysis failing when timestamps from user connected data doesn't exist in other metric data.
14+
- Workspace only reporting 24 hour period
15+
- Workspace not reporting tags
16+
- Updated micromatch to mitigate [CVE-2024-4067](https://avd.aquasec.com/nvd/2024/cve-2024-4067).
17+
718
## [2.7.0] - 2024-07
819
### Added
920
- Workspace performance metrics
@@ -13,6 +24,7 @@
1324
- Powertools logging
1425
- Operational insights CloudWatch dashboard
1526
- Support for G4DN workspaces
27+
1628
### Fixed
1729
- sts token expired after one hour
1830

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ npm run synth
148148
├── LICENSE.txt
149149
├── NOTICE.txt
150150
├── README.md
151+
├── SECURITY.md
151152
├── buildspec.yml
152153
├── deployment
153154
│   ├── build-open-source-dist.sh

SECURITY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Reporting Security Issues
2+
----------------------------------------------------------------------------------------------------------
3+
We take all security reports seriously. When we receive such reports, we will investigate and
4+
subsequently address any potential vulnerabilities as quickly as possible. If you discover a potential
5+
security issue in this project, please notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/) or
6+
directly via email to [AWS Security](mailto:[email protected]). Please do not create a public GitHub issue in this project.

source/lib/components/ecs-cluster-resources.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,10 @@ export class EcsClusterResources extends Construct {
403403
name: "NumberOfMonthsForTerminationCheck",
404404
value: props.numberOfmonthsForTerminationCheck,
405405
},
406+
{
407+
name: "ImageVersion",
408+
value: image,
409+
},
406410
],
407411
},
408412
],

source/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

source/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
"test": "jest --coverage",
1919
"license-report": "license-report --output=csv --delimiter=' under ' --fields=name --fields=licenseType",
2020
"cdk": "cdk",
21-
"bootstrap": "SOLUTION_VERSION=v2.7.0 SOLUTION_NAME=cost-optimizer-for-amazon-workspaces SOLUTION_TRADEMARKEDNAME=cost-optimizer-for-amazon-workspaces cdk bootstrap",
22-
"deploy": "SOLUTION_VERSION=v2.7.0 SOLUTION_NAME=cost-optimizer-for-amazon-workspaces SOLUTION_TRADEMARKEDNAME=cost-optimizer-for-amazon-workspaces cdk deploy cost-optimizer-for-amazon-workspaces",
23-
"deploySpoke": "SOLUTION_VERSION=v2.7.0 SOLUTION_NAME=cost-optimizer-for-amazon-workspaces SOLUTION_TRADEMARKEDNAME=cost-optimizer-for-amazon-workspaces cdk deploy cost-optimizer-for-amazon-workspaces-spoke",
24-
"synth": "SOLUTION_VERSION=v2.7.0 SOLUTION_NAME=cost-optimizer-for-amazon-workspaces SOLUTION_TRADEMARKEDNAME=cost-optimizer-for-amazon-workspaces DIST_OUTPUT_BUCKET=solutions-reference cdk synth"
21+
"bootstrap": "SOLUTION_VERSION=v2.7.1 SOLUTION_NAME=cost-optimizer-for-amazon-workspaces SOLUTION_TRADEMARKEDNAME=cost-optimizer-for-amazon-workspaces cdk bootstrap",
22+
"deploy": "SOLUTION_VERSION=v2.7.1 SOLUTION_NAME=cost-optimizer-for-amazon-workspaces SOLUTION_TRADEMARKEDNAME=cost-optimizer-for-amazon-workspaces cdk deploy cost-optimizer-for-amazon-workspaces",
23+
"deploySpoke": "SOLUTION_VERSION=v2.7.1 SOLUTION_NAME=cost-optimizer-for-amazon-workspaces SOLUTION_TRADEMARKEDNAME=cost-optimizer-for-amazon-workspaces cdk deploy cost-optimizer-for-amazon-workspaces-spoke",
24+
"synth": "SOLUTION_VERSION=v2.7.1 SOLUTION_NAME=cost-optimizer-for-amazon-workspaces SOLUTION_TRADEMARKEDNAME=cost-optimizer-for-amazon-workspaces DIST_OUTPUT_BUCKET=solutions-reference cdk synth"
2525
},
2626
"devDependencies": {
2727
"@aws-cdk/assert": "2.68.0",

source/test/__snapshots__/hub-snapshot.test.ts.snap

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,28 @@ exports[`hub stack synth matches the existing snapshot 1`] = `
13881388
"Ref": "NumberOfMonthsForTerminationCheck",
13891389
},
13901390
},
1391+
{
1392+
"Name": "ImageVersion",
1393+
"Value": {
1394+
"Fn::If": [
1395+
"UseStableTagCondition",
1396+
{
1397+
"Fn::FindInMap": [
1398+
"Solution",
1399+
"Data",
1400+
"StableImage",
1401+
],
1402+
},
1403+
{
1404+
"Fn::FindInMap": [
1405+
"Solution",
1406+
"Data",
1407+
"Image",
1408+
],
1409+
},
1410+
],
1411+
},
1412+
},
13911413
],
13921414
"Essential": true,
13931415
"Image": {

source/workspaces_app/workspaces_app/__tests__/test_directory_reader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ def ws_description(**kwargs):
7373
"username": "test-user",
7474
"computer_name": "test-computer",
7575
"initial_mode": "test-mode",
76-
"tags": ["tag1", "tag2"],
7776
}
7877
filtered_args = {
7978
key: value for key, value in kwargs.items() if key in default_args.keys()
@@ -118,6 +117,7 @@ def ws_record(ws_billing_data, ws_metrics):
118117
report_date="test-report-date",
119118
last_reported_metric_period="test-last-period",
120119
last_known_user_connection="test-last-connection",
120+
tags="[{'key1': 'tag1'}, {'key2': 'tag2'}]",
121121
)
122122

123123

@@ -203,7 +203,7 @@ def test_process_directory_without_ddb_item(
203203
unittest.mock.ANY, unittest.mock.ANY, dashboard_metrics
204204
)
205205
report_header = "WorkspaceID,Billable Hours,Usage Threshold,Change Reported,Bundle Type,Initial Mode,New Mode,Username,Computer Name,DirectoryId,WorkspaceTerminated,insessionlatency,cpuusage,memoryusage,rootvolumediskusage,uservolumediskusage,udppacketlossrate,Tags,ReportDate,\n"
206-
list_processed_workspaces = "test-ws-id,20,100,No change,test-bundle,test-mode,test-mode,test-user,test-computer,test-dir-id,,93.42,94.42,95.42,96.42,97.42,98.42,\"['tag1', 'tag2']\",test-report-date\n"
206+
list_processed_workspaces = "test-ws-id,20,100,No change,test-bundle,test-mode,test-mode,test-user,test-computer,test-dir-id,,93.42,94.42,95.42,96.42,97.42,98.42,[{'key1': 'tag1'}, {'key2': 'tag2'}],test-report-date\n"
207207
header_field_count = len(str.split(report_header, ","))
208208
data_field_count = len(str.split(list_processed_workspaces, ","))
209209
assert header_field_count == data_field_count

source/workspaces_app/workspaces_app/__tests__/test_metrics_helper.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ def ws_description(**kwargs):
4646
"username": "test-user",
4747
"computer_name": "test-computer",
4848
"initial_mode": "test-mode",
49-
"tags": ["tag1", "tag2"],
5049
}
5150
filtered_args = {
5251
key: value for key, value in kwargs.items() if key in default_args.keys()
@@ -91,6 +90,7 @@ def ws_record(ws_billing_data, ws_metrics):
9190
report_date="test-report-date",
9291
last_reported_metric_period="test-last-period",
9392
last_known_user_connection="test-last-connection",
93+
tags="[{'key1': 'tag1'}, {'key2': 'tag2'}]",
9494
)
9595

9696

@@ -180,13 +180,18 @@ def performance_metric_factory(length, start):
180180

181181
def metric_data_factory(indices, length, start):
182182
metrics = {}
183+
user_connected_timestamps = user_session_timestamps_factory(length)
183184
timestamps = user_session_timestamps_factory(length)
184185
for metric in METRIC_LIST:
185186
if metric == "UserConnected":
186187
data = user_connected_data_factory(indices, length)
188+
metrics[metric.lower()] = {
189+
"timestamps": user_connected_timestamps,
190+
"values": data,
191+
}
187192
else:
188193
data = performance_metric_factory(length, start)
189-
metrics[metric.lower()] = {"timestamps": timestamps, "values": data}
194+
metrics[metric.lower()] = {"timestamps": timestamps, "values": data}
190195
return metrics
191196

192197

@@ -241,9 +246,14 @@ def expected_sessions_factory(user_session_data, active_indices, zero_limit):
241246
session.setdefault("active_sessions", []).append(
242247
user_session_data["cpuusage"]["timestamps"][active_index]
243248
)
244-
expected_avg = WeightedAverage(
249+
current_avg = WeightedAverage(
245250
user_session_data["cpuusage"]["values"][active_index], 1
246-
).merge(expected_avg)
251+
)
252+
expected_avg = (
253+
current_avg.merge(expected_avg)
254+
if expected_avg is not None
255+
else current_avg
256+
)
247257
if session:
248258
duration_hours = math.ceil(
249259
(session["active_sessions"][-1] - session["active_sessions"][0]).seconds
@@ -691,7 +701,7 @@ def test_get_billable_hours_and_performance(mocker, session, ws_record, metric_d
691701
metrics_helper, "get_list_data_points", return_value=metric_data
692702
)
693703
mocker.patch.object(metrics_helper, "get_user_connected_hours")
694-
mocker.patch.object(metrics_helper, "get_user_sessions")
704+
mock_user_session = mocker.patch.object(metrics_helper, "get_user_sessions")
695705
mocker.patch.object(metrics_helper.session_table, "update_ddb_items"),
696706
spy_get_time_range = mocker.spy(metrics_helper, "get_time_range")
697707
spy_get_cloudwatch_metric_data_points = mocker.spy(
@@ -712,7 +722,13 @@ def test_get_billable_hours_and_performance(mocker, session, ws_record, metric_d
712722
)
713723
spy_get_cloudwatch_metric_data_points.assert_called_once()
714724
spy_get_list_data_points.assert_called_once()
715-
spy_get_user_connected_hours.assert_called_once()
725+
spy_get_user_connected_hours.assert_called_once_with(
726+
mock_user_session(),
727+
ws_record.description.workspace_id,
728+
ws_record.description.initial_mode,
729+
60,
730+
ws_record.billing_data.billable_hours,
731+
)
716732
spy_get_user_sessions.assert_called_once()
717733

718734

@@ -807,6 +823,9 @@ def test_get_user_sessions(session, ws_record):
807823
total_values = 26
808824
start_value = 1
809825
user_session_data = metric_data_factory(active_indices, total_values, start_value)
826+
user_session_data["userconnected"]["timestamps"][-1] += datetime.timedelta(
827+
minutes=5
828+
)
810829
result = metrics_helper.get_user_sessions(
811830
user_session_data,
812831
ws_description(),
@@ -1436,7 +1455,7 @@ def test_get_user_sessions_32(session, ws_record):
14361455
def test_process_performance_metrics(session, ws_record, metric_data):
14371456
metrics_helper = MetricsHelper(session, "us-east-1", "test-table")
14381457
current_weighted_avg = mean(metric_data["cpuusage"]["values"]) * 3
1439-
previous_weighted_avg = ws_record.performance_metrics.cpu_usage.weighted_avg
1458+
previous_weighted_avg = ws_record.performance_metrics.cpu_usage.weighted_avg()
14401459
expected_avg = Decimal(
14411460
(current_weighted_avg + previous_weighted_avg)
14421461
/ (ws_record.performance_metrics.cpu_usage.count + 3),
@@ -1462,9 +1481,8 @@ def test_process_performance_metrics_with_no_available_data_in_last_report(
14621481
assert result.memory_usage.avg == Decimal("5")
14631482
assert result.memory_usage.count == 3
14641483

1465-
# test when current dat doesn't exist
1466-
assert result.udp_packet_loss_rate.avg == None
1467-
assert result.udp_packet_loss_rate.count == 0
1484+
# test when current data doesn't exist
1485+
assert result.udp_packet_loss_rate == None
14681486

14691487

14701488
def test_process_performance_metrics_with_zero_avg(session, ws_record, metric_data):

source/workspaces_app/workspaces_app/__tests__/test_workspace_record.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
# SPDX-License-Identifier: Apache-2.0
55

66
# Standard Library
7-
from dataclasses import fields
87
from decimal import Decimal
98

109
# Third Party Libraries
@@ -35,7 +34,6 @@ def ws_description():
3534
username="test-user",
3635
computer_name="test-computer",
3736
initial_mode="test-mode",
38-
tags=["tag1", "tag2"],
3937
)
4038

4139

@@ -75,6 +73,7 @@ def ws_record(ws_description, ws_billing_data, ws_metrics):
7573
report_date="test-report-date",
7674
last_reported_metric_period="test-last-period",
7775
last_known_user_connection="test-last-connection",
76+
tags="[{'key1': 'tag1'}, {'key2': 'tag2'}]",
7877
)
7978

8079

@@ -155,7 +154,7 @@ def ddb_item(ws_record):
155154
"N": str(ws_record.performance_metrics.udp_packet_loss_rate.count)
156155
},
157156
"Tags": {
158-
"L": list(map(lambda x: {"S": x}, ws_record.description.tags)),
157+
"S": ws_record.tags,
159158
},
160159
"ReportDate": {"S": ws_record.report_date},
161160
"LastReportedMetricPeriod": {"S": ws_record.last_reported_metric_period},
@@ -245,11 +244,10 @@ def test_ddb_attr_to_class_field_with_caps():
245244
assert result == "test_string"
246245

247246

248-
def test_weighted_avg_post_init_sets_weighted_avg_field(ws_metrics):
249-
fields = vars(ws_metrics)
250-
for field in fields:
251-
value = getattr(ws_metrics, field)
252-
assert value.weighted_avg == value.avg * value.count
247+
def test_weighted_avg(ws_metrics):
248+
weighted_avg = ws_metrics.cpu_usage.weighted_avg()
249+
250+
assert weighted_avg == ws_metrics.cpu_usage.avg * ws_metrics.cpu_usage.count
253251

254252

255253
def test_weighted_average_merge(ws_metrics):
@@ -258,12 +256,11 @@ def test_weighted_average_merge(ws_metrics):
258256

259257
merged_wa = wa_1.merge(wa_2)
260258
expected_count = wa_1.count + wa_2.count
261-
expected_avg = Decimal((wa_1.weighted_avg + wa_2.weighted_avg) / expected_count)
259+
expected_avg = Decimal((wa_1.weighted_avg() + wa_2.weighted_avg()) / expected_count)
262260
assert merged_wa.avg == expected_avg
263261
assert merged_wa.count == expected_count
264-
assert merged_wa.weighted_avg == expected_count * expected_avg
265262

266263

267264
def test_to_csv(ws_record):
268-
expected = "test-ws-id,20,100,ToHourly,test-bundle,test-mode,test-mode,test-user,test-computer,test-dir-id,,93.42,94.42,95.42,96.42,97.42,98.42,\"['tag1', 'tag2']\",test-report-date\n"
265+
expected = "test-ws-id,20,100,ToHourly,test-bundle,test-mode,test-mode,test-user,test-computer,test-dir-id,,93.42,94.42,95.42,96.42,97.42,98.42,[{'key1': 'tag1'}, {'key2': 'tag2'}],test-report-date\n"
269266
assert ws_record.to_csv() == expected

0 commit comments

Comments
 (0)