Coverage for manila/scheduler/filters/json.py: 98%
72 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright (c) 2011 OpenStack Foundation.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16import operator
18from oslo_serialization import jsonutils
20from manila.scheduler.filters import base_host
23class JsonFilter(base_host.BaseHostFilter):
24 """Host Filter to allow simple JSON-based grammar for selecting hosts."""
26 def _op_compare(self, args, op):
27 """Check if operator can compare the first arg with the others.
29 Returns True if the specified operator can successfully
30 compare the first item in the args with all the rest. Will
31 return False if only one item is in the list.
32 """
33 if len(args) < 2:
34 return False
35 if op is operator.contains:
36 bad = args[0] not in args[1:]
37 else:
38 bad = [arg for arg in args[1:]
39 if not op(args[0], arg)]
40 return not bool(bad)
42 def _equals(self, args):
43 """First term is == all the other terms."""
44 return self._op_compare(args, operator.eq)
46 def _less_than(self, args):
47 """First term is < all the other terms."""
48 return self._op_compare(args, operator.lt)
50 def _greater_than(self, args):
51 """First term is > all the other terms."""
52 return self._op_compare(args, operator.gt)
54 def _in(self, args):
55 """First term is in set of remaining terms."""
56 return self._op_compare(args, operator.contains)
58 def _less_than_equal(self, args):
59 """First term is <= all the other terms."""
60 return self._op_compare(args, operator.le)
62 def _greater_than_equal(self, args):
63 """First term is >= all the other terms."""
64 return self._op_compare(args, operator.ge)
66 def _not(self, args):
67 """Flip each of the arguments."""
68 return [not arg for arg in args]
70 def _or(self, args):
71 """True if any arg is True."""
72 return any(args)
74 def _and(self, args):
75 """True if all args are True."""
76 return all(args)
78 commands = {
79 '=': _equals,
80 '<': _less_than,
81 '>': _greater_than,
82 'in': _in,
83 '<=': _less_than_equal,
84 '>=': _greater_than_equal,
85 'not': _not,
86 'or': _or,
87 'and': _and,
88 }
90 def _parse_string(self, string, host_state):
91 """Parse string.
93 Strings prefixed with $ are capability lookups in the
94 form '$variable' where 'variable' is an attribute in the
95 HostState class. If $variable is a dictionary, you may
96 use: $variable.dictkey
97 """
98 if not string: 98 ↛ 99line 98 didn't jump to line 99 because the condition on line 98 was never true
99 return None
100 if not string.startswith("$"):
101 return string
103 path = string[1:].split(".")
104 obj = getattr(host_state, path[0], None)
105 if obj is None:
106 return None
107 for item in path[1:]:
108 obj = obj.get(item)
109 if obj is None:
110 return None
111 return obj
113 def _process_filter(self, query, host_state):
114 """Recursively parse the query structure."""
115 if not query:
116 return True
117 cmd = query[0]
118 method = self.commands[cmd]
119 cooked_args = []
120 for arg in query[1:]:
121 if isinstance(arg, list):
122 arg = self._process_filter(arg, host_state)
123 elif isinstance(arg, str):
124 arg = self._parse_string(arg, host_state)
125 if arg is not None:
126 cooked_args.append(arg)
127 result = method(self, cooked_args)
128 return result
130 def host_passes(self, host_state, filter_properties):
131 """Filters hosts.
133 Return a list of hosts that can fulfill the requirements
134 specified in the query.
135 """
136 # TODO(zhiteng) Add description for filter_properties structure
137 # and scheduler_hints.
138 try:
139 query = filter_properties['scheduler_hints']['query']
140 # If filter_properties['scheduler_hints'] is None, and TypeError
141 # will occur, add TypeError exception here.
142 except (KeyError, TypeError):
143 query = None
144 if not query:
145 return True
147 # NOTE(comstud): Not checking capabilities or service for
148 # enabled/disabled so that a provided json filter can decide
150 result = self._process_filter(jsonutils.loads(query), host_state)
151 if isinstance(result, list):
152 # If any succeeded, include the host
153 result = any(result)
154 if result:
155 # Filter it out.
156 return True
157 return False