1919from oslo_log import log as logging
2020
2121import nova .conf
22+ from nova import context as nova_context
23+ from nova .db .main import api as main_db_api
24+ from nova .objects .aggregate import AggregateList
25+ from nova .objects .build_request import BuildRequest
26+ from nova .objects .instance import Instance
2227from nova .scheduler import filters
2328from nova .scheduler import utils
2429from nova import utils as nova_utils
2833CONF = nova .conf .CONF
2934
3035_SERVICE_AUTH = None
36+ GARDENER_PREFIX = "kubernetes.io-cluster-"
37+ KKS_PREFIX = "kubernikus:kluster"
38+ HANA_PREFIX = "hana_"
39+ VMWARE_HV_TYPE = 'VMware vCenter Server'
3140
3241
3342class ShardFilter (filters .BaseHostFilter ):
@@ -37,6 +46,8 @@ class ShardFilter(filters.BaseHostFilter):
3746
3847 Alternatively the project may have the "sharding_enabled" tag set, which
3948 enables the project for hosts in all shards.
49+
50+ Implements `filter_all` directly instead of `host_passes`
4051 """
4152
4253 _PROJECT_SHARD_CACHE = {}
@@ -114,11 +125,88 @@ def _get_shards(self, project_id):
114125
115126 return self ._PROJECT_SHARD_CACHE .get (project_id )
116127
117- def host_passes (self , host_state , spec_obj ):
128+ def _get_k8s_shard (self , spec_obj ):
129+ """Returns the dominant shard of a K8S cluster.
130+
131+ Returns None if the request is not for an instance that's part of
132+ a K8S cluster, or if this is the first instance of a new cluster.
133+ """
134+ if (spec_obj .flavor .name .startswith (HANA_PREFIX ) or
135+ utils .request_is_resize (spec_obj )):
136+ return None
137+ elevated = nova_context .get_admin_context ()
138+ build_request = None
139+ instance = None
140+
141+ def _get_tags ():
142+ return build_request .tags if build_request \
143+ else instance .tags
144+
145+ def _get_metadata ():
146+ return build_request .instance .metadata if build_request \
147+ else instance .metadata
148+
149+ check_type = spec_obj .get_scheduler_hint ('_nova_check_type' )
150+ if not check_type :
151+ build_request = BuildRequest .get_by_instance_uuid (
152+ elevated , spec_obj .instance_uuid )
153+ else :
154+ instance = Instance .get_by_uuid (
155+ elevated , spec_obj .instance_uuid ,
156+ expected_attrs = ['tags' , 'metadata' ])
157+
158+ kks_tag = next ((t .tag for t in _get_tags ()
159+ if t .tag .startswith (KKS_PREFIX )), None )
160+ gardener_meta = None
161+ if not kks_tag :
162+ gardener_meta = \
163+ {k : v for k , v in _get_metadata ().items ()
164+ if k .startswith (GARDENER_PREFIX )}
165+
166+ if not kks_tag and not gardener_meta :
167+ return None
168+
169+ q_filters = {'hv_type' : VMWARE_HV_TYPE }
170+ if spec_obj .availability_zone :
171+ q_filters ['availability_zone' ] = spec_obj .availability_zone
172+
173+ k8s_hosts = None
174+ if kks_tag :
175+ k8s_hosts = nova_context .scatter_gather_skip_cell0 (
176+ elevated , main_db_api .get_k8s_hosts_by_instances_tag ,
177+ kks_tag , filters = q_filters )
178+ elif gardener_meta :
179+ (meta_key , meta_value ) = next (iter (gardener_meta .items ()))
180+ k8s_hosts = nova_context .scatter_gather_skip_cell0 (
181+ elevated , main_db_api .get_k8s_hosts_by_instances_metadata ,
182+ meta_key , meta_value , filters = q_filters )
183+
184+ if not k8s_hosts :
185+ return None
186+
187+ all_shard_aggrs = [agg for agg in AggregateList .get_all (elevated )
188+ if agg .name .startswith (self ._SHARD_PREFIX )]
189+ if not all_shard_aggrs :
190+ return None
191+ k8s_hosts = set (k8s_hosts )
192+
193+ shard_aggr = sorted (all_shard_aggrs ,
194+ key = lambda i : len (set (i .hosts ) & k8s_hosts ))[0 ]
195+ return shard_aggr .name
196+
197+ def filter_all (self , filter_obj_list , spec_obj ):
118198 # Only VMware
119199 if utils .is_non_vmware_spec (spec_obj ):
120- return True
200+ LOG .debug ("ShardFilter is not applicable in this non-vmware "
201+ "request" )
202+ return filter_obj_list
203+
204+ k8s_shard = self ._get_k8s_shard (spec_obj )
121205
206+ return [host_state for host_state in filter_obj_list
207+ if self ._host_passes (host_state , spec_obj , k8s_shard )]
208+
209+ def _host_passes (self , host_state , spec_obj , k8s_shard ):
122210 host_shard_aggrs = [aggr for aggr in host_state .aggregates
123211 if aggr .name .startswith (self ._SHARD_PREFIX )]
124212
@@ -148,18 +236,34 @@ def host_passes(self, host_state, spec_obj):
148236 if self ._ALL_SHARDS in shards :
149237 LOG .debug ('project enabled for all shards %(project_shards)s.' ,
150238 {'project_shards' : shards })
151- return True
152239 elif host_shard_names & set (shards ):
153240 LOG .debug ('%(host_state)s shard %(host_shard)s found in project '
154241 'shards %(project_shards)s.' ,
155242 {'host_state' : host_state ,
156243 'host_shard' : host_shard_names ,
157244 'project_shards' : shards })
158- return True
159245 else :
160246 LOG .debug ('%(host_state)s shard %(host_shard)s not found in '
161247 'project shards %(project_shards)s.' ,
162248 {'host_state' : host_state ,
163249 'host_shard' : host_shard_names ,
164250 'project_shards' : shards })
165251 return False
252+
253+ if k8s_shard :
254+ matches = any (host_shard == k8s_shard
255+ for host_shard in host_shard_names )
256+ if not matches :
257+ LOG .debug ("%(host_state)s is not part of the requested "
258+ "K8S cluster." )
259+ return matches
260+
261+ return True
262+
263+ def _host_passes_k8s (self , host_shard_aggrs , k8s_hosts ):
264+ """Instances of a K8S cluster must end up on the same shard.
265+ The K8S cluster is identified by the metadata or tags set
266+ by the orchestrator (Gardener or Kubernikus).
267+ """
268+ return any (set (aggr .hosts ) & k8s_hosts
269+ for aggr in host_shard_aggrs )
0 commit comments