@@ -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 ###
665688if 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
933956for 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
10321078CLUSTER .prepare ()
10331079submission = Submission (user_name , user_email , CLUSTER )
10341080submission_json = json .dumps (submission , default = serialize_object ).encode ("utf-8" )
0 commit comments