|
12 | 12 | # under the License.
|
13 | 13 |
|
14 | 14 | import abc
|
| 15 | +import collections |
15 | 16 | import copy
|
| 17 | +import functools |
| 18 | +import itertools |
16 | 19 |
|
17 | 20 | from neutron_lib.api.definitions import port
|
18 | 21 | from neutron_lib.api import extensions as api_extensions
|
|
28 | 31 | from neutron._i18n import _
|
29 | 32 | from neutron.api import extensions
|
30 | 33 | from neutron.api.v2 import resource as api_resource
|
| 34 | +from neutron.objects import network as network_obj |
| 35 | +from neutron.objects import network_segment_range as network_segment_range_obj |
| 36 | +from neutron.objects import ports as ports_obj |
| 37 | +from neutron.objects.qos import policy as policy_obj |
| 38 | +from neutron.objects import router as router_obj |
| 39 | +from neutron.objects import securitygroup as securitygroup_obj |
| 40 | +from neutron.objects import subnet as subnet_obj |
| 41 | +from neutron.objects import subnetpool as subnetpool_obj |
| 42 | +from neutron.objects import trunk as trunk_obj |
| 43 | +from neutron import policy |
31 | 44 |
|
32 | 45 |
|
33 | 46 | TAG = 'tag'
|
|
55 | 68 | 'validate': {'type:list_of_unique_strings': MAX_TAG_LEN},
|
56 | 69 | 'default': [], 'is_visible': True, 'is_filter': True
|
57 | 70 | }
|
| 71 | +PARENTS = { |
| 72 | + 'floatingips': router_obj.FloatingIP, |
| 73 | + 'network_segment_ranges': network_segment_range_obj.NetworkSegmentRange, |
| 74 | + 'networks': network_obj.Network, |
| 75 | + 'policies': policy_obj.QosPolicy, |
| 76 | + 'ports': ports_obj.Port, |
| 77 | + 'routers': router_obj.Router, |
| 78 | + 'security_groups': securitygroup_obj.SecurityGroup, |
| 79 | + 'subnets': ('networks', subnet_obj.Subnet), |
| 80 | + 'subnetpools': subnetpool_obj.SubnetPool, |
| 81 | + 'trunks': trunk_obj.Trunk, |
| 82 | +} |
| 83 | +ResourceInfo = collections.namedtuple( |
| 84 | + 'ResourceInfo', ['project_id', |
| 85 | + 'parent_type', |
| 86 | + 'parent_id', |
| 87 | + 'upper_parent_type', |
| 88 | + 'upper_parent_id', |
| 89 | + ]) |
| 90 | +EMPTY_RESOURCE_INFO = ResourceInfo(None, None, None, None, None) |
| 91 | + |
| 92 | + |
| 93 | +def _policy_init(f): |
| 94 | + @functools.wraps(f) |
| 95 | + def func(self, *args, **kwargs): |
| 96 | + policy.init() |
| 97 | + return f(self, *args, **kwargs) |
| 98 | + return func |
58 | 99 |
|
59 | 100 |
|
60 | 101 | class TagResourceNotFound(exceptions.NotFound):
|
@@ -95,75 +136,161 @@ def __init__(self):
|
95 | 136 | self.plugin = directory.get_plugin(TAG_PLUGIN_TYPE)
|
96 | 137 | self.supported_resources = TAG_SUPPORTED_RESOURCES
|
97 | 138 |
|
98 |
| - def _get_parent_resource_and_id(self, kwargs): |
99 |
| - for key in kwargs: |
100 |
| - for resource in self.supported_resources: |
101 |
| - if key == self.supported_resources[resource] + '_id': |
102 |
| - return resource, kwargs[key] |
103 |
| - return None, None |
| 139 | + def _get_target(self, res_info): |
| 140 | + target = {'id': res_info.parent_id, |
| 141 | + 'tenant_id': res_info.project_id, |
| 142 | + 'project_id': res_info.project_id} |
| 143 | + if res_info.upper_parent_type: |
| 144 | + res_id = (self.supported_resources[res_info.upper_parent_type] + |
| 145 | + '_id') |
| 146 | + target[res_id] = res_info.upper_parent_id |
| 147 | + return target |
| 148 | + |
| 149 | + def _get_resource_info(self, context, kwargs): |
| 150 | + """Return the tag parent resource information |
| 151 | +
|
| 152 | + Some parent resources, like the subnets, depend on other upper parent |
| 153 | + resources (networks). In that case, it is needed to provide the upper |
| 154 | + parent resource information. |
| 155 | +
|
| 156 | + :param kwargs: dictionary with the parent resource ID, along with other |
| 157 | + information not needed. It is formated as |
| 158 | + {"resource_id": "id", ...} |
| 159 | + :return: ``ResourceInfo`` named tuple with the parent and upper parent |
| 160 | + information and the project ID (of the parent or upper |
| 161 | + parent). |
| 162 | + """ |
| 163 | + for key, parent_type in itertools.product( |
| 164 | + kwargs.keys(), self.supported_resources.keys()): |
| 165 | + if key != self.supported_resources[parent_type] + '_id': |
| 166 | + continue |
| 167 | + |
| 168 | + parent_id = kwargs[key] |
| 169 | + parent_obj = PARENTS[parent_type] |
| 170 | + if isinstance(parent_obj, tuple): |
| 171 | + upper_parent_type = parent_obj[0] |
| 172 | + parent_obj = parent_obj[1] |
| 173 | + res_id = (self.supported_resources[upper_parent_type] + |
| 174 | + '_id') |
| 175 | + upper_parent_id = parent_obj.get_values( |
| 176 | + context.elevated(), res_id, id=parent_id)[0] |
| 177 | + else: |
| 178 | + upper_parent_type = upper_parent_id = None |
| 179 | + |
| 180 | + try: |
| 181 | + project_id = parent_obj.get_values( |
| 182 | + context.elevated(), 'project_id', id=parent_id)[0] |
| 183 | + except IndexError: |
| 184 | + return EMPTY_RESOURCE_INFO |
| 185 | + |
| 186 | + return ResourceInfo(project_id, parent_type, parent_id, |
| 187 | + upper_parent_type, upper_parent_id) |
| 188 | + |
| 189 | + # This should never be returned. |
| 190 | + return EMPTY_RESOURCE_INFO |
104 | 191 |
|
105 | 192 | def index(self, request, **kwargs):
|
106 |
| - # GET /v2.0/networks/{network_id}/tags |
107 |
| - parent, parent_id = self._get_parent_resource_and_id(kwargs) |
108 |
| - return self.plugin.get_tags(request.context, parent, parent_id) |
| 193 | + # GET /v2.0/{parent_resource}/{parent_resource_id}/tags |
| 194 | + ctx = request.context |
| 195 | + rinfo = self._get_resource_info(ctx, kwargs) |
| 196 | + target = self._get_target(rinfo) |
| 197 | + policy.enforce(ctx, 'get_{}_{}'.format(rinfo.parent_type, TAGS), |
| 198 | + target) |
| 199 | + return self.plugin.get_tags(ctx, rinfo.parent_type, rinfo.parent_id) |
109 | 200 |
|
110 | 201 | def show(self, request, id, **kwargs):
|
111 | 202 | # GET /v2.0/networks/{network_id}/tags/{tag}
|
112 | 203 | # id == tag
|
113 | 204 | validate_tag(id)
|
114 |
| - parent, parent_id = self._get_parent_resource_and_id(kwargs) |
115 |
| - return self.plugin.get_tag(request.context, parent, parent_id, id) |
116 |
| - |
117 |
| - def create(self, request, **kwargs): |
118 |
| - # not supported |
119 |
| - # POST /v2.0/networks/{network_id}/tags |
120 |
| - raise webob.exc.HTTPNotFound("not supported") |
| 205 | + ctx = request.context |
| 206 | + rinfo = self._get_resource_info(ctx, kwargs) |
| 207 | + target = self._get_target(rinfo) |
| 208 | + policy.enforce(ctx, 'get_{}_{}'.format(rinfo.parent_type, TAGS), |
| 209 | + target) |
| 210 | + return self.plugin.get_tag(ctx, rinfo.parent_type, rinfo.parent_id, id) |
| 211 | + |
| 212 | + @_policy_init |
| 213 | + def create(self, request, body, **kwargs): |
| 214 | + # POST /v2.0/{parent_resource}/{parent_resource_id}/tags |
| 215 | + # body: {"tags": ["aaa", "bbb"]} |
| 216 | + validate_tags(body) |
| 217 | + ctx = request.context |
| 218 | + rinfo = self._get_resource_info(ctx, kwargs) |
| 219 | + target = self._get_target(rinfo) |
| 220 | + policy.enforce(ctx, 'create_{}_{}'.format(rinfo.parent_type, TAGS), |
| 221 | + target) |
| 222 | + notify_tag_action(ctx, 'create.start', rinfo.parent_type, |
| 223 | + rinfo.parent_id, body['tags']) |
| 224 | + result = self.plugin.create_tags(ctx, rinfo.parent_type, |
| 225 | + rinfo.parent_id, body) |
| 226 | + notify_tag_action(ctx, 'create.end', rinfo.parent_type, |
| 227 | + rinfo.parent_id, body['tags']) |
| 228 | + return result |
121 | 229 |
|
122 | 230 | def update(self, request, id, **kwargs):
|
123 | 231 | # PUT /v2.0/networks/{network_id}/tags/{tag}
|
124 | 232 | # id == tag
|
125 | 233 | validate_tag(id)
|
126 |
| - parent, parent_id = self._get_parent_resource_and_id(kwargs) |
127 |
| - notify_tag_action(request.context, 'create.start', |
128 |
| - parent, parent_id, [id]) |
129 |
| - result = self.plugin.update_tag(request.context, parent, parent_id, id) |
130 |
| - notify_tag_action(request.context, 'create.end', |
131 |
| - parent, parent_id, [id]) |
| 234 | + ctx = request.context |
| 235 | + rinfo = self._get_resource_info(ctx, kwargs) |
| 236 | + target = self._get_target(rinfo) |
| 237 | + policy.enforce(ctx, 'update_{}_{}'.format(rinfo.parent_type, TAGS), |
| 238 | + target) |
| 239 | + notify_tag_action(ctx, 'create.start', rinfo.parent_type, |
| 240 | + rinfo.parent_id, [id]) |
| 241 | + result = self.plugin.update_tag(ctx, rinfo.parent_type, |
| 242 | + rinfo.parent_id, id) |
| 243 | + notify_tag_action(ctx, 'create.end', rinfo.parent_type, |
| 244 | + rinfo.parent_id, [id]) |
132 | 245 | return result
|
133 | 246 |
|
134 | 247 | def update_all(self, request, body, **kwargs):
|
135 | 248 | # PUT /v2.0/networks/{network_id}/tags
|
136 | 249 | # body: {"tags": ["aaa", "bbb"]}
|
137 | 250 | validate_tags(body)
|
138 |
| - parent, parent_id = self._get_parent_resource_and_id(kwargs) |
139 |
| - notify_tag_action(request.context, 'update.start', |
140 |
| - parent, parent_id, body['tags']) |
141 |
| - result = self.plugin.update_tags(request.context, parent, |
142 |
| - parent_id, body) |
143 |
| - notify_tag_action(request.context, 'update.end', |
144 |
| - parent, parent_id, body['tags']) |
| 251 | + ctx = request.context |
| 252 | + rinfo = self._get_resource_info(ctx, kwargs) |
| 253 | + target = self._get_target(rinfo) |
| 254 | + policy.enforce(ctx, 'update_{}_{}'.format(rinfo.parent_type, TAGS), |
| 255 | + target) |
| 256 | + notify_tag_action(ctx, 'update.start', rinfo.parent_type, |
| 257 | + rinfo.parent_id, body['tags']) |
| 258 | + result = self.plugin.update_tags(ctx, rinfo.parent_type, |
| 259 | + rinfo.parent_id, body) |
| 260 | + notify_tag_action(ctx, 'update.end', rinfo.parent_type, |
| 261 | + rinfo.parent_id, body['tags']) |
145 | 262 | return result
|
146 | 263 |
|
147 | 264 | def delete(self, request, id, **kwargs):
|
148 | 265 | # DELETE /v2.0/networks/{network_id}/tags/{tag}
|
149 | 266 | # id == tag
|
150 | 267 | validate_tag(id)
|
151 |
| - parent, parent_id = self._get_parent_resource_and_id(kwargs) |
152 |
| - notify_tag_action(request.context, 'delete.start', |
153 |
| - parent, parent_id, [id]) |
154 |
| - result = self.plugin.delete_tag(request.context, parent, parent_id, id) |
155 |
| - notify_tag_action(request.context, 'delete.end', |
156 |
| - parent, parent_id, [id]) |
| 268 | + ctx = request.context |
| 269 | + rinfo = self._get_resource_info(ctx, kwargs) |
| 270 | + target = self._get_target(rinfo) |
| 271 | + policy.enforce(ctx, 'delete_{}_{}'.format(rinfo.parent_type, TAGS), |
| 272 | + target) |
| 273 | + notify_tag_action(ctx, 'delete.start', rinfo.parent_type, |
| 274 | + rinfo.parent_id, [id]) |
| 275 | + result = self.plugin.delete_tag(ctx, rinfo.parent_type, |
| 276 | + rinfo.parent_id, id) |
| 277 | + notify_tag_action(ctx, 'delete.end', rinfo.parent_type, |
| 278 | + rinfo.parent_id, [id]) |
157 | 279 | return result
|
158 | 280 |
|
159 | 281 | def delete_all(self, request, **kwargs):
|
160 |
| - # DELETE /v2.0/networks/{network_id}/tags |
161 |
| - parent, parent_id = self._get_parent_resource_and_id(kwargs) |
162 |
| - notify_tag_action(request.context, 'delete_all.start', |
163 |
| - parent, parent_id) |
164 |
| - result = self.plugin.delete_tags(request.context, parent, parent_id) |
165 |
| - notify_tag_action(request.context, 'delete_all.end', |
166 |
| - parent, parent_id) |
| 282 | + # DELETE /v2.0/{parent_resource}/{parent_resource_id}/tags |
| 283 | + ctx = request.context |
| 284 | + rinfo = self._get_resource_info(ctx, kwargs) |
| 285 | + target = self._get_target(rinfo) |
| 286 | + policy.enforce(ctx, 'delete_{}_{}'.format(rinfo.parent_type, TAGS), |
| 287 | + target) |
| 288 | + notify_tag_action(ctx, 'delete_all.start', rinfo.parent_type, |
| 289 | + rinfo.parent_id) |
| 290 | + result = self.plugin.delete_tags(ctx, rinfo.parent_type, |
| 291 | + rinfo.parent_id) |
| 292 | + notify_tag_action(ctx, 'delete_all.end', rinfo.parent_type, |
| 293 | + rinfo.parent_id) |
167 | 294 | return result
|
168 | 295 |
|
169 | 296 |
|
|
0 commit comments