Coverage for watcher/api/controllers/v1/strategy.py: 87%
176 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-17 12:22 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-17 12:22 +0000
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 b<>com
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain 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,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
17"""
18A :ref:`Strategy <strategy_definition>` is an algorithm implementation which is
19able to find a :ref:`Solution <solution_definition>` for a given
20:ref:`Goal <goal_definition>`.
22There may be several potential strategies which are able to achieve the same
23:ref:`Goal <goal_definition>`. This is why it is possible to configure which
24specific :ref:`Strategy <strategy_definition>` should be used for each goal.
26Some strategies may provide better optimization results but may take more time
27to find an optimal :ref:`Solution <solution_definition>`.
28"""
30import pecan
31from pecan import rest
32from wsme import types as wtypes
33import wsmeext.pecan as wsme_pecan
35from watcher.api.controllers import base
36from watcher.api.controllers import link
37from watcher.api.controllers.v1 import collection
38from watcher.api.controllers.v1 import types
39from watcher.api.controllers.v1 import utils as api_utils
40from watcher.common import exception
41from watcher.common import policy
42from watcher.common import utils as common_utils
43from watcher.decision_engine import rpcapi
44from watcher import objects
47def hide_fields_in_newer_versions(obj):
48 """This method hides fields that were added in newer API versions.
50 Certain node fields were introduced at certain API versions.
51 These fields are only made available when the request's API version
52 matches or exceeds the versions when these fields were introduced.
53 """
54 pass
57class Strategy(base.APIBase):
58 """API representation of a strategy.
60 This class enforces type checking and value constraints, and converts
61 between the internal object model and the API representation of a strategy.
62 """
63 _goal_uuid = None
64 _goal_name = None
66 def _get_goal(self, value):
67 if value == wtypes.Unset: 67 ↛ 68line 67 didn't jump to line 68 because the condition on line 67 was never true
68 return None
69 goal = None
70 try:
71 if (common_utils.is_uuid_like(value) or 71 ↛ 75line 71 didn't jump to line 75 because the condition on line 71 was always true
72 common_utils.is_int_like(value)):
73 goal = objects.Goal.get(pecan.request.context, value)
74 else:
75 goal = objects.Goal.get_by_name(pecan.request.context, value)
76 except exception.GoalNotFound:
77 pass
78 if goal: 78 ↛ 80line 78 didn't jump to line 80 because the condition on line 78 was always true
79 self.goal_id = goal.id
80 return goal
82 def _get_goal_uuid(self):
83 return self._goal_uuid
85 def _set_goal_uuid(self, value):
86 if value and self._goal_uuid != value: 86 ↛ exitline 86 didn't return from function '_set_goal_uuid' because the condition on line 86 was always true
87 self._goal_uuid = None
88 goal = self._get_goal(value)
89 if goal: 89 ↛ exitline 89 didn't return from function '_set_goal_uuid' because the condition on line 89 was always true
90 self._goal_uuid = goal.uuid
92 def _get_goal_name(self):
93 return self._goal_name
95 def _set_goal_name(self, value):
96 if value and self._goal_name != value: 96 ↛ exitline 96 didn't return from function '_set_goal_name' because the condition on line 96 was always true
97 self._goal_name = None
98 goal = self._get_goal(value)
99 if goal: 99 ↛ exitline 99 didn't return from function '_set_goal_name' because the condition on line 99 was always true
100 self._goal_name = goal.name
102 uuid = types.uuid
103 """Unique UUID for this strategy"""
105 name = wtypes.text
106 """Name of the strategy"""
108 display_name = wtypes.text
109 """Localized name of the strategy"""
111 links = wtypes.wsattr([link.Link], readonly=True)
112 """A list containing a self link and associated goal links"""
114 goal_uuid = wtypes.wsproperty(wtypes.text, _get_goal_uuid, _set_goal_uuid,
115 mandatory=True)
116 """The UUID of the goal this audit refers to"""
118 goal_name = wtypes.wsproperty(wtypes.text, _get_goal_name, _set_goal_name,
119 mandatory=False)
120 """The name of the goal this audit refers to"""
122 parameters_spec = {wtypes.text: types.jsontype}
123 """Parameters spec dict"""
125 def __init__(self, **kwargs):
126 super(Strategy, self).__init__()
128 self.fields = []
129 self.fields.append('uuid')
130 self.fields.append('name')
131 self.fields.append('display_name')
132 self.fields.append('goal_uuid')
133 self.fields.append('goal_name')
134 self.fields.append('parameters_spec')
135 setattr(self, 'uuid', kwargs.get('uuid', wtypes.Unset))
136 setattr(self, 'name', kwargs.get('name', wtypes.Unset))
137 setattr(self, 'display_name', kwargs.get('display_name', wtypes.Unset))
138 setattr(self, 'goal_uuid', kwargs.get('goal_id', wtypes.Unset))
139 setattr(self, 'goal_name', kwargs.get('goal_id', wtypes.Unset))
140 setattr(self, 'parameters_spec', kwargs.get('parameters_spec',
141 wtypes.Unset))
143 @staticmethod
144 def _convert_with_links(strategy, url, expand=True):
145 if not expand:
146 strategy.unset_fields_except(
147 ['uuid', 'name', 'display_name', 'goal_uuid', 'goal_name'])
149 strategy.links = [
150 link.Link.make_link('self', url, 'strategies', strategy.uuid),
151 link.Link.make_link('bookmark', url, 'strategies', strategy.uuid,
152 bookmark=True)]
153 return strategy
155 @classmethod
156 def convert_with_links(cls, strategy, expand=True):
157 strategy = Strategy(**strategy.as_dict())
158 hide_fields_in_newer_versions(strategy)
159 return cls._convert_with_links(
160 strategy, pecan.request.host_url, expand)
162 @classmethod
163 def sample(cls, expand=True):
164 sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
165 name='DUMMY',
166 display_name='Dummy strategy')
167 return cls._convert_with_links(sample, 'http://localhost:9322', expand)
170class StrategyCollection(collection.Collection):
171 """API representation of a collection of strategies."""
173 strategies = [Strategy]
174 """A list containing strategies objects"""
176 def __init__(self, **kwargs):
177 super(StrategyCollection, self).__init__()
178 self._type = 'strategies'
180 @staticmethod
181 def convert_with_links(strategies, limit, url=None, expand=False,
182 **kwargs):
183 strategy_collection = StrategyCollection()
184 strategy_collection.strategies = [
185 Strategy.convert_with_links(g, expand) for g in strategies]
186 strategy_collection.next = strategy_collection.get_next(
187 limit, url=url, **kwargs)
188 return strategy_collection
190 @classmethod
191 def sample(cls):
192 sample = cls()
193 sample.strategies = [Strategy.sample(expand=False)]
194 return sample
197class StrategiesController(rest.RestController):
198 """REST controller for Strategies."""
200 def __init__(self):
201 super(StrategiesController, self).__init__()
203 from_strategies = False
204 """A flag to indicate if the requests to this controller are coming
205 from the top-level resource Strategies."""
207 _custom_actions = {
208 'detail': ['GET'],
209 'state': ['GET'],
210 }
212 def _get_strategies_collection(self, filters, marker, limit, sort_key,
213 sort_dir, expand=False, resource_url=None):
214 additional_fields = ["goal_uuid", "goal_name"]
216 api_utils.validate_sort_key(
217 sort_key, list(objects.Strategy.fields) + additional_fields)
218 api_utils.validate_search_filters(
219 filters, list(objects.Strategy.fields) + additional_fields)
220 limit = api_utils.validate_limit(limit)
221 api_utils.validate_sort_dir(sort_dir)
223 marker_obj = None
224 if marker: 224 ↛ 225line 224 didn't jump to line 225 because the condition on line 224 was never true
225 marker_obj = objects.Strategy.get_by_uuid(
226 pecan.request.context, marker)
228 need_api_sort = api_utils.check_need_api_sort(sort_key,
229 additional_fields)
230 sort_db_key = (sort_key if not need_api_sort
231 else None)
233 strategies = objects.Strategy.list(
234 pecan.request.context, limit, marker_obj, filters=filters,
235 sort_key=sort_db_key, sort_dir=sort_dir)
237 strategies_collection = StrategyCollection.convert_with_links(
238 strategies, limit, url=resource_url, expand=expand,
239 sort_key=sort_key, sort_dir=sort_dir)
241 if need_api_sort:
242 api_utils.make_api_sort(strategies_collection.strategies,
243 sort_key, sort_dir)
245 return strategies_collection
247 @wsme_pecan.wsexpose(StrategyCollection, wtypes.text, wtypes.text,
248 int, wtypes.text, wtypes.text)
249 def get_all(self, goal=None, marker=None, limit=None,
250 sort_key='id', sort_dir='asc'):
251 """Retrieve a list of strategies.
253 :param goal: goal UUID or name to filter by.
254 :param marker: pagination marker for large data sets.
255 :param limit: maximum number of resources to return in a single result.
256 :param sort_key: column to sort results by. Default: id.
257 :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
258 """
259 context = pecan.request.context
260 policy.enforce(context, 'strategy:get_all',
261 action='strategy:get_all')
262 filters = {}
263 if goal:
264 if common_utils.is_uuid_like(goal):
265 filters['goal_uuid'] = goal
266 else:
267 filters['goal_name'] = goal
269 return self._get_strategies_collection(
270 filters, marker, limit, sort_key, sort_dir)
272 @wsme_pecan.wsexpose(StrategyCollection, wtypes.text, wtypes.text, int,
273 wtypes.text, wtypes.text)
274 def detail(self, goal=None, marker=None, limit=None,
275 sort_key='id', sort_dir='asc'):
276 """Retrieve a list of strategies with detail.
278 :param goal: goal UUID or name to filter by.
279 :param marker: pagination marker for large data sets.
280 :param limit: maximum number of resources to return in a single result.
281 :param sort_key: column to sort results by. Default: id.
282 :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
283 """
284 context = pecan.request.context
285 policy.enforce(context, 'strategy:detail',
286 action='strategy:detail')
287 # NOTE(lucasagomes): /detail should only work against collections
288 parent = pecan.request.path.split('/')[:-1][-1]
289 if parent != "strategies":
290 raise exception.HTTPNotFound
291 expand = True
292 resource_url = '/'.join(['strategies', 'detail'])
294 filters = {}
295 if goal: 295 ↛ 296line 295 didn't jump to line 296 because the condition on line 295 was never true
296 if common_utils.is_uuid_like(goal):
297 filters['goal_uuid'] = goal
298 else:
299 filters['goal_name'] = goal
301 return self._get_strategies_collection(
302 filters, marker, limit, sort_key, sort_dir, expand, resource_url)
304 @wsme_pecan.wsexpose(wtypes.text, wtypes.text)
305 def state(self, strategy):
306 """Retrieve an information about strategy requirements.
308 :param strategy: name of the strategy.
309 """
310 context = pecan.request.context
311 policy.enforce(context, 'strategy:state', action='strategy:state')
312 parents = pecan.request.path.split('/')[:-1]
313 if parents[-2] != "strategies": 313 ↛ 314line 313 didn't jump to line 314 because the condition on line 313 was never true
314 raise exception.HTTPNotFound
315 rpc_strategy = api_utils.get_resource('Strategy', strategy)
316 de_client = rpcapi.DecisionEngineAPI()
317 strategy_state = de_client.get_strategy_info(context,
318 rpc_strategy.name)
319 strategy_state.extend([{
320 'type': 'Name', 'state': rpc_strategy.name,
321 'mandatory': '', 'comment': ''}])
322 return strategy_state
324 @wsme_pecan.wsexpose(Strategy, wtypes.text)
325 def get_one(self, strategy):
326 """Retrieve information about the given strategy.
328 :param strategy: UUID or name of the strategy.
329 """
330 if self.from_strategies: 330 ↛ 331line 330 didn't jump to line 331 because the condition on line 330 was never true
331 raise exception.OperationNotPermitted
333 context = pecan.request.context
334 rpc_strategy = api_utils.get_resource('Strategy', strategy)
335 policy.enforce(context, 'strategy:get', rpc_strategy,
336 action='strategy:get')
338 return Strategy.convert_with_links(rpc_strategy)