@@ -111,10 +111,40 @@ def _from_db_object(cluster, db_cluster):
111111 cluster .obj_reset_changes ()
112112 return cluster
113113
114+ # Call-scoped nodegroups cache: None means inactive (no as_dict() in
115+ # progress). Set to a list by as_dict() before computing derived fields
116+ # and cleared afterwards. Outside of as_dict() _get_nodegroups() always
117+ # goes directly to the DB, so nodegroup mutations (create/delete) in
118+ # conductor code and tests are always visible.
119+ _nodegroups_cache = None
120+
121+ def _get_nodegroups (self ):
122+ """Fetch nodegroups, using a call-scoped cache when active.
123+
124+ as_dict() accesses four derived properties (node_count, master_count,
125+ node_addresses, master_addresses) that each call self.nodegroups.
126+ Without caching, a single as_dict() triggers four identical
127+ NodeGroup.list() RPC calls.
128+
129+ Rather than caching for the object lifetime (which would return stale
130+ data if nodegroups are created/deleted after first access, as happens
131+ in conductor and test code), the cache is scoped to a single
132+ as_dict() call: as_dict() populates _nodegroups_cache before
133+ computing derived fields and clears it on exit. At all other times
134+ _nodegroups_cache is None and this method goes directly to the DB.
135+ """
136+ if self ._nodegroups_cache is not None :
137+ return self ._nodegroups_cache
138+ return NodeGroup .list (self ._context , self .uuid )
139+
140+ def _invalidate_nodegroups_cache (self ):
141+ """Clear the call-scoped nodegroups cache."""
142+ self ._nodegroups_cache = None
143+
114144 @property
115145 def nodegroups (self ):
116146 # Returns all nodegroups that belong to the cluster.
117- return NodeGroup . list ( self ._context , self . uuid )
147+ return self ._get_nodegroups ( )
118148
119149 @property
120150 def default_ng_worker (self ):
@@ -342,12 +372,20 @@ def obj_load_attr(self, attrname):
342372
343373 def as_dict (self ):
344374 dict_ = super (Cluster , self ).as_dict ()
345- # Update the dict with the attributes coming form
346- # the cluster's nodegroups.
347- dict_ .update ({
348- 'node_count' : self .node_count ,
349- 'master_count' : self .master_count ,
350- 'node_addresses' : self .node_addresses ,
351- 'master_addresses' : self .master_addresses
352- })
375+ # Populate the call-scoped nodegroups cache so that the four derived
376+ # properties below (node_count, master_count, node_addresses,
377+ # master_addresses) all share a single NodeGroup.list() fetch instead
378+ # of each issuing their own RPC call. The cache is cleared on exit
379+ # so that any subsequent access (e.g. from conductor code that has
380+ # mutated nodegroups) sees a fresh DB result.
381+ self ._nodegroups_cache = NodeGroup .list (self ._context , self .uuid )
382+ try :
383+ dict_ .update ({
384+ 'node_count' : self .node_count ,
385+ 'master_count' : self .master_count ,
386+ 'node_addresses' : self .node_addresses ,
387+ 'master_addresses' : self .master_addresses
388+ })
389+ finally :
390+ self ._nodegroups_cache = None
353391 return dict_
0 commit comments