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

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. 

17 

18""" 

19A :ref:`Goal <goal_definition>` is a human readable, observable and measurable 

20end result having one objective to be achieved. 

21 

22Here are some examples of :ref:`Goals <goal_definition>`: 

23 

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""" 

34 

35import pecan 

36from pecan import rest 

37from wsme import types as wtypes 

38import wsmeext.pecan as wsme_pecan 

39 

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 

48 

49 

50def hide_fields_in_newer_versions(obj): 

51 """This method hides fields that were added in newer API versions. 

52 

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 

58 

59 

60class Goal(base.APIBase): 

61 """API representation of a goal. 

62 

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 """ 

66 

67 uuid = types.uuid 

68 """Unique UUID for this goal""" 

69 

70 name = wtypes.text 

71 """Name of the goal""" 

72 

73 display_name = wtypes.text 

74 """Localized name of the goal""" 

75 

76 efficacy_specification = wtypes.wsattr(types.jsontype, readonly=True) 

77 """Efficacy specification for this goal""" 

78 

79 links = wtypes.wsattr([link.Link], readonly=True) 

80 """A list containing a self link and associated audit template links""" 

81 

82 def __init__(self, **kwargs): 

83 self.fields = [] 

84 fields = list(objects.Goal.fields) 

85 

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)) 

92 

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']) 

98 

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 

105 

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) 

111 

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) 

125 

126 

127class GoalCollection(collection.Collection): 

128 """API representation of a collection of goals.""" 

129 

130 goals = [Goal] 

131 """A list containing goals objects""" 

132 

133 def __init__(self, **kwargs): 

134 super(GoalCollection, self).__init__() 

135 self._type = 'goals' 

136 

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 

146 

147 @classmethod 

148 def sample(cls): 

149 sample = cls() 

150 sample.goals = [Goal.sample(expand=False)] 

151 return sample 

152 

153 

154class GoalsController(rest.RestController): 

155 """REST controller for Goals.""" 

156 

157 def __init__(self): 

158 super(GoalsController, self).__init__() 

159 

160 from_goals = False 

161 """A flag to indicate if the requests to this controller are coming 

162 from the top-level resource Goals.""" 

163 

164 _custom_actions = { 

165 'detail': ['GET'], 

166 } 

167 

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) 

174 

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) 

179 

180 sort_db_key = (sort_key if sort_key in objects.Goal.fields 

181 else None) 

182 

183 goals = objects.Goal.list(pecan.request.context, limit, marker_obj, 

184 sort_key=sort_db_key, sort_dir=sort_dir) 

185 

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) 

191 

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. 

196 

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) 

206 

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. 

211 

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) 

228 

229 @wsme_pecan.wsexpose(Goal, wtypes.text) 

230 def get_one(self, goal): 

231 """Retrieve information about the given goal. 

232 

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 

237 

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') 

241 

242 return Goal.convert_with_links(rpc_goal)