Coverage for watcher/api/controllers/v1/goal.py: 92%
104 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 2013 Red Hat, Inc.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
18"""
19A :ref:`Goal <goal_definition>` is a human readable, observable and measurable
20end result having one objective to be achieved.
22Here are some examples of :ref:`Goals <goal_definition>`:
24- minimize the energy consumption
25- minimize the number of compute nodes (consolidation)
26- balance the workload among compute nodes
27- minimize the license cost (some software have a licensing model which is
28 based on the number of sockets or cores where the software is deployed)
29- find the most appropriate moment for a planned maintenance on a
30 given group of host (which may be an entire availability zone):
31 power supply replacement, cooling system replacement, hardware
32 modification, ...
33"""
35import pecan
36from pecan import rest
37from wsme import types as wtypes
38import wsmeext.pecan as wsme_pecan
40from watcher.api.controllers import base
41from watcher.api.controllers import link
42from watcher.api.controllers.v1 import collection
43from watcher.api.controllers.v1 import types
44from watcher.api.controllers.v1 import utils as api_utils
45from watcher.common import exception
46from watcher.common import policy
47from watcher import objects
50def hide_fields_in_newer_versions(obj):
51 """This method hides fields that were added in newer API versions.
53 Certain node fields were introduced at certain API versions.
54 These fields are only made available when the request's API version
55 matches or exceeds the versions when these fields were introduced.
56 """
57 pass
60class Goal(base.APIBase):
61 """API representation of a goal.
63 This class enforces type checking and value constraints, and converts
64 between the internal object model and the API representation of a goal.
65 """
67 uuid = types.uuid
68 """Unique UUID for this goal"""
70 name = wtypes.text
71 """Name of the goal"""
73 display_name = wtypes.text
74 """Localized name of the goal"""
76 efficacy_specification = wtypes.wsattr(types.jsontype, readonly=True)
77 """Efficacy specification for this goal"""
79 links = wtypes.wsattr([link.Link], readonly=True)
80 """A list containing a self link and associated audit template links"""
82 def __init__(self, **kwargs):
83 self.fields = []
84 fields = list(objects.Goal.fields)
86 for k in fields:
87 # Skip fields we do not expose.
88 if not hasattr(self, k):
89 continue
90 self.fields.append(k)
91 setattr(self, k, kwargs.get(k, wtypes.Unset))
93 @staticmethod
94 def _convert_with_links(goal, url, expand=True):
95 if not expand:
96 goal.unset_fields_except(['uuid', 'name', 'display_name',
97 'efficacy_specification'])
99 goal.links = [link.Link.make_link('self', url,
100 'goals', goal.uuid),
101 link.Link.make_link('bookmark', url,
102 'goals', goal.uuid,
103 bookmark=True)]
104 return goal
106 @classmethod
107 def convert_with_links(cls, goal, expand=True):
108 goal = Goal(**goal.as_dict())
109 hide_fields_in_newer_versions(goal)
110 return cls._convert_with_links(goal, pecan.request.host_url, expand)
112 @classmethod
113 def sample(cls, expand=True):
114 sample = cls(
115 uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
116 name='DUMMY',
117 display_name='Dummy strategy',
118 efficacy_specification=[
119 {'description': 'Dummy indicator', 'name': 'dummy',
120 'schema': 'Range(min=0, max=100, min_included=True, '
121 'max_included=True, msg=None)',
122 'unit': '%'}
123 ])
124 return cls._convert_with_links(sample, 'http://localhost:9322', expand)
127class GoalCollection(collection.Collection):
128 """API representation of a collection of goals."""
130 goals = [Goal]
131 """A list containing goals objects"""
133 def __init__(self, **kwargs):
134 super(GoalCollection, self).__init__()
135 self._type = 'goals'
137 @staticmethod
138 def convert_with_links(goals, limit, url=None, expand=False,
139 **kwargs):
140 goal_collection = GoalCollection()
141 goal_collection.goals = [
142 Goal.convert_with_links(g, expand) for g in goals]
143 goal_collection.next = goal_collection.get_next(
144 limit, url=url, **kwargs)
145 return goal_collection
147 @classmethod
148 def sample(cls):
149 sample = cls()
150 sample.goals = [Goal.sample(expand=False)]
151 return sample
154class GoalsController(rest.RestController):
155 """REST controller for Goals."""
157 def __init__(self):
158 super(GoalsController, self).__init__()
160 from_goals = False
161 """A flag to indicate if the requests to this controller are coming
162 from the top-level resource Goals."""
164 _custom_actions = {
165 'detail': ['GET'],
166 }
168 def _get_goals_collection(self, marker, limit, sort_key, sort_dir,
169 expand=False, resource_url=None):
170 api_utils.validate_sort_key(
171 sort_key, list(objects.Goal.fields))
172 limit = api_utils.validate_limit(limit)
173 api_utils.validate_sort_dir(sort_dir)
175 marker_obj = None
176 if marker: 176 ↛ 177line 176 didn't jump to line 177 because the condition on line 176 was never true
177 marker_obj = objects.Goal.get_by_uuid(
178 pecan.request.context, marker)
180 sort_db_key = (sort_key if sort_key in objects.Goal.fields
181 else None)
183 goals = objects.Goal.list(pecan.request.context, limit, marker_obj,
184 sort_key=sort_db_key, sort_dir=sort_dir)
186 return GoalCollection.convert_with_links(goals, limit,
187 url=resource_url,
188 expand=expand,
189 sort_key=sort_key,
190 sort_dir=sort_dir)
192 @wsme_pecan.wsexpose(GoalCollection, wtypes.text,
193 int, wtypes.text, wtypes.text)
194 def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
195 """Retrieve a list of goals.
197 :param marker: pagination marker for large data sets.
198 :param limit: maximum number of resources to return in a single result.
199 :param sort_key: column to sort results by. Default: id.
200 :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
201 """
202 context = pecan.request.context
203 policy.enforce(context, 'goal:get_all',
204 action='goal:get_all')
205 return self._get_goals_collection(marker, limit, sort_key, sort_dir)
207 @wsme_pecan.wsexpose(GoalCollection, wtypes.text, int,
208 wtypes.text, wtypes.text)
209 def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
210 """Retrieve a list of goals with detail.
212 :param marker: pagination marker for large data sets.
213 :param limit: maximum number of resources to return in a single result.
214 :param sort_key: column to sort results by. Default: id.
215 :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
216 """
217 context = pecan.request.context
218 policy.enforce(context, 'goal:detail',
219 action='goal:detail')
220 # NOTE(lucasagomes): /detail should only work against collections
221 parent = pecan.request.path.split('/')[:-1][-1]
222 if parent != "goals":
223 raise exception.HTTPNotFound
224 expand = True
225 resource_url = '/'.join(['goals', 'detail'])
226 return self._get_goals_collection(marker, limit, sort_key, sort_dir,
227 expand, resource_url)
229 @wsme_pecan.wsexpose(Goal, wtypes.text)
230 def get_one(self, goal):
231 """Retrieve information about the given goal.
233 :param goal: UUID or name of the goal.
234 """
235 if self.from_goals: 235 ↛ 236line 235 didn't jump to line 236 because the condition on line 235 was never true
236 raise exception.OperationNotPermitted
238 context = pecan.request.context
239 rpc_goal = api_utils.get_resource('Goal', goal)
240 policy.enforce(context, 'goal:get', rpc_goal, action='goal:get')
242 return Goal.convert_with_links(rpc_goal)