Coverage for manila/api/v2/share_servers.py: 76%
338 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 2019 NetApp, Inc.
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.
16from http import client as http_client
18from oslo_log import log
19import webob
20from webob import exc
22from manila.api import common
23from manila.api.openstack import wsgi
24from manila.api.views import share_server_migration as server_migration_views
25from manila.api.views import share_servers as share_servers_views
26from manila.common import constants
27from manila.db import api as db_api
28from manila import exception
29from manila.i18n import _
30from manila import share
31from manila.share import utils as share_utils
32from manila import utils
34LOG = log.getLogger(__name__)
37class ShareServerController(wsgi.Controller, wsgi.AdminActionsMixin):
38 """The Share Server API V2 controller for the OpenStack API."""
40 _view_builder_class = share_servers_views.ViewBuilder
41 resource_name = 'share_server'
43 def __init__(self):
44 self.share_api = share.API()
45 super().__init__()
46 self._migration_view_builder = server_migration_views.ViewBuilder()
48 valid_statuses = {
49 'status': set(constants.SHARE_SERVER_STATUSES),
50 'task_state': set(constants.SERVER_TASK_STATE_STATUSES),
51 }
53 def _get(self, *args, **kwargs):
54 return db_api.share_server_get(*args, **kwargs)
56 def _update(self, context, id, update):
57 db_api.share_server_update(context, id, update)
59 @wsgi.Controller.authorize
60 def index(self, req):
61 """Returns a list of share servers."""
63 context = req.environ['manila.context']
65 search_opts = {}
66 search_opts.update(req.GET)
67 share_servers = db_api.share_server_get_all(context)
68 for s in share_servers:
69 try:
70 share_network = db_api.share_network_get(
71 context, s.share_network_id)
72 s.project_id = share_network['project_id']
73 if share_network['name']:
74 s.share_network_name = share_network['name']
75 else:
76 s.share_network_name = share_network['id']
77 except exception.ShareNetworkNotFound:
78 # NOTE(dviroel): The share-network may already be deleted while
79 # the share-server is in 'deleting' state. In this scenario,
80 # we will return some empty values.
81 LOG.debug("Unable to retrieve share network details for share "
82 "server %(server)s, the network %(network)s was "
83 "not found.",
84 {'server': s.id, 'network': s.share_network_id})
85 s.project_id = ''
86 s.share_network_name = ''
87 if search_opts:
88 for k, v in search_opts.items():
89 share_servers = [s for s in share_servers if
90 (hasattr(s, k) and
91 s[k] == v or k == 'share_network' and
92 v in [s.share_network_name,
93 s.share_network_id] or
94 k == 'share_network_subnet_id' and
95 v in s.share_network_subnet_ids)]
96 return self._view_builder.build_share_servers(req, share_servers)
98 @wsgi.Controller.authorize
99 def show(self, req, id):
100 """Return data about the requested share server."""
101 context = req.environ['manila.context']
102 try:
103 server = db_api.share_server_get(context, id)
104 share_network = db_api.share_network_get(
105 context, server['share_network_id'])
106 server.project_id = share_network['project_id']
107 if share_network['name']:
108 server.share_network_name = share_network['name']
109 else:
110 server.share_network_name = share_network['id']
111 except exception.ShareServerNotFound as e:
112 raise exc.HTTPNotFound(explanation=e.msg)
113 except exception.ShareNetworkNotFound:
114 msg = _("Share server could not be found. Its associated share "
115 "network %s does not exist.") % server['share_network_id']
116 raise exc.HTTPNotFound(explanation=msg)
117 return self._view_builder.build_share_server(req, server)
119 @wsgi.Controller.authorize
120 def details(self, req, id):
121 """Return details for requested share server."""
122 context = req.environ['manila.context']
123 try:
124 share_server = db_api.share_server_get(context, id)
125 except exception.ShareServerNotFound as e:
126 raise exc.HTTPNotFound(explanation=e.msg)
128 return self._view_builder.build_share_server_details(
129 share_server['backend_details'])
131 @wsgi.Controller.authorize
132 def delete(self, req, id):
133 """Delete specified share server."""
134 context = req.environ['manila.context']
135 try:
136 share_server = db_api.share_server_get(context, id)
137 except exception.ShareServerNotFound as e:
138 raise exc.HTTPNotFound(explanation=e.msg)
139 allowed_statuses = [constants.STATUS_ERROR, constants.STATUS_ACTIVE]
140 if share_server['status'] not in allowed_statuses:
141 data = {
142 'status': share_server['status'],
143 'allowed_statuses': allowed_statuses,
144 }
145 msg = _("Share server's actual status is %(status)s, allowed "
146 "statuses for deletion are %(allowed_statuses)s.") % (data)
147 raise exc.HTTPForbidden(explanation=msg)
148 LOG.debug("Deleting share server with id: %s.", id)
149 try:
150 self.share_api.delete_share_server(context, share_server)
151 except exception.ShareServerInUse as e:
152 raise exc.HTTPConflict(explanation=e.msg)
153 return webob.Response(status_int=http_client.ACCEPTED)
155 @wsgi.Controller.api_version('2.49')
156 @wsgi.action('reset_status')
157 def share_server_reset_status(self, req, id, body):
158 return self._reset_status(req, id, body)
160 @wsgi.Controller.authorize('manage_share_server')
161 def _manage(self, req, body):
162 """Manage a share server."""
163 LOG.debug("Manage Share Server with id: %s", id)
165 context = req.environ['manila.context']
166 identifier, host, share_network, driver_opts, network_subnet = (
167 self._validate_manage_share_server_parameters(context, body))
169 try:
170 result = self.share_api.manage_share_server(
171 context, identifier, host, network_subnet, driver_opts)
172 except exception.InvalidInput as e:
173 raise exc.HTTPBadRequest(explanation=e.msg)
174 except exception.PolicyNotAuthorized as e:
175 raise exc.HTTPForbidden(explanation=e.msg)
177 result.project_id = share_network["project_id"]
178 if share_network['name']:
179 result.share_network_name = share_network['name']
180 else:
181 result.share_network_name = share_network['id']
182 return self._view_builder.build_share_server(req, result)
184 @wsgi.Controller.api_version('2.51')
185 @wsgi.response(202)
186 def manage(self, req, body):
187 return self._manage(req, body)
189 @wsgi.Controller.api_version('2.49') # noqa
190 @wsgi.response(202)
191 def manage(self, req, body): # pylint: disable=function-redefined # noqa F811
192 body.get('share_server', {}).pop('share_network_subnet_id', None)
193 return self._manage(req, body)
195 @wsgi.Controller.authorize('unmanage_share_server')
196 def _unmanage(self, req, id, body=None):
197 context = req.environ['manila.context']
199 LOG.debug("Unmanage Share Server with id: %s", id)
201 # force's default value is False
202 # force will be True if body is {'unmanage': {'force': True}}
203 force = (body.get('unmanage') or {}).get('force', False) or False
205 try:
206 share_server = db_api.share_server_get(
207 context, id)
208 except exception.ShareServerNotFound as e:
209 raise exc.HTTPNotFound(explanation=e.msg)
211 if len(share_server['share_network_subnets']) > 1:
212 msg = _("Cannot unmanage the share server containing multiple "
213 "subnets.")
214 raise exc.HTTPBadRequest(explanation=msg)
216 if share_server.get('encryption_key_ref'): 216 ↛ 217line 216 didn't jump to line 217 because the condition on line 216 was never true
217 msg = _("Cannot unmanage the share server containing encryption "
218 "key reference")
219 raise exc.HTTPBadRequest(explanation=msg)
221 share_network_id = share_server['share_network_id']
222 share_network = db_api.share_network_get(context, share_network_id)
223 common.check_share_network_is_active(share_network)
225 allowed_statuses = [constants.STATUS_ERROR, constants.STATUS_ACTIVE,
226 constants.STATUS_MANAGE_ERROR,
227 constants.STATUS_UNMANAGE_ERROR]
228 if share_server['status'] not in allowed_statuses:
229 data = {
230 'status': share_server['status'],
231 'allowed_statuses': ', '.join(allowed_statuses),
232 }
233 msg = _("Share server's actual status is %(status)s, allowed "
234 "statuses for unmanaging are "
235 "%(allowed_statuses)s.") % data
236 raise exc.HTTPBadRequest(explanation=msg)
238 try:
239 self.share_api.unmanage_share_server(
240 context, share_server, force=force)
241 except (exception.ShareServerInUse,
242 exception.PolicyNotAuthorized) as e:
243 raise exc.HTTPBadRequest(explanation=e.msg)
245 return webob.Response(status_int=http_client.ACCEPTED)
247 @wsgi.Controller.api_version("2.49")
248 @wsgi.action('unmanage')
249 def unmanage(self, req, id, body=None):
250 """Unmanage a share server."""
251 return self._unmanage(req, id, body)
253 def _validate_manage_share_server_parameters(self, context, body):
255 if not (body and self.is_valid_body(body, 'share_server')):
256 msg = _("Share Server entity not found in request body")
257 raise exc.HTTPUnprocessableEntity(explanation=msg)
259 required_parameters = ('host', 'share_network_id', 'identifier')
260 data = body['share_server']
262 for parameter in required_parameters:
263 if parameter not in data:
264 msg = _("Required parameter %s not found") % parameter
265 raise exc.HTTPBadRequest(explanation=msg)
266 if not data.get(parameter):
267 msg = _("Required parameter %s is empty") % parameter
268 raise exc.HTTPBadRequest(explanation=msg)
270 identifier = data['identifier']
271 host, share_network_id = data['host'], data['share_network_id']
273 network_subnet_id = data.get('share_network_subnet_id')
274 if network_subnet_id:
275 try:
276 network_subnets = (
277 db_api.share_network_subnet_get_all_with_same_az(
278 context, network_subnet_id))
279 except exception.ShareNetworkSubnetNotFound:
280 msg = _("The share network subnet %s does not "
281 "exist.") % network_subnet_id
282 raise exc.HTTPBadRequest(explanation=msg)
283 else:
284 network_subnets = db_api.share_network_subnet_get_default_subnets(
285 context, share_network_id)
287 if not network_subnets:
288 msg = _("The share network %s does have a default subnet. Create "
289 "one or use a specific subnet to manage this share server "
290 "with API version >= 2.51.") % share_network_id
291 raise exc.HTTPBadRequest(explanation=msg)
293 if len(network_subnets) > 1:
294 msg = _("Cannot manage the share server, since the share network "
295 "subnet %s has more subnets in its availability "
296 "zone and share network.") % network_subnet_id
297 raise exc.HTTPBadRequest(explanation=msg)
299 network_subnet = network_subnets[0]
300 common.check_share_network_is_active(network_subnet['share_network'])
302 if share_utils.extract_host(host, 'pool'): 302 ↛ 303line 302 didn't jump to line 303 because the condition on line 302 was never true
303 msg = _("Host parameter should not contain pool.")
304 raise exc.HTTPBadRequest(explanation=msg)
306 try:
307 utils.validate_service_host(
308 context, share_utils.extract_host(host))
309 except exception.ServiceNotFound as e:
310 raise exc.HTTPBadRequest(explanation=e.msg)
311 except exception.PolicyNotAuthorized as e:
312 raise exc.HTTPForbidden(explanation=e.msg)
313 except exception.AdminRequired as e:
314 raise exc.HTTPForbidden(explanation=e.msg)
315 except exception.ServiceIsDown as e:
316 raise exc.HTTPBadRequest(explanation=e.msg)
318 try:
319 share_network = db_api.share_network_get(
320 context, share_network_id)
321 except exception.ShareNetworkNotFound as e:
322 raise exc.HTTPBadRequest(explanation=e.msg)
324 driver_opts = data.get('driver_options')
325 if driver_opts is not None and not isinstance(driver_opts, dict):
326 msg = _("Driver options must be in dictionary format.")
327 raise exc.HTTPBadRequest(explanation=msg)
329 return identifier, host, share_network, driver_opts, network_subnet
331 @wsgi.Controller.api_version('2.57', experimental=True)
332 @wsgi.action("migration_start")
333 @wsgi.Controller.authorize
334 @wsgi.response(http_client.ACCEPTED)
335 def share_server_migration_start(self, req, id, body):
336 """Migrate a share server to the specified host."""
337 context = req.environ['manila.context']
338 try:
339 share_server = db_api.share_server_get(
340 context, id)
341 except exception.ShareServerNotFound as e:
342 raise exc.HTTPNotFound(explanation=e.msg)
344 params = body.get('migration_start')
346 if not params:
347 raise exc.HTTPBadRequest(explanation=_("Request is missing body."))
349 if share_server['encryption_key_ref']: 349 ↛ 350line 349 didn't jump to line 350 because the condition on line 349 was never true
350 msg = _("Cannot migrate the share server containing encryption "
351 "key reference")
352 raise exc.HTTPBadRequest(explanation=msg)
354 bool_params = ['writable', 'nondisruptive', 'preserve_snapshots']
355 mandatory_params = bool_params + ['host']
357 utils.check_params_exist(mandatory_params, params)
358 bool_param_values = utils.check_params_are_boolean(bool_params, params)
360 pool_was_specified = len(params['host'].split('#')) > 1
362 if pool_was_specified:
363 msg = _('The destination host can not contain pool information.')
364 raise exc.HTTPBadRequest(explanation=msg)
366 new_share_network = None
368 new_share_network_id = params.get('new_share_network_id', None)
369 if new_share_network_id:
370 try:
371 new_share_network = db_api.share_network_get(
372 context, new_share_network_id)
373 except exception.NotFound:
374 msg = _("Share network %s not "
375 "found.") % new_share_network_id
376 raise exc.HTTPBadRequest(explanation=msg)
377 common.check_share_network_is_active(new_share_network)
378 else:
379 share_network_id = (
380 share_server['share_network_id'])
381 current_share_network = db_api.share_network_get(
382 context, share_network_id)
383 common.check_share_network_is_active(current_share_network)
385 try:
386 self.share_api.share_server_migration_start(
387 context, share_server, params['host'],
388 bool_param_values['writable'],
389 bool_param_values['nondisruptive'],
390 bool_param_values['preserve_snapshots'],
391 new_share_network=new_share_network)
392 except exception.ServiceIsDown as e:
393 # NOTE(dviroel): user should check if the host is healthy
394 raise exc.HTTPBadRequest(explanation=e.msg)
395 except exception.InvalidShareServer as e:
396 # NOTE(dviroel): invalid share server meaning that some internal
397 # resource have a invalid state.
398 raise exc.HTTPConflict(explanation=e.msg)
399 except exception.InvalidInput as e:
400 # User provided controversial parameters in the request
401 raise exc.HTTPBadRequest(explanation=e.msg)
403 @wsgi.Controller.api_version('2.57', experimental=True)
404 @wsgi.action("migration_complete")
405 @wsgi.Controller.authorize
406 def share_server_migration_complete(self, req, id, body):
407 """Invokes 2nd phase of share server migration."""
408 context = req.environ['manila.context']
409 try:
410 share_server = db_api.share_server_get(
411 context, id)
412 except exception.ShareServerNotFound as e:
413 raise exc.HTTPNotFound(explanation=e.msg)
415 try:
416 result = self.share_api.share_server_migration_complete(
417 context, share_server)
418 except (exception.InvalidShareServer,
419 exception.ServiceIsDown) as e:
420 raise exc.HTTPBadRequest(explanation=e.msg)
422 return self._migration_view_builder.migration_complete(req, result)
424 @wsgi.Controller.api_version('2.57', experimental=True)
425 @wsgi.action("migration_cancel")
426 @wsgi.Controller.authorize
427 @wsgi.response(http_client.ACCEPTED)
428 def share_server_migration_cancel(self, req, id, body):
429 """Attempts to cancel share migration."""
430 context = req.environ['manila.context']
431 try:
432 share_server = db_api.share_server_get(
433 context, id)
434 except exception.ShareServerNotFound as e:
435 raise exc.HTTPNotFound(explanation=e.msg)
437 try:
438 self.share_api.share_server_migration_cancel(context, share_server)
439 except (exception.InvalidShareServer,
440 exception.ServiceIsDown) as e:
441 raise exc.HTTPBadRequest(explanation=e.msg)
443 @wsgi.Controller.api_version('2.57', experimental=True)
444 @wsgi.action("migration_get_progress")
445 @wsgi.Controller.authorize
446 def share_server_migration_get_progress(self, req, id, body):
447 """Retrieve share server migration progress for a given share."""
448 context = req.environ['manila.context']
449 try:
450 result = self.share_api.share_server_migration_get_progress(
451 context, id)
452 except exception.ServiceIsDown as e:
453 raise exc.HTTPConflict(explanation=e.msg)
454 except exception.InvalidShareServer as e:
455 raise exc.HTTPBadRequest(explanation=e.msg)
457 return self._migration_view_builder.get_progress(req, result)
459 @wsgi.Controller.api_version('2.57', experimental=True)
460 @wsgi.action("reset_task_state")
461 @wsgi.Controller.authorize
462 def share_server_reset_task_state(self, req, id, body):
463 return self._reset_status(req, id, body, status_attr='task_state')
465 @wsgi.Controller.api_version('2.57', experimental=True)
466 @wsgi.action("migration_check")
467 @wsgi.Controller.authorize
468 def share_server_migration_check(self, req, id, body):
469 """Check if can migrate a share server to the specified host."""
470 context = req.environ['manila.context']
471 try:
472 share_server = db_api.share_server_get(
473 context, id)
474 except exception.ShareServerNotFound as e:
475 raise exc.HTTPNotFound(explanation=e.msg)
477 params = body.get('migration_check')
479 if not params:
480 raise exc.HTTPBadRequest(explanation=_("Request is missing body."))
482 if share_server.get('encryption_key_ref'): 482 ↛ 483line 482 didn't jump to line 483 because the condition on line 482 was never true
483 msg = _("Cannot migrate the share server containing encryption "
484 "key reference")
485 raise exc.HTTPBadRequest(explanation=msg)
487 bool_params = ['writable', 'nondisruptive', 'preserve_snapshots']
488 mandatory_params = bool_params + ['host']
490 utils.check_params_exist(mandatory_params, params)
491 bool_param_values = utils.check_params_are_boolean(bool_params, params)
493 pool_was_specified = len(params['host'].split('#')) > 1
495 if pool_was_specified: 495 ↛ 496line 495 didn't jump to line 496 because the condition on line 495 was never true
496 msg = _('The destination host can not contain pool information.')
497 raise exc.HTTPBadRequest(explanation=msg)
499 new_share_network = None
500 new_share_network_id = params.get('new_share_network_id', None)
501 if new_share_network_id: 501 ↛ 511line 501 didn't jump to line 511 because the condition on line 501 was always true
502 try:
503 new_share_network = db_api.share_network_get(
504 context, new_share_network_id)
505 except exception.NotFound:
506 msg = _("Share network %s not "
507 "found.") % new_share_network_id
508 raise exc.HTTPBadRequest(explanation=msg)
509 common.check_share_network_is_active(new_share_network)
510 else:
511 share_network_id = (
512 share_server['share_network_id'])
513 current_share_network = db_api.share_network_get(
514 context, share_network_id)
515 common.check_share_network_is_active(current_share_network)
517 try:
518 result = self.share_api.share_server_migration_check(
519 context, share_server, params['host'],
520 bool_param_values['writable'],
521 bool_param_values['nondisruptive'],
522 bool_param_values['preserve_snapshots'],
523 new_share_network=new_share_network)
524 except exception.ServiceIsDown as e:
525 # NOTE(dviroel): user should check if the host is healthy
526 raise exc.HTTPBadRequest(explanation=e.msg)
527 except exception.InvalidShareServer as e:
528 # NOTE(dviroel): invalid share server meaning that some internal
529 # resource have a invalid state.
530 raise exc.HTTPConflict(explanation=e.msg)
532 return self._migration_view_builder.build_check_migration(
533 req, params, result)
536def create_resource():
537 return wsgi.Resource(ShareServerController())