Skip to content

Commit 2690633

Browse files
committed
Safely retrieve a nested value from a dictionary + Validate a known cloud provider node
1 parent e2bcd18 commit 2690633

File tree

2 files changed

+104
-58
lines changed

2 files changed

+104
-58
lines changed

config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"version": "1.0.3"
2+
"version": "1.0.4"
33
}

main.py

Lines changed: 103 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -661,9 +661,32 @@ def serialize_object(obj):
661661
return obj.__dict__
662662

663663

664+
def get_nested_value(keys_array, nested_dict, default_value):
665+
"""
666+
Safely retrieves a nested value from a dictionary based on the provided keys array.
667+
668+
Parameters:
669+
keys_array (list): A list of strings representing the sequence of nested keys.
670+
nested_dict (dict): The dictionary from which the nested value will be retrieved.
671+
default_value: The default value to be returned if any key is missing or the nesting structure is invalid..
672+
673+
Returns:
674+
The nested value from the dictionary if all keys are valid and the nesting structure is correct.
675+
If any key is missing or the nesting structure is invalid, it returns the default_value
676+
(or None if not specified).
677+
"""
678+
679+
try:
680+
for key in keys_array:
681+
nested_dict = nested_dict[key]
682+
return nested_dict
683+
except (KeyError, TypeError):
684+
return default_value
685+
686+
664687
### Handle Arguments ###
665688
if len(sys.argv) > 1 and sys.argv[1] == "--version":
666-
print("v1.0.3")
689+
print("v1.0.4")
667690
sys.exit(0)
668691

669692
### START ###
@@ -932,44 +955,49 @@ def serialize_object(obj):
932955

933956
for index, node_json in enumerate(nodes_json["items"]):
934957
new_node = Node()
935-
node_has_cloud_provider = True
936-
CLUSTER.nodes.append(new_node)
937958

938-
try:
939-
node_name = node_json["metadata"]["name"]
940-
node_capacity_cpu = node_json["status"]["capacity"]["cpu"]
941-
node_capacity_memory = node_json["status"]["capacity"]["memory"]
942-
node_allocatable_cpu = node_json["status"]["allocatable"]["cpu"]
943-
node_allocatable_memory = node_json["status"]["allocatable"]["memory"]
944-
node_provider_id = node_json["spec"]["providerID"]
945-
node_labels = node_json["metadata"]["labels"].items()
946-
except Exception as e:
947-
log_info(
948-
f"WARNING: Skipping node number {index + 1} because it is missing expected keys in it's json output"
959+
new_node.name = get_nested_value(["metadata", "name"], node_json, "unknown")
960+
new_node.capacity_cpu = extract_value_from_unit(
961+
get_nested_value(["status", "capacity", "cpu"], node_json, 0.0)
962+
)
963+
new_node.capacity_memory = extract_value_from_unit(
964+
get_nested_value(["status", "capacity", "memory"], node_json, 0.0)
965+
)
966+
new_node.allocatable_cpu = (
967+
extract_value_from_unit(
968+
get_nested_value(["status", "allocatable", "cpu"], node_json, 0.0)
949969
)
950-
continue
951-
952-
new_node.name = node_name
953-
new_node.capacity_cpu = extract_value_from_unit(node_capacity_cpu)
954-
new_node.capacity_memory = extract_value_from_unit(node_capacity_memory)
955-
new_node.allocatable_cpu = extract_value_from_unit(node_allocatable_cpu) / 1000
970+
/ 1000
971+
)
956972
new_node.allocatable_memory = (
957-
extract_value_from_unit(node_allocatable_memory) / 1024
973+
extract_value_from_unit(
974+
get_nested_value(["status", "allocatable", "memory"], node_json, 0.0)
975+
)
976+
/ 1024
977+
)
978+
new_node.cloud_provider = get_nested_value(
979+
["spec", "providerID"], node_json, CLOUD_PROVIDER_UNKNOWN
958980
)
959981

960-
# Get node consumbed cpu and memory
982+
node_labels = get_nested_value(["metadata", "labels"], node_json, None)
983+
984+
# Assume the node has a cloud provider now, determine later
985+
node_has_cloud_provider = True
986+
cost_request_message = f"this node with the name `{node_name}`"
987+
988+
# Get node's consumbed cpu and memory
961989
if not has_prometheus:
962990
new_node.consumbed_cpu = top_nodes_dict[node_name].cpu
963991
new_node.consumbed_memory = top_nodes_dict[node_name].memory
964992
new_node.requests_cpu = top_nodes_dict[node_name].requests_cpu
965993
new_node.requests_memory = top_nodes_dict[node_name].requests_memory
966994

967-
# Get node cloud provider
968-
if node_provider_id.startswith("aws:"):
995+
# Determine cloud provider
996+
if new_node.cloud_provider.startswith("aws:"):
969997
new_node.cloud_provider = CLOUD_PROVIDER_AWS
970-
elif node_provider_id.startswith("gce:"):
998+
elif new_node.cloud_provider.startswith("gce:"):
971999
new_node.cloud_provider = CLOUD_PROVIDER_GCP
972-
elif node_provider_id.startswith("azure:"):
1000+
elif new_node.cloud_provider.startswith("azure:"):
9731001
new_node.cloud_provider = CLOUD_PROVIDER_AZURE
9741002
else:
9751003
node_has_cloud_provider = False
@@ -978,47 +1006,65 @@ def serialize_object(obj):
9781006
if node_has_cloud_provider:
9791007
new_node.purchase_option = "on_demand"
9801008

981-
# Get instance-type and region from node labels
982-
for key, value in node_labels:
983-
if "instance-type" in key:
984-
new_node.instance_type = value
985-
986-
if "region" in key:
987-
new_node.region = value
988-
989-
# Set node purchase option based on the cloud provider
990-
if (
991-
new_node.cloud_provider == "aws"
992-
and ("eks.amazonaws.com/capacityType" == key or "node-lifecycle" in key)
993-
and value.lower() == "spot"
994-
):
995-
new_node.purchase_option = "spot"
996-
997-
elif (
998-
new_node.cloud_provider == "gcp"
999-
and "cloud.google.com/gke-spot" == key
1000-
and value.lower() == "true"
1001-
):
1002-
new_node.purchase_option = "spot"
1003-
1004-
elif (
1005-
new_node.cloud_provider == "azure"
1006-
and "kubernetes.azure.com/scalesetpriority" == key
1007-
and value.lower() == "spot"
1008-
):
1009-
new_node.purchase_option = "spot"
1009+
if node_labels != None:
1010+
node_labels = node_labels.items()
1011+
1012+
# Get instance-type and region from node labels
1013+
for key, value in node_labels:
1014+
if "instance-type" in key:
1015+
new_node.instance_type = value
1016+
1017+
if "region" in key:
1018+
new_node.region = value
1019+
1020+
# Set node purchase option based on the cloud provider
1021+
if (
1022+
new_node.cloud_provider == "aws"
1023+
and ("eks.amazonaws.com/capacityType" == key or "node-lifecycle" in key)
1024+
and value.lower() == "spot"
1025+
):
1026+
new_node.purchase_option = "spot"
1027+
1028+
elif (
1029+
new_node.cloud_provider == "gcp"
1030+
and "cloud.google.com/gke-spot" == key
1031+
and value.lower() == "true"
1032+
):
1033+
new_node.purchase_option = "spot"
1034+
1035+
elif (
1036+
new_node.cloud_provider == "azure"
1037+
and "kubernetes.azure.com/scalesetpriority" == key
1038+
and value.lower() == "spot"
1039+
):
1040+
new_node.purchase_option = "spot"
1041+
1042+
if node_has_cloud_provider and (
1043+
not new_node.instance_type
1044+
or not new_node.region
1045+
or not new_node.purchase_option
1046+
):
1047+
log_info(
1048+
f"WARNING: Skipping node number {index + 1} because it is missing labels"
1049+
)
1050+
continue
1051+
1052+
# Add node after it has passed validation
1053+
CLUSTER.nodes.append(new_node)
10101054

1055+
# Set node's explicit price using another node that shares the same instance type
10111056
if new_node.instance_type in nodes_sharing_type:
10121057
new_node.explicit_price = nodes_sharing_type[
10131058
new_node.instance_type
10141059
].explicit_price
10151060
continue
10161061

1017-
cost_request_message = f"this node with the name `{node_name}`"
1062+
# If not continued, add the node to shared nodes if it has an instance
10181063
if new_node.instance_type:
10191064
cost_request_message = f"one node of type {new_node.instance_type}"
10201065
nodes_sharing_type[new_node.instance_type] = new_node
10211066

1067+
# If the cloud provider is unknown, let the user enter the explicit price of the node
10221068
if not node_has_cloud_provider:
10231069
new_node.explicit_price = float(
10241070
get_input(
@@ -1028,7 +1074,7 @@ def serialize_object(obj):
10281074
)
10291075

10301076

1031-
# STEP 6 - Prepare to submit to the data
1077+
# STEP 6 - Prepare to submit the data
10321078
CLUSTER.prepare()
10331079
submission = Submission(user_name, user_email, CLUSTER)
10341080
submission_json = json.dumps(submission, default=serialize_object).encode("utf-8")

0 commit comments

Comments
 (0)